Selaa lähdekoodia

Merge branch 'dicombrowser-support-import-multiple-dirs'

* dicombrowser-support-import-multiple-dirs:
  ENH: ctkDICOMDatabase: Display schema queries only if LoggedExecVerbose is true
  BUG: Update ctkDICOMBrowser to fix "testImportDirectoryMode"
  BUG: ctkDICOMBrowserTest: Add testImportDirectoryMode
  ENH: ctkDICOMBrowser: Remove import confirmation dialog
  ENH: ctkDICOMBrowser: Rename "onImportDir..." function to "importDir..."
  BUG: ctkDICOMBrowser: Fix ImportDirectories test
  BUG: ctkDICOMBrowser: Add ImportDirectories and ImportDirectory test
  ENH: ctkDICOMBrowser: Refactor introducing databaseDirectorySettingsKey()
  BUG: ctkDICOMBrowser: Fix test "testDefaults"
  BUG: ctkDICOMBrowser: Add test "testDefaults"
  COMP: Conditionally add DICOMWidgets tests depending on CTKData_DIR
  ENH: ctkDICOMBrowser: Improve import of multiple directories
  ENH: Update ctkDICOMBrowser to support importing multiple directories
  ENH: Update ctkFileDialog API adding support for setting selection mode
  BUG: Fix ctkFileDialogEventTranslatorPlayerTest1 sorting file lists
  STYLE: Refactor ctkDICOMBrowserTest1 to use testing macros
Jean-Christophe Fillion-Robin 7 vuotta sitten
vanhempi
commit
b1d9fb53fd

+ 5 - 2
Libs/DICOM/Core/ctkDICOMDatabase.cpp

@@ -429,7 +429,10 @@ bool ctkDICOMDatabasePrivate::executeScript(const QString script) {
     {
       if (! (*it).startsWith("--") )
         {
-          qDebug() << *it << "\n";
+          if (LoggedExecVerbose)
+            {
+            qDebug() << *it << "\n";
+            }
           query.exec(*it);
           if (query.lastError().type())
             {
@@ -745,7 +748,7 @@ QStringList ctkDICOMDatabase::instancesForSeries(const QString seriesUID)
   query.bindValue(0, seriesUID);
   query.exec();
   QStringList result;
-  if (query.next())
+  while (query.next())
   {
     result << query.value(0).toString();
   }

+ 37 - 6
Libs/DICOM/Widgets/Testing/Cpp/CMakeLists.txt

@@ -2,6 +2,7 @@ set(KIT ${PROJECT_NAME})
 
 create_test_sourcelist(Tests ${KIT}CppTests.cpp
   ctkDICOMAppWidgetTest1.cpp
+  ctkDICOMBrowserTest.cpp
   ctkDICOMBrowserTest1.cpp
   ctkDICOMItemViewTest1.cpp
   ctkDICOMDirectoryListWidgetTest1.cpp
@@ -16,23 +17,38 @@ create_test_sourcelist(Tests ${KIT}CppTests.cpp
   ctkDICOMThumbnailListWidgetTest1.cpp
   )
 
+set(Tests_MOC_CPPS
+  ctkDICOMBrowserTest.cpp
+  )
+
+include_directories(
+  ${CMAKE_SOURCE_DIR}/Libs/Testing
+  ${CMAKE_CURRENT_BINARY_DIR}
+  )
+
+set(CTK_QT_TEST_LIBRARY )
+
+if (CTK_QT_VERSION VERSION_GREATER "4")
+  find_package(Qt5Test REQUIRED)
+  set(CTK_QT_TEST_LIBRARY Qt5::Test)
+  qt5_generate_mocs(${Tests_MOC_CPPS})
+else()
+  qt4_generate_mocs(${Tests_MOC_CPPS})
+endif()
+
 SET (TestsToRun ${Tests})
 REMOVE (TestsToRun ${KIT}CppTests.cpp)
 
 set(LIBRARY_NAME ${PROJECT_NAME})
 
 add_executable(${KIT}CppTests ${Tests})
-target_link_libraries(${KIT}CppTests ${LIBRARY_NAME})
+target_link_libraries(${KIT}CppTests ${LIBRARY_NAME} ${CTK_BASE_LIBRARIES} ${CTK_QT_TEST_LIBRARY})
 
 #
 # Add Tests
 #
 
-SIMPLE_TEST(ctkDICOMAppWidgetTest1 ${CTKData_DIR}/Data/DICOM/MRHEAD)
-SIMPLE_TEST(ctkDICOMBrowserTest1 ${CTKData_DIR}/Data/DICOM/MRHEAD)
-SIMPLE_TEST(ctkDICOMItemViewTest1 ${CTKData_DIR}/Data/DICOM/MRHEAD/000055.IMA)
 SIMPLE_TEST(ctkDICOMDirectoryListWidgetTest1)
-SIMPLE_TEST(ctkDICOMImageTest1 ${CTKData_DIR}/Data/DICOM/MRHEAD/000055.IMA)
 SIMPLE_TEST(ctkDICOMImportWidgetTest1)
 SIMPLE_TEST(ctkDICOMListenerWidgetTest1)
 SIMPLE_TEST(ctkDICOMModelTest2
@@ -41,4 +57,19 @@ SIMPLE_TEST(ctkDICOMModelTest2
   )
 SIMPLE_TEST(ctkDICOMQueryRetrieveWidgetTest1)
 SIMPLE_TEST(ctkDICOMQueryResultsTabWidgetTest1)
-SIMPLE_TEST(ctkDICOMThumbnailListWidgetTest1 ${CMAKE_CURRENT_BINARY_DIR}/dicom.db ${CMAKE_CURRENT_SOURCE_DIR}/../../../Core/Resources/dicom-sample.sql)
+SIMPLE_TEST(ctkDICOMThumbnailListWidgetTest1
+  ${CMAKE_CURRENT_BINARY_DIR}/dicom.db
+  ${CMAKE_CURRENT_SOURCE_DIR}/../../../Core/Resources/dicom-sample.sql
+  )
+
+#
+# Add Tests expecting CTKData to be set
+#
+if(EXISTS "${CTKData_DIR}")
+  SIMPLE_TEST(ctkDICOMAppWidgetTest1 ${CTKData_DIR}/Data/DICOM/MRHEAD)
+  SIMPLE_TEST(ctkDICOMBrowserTest)
+  set_property(TEST ctkDICOMBrowserTest PROPERTY ENVIRONMENT "CTKData_DIR=${CTKData_DIR}")
+  SIMPLE_TEST(ctkDICOMBrowserTest1 ${CTKData_DIR}/Data/DICOM/MRHEAD)
+  SIMPLE_TEST(ctkDICOMItemViewTest1 ${CTKData_DIR}/Data/DICOM/MRHEAD/000055.IMA)
+  SIMPLE_TEST(ctkDICOMImageTest1 ${CTKData_DIR}/Data/DICOM/MRHEAD/000055.IMA)
+endif()

+ 298 - 0
Libs/DICOM/Widgets/Testing/Cpp/ctkDICOMBrowserTest.cpp

@@ -0,0 +1,298 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.commontk.org/LICENSE
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+// Qt includes
+#include <QApplication>
+#include <QComboBox>
+#include <QTest>
+
+// CTK includes
+#include "ctkDICOMDatabase.h"
+#include "ctkDICOMBrowser.h"
+#include "ctkFileDialog.h"
+#include "ctkScopedCurrentDir.h"
+#include "ctkTest.h"
+#include "ctkUtils.h"
+
+//-----------------------------------------------------------------------------
+class ctkDICOMBrowserTester: public QObject
+{
+  Q_OBJECT
+private slots:
+
+  void initTestCase();
+  void init();
+
+  void testDefaults();
+
+  void testDatabaseDirectory();
+
+  void testImportDirectoryMode();
+
+  void testImportDirectories();
+  void testImportDirectories_data();
+
+  void testImportDirectory();
+  void testImportDirectory_data();
+
+  // Deprecated
+  void testOnImportDirectory();
+  void testOnImportDirectory_data();
+
+private:
+  void _testImportCommon(ctkDICOMBrowser& browser);
+
+  QString TemporaryDatabaseDirectoryName;
+
+  QDir DICOMDir;
+};
+
+// ----------------------------------------------------------------------------
+void ctkDICOMBrowserTester::initTestCase()
+{
+  // Get data directory from environment
+  QDir dataDir = QDir(
+        QProcessEnvironment::systemEnvironment().value("CTKData_DIR", ""));
+  QVERIFY(dataDir.exists());
+
+  this->DICOMDir = dataDir.filePath("Data/DICOM");
+  QVERIFY(this->DICOMDir.exists());
+}
+
+// ----------------------------------------------------------------------------
+void ctkDICOMBrowserTester::init()
+{
+  // If any, remove default ./ctkDICOM-Database directory
+  {
+  ctkScopedCurrentDir scopedCurrentDir(QDir::tempPath());
+  ctk::removeDirRecursively("./ctkDICOM-Database");
+  QCOMPARE(QFileInfo("./ctkDICOM-Database").isDir(), false);
+  }
+
+  // Generate a new database directory name
+  this->TemporaryDatabaseDirectoryName =
+      QString("ctkDICOMBrowserTest.%1.DICOM-Database").arg(QTime::currentTime().toString("hhmmsszzz"));
+}
+
+// ----------------------------------------------------------------------------
+void ctkDICOMBrowserTester::testDefaults()
+{
+  // Clear left over settings
+  QSettings().remove(ctkDICOMBrowser::databaseDirectorySettingsKey());
+
+  ctkDICOMBrowser browser;
+
+  QVERIFY(QFileInfo("./ctkDICOM-Database").isDir());
+  QVERIFY(QFileInfo("./ctkDICOM-Database/ctkDICOM.sql").isFile());
+  QVERIFY(QFileInfo("./ctkDICOM-Database/ctkDICOMTagCache.sql").isFile());
+
+  QCOMPARE(ctkDICOMBrowser::databaseDirectorySettingsKey(), QString("DatabaseDirectory"));
+
+  QCOMPARE(browser.databaseDirectory(), QString("./ctkDICOM-Database"));
+  QVERIFY(browser.database() != 0);
+
+  QCOMPARE(browser.tagsToPrecache(), QStringList());
+
+  QVERIFY(browser.dicomTableManager() != 0);
+
+  QCOMPARE(browser.displayImportSummary(), true);
+  QCOMPARE(browser.patientsAddedDuringImport(), 0);
+  QCOMPARE(browser.studiesAddedDuringImport(), 0);
+  QCOMPARE(browser.seriesAddedDuringImport(), 0);
+  QCOMPARE(browser.instancesAddedDuringImport(), 0);
+
+  QCOMPARE(browser.importDirectoryMode(), ctkDICOMBrowser::ImportDirectoryAddLink);
+}
+
+// ----------------------------------------------------------------------------
+void ctkDICOMBrowserTester::testDatabaseDirectory()
+{
+  // Check that value from setting is picked up
+  {
+  QSettings().setValue(ctkDICOMBrowser::databaseDirectorySettingsKey(), this->TemporaryDatabaseDirectoryName);
+  QCOMPARE(QFileInfo(this->TemporaryDatabaseDirectoryName).isDir(), false);
+
+  ctkDICOMBrowser browser;
+
+  QCOMPARE(QFileInfo(browser.databaseDirectory()).absoluteFilePath(),
+           QFileInfo(this->TemporaryDatabaseDirectoryName).absoluteFilePath());
+  QVERIFY(QDir(browser.databaseDirectory()).exists());
+  }
+}
+
+// ----------------------------------------------------------------------------
+void ctkDICOMBrowserTester::testImportDirectoryMode()
+{
+  QSettings().setValue(ctkDICOMBrowser::databaseDirectorySettingsKey(), this->TemporaryDatabaseDirectoryName);
+
+  ctkDICOMBrowser browser;
+
+  browser.setImportDirectoryMode(ctkDICOMBrowser::ImportDirectoryCopy);
+  QCOMPARE(browser.importDirectoryMode(), ctkDICOMBrowser::ImportDirectoryCopy);
+
+  browser.setImportDirectoryMode(ctkDICOMBrowser::ImportDirectoryAddLink);
+  QCOMPARE(browser.importDirectoryMode(), ctkDICOMBrowser::ImportDirectoryAddLink);
+
+  QComboBox* comboBox = browser.importDialog()->bottomWidget()->findChild<QComboBox*>();
+
+  comboBox->setCurrentIndex(comboBox->findData(ctkDICOMBrowser::ImportDirectoryCopy));
+  QCOMPARE(browser.importDirectoryMode(), ctkDICOMBrowser::ImportDirectoryCopy);
+
+  comboBox->setCurrentIndex(comboBox->findData(ctkDICOMBrowser::ImportDirectoryAddLink));
+  QCOMPARE(browser.importDirectoryMode(), ctkDICOMBrowser::ImportDirectoryAddLink);
+}
+
+// ----------------------------------------------------------------------------
+void ctkDICOMBrowserTester::testImportDirectories()
+{
+  QFETCH(QStringList, directories);
+  QFETCH(ctkDICOMBrowser::ImportDirectoryMode, importDirectoryMode);
+
+  QSettings().setValue(ctkDICOMBrowser::databaseDirectorySettingsKey(), this->TemporaryDatabaseDirectoryName);
+
+  ctkDICOMBrowser browser;
+
+  browser.setDisplayImportSummary(false);
+  browser.importDirectories(directories, /* mode= */ importDirectoryMode);
+
+  this->_testImportCommon(browser);
+}
+
+// ----------------------------------------------------------------------------
+void ctkDICOMBrowserTester::_testImportCommon(ctkDICOMBrowser& browser)
+{
+  QFETCH(int, expectedImporedPatients);
+  QFETCH(int, expectedImporedStudies);
+  QFETCH(int, expectedImporedSeries);
+  QFETCH(int, expectedImporedInstances);
+
+  QCOMPARE(browser.patientsAddedDuringImport(), expectedImporedPatients);
+  QCOMPARE(browser.studiesAddedDuringImport(), expectedImporedStudies);
+  QCOMPARE(browser.seriesAddedDuringImport(), expectedImporedSeries);
+  QCOMPARE(browser.instancesAddedDuringImport(), expectedImporedInstances);
+
+  QFETCH(int, expectedTotalPatients);
+  QFETCH(int, expectedTotalStudies);
+  QFETCH(int, expectedTotalSeries);
+  QFETCH(int, expectedTotalInstances);
+
+  int currentPatientCount = 0;
+  int currentStudyCount = 0;
+  int currentSerieCount = 0;
+  int currentInstanceCount = 0;
+
+  QStringList patients =  browser.database()->patients();
+  currentPatientCount += patients.count();
+  foreach(const QString& patient, patients)
+    {
+    QStringList studies = browser.database()->studiesForPatient(patient);
+    currentStudyCount += studies.count();
+    foreach(const QString& study, studies)
+      {
+      QStringList series = browser.database()->seriesForStudy(study);
+      currentSerieCount += series.count();
+      foreach(const QString& serie, series)
+        {
+        QStringList instances = browser.database()->instancesForSeries(serie);
+        currentInstanceCount += instances.count();
+        }
+      }
+    }
+
+  QCOMPARE(currentPatientCount, expectedTotalPatients);
+  QCOMPARE(currentStudyCount, expectedTotalStudies);
+  QCOMPARE(currentSerieCount, expectedTotalSeries);
+  QCOMPARE(currentInstanceCount, expectedTotalInstances);
+}
+
+// ----------------------------------------------------------------------------
+void ctkDICOMBrowserTester::testImportDirectories_data()
+{
+  this->testImportDirectory_data();
+}
+
+// ----------------------------------------------------------------------------
+void ctkDICOMBrowserTester::testImportDirectory()
+{
+  QFETCH(QStringList, directories);
+  QFETCH(ctkDICOMBrowser::ImportDirectoryMode, importDirectoryMode);
+
+  QSettings().setValue(ctkDICOMBrowser::databaseDirectorySettingsKey(), this->TemporaryDatabaseDirectoryName);
+
+  ctkDICOMBrowser browser;
+
+  browser.setDisplayImportSummary(false);
+  browser.importDirectory(directories[0], /* mode= */ importDirectoryMode);
+
+  this->_testImportCommon(browser);
+}
+
+// ----------------------------------------------------------------------------
+void ctkDICOMBrowserTester::testImportDirectory_data()
+{
+  QTest::addColumn<QStringList>("directories");
+  QTest::addColumn<ctkDICOMBrowser::ImportDirectoryMode>("importDirectoryMode");
+  QTest::addColumn<int>("expectedImporedPatients");
+  QTest::addColumn<int>("expectedImporedStudies");
+  QTest::addColumn<int>("expectedImporedSeries");
+  QTest::addColumn<int>("expectedImporedInstances");
+  QTest::addColumn<int>("expectedTotalPatients");
+  QTest::addColumn<int>("expectedTotalStudies");
+  QTest::addColumn<int>("expectedTotalSeries");
+  QTest::addColumn<int>("expectedTotalInstances");
+
+  QTest::newRow("1-MRHEAD")
+      << /* directories */ (QStringList() << this->DICOMDir.filePath("MRHEAD"))
+      << /* importDirectoryMode */ ctkDICOMBrowser::ImportDirectoryAddLink
+      << /* expectedImporedPatients */ 1
+      << /* expectedImporedStudies */ 1
+      << /* expectedImporedSeries */ 1
+      << /* expectedImporedInstances */ 100
+      << /* expectedTotalPatients */ 1
+      << /* expectedTotalStudies */ 1
+      << /* expectedTotalSeries */ 1
+      << /* expectedTotalInstances */ 100;
+}
+
+// ----------------------------------------------------------------------------
+void ctkDICOMBrowserTester::testOnImportDirectory()
+{
+  QFETCH(QStringList, directories);
+  QFETCH(ctkDICOMBrowser::ImportDirectoryMode, importDirectoryMode);
+
+  QSettings().setValue(ctkDICOMBrowser::databaseDirectorySettingsKey(), this->TemporaryDatabaseDirectoryName);
+
+  ctkDICOMBrowser browser;
+
+  browser.setDisplayImportSummary(false);
+  browser.onImportDirectory(directories[0], /* mode= */ importDirectoryMode);
+
+  this->_testImportCommon(browser);
+}
+
+// ----------------------------------------------------------------------------
+void ctkDICOMBrowserTester::testOnImportDirectory_data()
+{
+  this->testImportDirectory_data();
+}
+
+// ----------------------------------------------------------------------------
+CTK_TEST_MAIN(ctkDICOMBrowserTest)
+#include "moc_ctkDICOMBrowserTest.cpp"

+ 12 - 12
Libs/DICOM/Widgets/Testing/Cpp/ctkDICOMBrowserTest1.cpp

@@ -24,6 +24,7 @@
 #include <QTimer>
 
 // ctk includes
+#include "ctkCoreTestingMacros.h"
 #include "ctkUtils.h"
 
 // ctkDICOMCore includes
@@ -65,24 +66,25 @@ int ctkDICOMBrowserTest1( int argc, char * argv [] )
   browser.setDisplayImportSummary(false);
   qDebug() << "Importing directory " << argv[1];
 
+  // [Deprecated]
   // make sure copy/link dialog doesn't pop up, always copy on import
   QSettings settings;
   QString settingsString = settings.value("MainWindow/DontConfirmCopyOnImport").toString();
-  settings.setValue("MainWindow/DontConfirmCopyOnImport", QString("0"));
+  settings.setValue("MainWindow/DontConfirmCopyOnImport", QString("0")); // QMessageBox::AcceptRole
+  CHECK_INT(browser.importDirectoryMode(), static_cast<int>(ctkDICOMBrowser::ImportDirectoryCopy));
+  // [/Deprecated]
 
-  browser.onImportDirectory(argv[1]);
+  browser.importDirectories(QStringList() << argv[1]);
 
+  // [Deprecated]
   // reset to the original copy/import setting
   settings.setValue("MainWindow/DontConfirmCopyOnImport", settingsString);
+  // [/Deprecated]
 
-  if (browser.patientsAddedDuringImport() != 1
-    || browser.studiesAddedDuringImport() != 1
-    || browser.seriesAddedDuringImport() != 1
-    || browser.instancesAddedDuringImport() != 100)
-    {
-    qDebug() << "\n\nDirectory did not import as expected!\n\n";
-    return EXIT_FAILURE;
-    }
+  CHECK_INT(browser.patientsAddedDuringImport(), 1);
+  CHECK_INT(browser.studiesAddedDuringImport(), 1);
+  CHECK_INT(browser.seriesAddedDuringImport(), 1);
+  CHECK_INT(browser.instancesAddedDuringImport(), 100);
 
   qDebug() << "\n\nAdded to database directory: " << dbDir;
 
@@ -91,7 +93,5 @@ int ctkDICOMBrowserTest1( int argc, char * argv [] )
     QTimer::singleShot(200, &app, SLOT(quit()));
     }
 
-
-
   return app.exec();
 }

+ 191 - 50
Libs/DICOM/Widgets/ctkDICOMBrowser.cpp

@@ -26,12 +26,16 @@
 #include <QApplication>
 #include <QCoreApplication>
 #include <QCheckBox>
+#include <QComboBox>
 #include <QDebug>
+#include <QDialogButtonBox>
 #include <QFile>
+#include <QFormLayout>
 #include <QListView>
 #include <QMenu>
 #include <QMessageBox>
 #include <QProgressDialog>
+#include <QPushButton>
 #include <QSettings>
 #include <QStringListModel>
 #include <QWidgetAction>
@@ -68,7 +72,12 @@ public:
   ctkDICOMBrowserPrivate(ctkDICOMBrowser* );
   ~ctkDICOMBrowserPrivate();
 
+  void importDirectory(QString directory, ctkDICOMBrowser::ImportDirectoryMode mode);
+
+  void importOldSettings();
+
   ctkFileDialog* ImportDialog;
+
   ctkDICOMQueryRetrieveWidget* QueryRetrieveWidget;
 
   QSharedPointer<ctkDICOMDatabase> DICOMDatabase;
@@ -93,9 +102,42 @@ public:
 };
 
 //----------------------------------------------------------------------------
+class ctkDICOMImportStats
+{
+public:
+  ctkDICOMImportStats(ctkDICOMBrowserPrivate* dicomBrowserPrivate) :
+    DICOMBrowserPrivate(dicomBrowserPrivate)
+  {
+    // reset counts
+    ctkDICOMBrowserPrivate* d = this->DICOMBrowserPrivate;
+    d->PatientsAddedDuringImport = 0;
+    d->StudiesAddedDuringImport = 0;
+    d->SeriesAddedDuringImport = 0;
+    d->InstancesAddedDuringImport = 0;
+  }
+
+  QString summary()
+  {
+    ctkDICOMBrowserPrivate* d = this->DICOMBrowserPrivate;
+    QString message = "Directory import completed.\n\n";
+    message += QString("%1 New Patients\n").arg(QString::number(d->PatientsAddedDuringImport));
+    message += QString("%1 New Studies\n").arg(QString::number(d->StudiesAddedDuringImport));
+    message += QString("%1 New Series\n").arg(QString::number(d->SeriesAddedDuringImport));
+    message += QString("%1 New Instances\n").arg(QString::number(d->InstancesAddedDuringImport));
+    return message;
+  }
+
+  ctkDICOMBrowserPrivate* DICOMBrowserPrivate;
+};
+
+//----------------------------------------------------------------------------
 // ctkDICOMBrowserPrivate methods
 
-ctkDICOMBrowserPrivate::ctkDICOMBrowserPrivate(ctkDICOMBrowser* parent): q_ptr(parent){
+//----------------------------------------------------------------------------
+ctkDICOMBrowserPrivate::ctkDICOMBrowserPrivate(ctkDICOMBrowser* parent): q_ptr(parent)
+{
+  ImportDialog = 0;
+  QueryRetrieveWidget = 0;
   DICOMDatabase = QSharedPointer<ctkDICOMDatabase> (new ctkDICOMDatabase);
   DICOMIndexer = QSharedPointer<ctkDICOMIndexer> (new ctkDICOMIndexer);
   IndexerProgress = 0;
@@ -108,6 +150,7 @@ ctkDICOMBrowserPrivate::ctkDICOMBrowserPrivate(ctkDICOMBrowser* parent): q_ptr(p
   InstancesAddedDuringImport = 0;
 }
 
+//----------------------------------------------------------------------------
 ctkDICOMBrowserPrivate::~ctkDICOMBrowserPrivate()
 {
   if ( IndexerProgress )
@@ -124,6 +167,7 @@ ctkDICOMBrowserPrivate::~ctkDICOMBrowserPrivate()
     }
 }
 
+//----------------------------------------------------------------------------
 void ctkDICOMBrowserPrivate::showUpdateSchemaDialog()
 {
   Q_Q(ctkDICOMBrowser);
@@ -158,6 +202,7 @@ void ctkDICOMBrowserPrivate::showUpdateSchemaDialog()
   UpdateSchemaProgress->show();
 }
 
+//----------------------------------------------------------------------------
 void ctkDICOMBrowserPrivate::showIndexerDialog()
 {
   Q_Q(ctkDICOMBrowser);
@@ -212,6 +257,8 @@ ctkDICOMBrowser::ctkDICOMBrowser(QWidget* _parent):Superclass(_parent),
 {
   Q_D(ctkDICOMBrowser);
 
+  qRegisterMetaType<ctkDICOMBrowser::ImportDirectoryMode>("ctkDICOMBrowser::ImportDirectoryMode");
+
   d->setupUi(this);
 
   // signals related to tracking inserts
@@ -233,13 +280,12 @@ ctkDICOMBrowser::ctkDICOMBrowser(QWidget* _parent):Superclass(_parent),
 
   //initialize directory from settings, then listen for changes
   QSettings settings;
-  if ( settings.value("DatabaseDirectory", "") == "" )
+  if ( settings.value(Self::databaseDirectorySettingsKey(), "") == "" )
     {
-    QString directory = QString("./ctkDICOM-Database");
-    settings.setValue("DatabaseDirectory", directory);
+    settings.setValue(Self::databaseDirectorySettingsKey(), QString("./ctkDICOM-Database"));
     settings.sync();
     }
-  QString databaseDirectory = settings.value("DatabaseDirectory").toString();
+  QString databaseDirectory = this->databaseDirectory();
   this->setDatabaseDirectory(databaseDirectory);
   d->DirectoryButton->setDirectory(databaseDirectory);
 
@@ -263,18 +309,39 @@ ctkDICOMBrowser::ctkDICOMBrowser(QWidget* _parent):Superclass(_parent),
 
   connect(d->DirectoryButton, SIGNAL(directoryChanged(QString)), this, SLOT(setDatabaseDirectory(QString)));
 
+  // Initialize directoryMode widget
+  QFormLayout *layout = new QFormLayout;
+  QComboBox* importDirectoryModeComboBox = new QComboBox();
+  importDirectoryModeComboBox->addItem("Add Link", ctkDICOMBrowser::ImportDirectoryAddLink);
+  importDirectoryModeComboBox->addItem("Copy", ctkDICOMBrowser::ImportDirectoryCopy);
+  importDirectoryModeComboBox->setToolTip(
+        tr("Indicate if the files should be copied to the local database"
+           " directory or if only links should be created ?"));
+  layout->addRow(new QLabel("Import Directory Mode:"), importDirectoryModeComboBox);
+  layout->setContentsMargins(0, 0, 0, 0);
+  QWidget* importDirectoryBottomWidget = new QWidget();
+  importDirectoryBottomWidget->setLayout(layout);
+
+  // Default values
+  importDirectoryModeComboBox->setCurrentIndex(
+        importDirectoryModeComboBox->findData(this->importDirectoryMode()));
+
   //Initialize import widget
   d->ImportDialog = new ctkFileDialog();
-  QCheckBox* importCheckbox = new QCheckBox("Copy on import", d->ImportDialog);
-  importCheckbox->setCheckState(Qt::Checked);
-  d->ImportDialog->setBottomWidget(importCheckbox);
+  d->ImportDialog->setBottomWidget(importDirectoryBottomWidget);
   d->ImportDialog->setFileMode(QFileDialog::Directory);
+  // XXX Method setSelectionMode must be called after setFileMode
+  d->ImportDialog->setSelectionMode(QAbstractItemView::ExtendedSelection);
   d->ImportDialog->setLabelText(QFileDialog::Accept,"Import");
   d->ImportDialog->setWindowTitle("Import DICOM files from directory ...");
   d->ImportDialog->setWindowModality(Qt::ApplicationModal);
 
   //connect signal and slots
-  connect(d->ImportDialog, SIGNAL(fileSelected(QString)),this,SLOT(onImportDirectory(QString)));
+  connect(d->ImportDialog, SIGNAL(filesSelected(QStringList)),
+          this,SLOT(onImportDirectoriesSelected(QStringList)));
+
+  connect(importDirectoryModeComboBox, SIGNAL(currentIndexChanged(int)),
+          this, SLOT(onImportDirectoryComboBoxCurrentIndexChanged(int)));
 
   connect(d->QueryRetrieveWidget, SIGNAL(canceled()), d->QueryRetrieveWidget, SLOT(hide()) );
   connect(d->QueryRetrieveWidget, SIGNAL(canceled()), this, SLOT(onQueryRetrieveFinished()) );
@@ -352,8 +419,14 @@ void ctkDICOMBrowser::setDatabaseDirectory(const QString& directory)
 {
   Q_D(ctkDICOMBrowser);
 
+  // If needed, create database directory
+  if (!QDir(directory).exists())
+    {
+    QDir().mkdir(directory);
+    }
+
   QSettings settings;
-  settings.setValue("DatabaseDirectory", directory);
+  settings.setValue(Self::databaseDirectorySettingsKey(), directory);
   settings.sync();
 
   //close the active DICOM database
@@ -387,8 +460,13 @@ void ctkDICOMBrowser::setDatabaseDirectory(const QString& directory)
 //----------------------------------------------------------------------------
 QString ctkDICOMBrowser::databaseDirectory() const
 {
-  QSettings settings;
-  return settings.value("DatabaseDirectory").toString();
+  return QSettings().value(Self::databaseDirectorySettingsKey()).toString();
+}
+
+//------------------------------------------------------------------------------
+QString ctkDICOMBrowser::databaseDirectorySettingsKey()
+{
+  return QLatin1String("DatabaseDirectory");
 }
 
 //------------------------------------------------------------------------------
@@ -405,8 +483,6 @@ const QStringList ctkDICOMBrowser::tagsToPrecache()
   return d->DICOMDatabase->tagsToPrecache();
 }
 
-
-
 //----------------------------------------------------------------------------
 ctkDICOMDatabase* ctkDICOMBrowser::database(){
   Q_D(ctkDICOMBrowser);
@@ -423,6 +499,7 @@ ctkDICOMTableManager* ctkDICOMBrowser::dicomTableManager()
 //----------------------------------------------------------------------------
 void ctkDICOMBrowser::onFileIndexed(const QString& filePath)
 {
+  Q_UNUSED(filePath);
 }
 
 //----------------------------------------------------------------------------
@@ -603,61 +680,125 @@ void ctkDICOMBrowser::onInstanceAdded(QString instanceUID)
 }
 
 //----------------------------------------------------------------------------
-void ctkDICOMBrowser::onImportDirectory(QString directory)
+void ctkDICOMBrowser::onImportDirectoriesSelected(QStringList directories)
 {
   Q_D(ctkDICOMBrowser);
+  this->importDirectories(directories, this->importDirectoryMode());
 
-  if (QDir(directory).exists())
-  {
-    QString targetDirectory;
+  // Clear selection
+  d->ImportDialog->clearSelection();
+}
 
-    QCheckBox* copyOnImport = qobject_cast<QCheckBox*>(d->ImportDialog->bottomWidget());
+//----------------------------------------------------------------------------
+void ctkDICOMBrowser::onImportDirectoryComboBoxCurrentIndexChanged(int index)
+{
+  Q_D(ctkDICOMBrowser);
+  Q_UNUSED(index);
+  QComboBox* comboBox = d->ImportDialog->bottomWidget()->findChild<QComboBox*>();
+  ctkDICOMBrowser::ImportDirectoryMode mode =
+      static_cast<ctkDICOMBrowser::ImportDirectoryMode>(comboBox->itemData(index).toInt());
+  this->setImportDirectoryMode(mode);
+}
 
-    ctkMessageBox importTypeDialog;
-    QString message("Do you want to copy the files to the local database directory or just add the links?");
-    importTypeDialog.setText(message);
-    importTypeDialog.setIcon(QMessageBox::Question);
+//----------------------------------------------------------------------------
+void ctkDICOMBrowser::importDirectories(QStringList directories, ctkDICOMBrowser::ImportDirectoryMode mode)
+{
+  Q_D(ctkDICOMBrowser);
+  ctkDICOMImportStats stats(d);
+  foreach (const QString& directory, directories)
+    {
+    d->importDirectory(directory, mode);
+    }
 
-    importTypeDialog.addButton("Copy",QMessageBox::AcceptRole);
-    importTypeDialog.addButton("Add Link",QMessageBox::RejectRole);
-    importTypeDialog.setDontShowAgainSettingsKey( "MainWindow/DontConfirmCopyOnImport" );
-    int selection = importTypeDialog.exec();
+  if (d->DisplayImportSummary)
+    {
+    QMessageBox::information(this,"DICOM Directory Import", stats.summary());
+    }
+}
 
-    if (selection== QMessageBox::AcceptRole)
+//----------------------------------------------------------------------------
+void ctkDICOMBrowser::importDirectory(QString directory, ctkDICOMBrowser::ImportDirectoryMode mode)
+{
+  Q_D(ctkDICOMBrowser);
+  ctkDICOMImportStats stats(d);
+  d->importDirectory(directory, mode);
+  if (d->DisplayImportSummary)
     {
-      copyOnImport->setCheckState(Qt::Checked);
+    QMessageBox::information(this,"DICOM Directory Import", stats.summary());
     }
-    else
+}
+
+//----------------------------------------------------------------------------
+void ctkDICOMBrowser::onImportDirectory(QString directory, ctkDICOMBrowser::ImportDirectoryMode mode)
+{
+  this->importDirectory(directory, mode);
+}
+
+//----------------------------------------------------------------------------
+void ctkDICOMBrowserPrivate::importDirectory(QString directory, ctkDICOMBrowser::ImportDirectoryMode mode)
+{
+  if (!QDir(directory).exists())
     {
-      copyOnImport->setCheckState(Qt::Unchecked);
+    return;
     }
 
-    // reset counts
-    d->PatientsAddedDuringImport = 0;
-    d->StudiesAddedDuringImport = 0;
-    d->SeriesAddedDuringImport = 0;
-    d->InstancesAddedDuringImport = 0;
+  QString targetDirectory;
+  if (mode == ctkDICOMBrowser::ImportDirectoryCopy)
+    {
+    targetDirectory = this->DICOMDatabase->databaseDirectory();
+    }
+
+  // show progress dialog and perform indexing
+  this->showIndexerDialog();
+  this->DICOMIndexer->addDirectory(*this->DICOMDatabase, directory, targetDirectory);
+}
 
-    if (copyOnImport->checkState() == Qt::Checked)
+//----------------------------------------------------------------------------
+void ctkDICOMBrowserPrivate::importOldSettings()
+{
+  Q_Q(ctkDICOMBrowser);
+  // Backward compatibility
+  QSettings settings;
+  int dontConfirmCopyOnImport = settings.value("MainWindow/DontConfirmCopyOnImport", static_cast<int>(QMessageBox::InvalidRole)).toInt();
+  if (dontConfirmCopyOnImport == QMessageBox::AcceptRole)
     {
-      targetDirectory = d->DICOMDatabase->databaseDirectory();
+    settings.setValue("DICOM/ImportDirectoryMode", static_cast<int>(ctkDICOMBrowser::ImportDirectoryCopy));
     }
+  settings.remove("MainWindow/DontConfirmCopyOnImport");
+}
 
-    // show progress dialog and perform indexing
-    d->showIndexerDialog();
-    d->DICOMIndexer->addDirectory(*d->DICOMDatabase,directory,targetDirectory);
+//----------------------------------------------------------------------------
+ctkFileDialog* ctkDICOMBrowser::importDialog() const
+{
+  Q_D(const ctkDICOMBrowser);
+  return d->ImportDialog;
+}
 
-    // display summary result
-    if (d->DisplayImportSummary)
+//----------------------------------------------------------------------------
+ctkDICOMBrowser::ImportDirectoryMode ctkDICOMBrowser::importDirectoryMode()const
+{
+  Q_D(const ctkDICOMBrowser);
+  ctkDICOMBrowserPrivate* mutable_d =
+    const_cast<ctkDICOMBrowserPrivate*>(d);
+  mutable_d->importOldSettings();
+  QSettings settings;
+  return static_cast<ctkDICOMBrowser::ImportDirectoryMode>(settings.value(
+        "DICOM/ImportDirectoryMode", static_cast<int>(ctkDICOMBrowser::ImportDirectoryAddLink)).toInt());
+}
+
+//----------------------------------------------------------------------------
+void ctkDICOMBrowser::setImportDirectoryMode(ctkDICOMBrowser::ImportDirectoryMode mode)
+{
+  Q_D(ctkDICOMBrowser);
+
+  QSettings settings;
+  settings.setValue("DICOM/ImportDirectoryMode", static_cast<int>(mode));
+  if (!d->ImportDialog)
     {
-      QString message = "Directory import completed.\n\n";
-      message += QString("%1 New Patients\n").arg(QString::number(d->PatientsAddedDuringImport));
-      message += QString("%1 New Studies\n").arg(QString::number(d->StudiesAddedDuringImport));
-      message += QString("%1 New Series\n").arg(QString::number(d->SeriesAddedDuringImport));
-      message += QString("%1 New Instances\n").arg(QString::number(d->InstancesAddedDuringImport));
-      QMessageBox::information(this,"DICOM Directory Import", message);
+    return;
     }
-  }
+  QComboBox* comboBox = d->ImportDialog->bottomWidget()->findChild<QComboBox*>();
+  comboBox->setCurrentIndex(comboBox->findData(mode));
 }
 
 //----------------------------------------------------------------------------

+ 93 - 9
Libs/DICOM/Widgets/ctkDICOMBrowser.h

@@ -21,30 +21,51 @@
 #ifndef __ctkDICOMBrowser_h
 #define __ctkDICOMBrowser_h
 
-// Qt includes 
+// Qt includes
 #include <QItemSelection>
 #include <QWidget>
 
 #include "ctkDICOMWidgetsExport.h"
 
 class ctkDICOMBrowserPrivate;
+class ctkDICOMDatabase;
+class ctkDICOMTableManager;
+class ctkFileDialog;
 class ctkThumbnailLabel;
 class QMenu;
 class QModelIndex;
-class ctkDICOMDatabase;
-class ctkDICOMTableManager;
 
 /// \ingroup DICOM_Widgets
+///
+/// \brief The DICOM browser widget provides an interface to organize DICOM
+/// data stored in a local ctkDICOMDatabase.
+///
+/// Using a local database avoids redundant calculations and speed up subsequent
+/// access.
+///
+/// Supported operations are:
+///
+/// * Import
+/// * Export
+/// * Send
+/// * Query
+/// * Remove
+/// * Repair
+///
 class CTK_DICOM_WIDGETS_EXPORT ctkDICOMBrowser : public QWidget
 {
   Q_OBJECT
+  Q_ENUMS(ImportDirectoryMode)
   Q_PROPERTY(ctkDICOMDatabase* database READ database)
   Q_PROPERTY(QString databaseDirectory READ databaseDirectory WRITE setDatabaseDirectory)
   Q_PROPERTY(QStringList tagsToPrecache READ tagsToPrecache WRITE setTagsToPrecache)
   Q_PROPERTY(bool displayImportSummary READ displayImportSummary WRITE setDisplayImportSummary)
   Q_PROPERTY(ctkDICOMTableManager* dicomTableManager READ dicomTableManager)
+  Q_PROPERTY(ctkDICOMBrowser::ImportDirectoryMode ImportDirectoryMode READ importDirectoryMode WRITE setImportDirectoryMode)
 
 public:
+  typedef ctkDICOMBrowser Self;
+
   typedef QWidget Superclass;
   explicit ctkDICOMBrowser(QWidget* parent=0);
   virtual ~ctkDICOMBrowser();
@@ -52,6 +73,9 @@ public:
   /// Directory being used to store the dicom database
   QString databaseDirectory() const;
 
+  /// Return settings key used to store the directory.
+  static QString databaseDirectorySettingsKey();
+
   /// See ctkDICOMDatabase for description - these accessors
   /// delegate to the corresponding routines of the internal
   /// instance of the database.
@@ -79,11 +103,44 @@ public:
   int seriesAddedDuringImport();
   int instancesAddedDuringImport();
 
+  enum ImportDirectoryMode
+  {
+    ImportDirectoryCopy = 0,
+    ImportDirectoryAddLink
+  };
+
+  /// \brief Get value of ImportDirectoryMode settings.
+  ///
+  /// \sa setImportDirectoryMode(ctkDICOMBrowser::ImportDirectoryMode)
+  ctkDICOMBrowser::ImportDirectoryMode importDirectoryMode()const;
+
+  /// \brief Return instance of import dialog.
+  ///
+  /// \internal
+  Q_INVOKABLE ctkFileDialog* importDialog()const;
+
 public Q_SLOTS:
+
+  /// \brief Set value of ImportDirectoryMode settings.
+  ///
+  /// Setting the value will update the comboBox found at the bottom
+  /// of the import dialog.
+  ///
+  /// \sa importDirectoryMode()
+  void setImportDirectoryMode(ctkDICOMBrowser::ImportDirectoryMode mode);
+
   void setDatabaseDirectory(const QString& directory);
   void onFileIndexed(const QString& filePath);
 
+  /// \brief Pop-up file dialog allowing to select and import one or multiple
+  /// DICOM directories.
+  ///
+  /// The dialog is extented with two additional controls:
+  ///
+  /// * **ImportDirectoryMode** combox: Allow user to select "Add Link" or "Copy" mode.
+  ///   Associated settings is stored using key `DICOM/ImportDirectoryMode`.
   void openImportDialog();
+
   void openExportDialog();
   void openQueryDialog();
   void onRemoveAction();
@@ -91,12 +148,26 @@ public Q_SLOTS:
 
   void onTablesDensityComboBox(QString);
 
-  /// Import a directory - this is used when the user selects a directory
-  /// from the Import Dialog, but can also be used externally to trigger
-  /// an import (i.e. for testing or to support drag-and-drop)
-  void onImportDirectory(QString directory);
-
-  /// slots to capture status updates from the database during an 
+  /// \brief Import directories
+  ///
+  /// This can be used to externally trigger an import (i.e. for testing or to support drag-and-drop)
+  ///
+  /// By default, \a mode is ImportDirectoryMode::ImportDirectoryAddLink is set.
+  ///
+  /// \sa importDirectory(QString directory, int mode)
+  void importDirectories(QStringList directories, ctkDICOMBrowser::ImportDirectoryMode mode = ImportDirectoryAddLink);
+
+  /// \brief Import a directory
+  ///
+  /// This can be used to externally trigger an import (i.e. for testing or to support drag-and-drop)
+  ///
+  /// By default, \a mode is ImportDirectoryMode::ImportDirectoryAddLink is set.
+  void importDirectory(QString directory, ctkDICOMBrowser::ImportDirectoryMode mode = ImportDirectoryAddLink);
+
+  /// \deprecated importDirectory() should be used
+  void onImportDirectory(QString directory, ctkDICOMBrowser::ImportDirectoryMode mode = ImportDirectoryAddLink);
+
+  /// slots to capture status updates from the database during an
   /// import operation
   void onPatientAdded(int, QString, QString, QString);
   void onStudyAdded(QString);
@@ -123,6 +194,17 @@ protected:
     bool confirmDeleteSelectedUIDs(QStringList uids);
 
 protected Q_SLOTS:
+
+    /// \brief Import directories
+    ///
+    /// This is used when user selected one or multiple
+    /// directories from the Import Dialog.
+    ///
+    /// \sa importDirectories(QString directory, int mode)
+    void onImportDirectoriesSelected(QStringList directories);
+
+    void onImportDirectoryComboBoxCurrentIndexChanged(int index);
+
     void onModelSelected(const QItemSelection&, const QItemSelection&);
 
     /// Called when a right mouse click is made in the patients table
@@ -152,4 +234,6 @@ private:
   Q_DISABLE_COPY(ctkDICOMBrowser);
 };
 
+Q_DECLARE_METATYPE(ctkDICOMBrowser::ImportDirectoryMode)
+
 #endif

+ 5 - 1
Libs/Widgets/Testing/Cpp/ctkFileDialogEventTranslatorPlayerTest1.cpp

@@ -52,8 +52,12 @@ void checkFinalWidgetState(void* data)
   QStringList expectedSelectedFiles;
   expectedSelectedFiles << CTK_SOURCE_DIR"/ctkLogo.png";
   expectedSelectedFiles << CTK_SOURCE_DIR"/ctkLogo-small.png";
+  expectedSelectedFiles.sort();
 
-  CTKCOMPARE(widget->selectedFiles(), expectedSelectedFiles);
+  QStringList selectedFiles = widget->selectedFiles();
+  selectedFiles.sort();
+
+  CTKCOMPARE(selectedFiles, expectedSelectedFiles);
 }
 
 //-----------------------------------------------------------------------------

+ 37 - 0
Libs/Widgets/Testing/Cpp/ctkFileDialogTest1.cpp

@@ -23,6 +23,7 @@
 #include <QCheckBox>
 
 // CTK includes
+#include "ctkCoreTestingMacros.h"
 #include "ctkFileDialog.h"
 
 // STD includes
@@ -30,6 +31,38 @@
 #include <iostream>
 
 //-----------------------------------------------------------------------------
+int testSelectionMode(ctkFileDialog* fileDialog)
+{
+  fileDialog->setSelectionMode(QAbstractItemView::ExtendedSelection);
+  CHECK_INT(fileDialog->selectionMode(), static_cast<int>(QAbstractItemView::ExtendedSelection));
+
+  // Due to limitation of QFileDialog API, calling setFileMode resets
+  // the selection mode.
+
+  fileDialog->setSelectionMode(QAbstractItemView::ExtendedSelection);
+  fileDialog->setFileMode(QFileDialog::Directory);
+  CHECK_INT(fileDialog->selectionMode(), static_cast<int>(QAbstractItemView::SingleSelection));
+
+  fileDialog->setSelectionMode(QAbstractItemView::ExtendedSelection);
+  fileDialog->setFileMode(QFileDialog::DirectoryOnly);
+  CHECK_INT(fileDialog->selectionMode(), static_cast<int>(QAbstractItemView::SingleSelection));
+
+  fileDialog->setSelectionMode(QAbstractItemView::ExtendedSelection);
+  fileDialog->setFileMode(QFileDialog::ExistingFile);
+  CHECK_INT(fileDialog->selectionMode(), static_cast<int>(QAbstractItemView::SingleSelection));
+
+  fileDialog->setSelectionMode(QAbstractItemView::ExtendedSelection);
+  fileDialog->setFileMode(QFileDialog::ExistingFiles);
+  CHECK_INT(fileDialog->selectionMode(), static_cast<int>(QAbstractItemView::ExtendedSelection));
+
+  fileDialog->setSelectionMode(QAbstractItemView::ExtendedSelection);
+  fileDialog->setFileMode(QFileDialog::AnyFile);
+  CHECK_INT(fileDialog->selectionMode(), static_cast<int>(QAbstractItemView::SingleSelection));
+
+  return EXIT_SUCCESS;
+}
+
+//-----------------------------------------------------------------------------
 int ctkFileDialogTest1(int argc, char * argv [] )
 {
   QApplication app(argc, argv);
@@ -44,6 +77,9 @@ int ctkFileDialogTest1(int argc, char * argv [] )
     {
     return EXIT_FAILURE;
     }
+
+  CHECK_EXIT_SUCCESS(testSelectionMode(&fileDialog));
+
   // the following is only in interactive mode
   if (argc < 2 || QString(argv[1]) != "-I" )
     {
@@ -56,6 +92,7 @@ int ctkFileDialogTest1(int argc, char * argv [] )
     {
     return EXIT_FAILURE;
     }
+
   return EXIT_SUCCESS;
 
 }

+ 44 - 0
Libs/Widgets/ctkFileDialog.cpp

@@ -28,6 +28,7 @@
 #include <QLineEdit>
 #include <QListView>
 #include <QPushButton>
+#include <QTreeView>
 
 // CTK includes
 #include "ctkFileDialog.h"
@@ -45,6 +46,7 @@ public:
 
   QPushButton* acceptButton()const;
   QListView* listView()const;
+  QTreeView* treeView()const;
 
   bool AcceptButtonEnable;
   bool AcceptButtonState;
@@ -93,6 +95,15 @@ QListView* ctkFileDialogPrivate::listView()const
 }
 
 //------------------------------------------------------------------------------
+QTreeView* ctkFileDialogPrivate::treeView()const
+{
+  Q_Q(const ctkFileDialog);
+  QTreeView* treeView = q->findChild<QTreeView*>();
+  Q_ASSERT(treeView);
+  return treeView;
+}
+
+//------------------------------------------------------------------------------
 void ctkFileDialogPrivate::observeAcceptButton()
 {
   Q_Q(ctkFileDialog);
@@ -176,6 +187,39 @@ QWidget* ctkFileDialog::bottomWidget()const
 }
 
 //------------------------------------------------------------------------------
+void ctkFileDialog::setSelectionMode(QAbstractItemView::SelectionMode mode)
+{
+  Q_D(ctkFileDialog);
+  foreach(QAbstractItemView* view, QList<QAbstractItemView*>()
+          << d->listView()
+          << d->treeView()
+          )
+    {
+    view->setSelectionMode(mode);
+    }
+}
+
+//------------------------------------------------------------------------------
+QAbstractItemView::SelectionMode ctkFileDialog::selectionMode() const
+{
+  Q_D(const ctkFileDialog);
+  return d->listView()->selectionMode();
+}
+
+//------------------------------------------------------------------------------
+void ctkFileDialog::clearSelection()
+{
+  Q_D(ctkFileDialog);
+  foreach(QAbstractItemView* view, QList<QAbstractItemView*>()
+          << d->listView()
+          << d->treeView()
+          )
+    {
+    view->clearSelection();
+    }
+}
+
+//------------------------------------------------------------------------------
 void ctkFileDialog::setAcceptButtonEnable(bool enable)
 {
   Q_D(ctkFileDialog);

+ 20 - 0
Libs/Widgets/ctkFileDialog.h

@@ -22,6 +22,7 @@
 #define __ctkFileDialog_h
 
 // Qt includes
+#include <QAbstractItemView>
 #include <QFileDialog>
 
 // CTK includes
@@ -41,6 +42,7 @@ class ctkFileDialogPrivate;
 class CTK_WIDGETS_EXPORT ctkFileDialog : public QFileDialog
 {
   Q_OBJECT
+  Q_PROPERTY(QAbstractItemView::SelectionMode SelectionMode READ selectionMode WRITE setSelectionMode)
 
 public:
   // Superclass typedef
@@ -62,6 +64,21 @@ public:
   /// Return the extra widget if any
   Q_INVOKABLE QWidget* bottomWidget()const;
 
+  /// Set the selection mode the views operate in.
+  ///
+  /// \warning The selection mode must explicitly be set each time
+  /// QFileDialog::setFileMode(FileMode mode) is invoked. This is required
+  /// because the QFileDialog::setFileMode(FileMode mode) method is not virtual
+  /// and it internally resets the selection mode.
+  ///
+  /// \sa clearSelection()
+  void setSelectionMode(QAbstractItemView::SelectionMode mode);
+
+  /// Get the selection mode of the views.
+  ///
+  /// \sa setSelectionMode(QAbstractItemView::SelectionMode)
+  QAbstractItemView::SelectionMode selectionMode() const;
+
   /// Internally used
   bool eventFilter(QObject *obj, QEvent *event);
 
@@ -71,6 +88,9 @@ public Q_SLOTS:
   /// dialog if a value is not set in the extra bottom widget.
   void setAcceptButtonEnable(bool enable);
 
+  /// Deselect all selected directories or files.
+  void clearSelection();
+
 Q_SIGNALS:
   /// Signals QFileDialog::file[s]Selected() are fired only when the Ok button
   /// is pressed, fileSelectionChanged(QStringList) is emitted when the