Prechádzať zdrojové kódy

Reworked ctkDirectoryListWidget for improved flexibility.

There is now a ctkPathListWidget which is a specialized QListView and
a separate ctkPathListButtonsWidget which is meant to work together
with a ctkPathListWidget instance. Both widgets can be layed out and
customized in the Qt designer independently.
Sascha Zelzer 12 rokov pred
rodič
commit
3f28910a32

+ 12 - 9
Libs/Widgets/CMakeLists.txt

@@ -63,11 +63,6 @@ set(KIT_SRCS
   ctkDateRangeWidget.h
   ctkDirectoryButton.cpp
   ctkDirectoryButton.h
-  ctkDirectoryListView.cpp
-  ctkDirectoryListView.h
-  ctkDirectoryListWidget.cpp
-  ctkDirectoryListWidget.h
-  ctkDirectoryListWidget_p.h
   ctkDoubleRangeSlider.cpp
   ctkDoubleRangeSlider.h
   ctkDoubleSlider.cpp
@@ -113,6 +108,11 @@ set(KIT_SRCS
   ctkModalityWidget.h
   ctkPathLineEdit.cpp
   ctkPathLineEdit.h
+  ctkPathListButtonsWidget.cpp
+  ctkPathListButtonsWidget.h
+  ctkPathListButtonsWidget_p.h
+  ctkPathListWidget.cpp
+  ctkPathListWidget.h
   ctkPixmapIconEngine.cpp
   ctkPixmapIconEngine.h
   ctkPopupWidget.cpp
@@ -208,9 +208,6 @@ set(KIT_MOC_SRCS
   ctkCrosshairLabel.h
   ctkDateRangeWidget.h
   ctkDirectoryButton.h
-  ctkDirectoryListView.h
-  ctkDirectoryListWidget.h
-  ctkDirectoryListWidget_p.h
   ctkDoubleRangeSlider.h
   ctkDoubleSlider.h
   ctkDynamicSpacer.h
@@ -234,6 +231,8 @@ set(KIT_MOC_SRCS
   ctkMessageBox.h
   ctkModalityWidget.h
   ctkPathLineEdit.h
+  ctkPathListButtonsWidget.h
+  ctkPathListButtonsWidget_p.h
   ctkPopupWidget.h
   ctkPopupWidget_p.h
   ctkQImageView.h
@@ -269,6 +268,10 @@ set(KIT_MOC_SRCS
   ctkWorkflowWidgetStep_p.h
   )
 
+QT4_GENERATE_MOCS(
+  ctkPathListWidget.h
+)
+
 # UI files
 set(KIT_UI_FORMS
   Resources/UI/ctkAddRemoveComboBox.ui
@@ -277,13 +280,13 @@ set(KIT_UI_FORMS
   Resources/UI/ctkErrorLogWidget.ui
   Resources/UI/ctkMaterialPropertyWidget.ui
   Resources/UI/ctkModalityWidget.ui
+  Resources/UI/ctkPathListButtonsWidget.ui
   Resources/UI/ctkScreenshotDialog.ui
   Resources/UI/ctkSettingsDialog.ui
   Resources/UI/ctkSliderWidget.ui
   Resources/UI/ctkThumbnailLabel.ui
   Resources/UI/ctkThumbnailListWidget.ui
   Resources/UI/ctkWorkflowGroupBox.ui
-  Resources/UI/ctkDirectoryListWidget.ui
   )
 
 # Resources

+ 6 - 0
Libs/Widgets/Plugins/CMakeLists.txt

@@ -64,6 +64,10 @@ set(PLUGIN_SRCS
   ctkModalityWidgetPlugin.h
   ctkPathLineEditPlugin.cpp
   ctkPathLineEditPlugin.h
+  ctkPathListButtonsWidgetPlugin.cpp
+  ctkPathListButtonsWidgetPlugin.h
+  ctkPathListWidgetPlugin.cpp
+  ctkPathListWidgetPlugin.h
   ctkPopupWidgetPlugin.cpp
   ctkPopupWidgetPlugin.h
   ctkRangeSliderPlugin.cpp
@@ -120,6 +124,8 @@ set(PLUGIN_MOC_SRCS
   ctkMenuButtonPlugin.h
   ctkModalityWidgetPlugin.h
   ctkPathLineEditPlugin.h
+  ctkPathListButtonsWidgetPlugin.h
+  ctkPathListWidgetPlugin.h
   ctkPopupWidgetPlugin.h
   ctkRangeSliderPlugin.h
   ctkRangeWidgetPlugin.h

+ 70 - 0
Libs/Widgets/Plugins/ctkPathListButtonsWidgetPlugin.cpp

@@ -0,0 +1,70 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  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.apache.org/licenses/LICENSE-2.0.txt
+
+  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.
+
+=========================================================================*/
+
+// CTK includes
+#include "ctkPathListButtonsWidgetPlugin.h"
+#include "ctkPathListButtonsWidget.h"
+
+//-----------------------------------------------------------------------------
+ctkPathListButtonsWidgetPlugin::ctkPathListButtonsWidgetPlugin(QObject* pluginParent)
+  : QObject(pluginParent)
+{
+
+}
+
+//-----------------------------------------------------------------------------
+QWidget *ctkPathListButtonsWidgetPlugin::createWidget(QWidget* parentForWidget)
+{
+  ctkPathListButtonsWidget* newWidget = new ctkPathListButtonsWidget(parentForWidget);
+  return newWidget;
+}
+
+//-----------------------------------------------------------------------------
+QString ctkPathListButtonsWidgetPlugin::domXml() const
+{
+  return "<widget class=\"ctkPathListButtonsWidget\" \
+          name=\"PathListButtonsWidget\">\n"
+          "</widget>\n";
+}
+
+// --------------------------------------------------------------------------
+QIcon ctkPathListButtonsWidgetPlugin::icon() const
+{
+  return QIcon();
+}
+
+//-----------------------------------------------------------------------------
+QString ctkPathListButtonsWidgetPlugin::includeFile() const
+{
+  return "ctkPathListButtonsWidget.h";
+}
+
+//-----------------------------------------------------------------------------
+bool ctkPathListButtonsWidgetPlugin::isContainer() const
+{
+  return false;
+}
+
+//-----------------------------------------------------------------------------
+QString ctkPathListButtonsWidgetPlugin::name() const
+{
+  return "ctkPathListButtonsWidget";
+}

+ 46 - 0
Libs/Widgets/Plugins/ctkPathListButtonsWidgetPlugin.h

@@ -0,0 +1,46 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  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.apache.org/licenses/LICENSE-2.0.txt
+
+  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.
+
+=========================================================================*/
+
+#ifndef __ctkPathListButtonsWidgetPlugin_h
+#define __ctkPathListButtonsWidgetPlugin_h
+
+// CTK includes
+#include "ctkWidgetsAbstractPlugin.h"
+
+class CTK_WIDGETS_PLUGINS_EXPORT ctkPathListButtonsWidgetPlugin :
+  public QObject,
+  public ctkWidgetsAbstractPlugin
+{
+  Q_OBJECT
+
+public:
+  ctkPathListButtonsWidgetPlugin(QObject *_parent = 0);
+
+  QWidget *createWidget(QWidget *_parent);
+  QString  domXml() const;
+  QIcon    icon() const;
+  QString  includeFile() const;
+  bool     isContainer() const;
+  QString  name() const;
+
+};
+
+#endif

+ 70 - 0
Libs/Widgets/Plugins/ctkPathListWidgetPlugin.cpp

@@ -0,0 +1,70 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  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.apache.org/licenses/LICENSE-2.0.txt
+
+  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.
+
+=========================================================================*/
+
+// CTK includes
+#include "ctkPathListWidgetPlugin.h"
+#include "ctkPathListWidget.h"
+
+//-----------------------------------------------------------------------------
+ctkPathListWidgetPlugin::ctkPathListWidgetPlugin(QObject* pluginParent)
+  : QObject(pluginParent)
+{
+
+}
+
+//-----------------------------------------------------------------------------
+QWidget *ctkPathListWidgetPlugin::createWidget(QWidget* parentForWidget)
+{
+  ctkPathListWidget* newWidget = new ctkPathListWidget(parentForWidget);
+  return newWidget;
+}
+
+//-----------------------------------------------------------------------------
+QString ctkPathListWidgetPlugin::domXml() const
+{
+  return "<widget class=\"ctkPathListWidget\" \
+          name=\"PathListWidget\">\n"
+          "</widget>\n";
+}
+
+// --------------------------------------------------------------------------
+QIcon ctkPathListWidgetPlugin::icon() const
+{
+  return QIcon(":/Icons/listview.png");
+}
+
+//-----------------------------------------------------------------------------
+QString ctkPathListWidgetPlugin::includeFile() const
+{
+  return "ctkPathListWidget.h";
+}
+
+//-----------------------------------------------------------------------------
+bool ctkPathListWidgetPlugin::isContainer() const
+{
+  return false;
+}
+
+//-----------------------------------------------------------------------------
+QString ctkPathListWidgetPlugin::name() const
+{
+  return "ctkPathListWidget";
+}

+ 46 - 0
Libs/Widgets/Plugins/ctkPathListWidgetPlugin.h

@@ -0,0 +1,46 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  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.apache.org/licenses/LICENSE-2.0.txt
+
+  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.
+
+=========================================================================*/
+
+#ifndef __ctkPathListWidgetPlugin_h
+#define __ctkPathListWidgetPlugin_h
+
+// CTK includes
+#include "ctkWidgetsAbstractPlugin.h"
+
+class CTK_WIDGETS_PLUGINS_EXPORT ctkPathListWidgetPlugin :
+  public QObject,
+  public ctkWidgetsAbstractPlugin
+{
+  Q_OBJECT
+
+public:
+  ctkPathListWidgetPlugin(QObject *_parent = 0);
+
+  QWidget *createWidget(QWidget *_parent);
+  QString  domXml() const;
+  QIcon    icon() const;
+  QString  includeFile() const;
+  bool     isContainer() const;
+  QString  name() const;
+
+};
+
+#endif

+ 4 - 0
Libs/Widgets/Plugins/ctkWidgetsPlugins.h

@@ -50,6 +50,8 @@
 #include "ctkMenuButtonPlugin.h"
 #include "ctkModalityWidgetPlugin.h"
 #include "ctkPathLineEditPlugin.h"
+#include "ctkPathListButtonsWidgetPlugin.h"
+#include "ctkPathListWidgetPlugin.h"
 #include "ctkPopupWidgetPlugin.h"
 #include "ctkRangeSliderPlugin.h"
 #include "ctkRangeWidgetPlugin.h"
@@ -98,6 +100,8 @@ public:
             << new ctkMenuButtonPlugin
             << new ctkModalityWidgetPlugin
             << new ctkPathLineEditPlugin
+            << new ctkPathListButtonsWidgetPlugin
+            << new ctkPathListWidgetPlugin
             << new ctkPopupWidgetPlugin
             << new ctkRangeSliderPlugin
             << new ctkRangeWidgetPlugin

+ 0 - 97
Libs/Widgets/Resources/UI/ctkDirectoryListWidget.ui

@@ -1,97 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>ctkDirectoryListWidget</class>
- <widget class="QWidget" name="ctkDirectoryListWidget">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>435</width>
-    <height>294</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>Form</string>
-  </property>
-  <layout class="QHBoxLayout" name="horizontalLayout">
-   <item>
-    <widget class="ctkDirectoryListView" name="DirectoryList" native="true">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
-     </property>
-    </widget>
-   </item>
-   <item>
-    <widget class="QGroupBox" name="GroupBox">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
-     </property>
-     <property name="title">
-      <string>Paths</string>
-     </property>
-     <layout class="QVBoxLayout" name="verticalLayout">
-      <item>
-       <widget class="QPushButton" name="AddButton">
-        <property name="text">
-         <string>Add</string>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <widget class="QPushButton" name="RemoveButton">
-        <property name="text">
-         <string>Remove</string>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <spacer name="ButtonSpacer">
-        <property name="orientation">
-         <enum>Qt::Vertical</enum>
-        </property>
-        <property name="sizeHint" stdset="0">
-         <size>
-          <width>20</width>
-          <height>40</height>
-         </size>
-        </property>
-       </spacer>
-      </item>
-     </layout>
-    </widget>
-   </item>
-   <item>
-    <widget class="ctkExpandButton" name="ExpandButton" native="true">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
-     </property>
-    </widget>
-   </item>
-  </layout>
- </widget>
- <customwidgets>
-  <customwidget>
-   <class>ctkDirectoryListView</class>
-   <extends>QWidget</extends>
-   <header>ctkDirectoryListView.h</header>
-   <container>1</container>
-  </customwidget>
-  <customwidget>
-   <class>ctkExpandButton</class>
-   <extends>QWidget</extends>
-   <header>ctkExpandButton.h</header>
-   <container>1</container>
-  </customwidget>
- </customwidgets>
- <resources/>
- <connections/>
-</ui>

+ 64 - 0
Libs/Widgets/Resources/UI/ctkPathListButtonsWidget.ui

@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ctkPathListButtonsWidget</class>
+ <widget class="QWidget" name="ctkPathListButtonsWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>302</width>
+    <height>25</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QHBoxLayout" name="horizontalLayout">
+   <property name="margin">
+    <number>0</number>
+   </property>
+   <item>
+    <widget class="QToolButton" name="AddFilesButton">
+     <property name="toolTip">
+      <string>Add files</string>
+     </property>
+     <property name="text">
+      <string>Add files</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QToolButton" name="AddDirectoryButton">
+     <property name="toolTip">
+      <string>Add a directory</string>
+     </property>
+     <property name="text">
+      <string>Add directory</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QToolButton" name="RemoveButton">
+     <property name="toolTip">
+      <string>Remove selected entries</string>
+     </property>
+     <property name="text">
+      <string>Remove</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QToolButton" name="EditButton">
+     <property name="toolTip">
+      <string>Edit current entry</string>
+     </property>
+     <property name="text">
+      <string>Edit</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 6 - 0
Libs/Widgets/Testing/Cpp/CMakeLists.txt

@@ -50,6 +50,8 @@ set(TEST_SOURCES
   ctkMessageBoxDontShowAgainTest.cpp
   ctkModalityWidgetTest1.cpp
   ctkPathLineEditTest1.cpp
+  ctkPathListWidgetTest.cpp
+  ctkPathListWidgetWithButtonsTest.cpp
   ctkPopupWidgetTest1.cpp
   ctkRangeSliderTest.cpp
   ctkRangeSliderTest1.cpp
@@ -173,6 +175,8 @@ QT4_GENERATE_MOCS(
   ctkFlatProxyModelTest.cpp
   ctkFontButtonTest.cpp
   ctkMessageBoxDontShowAgainTest.cpp
+  ctkPathListWidgetTest.cpp
+  ctkPathListWidgetWithButtonsTest.cpp
   ctkRangeSliderTest.cpp
   ctkSettingsPanelTest.cpp
   )
@@ -242,6 +246,8 @@ SIMPLE_TEST( ctkMenuComboBoxTest3 )
 SIMPLE_TEST( ctkMessageBoxDontShowAgainTest )
 SIMPLE_TEST( ctkModalityWidgetTest1 )
 SIMPLE_TEST( ctkPathLineEditTest1 )
+SIMPLE_TEST( ctkPathListWidgetTest )
+SIMPLE_TEST( ctkPathListWidgetWithButtonsTest )
 SIMPLE_TEST( ctkPopupWidgetTest1 )
 SIMPLE_TEST( ctkRangeSliderTest )
 SIMPLE_TEST( ctkRangeSliderTest1 )

+ 307 - 0
Libs/Widgets/Testing/Cpp/ctkPathListWidgetTest.cpp

@@ -0,0 +1,307 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  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.apache.org/licenses/LICENSE-2.0.txt
+
+  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 <QCoreApplication>
+#include <QSignalSpy>
+#include <QTimer>
+
+// CTK includes
+#include "ctkPathListWidget.h"
+#include "ctkTest.h"
+
+// STD includes
+#include <cstdlib>
+#include <iostream>
+
+//-----------------------------------------------------------------------------
+class ctkPathListWidgetTester : public QObject
+{
+  Q_OBJECT
+
+private slots:
+
+  void testDefaults();
+  void testChangeOptions();
+  void testMode();
+  void testRelativeAndAbsolutePaths();
+  void testSimilarPaths();
+  void testSetPaths();
+  void testEditPaths();
+};
+
+// ----------------------------------------------------------------------------
+void ctkPathListWidgetTester::testDefaults()
+{
+  ctkPathListWidget pathList;
+
+  QSignalSpy pathListChangedSpy(&pathList, SIGNAL(pathsChanged(QStringList,QStringList)));
+
+  // The default options are Readable and Exists
+
+  QCOMPARE(pathList.fileOptions(), ctkPathListWidget::Exists | ctkPathListWidget::Readable);
+  QCOMPARE(pathList.directoryOptions(), ctkPathListWidget::Exists | ctkPathListWidget::Readable);
+
+  QVERIFY(!pathList.addPath("/shouldnotexist/"));
+  QVERIFY(!pathList.addPath("/should/also/not/exist"));
+
+  QVERIFY(pathList.addPath(QDir::tempPath()));
+  QVERIFY(!pathList.addPath(QDir::tempPath()));
+
+  QVERIFY(!pathList.removePath("/shouldnotexist/"));
+
+
+  QList<QVariant> expectedArgs;
+  expectedArgs.push_back(QStringList(QDir::tempPath()));
+  expectedArgs.push_back(QStringList());
+
+  QCOMPARE(pathList.paths(), expectedArgs.front().toStringList());
+
+  QCOMPARE(pathListChangedSpy.count(), 1);
+  QCOMPARE(pathListChangedSpy.front(), expectedArgs);
+}
+
+// ----------------------------------------------------------------------------
+void ctkPathListWidgetTester::testChangeOptions()
+{
+  ctkPathListWidget pathList;
+
+  QSignalSpy pathListChangedSpy(&pathList, SIGNAL(pathsChanged(QStringList,QStringList)));
+
+  // The default options are Readable and Exists
+
+  QCOMPARE(pathList.fileOptions(), ctkPathListWidget::Exists | ctkPathListWidget::Readable);
+  QCOMPARE(pathList.directoryOptions(), ctkPathListWidget::Exists | ctkPathListWidget::Readable);
+
+  QCOMPARE(pathList.mode(), ctkPathListWidget::Any);
+
+  QTemporaryFile tmpFile;
+  QVERIFY(tmpFile.open());
+
+  QVERIFY(!tmpFile.permissions().testFlag(QFile::ExeOwner));
+
+  QVERIFY(pathList.addPath(tmpFile.fileName()));
+  QCOMPARE(pathList.path(0), tmpFile.fileName());
+  pathList.clear();
+  QCOMPARE(pathList.count(), 0);
+  QCOMPARE(pathList.path(0), QString());
+
+  QCOMPARE(pathListChangedSpy.size(), 2);
+  QCOMPARE(pathListChangedSpy.at(0), QList<QVariant>() << (QStringList() << tmpFile.fileName()) << QStringList());
+  QCOMPARE(pathListChangedSpy.at(1), QList<QVariant>() << QStringList() << (QStringList() << tmpFile.fileName()));
+
+  pathListChangedSpy.clear();
+
+  // Add another temporary non-executable file
+  QTemporaryFile tmpFile2;
+  QVERIFY(tmpFile2.open());
+  QVERIFY(pathList.addPath(tmpFile2.fileName()));
+  QCOMPARE(pathList.count(), 1);
+  QCOMPARE(pathList.path(0), tmpFile2.fileName());
+
+  pathListChangedSpy.clear();
+
+  // Changing the file options. This should invalidate tmpFile2 and remove it
+  pathList.setFileOptions(pathList.fileOptions() | ctkPathListWidget::Executable);
+  QCOMPARE(pathList.fileOptions(), ctkPathListWidget::Exists | ctkPathListWidget::Readable | ctkPathListWidget::Executable);
+  pathList.setDirectoryOptions(pathList.directoryOptions() | ctkPathListWidget::Writable);
+  QCOMPARE(pathList.directoryOptions(), ctkPathListWidget::Exists | ctkPathListWidget::Readable | ctkPathListWidget::Writable);
+
+  QCOMPARE(pathList.count(), 0);
+
+  QCOMPARE(pathListChangedSpy.count(), 1);
+  QCOMPARE(pathListChangedSpy.at(0), QList<QVariant>() << QStringList() << (QStringList() << tmpFile2.fileName()));
+  pathListChangedSpy.clear();
+
+  // The tmp file is not executable, so it should not be added now
+  QVERIFY(!pathList.addPath(tmpFile.fileName()));
+  QVERIFY(pathListChangedSpy.isEmpty());
+
+  QVERIFY(tmpFile.setPermissions(tmpFile.permissions() | QFile::ExeOwner));
+
+  // Try again
+  QVERIFY(pathList.addPath(tmpFile.fileName()));
+  QCOMPARE(pathList.count(), 1);
+  QCOMPARE(pathList.path(0), tmpFile.fileName());
+
+  // Change the file options again. This should not invalidate the executable temporary file
+  pathList.setFileOptions(ctkPathListWidget::Exists | ctkPathListWidget::Readable);
+  QCOMPARE(pathList.fileOptions(), ctkPathListWidget::Exists | ctkPathListWidget::Readable);
+  QCOMPARE(pathList.paths(), QStringList() << tmpFile.fileName());
+
+  // Add the non-executable tmpFile2 again
+  pathList.addPath(tmpFile2.fileName());
+  QCOMPARE(pathList.count(), 2);
+  QCOMPARE(pathList.paths(), QStringList() << tmpFile.fileName() << tmpFile2.fileName());
+
+  QCOMPARE(pathListChangedSpy.count(), 2);
+  pathListChangedSpy.clear();
+
+  // Remove all
+  pathList.clear();
+  QCOMPARE(pathList.count(), 0);
+  QCOMPARE(pathListChangedSpy.count(), 1);
+  QCOMPARE(pathListChangedSpy.at(0), QList<QVariant>() << QStringList()
+           << (QStringList() << tmpFile.fileName() << tmpFile2.fileName()));
+  pathListChangedSpy.clear();
+
+  // Add two at once
+  pathList.addPaths(QStringList() << tmpFile.fileName() << tmpFile2.fileName());
+  QCOMPARE(pathList.count(), 2);
+  QCOMPARE(pathList.path(0), tmpFile.fileName());
+  QCOMPARE(pathList.path(1), tmpFile2.fileName());
+
+  QCOMPARE(pathListChangedSpy.count(), 1);
+  QCOMPARE(pathListChangedSpy.at(0), QList<QVariant>()
+           << (QStringList() << tmpFile.fileName() << tmpFile2.fileName())
+           << QStringList());
+}
+
+// ----------------------------------------------------------------------------
+void ctkPathListWidgetTester::testMode()
+{
+  ctkPathListWidget listWidget;
+
+  listWidget.setFileOptions(ctkPathListWidget::None);
+  QVERIFY(listWidget.fileOptions() == ctkPathListWidget::None);
+  listWidget.setDirectoryOptions(ctkPathListWidget::None);
+  QVERIFY(listWidget.directoryOptions() == ctkPathListWidget::None);
+
+  QVERIFY(listWidget.addPath("/a"));
+  QVERIFY(listWidget.addPath("/a/"));
+
+  listWidget.setMode(ctkPathListWidget::FilesOnly);
+  QVERIFY(listWidget.addPath("/b"));
+  QVERIFY(!listWidget.addPath("/b/"));
+
+  listWidget.setMode(ctkPathListWidget::DirectoriesOnly);
+  QVERIFY(!listWidget.addPath("/c"));
+  QVERIFY(listWidget.addPath("/c/"));
+}
+
+// ----------------------------------------------------------------------------
+void ctkPathListWidgetTester::testRelativeAndAbsolutePaths()
+{
+  ctkPathListWidget listWidget;
+
+  listWidget.setFileOptions(ctkPathListWidget::None);
+  QVERIFY(listWidget.fileOptions() == ctkPathListWidget::None);
+  listWidget.setDirectoryOptions(ctkPathListWidget::None);
+  QVERIFY(listWidget.directoryOptions() == ctkPathListWidget::None);
+
+  QStringList paths = QStringList()
+      << "/some/absolute/path/to/file"
+      << "/some/absolute/path/to/dir/"
+      << "relative/path/to/file"
+      << "relative/path/to/dir/";
+
+  QStringList resultPaths = QStringList()
+      << "/some/absolute/path/to/file"
+      << "/some/absolute/path/to/dir/"
+      << QDir::currentPath() + "/relative/path/to/file"
+      << QDir::currentPath() + "/relative/path/to/dir/";
+
+  QCOMPARE(listWidget.addPaths(paths), resultPaths);
+
+  QCOMPARE(listWidget.path(0), resultPaths[0]);
+  QCOMPARE(listWidget.path(1), resultPaths[1]);
+  QCOMPARE(listWidget.path(2), resultPaths[2]);
+  QCOMPARE(listWidget.path(3), resultPaths[3]);
+
+  QCOMPARE(listWidget.files(), QStringList() << paths[0] << paths[2]);
+  QCOMPARE(listWidget.files(true), QStringList() << resultPaths[0] << resultPaths[2]);
+  QCOMPARE(listWidget.directories(), QStringList() << paths[1] << paths[3]);
+  QCOMPARE(listWidget.directories(true), QStringList() << resultPaths[1] << resultPaths[3]);
+}
+
+// ----------------------------------------------------------------------------
+void ctkPathListWidgetTester::testSimilarPaths()
+{
+  ctkPathListWidget listWidget;
+
+  listWidget.setFileOptions(ctkPathListWidget::None);
+  listWidget.setDirectoryOptions(ctkPathListWidget::None);
+
+  QVERIFY(listWidget.addPath("/one/path"));
+  QVERIFY(listWidget.addPath("/one/path/"));
+  QVERIFY(listWidget.addPath("/one"));
+  QVERIFY(listWidget.addPath("/one/"));
+}
+
+// ----------------------------------------------------------------------------
+void ctkPathListWidgetTester::testSetPaths()
+{
+  ctkPathListWidget listWidget;
+
+  listWidget.setFileOptions(ctkPathListWidget::None);
+  QVERIFY(listWidget.fileOptions() == ctkPathListWidget::None);
+  listWidget.setDirectoryOptions(ctkPathListWidget::None);
+  QVERIFY(listWidget.directoryOptions() == ctkPathListWidget::None);
+
+  QVERIFY(listWidget.addPath("/file/a"));
+  QVERIFY(listWidget.addPath("/file/b"));
+  QVERIFY(listWidget.addPath("/dir/a/"));
+
+  QSignalSpy pathListChangedSpy(&listWidget, SIGNAL(pathsChanged(QStringList,QStringList)));
+
+  QStringList newPaths = QStringList()
+      << "/new/file/x"
+      << "/file/b"
+      << "/new/dir/x";
+
+  listWidget.setPaths(newPaths);
+  QCOMPARE(pathListChangedSpy.count(), 1);
+  QCOMPARE(pathListChangedSpy.front().at(0).toStringList(), QStringList() << "/new/file/x" << "/new/dir/x");
+}
+
+// ----------------------------------------------------------------------------
+void ctkPathListWidgetTester::testEditPaths()
+{
+  ctkPathListWidget listWidget;
+
+  listWidget.setFileOptions(ctkPathListWidget::None);
+  listWidget.setDirectoryOptions(ctkPathListWidget::None);
+
+  QVERIFY(listWidget.addPath("/file/a"));
+  QVERIFY(listWidget.addPath("/file/b"));
+  QVERIFY(listWidget.addPath("/dir/a/"));
+
+  QSignalSpy pathListChangedSpy(&listWidget, SIGNAL(pathsChanged(QStringList,QStringList)));
+
+  QVERIFY(!listWidget.editPath(QModelIndex(), "bla"));
+  QVERIFY(!listWidget.editPath(listWidget.model()->index(0,0), "/new/file/a/"));
+  QVERIFY(listWidget.editPath(listWidget.model()->index(0,0), "/new/file/a"));
+  QCOMPARE(listWidget.path(0), QString("/new/file/a"));
+
+  QVERIFY(listWidget.editPath("/dir/a/", "/new/dir/a/"));
+  QCOMPARE(listWidget.path(2), QString("/new/dir/a/"));
+
+  QCOMPARE(pathListChangedSpy.count(), 2);
+  QCOMPARE(pathListChangedSpy.at(0).at(0).toString(), QString("/new/file/a"));
+  QCOMPARE(pathListChangedSpy.at(0).at(1).toString(), QString("/file/a"));
+
+  QCOMPARE(pathListChangedSpy.at(1).at(0).toString(), QString("/new/dir/a/"));
+  QCOMPARE(pathListChangedSpy.at(1).at(1).toString(), QString("/dir/a/"));
+}
+
+// ----------------------------------------------------------------------------
+CTK_TEST_MAIN(ctkPathListWidgetTest)
+#include "moc_ctkPathListWidgetTest.cpp"

+ 99 - 0
Libs/Widgets/Testing/Cpp/ctkPathListWidgetWithButtonsTest.cpp

@@ -0,0 +1,99 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  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.apache.org/licenses/LICENSE-2.0.txt
+
+  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 <QWidget>
+#include <QHBoxLayout>
+#include <QTimer>
+#include <QToolButton>
+
+// CTK includes
+#include "ctkPathListWidget.h"
+#include "ctkPathListButtonsWidget.h"
+#include "ctkUtils.h"
+#include "ctkTest.h"
+
+// STD includes
+#include <cstdlib>
+#include <iostream>
+
+//-----------------------------------------------------------------------------
+class ctkPathListWidgetWithButtonsTester : public QObject
+{
+  Q_OBJECT
+
+private slots:
+
+  void testButtons();
+
+};
+
+// ----------------------------------------------------------------------------
+void ctkPathListWidgetWithButtonsTester::testButtons()
+{
+  QWidget topLevel;
+  topLevel.setLayout(new QHBoxLayout());
+
+  ctkPathListWidget pathList(&topLevel);
+  QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+  sizePolicy.setHorizontalStretch(1);
+  pathList.setSizePolicy(sizePolicy);
+
+  ctkPathListButtonsWidget pathListButtons(&topLevel);
+  pathListButtons.init(&pathList);
+  pathListButtons.setOrientation(Qt::Vertical);
+
+
+  topLevel.layout()->addWidget(&pathList);
+  topLevel.layout()->addWidget(&pathListButtons);
+
+  topLevel.show();
+  QTest::qWaitForWindowShown(&topLevel);
+
+  struct CloseModalDialog : public QRunnable
+  {
+    void run()
+    {
+      QTest::qWait(1000);
+      QTimer::singleShot(0, QApplication::activeModalWidget(), SLOT(accept()));
+    }
+  };
+  QThreadPool::globalInstance()->start(new CloseModalDialog);
+  QTest::mouseClick(pathListButtons.buttonAddDirectory(), Qt::LeftButton);
+
+  QCOMPARE(pathList.count(), 1);
+  QVERIFY(!pathListButtons.buttonRemove()->isEnabled());
+  QVERIFY(!pathListButtons.buttonEdit()->isEnabled());
+
+  pathList.selectAll();
+
+  QVERIFY(pathListButtons.buttonRemove()->isEnabled());
+  QVERIFY(pathListButtons.buttonEdit()->isEnabled());
+
+  QTest::mouseClick(pathListButtons.buttonRemove(), Qt::LeftButton);
+  QCOMPARE(pathList.count(), 0);
+}
+
+
+// ----------------------------------------------------------------------------
+CTK_TEST_MAIN(ctkPathListWidgetWithButtonsTest)
+#include "moc_ctkPathListWidgetWithButtonsTest.cpp"

+ 0 - 245
Libs/Widgets/ctkDirectoryListView.cpp

@@ -1,245 +0,0 @@
-/*=========================================================================
-
-  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.apache.org/licenses/LICENSE-2.0.txt
-
-  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.
-
-  This file was originally developed by Jean-Christophe Fillion-Robin, Kitware Inc.
-  and was partially funded by NIH grant 3P41RR013218-12S1
-
-=========================================================================*/
-
-// Qt includes
-#include <QFileInfo>
-#include <QHBoxLayout>
-#include <QListView>
-#include <QStandardItemModel>
-
-// QtGUI includes
-#include "ctkDirectoryListView.h"
-
-// --------------------------------------------------------------------------
-// ctkDirectoryListViewPrivate
-
-//-----------------------------------------------------------------------------
-class ctkDirectoryListViewPrivate
-{
-  Q_DECLARE_PUBLIC(ctkDirectoryListView);
-protected:
-  ctkDirectoryListView* const q_ptr;
-
-public:
-  ctkDirectoryListViewPrivate(ctkDirectoryListView& object);
-  void init();
-
-  void addDirectory(const QString& path);
-
-  enum
-    {
-    AbsolutePathRole = Qt::UserRole + 1
-    };
-
-  QListView*         ListView;
-  QStandardItemModel DirectoryListModel;
-};
-
-// --------------------------------------------------------------------------
-// ctkDirectoryListViewPrivate methods
-
-// --------------------------------------------------------------------------
-ctkDirectoryListViewPrivate::ctkDirectoryListViewPrivate(ctkDirectoryListView& object)
-  :q_ptr(&object)
-{
-}
-
-// --------------------------------------------------------------------------
-void ctkDirectoryListViewPrivate::init()
-{
-  Q_Q(ctkDirectoryListView);
-
-  this->ListView = new QListView();
-  this->ListView->setSelectionBehavior(QAbstractItemView::SelectRows);
-  this->ListView->setSelectionMode(QAbstractItemView::ExtendedSelection);
-  this->ListView->setEditTriggers(QAbstractItemView::NoEditTriggers);
-  QHBoxLayout * layout = new QHBoxLayout();
-  layout->setContentsMargins(0, 0, 0, 0);
-  layout->addWidget(this->ListView);
-  q->setLayout(layout);
-
-  this->ListView->setModel(&this->DirectoryListModel);
-}
-
-// --------------------------------------------------------------------------
-void ctkDirectoryListViewPrivate::addDirectory(const QString& path)
-{
-  Q_Q(ctkDirectoryListView);
-  QString absolutePath = QFileInfo(path).absoluteFilePath();
-  if (!QFile::exists(absolutePath) || q->hasDirectory(absolutePath))
-    {
-    return;
-    }
-  QStandardItem * item = new QStandardItem(path);
-  item->setData(QVariant(absolutePath), Qt::ToolTipRole);
-  item->setData(QVariant(absolutePath), ctkDirectoryListViewPrivate::AbsolutePathRole);
-  this->DirectoryListModel.appendRow(item);
-}
-
-// --------------------------------------------------------------------------
-// ctkDirectoryListView methods
-
-// --------------------------------------------------------------------------
-ctkDirectoryListView::ctkDirectoryListView(QWidget* _parent)
-  : Superclass(_parent)
-  , d_ptr(new ctkDirectoryListViewPrivate(*this))
-{
-  Q_D(ctkDirectoryListView);
-  d->init();
-}
-
-// --------------------------------------------------------------------------
-ctkDirectoryListView::~ctkDirectoryListView()
-{
-}
-
-// --------------------------------------------------------------------------
-QStringList ctkDirectoryListView::directoryList(bool absolutePath)const
-{
-  Q_D(const ctkDirectoryListView);
-  QStringList directoryList;
-  int role = Qt::DisplayRole;
-  if (absolutePath)
-    {
-    role = ctkDirectoryListViewPrivate::AbsolutePathRole;
-    }
-  for(int i = 0; i < d->DirectoryListModel.rowCount(); ++i)
-    {
-    directoryList << d->DirectoryListModel.data(d->DirectoryListModel.index(i, 0), role).toString();
-    }
-  return directoryList;
-}
-
-// --------------------------------------------------------------------------
-QStringList ctkDirectoryListView::selectedDirectoryList(bool absolutePath)const
-{
-  Q_D(const ctkDirectoryListView);
-  QStringList directoryList;
-  int role = Qt::DisplayRole;
-  if (absolutePath)
-    {
-    role = ctkDirectoryListViewPrivate::AbsolutePathRole;
-    }
-  QModelIndexList selectedIndexes = d->ListView->selectionModel()->selectedRows();
-  foreach(const QModelIndex& index, selectedIndexes)
-    {
-    directoryList << d->DirectoryListModel.data(index, role).toString();
-    }
-  return directoryList;
-}
-
-// --------------------------------------------------------------------------
-bool ctkDirectoryListView::hasDirectory(const QString& path)const
-{
-  Q_D(const ctkDirectoryListView);
-  QString absolutePath = QFileInfo(path).absoluteFilePath();
-  QModelIndexList foundIndexes = d->DirectoryListModel.match(
-        d->DirectoryListModel.index(0, 0), ctkDirectoryListViewPrivate::AbsolutePathRole,
-        QVariant(absolutePath));
-  Q_ASSERT(foundIndexes.size() < 2);
-  return (foundIndexes.size() != 0);
-}
-
-// --------------------------------------------------------------------------
-void ctkDirectoryListView::addDirectory(const QString& path)
-{
-  Q_D(ctkDirectoryListView);
-  d->addDirectory(path);
-  emit this->directoryListChanged();
-}
-
-// --------------------------------------------------------------------------
-void ctkDirectoryListView::removeDirectory(const QString& path)
-{
-  Q_D(ctkDirectoryListView);
-  QList<QStandardItem*> foundItems = d->DirectoryListModel.findItems(path);
-  Q_ASSERT(foundItems.count() < 2);
-  if (foundItems.count() == 1)
-    {
-    d->DirectoryListModel.removeRow(foundItems.at(0)->row());
-    emit this->directoryListChanged();
-    }
-}
-
-// --------------------------------------------------------------------------
-void ctkDirectoryListView::removeSelectedDirectories()
-{
-  Q_D(ctkDirectoryListView);
-
-  QModelIndexList selectedIndexes = d->ListView->selectionModel()->selectedRows();
-  bool selectedCount = selectedIndexes.count();
-  while(selectedIndexes.count() > 0)
-    {
-    d->DirectoryListModel.removeRow(selectedIndexes.at(0).row());
-    selectedIndexes = d->ListView->selectionModel()->selectedRows();
-    }
-  if (selectedCount)
-    {
-    emit this->directoryListChanged();
-    }
-}
-
-// --------------------------------------------------------------------------
-void ctkDirectoryListView::selectAllDirectories()
-{
-  Q_D(ctkDirectoryListView);
-  d->ListView->selectAll();
-}
-
-// --------------------------------------------------------------------------
-void ctkDirectoryListView::clearDirectorySelection()
-{
-  Q_D(ctkDirectoryListView);
-  d->ListView->clearSelection();
-}
-
-// --------------------------------------------------------------------------
-void ctkDirectoryListView::setDirectoryList(const QStringList& paths)
-{
-  Q_D(ctkDirectoryListView);
-
-  if (paths.count() == this->directoryList().count())
-    {
-    int found = 0;
-    foreach(const QString& path, paths)
-      {
-      if (this->hasDirectory(path))
-        {
-        ++found;
-        }
-      }
-    if (found == paths.count())
-      {
-      return;
-      }
-    }
-
-  d->DirectoryListModel.removeRows(0, d->DirectoryListModel.rowCount());
-
-  foreach(const QString& path, paths)
-    {
-    d->addDirectory(path);
-    }
-  emit this->directoryListChanged();
-}
-

+ 0 - 96
Libs/Widgets/ctkDirectoryListView.h

@@ -1,96 +0,0 @@
-/*=========================================================================
-
-  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.apache.org/licenses/LICENSE-2.0.txt
-
-  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.
-
-  This file was originally developed by Jean-Christophe Fillion-Robin, Kitware Inc.
-  and was partially funded by NIH grant 3P41RR013218-12S1
-
-=========================================================================*/
-
-#ifndef __ctkDirectoryListView_h
-#define __ctkDirectoryListView_h
-
-// Qt includes
-#include <QWidget>
-
-// QtGUI includes
-#include "ctkWidgetsExport.h"
-
-class ctkDirectoryListViewPrivate;
-
-class CTK_WIDGETS_EXPORT ctkDirectoryListView : public QWidget
-{
-  Q_OBJECT
-  Q_PROPERTY(QStringList directoryList READ directoryList WRITE setDirectoryList NOTIFY directoryListChanged);
-public:
-  /// Superclass typedef
-  typedef QWidget Superclass;
-
-  /// Constructor
-  explicit ctkDirectoryListView(QWidget* parent = 0);
-
-  /// Destructor
-  virtual ~ctkDirectoryListView();
-
-  /// Return True if the \a path has already been added
-  bool hasDirectory(const QString& path)const;
-
-  QStringList directoryList(bool absolutePath = false)const;
-
-  QStringList selectedDirectoryList(bool absolutePath = false)const;
-
-public slots:
-
-  /// If \a path exists, add it to the view and emit signal directoryListChanged().
-  /// \sa directoryListChanged()
-  void addDirectory(const QString& path);
-
-  /// Remove all entries and set \a paths has current list.
-  /// The signal directoryListChanged() is emitted if the current list of directories is
-  /// different from the provided one.
-  /// \sa addDirectory(), directoryListChanged()
-  void setDirectoryList(const QStringList& paths);
-
-  /// Remove \a path from the list.
-  /// The signal directoryListChanged() is emitted if the path was in the list.
-  /// \sa directoryListChanged()
-  void removeDirectory(const QString& path);
-
-  /// \sa selectAllDirectories()
-  void removeSelectedDirectories();
-
-  /// Select all directories.
-  void selectAllDirectories();
-
-  /// Clear the current directory selection.
-  void clearDirectorySelection();
-
-signals:
-  /// This signal is emitted when a directory is added to the view.
-  void directoryListChanged();
-
-protected:
-  QScopedPointer<ctkDirectoryListViewPrivate> d_ptr;
-
-private:
-  Q_DECLARE_PRIVATE(ctkDirectoryListView);
-  Q_DISABLE_COPY(ctkDirectoryListView);
-
-};
-
-#endif
-

+ 0 - 146
Libs/Widgets/ctkDirectoryListWidget.cpp

@@ -1,146 +0,0 @@
-/*=========================================================================
-
-  Library:   CTK
-
-  Copyright (c) University College London.
-
-  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.apache.org/licenses/LICENSE-2.0.txt
-
-  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.
-
-=========================================================================*/
-
-#include <iostream>
-
-// Qt includes
-#include <QDebug>
-#include <QFileDialog>
-
-// CTK includes
-#include "ctkDirectoryListWidget.h"
-#include "ctkDirectoryListWidget_p.h"
-
-//-----------------------------------------------------------------------------
-// ctkDirectoryListWidgetPrivate methods
-
-//-----------------------------------------------------------------------------
-ctkDirectoryListWidgetPrivate::~ctkDirectoryListWidgetPrivate()
-{
-}
-
-//-----------------------------------------------------------------------------
-ctkDirectoryListWidgetPrivate::ctkDirectoryListWidgetPrivate(ctkDirectoryListWidget& object)
-  : QObject(&object), q_ptr(&object)
-{
-}
-
-//-----------------------------------------------------------------------------
-void ctkDirectoryListWidgetPrivate::init()
-{
-  Q_Q(ctkDirectoryListWidget);
-  this->setupUi(q);
-}
-
-//-----------------------------------------------------------------------------
-void ctkDirectoryListWidgetPrivate::setupUi(QWidget * widget)
-{
-  this->Ui_ctkDirectoryListWidget::setupUi(widget);
-
-  this->ExpandButton->setChecked(false);
-  this->ExpandButton->setMirrorOnExpand(true);
-  this->GroupBox->hide();
-
-  QObject::connect(this->AddButton, SIGNAL(clicked()),
-                   this, SLOT(onAddClicked()));
-  QObject::connect(this->RemoveButton, SIGNAL(clicked()),
-                   this, SLOT(onRemoveClicked()));
-  QObject::connect(this->ExpandButton, SIGNAL(clicked(bool)),
-                   this, SLOT(onExpandClicked(bool)));
-  QObject::connect(this->DirectoryList, SIGNAL(directoryListChanged()),
-                   this, SLOT(onDirectoryListChanged()));
-}
-
-//-----------------------------------------------------------------------------
-void ctkDirectoryListWidgetPrivate::setDirectoryList(const QStringList& list)
-{
-  this->DirectoryList->setDirectoryList(list);
-}
-
-//-----------------------------------------------------------------------------
-QStringList ctkDirectoryListWidgetPrivate::directoryList() const
-{
-  return this->DirectoryList->directoryList(true); // true for absolute path.
-}
-
-//-----------------------------------------------------------------------------
-void ctkDirectoryListWidgetPrivate::onAddClicked()
-{
-  QString path = QFileDialog::getExistingDirectory(
-        this->DirectoryList, tr("Select folder"),
-        QString(""));
-  // An empty directory means that the user cancelled the dialog.
-  if (path.isEmpty())
-    {
-    return;
-    }
-  this->DirectoryList->addDirectory(path);
-}
-
-//-----------------------------------------------------------------------------
-void ctkDirectoryListWidgetPrivate::onRemoveClicked()
-{
-  this->DirectoryList->removeSelectedDirectories();
-}
-
-//-----------------------------------------------------------------------------
-void ctkDirectoryListWidgetPrivate::onExpandClicked(bool state)
-{
-  this->GroupBox->setVisible(state);
-}
-
-//-----------------------------------------------------------------------------
-void ctkDirectoryListWidgetPrivate::onDirectoryListChanged()
-{
-  Q_Q(ctkDirectoryListWidget);
-  emit (q->directoryListChanged(this->DirectoryList->directoryList()));
-}
-
-//-----------------------------------------------------------------------------
-// ctkDirectoryListWidget methods
-
-//-----------------------------------------------------------------------------
-ctkDirectoryListWidget::~ctkDirectoryListWidget()
-{
-}
-
-//-----------------------------------------------------------------------------
-ctkDirectoryListWidget::ctkDirectoryListWidget(QWidget* newParent)
-  : Superclass(newParent)
-  , d_ptr(new ctkDirectoryListWidgetPrivate(*this))
-{
-  Q_D(ctkDirectoryListWidget);
-  d->init();
-}
-
-//-----------------------------------------------------------------------------
-void ctkDirectoryListWidget::setDirectoryList(const QStringList& list)
-{
-  Q_D(ctkDirectoryListWidget);
-  d->setDirectoryList(list);
-}
-
-//-----------------------------------------------------------------------------
-QStringList ctkDirectoryListWidget::directoryList() const
-{
-  Q_D(const ctkDirectoryListWidget);
-  return d->directoryList();
-}
-

+ 0 - 69
Libs/Widgets/ctkDirectoryListWidget.h

@@ -1,69 +0,0 @@
-/*=========================================================================
-
-  Library:   CTK
-
-  Copyright (c) University College London.
-
-  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.apache.org/licenses/LICENSE-2.0.txt
-
-  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.
-
-=========================================================================*/
-
-#ifndef __ctkDirectoryListWidget_h
-#define __ctkDirectoryListWidget_h
-
-// Qt includes
-#include <QWidget>
-#include <QStringList>
-
-// QtGUI includes
-#include "ctkWidgetsExport.h"
-
-class ctkDirectoryListWidgetPrivate;
-
-/**
- * \class ctkDirectoryListWidget
- * \brief A widget to maintain a list of directories, with add and remove buttons,
- * such as might be used in a settings panel to select a series of directories to search.
- *
- * \author m.clarkson@ucl.ac.uk
- */
-class CTK_WIDGETS_EXPORT ctkDirectoryListWidget : public QWidget
-{
-  Q_OBJECT
-  Q_PROPERTY(QStringList directoryList READ directoryList WRITE setDirectoryList)
-
-public:
-  /// Superclass typedef
-  typedef QWidget Superclass;
-  ctkDirectoryListWidget(QWidget* parent = 0);
-  virtual ~ctkDirectoryListWidget();
-
-  /// Set the directory list, which will overwrite any existing list.
-  void setDirectoryList(const QStringList& list);
-  QStringList directoryList() const;
-
-public Q_SLOTS:
-
-Q_SIGNALS:
-  /// directoryListChanged emmitted whenever the list of directories is changed.
-  void directoryListChanged(const QStringList& directories);
-
-protected:
-  QScopedPointer<ctkDirectoryListWidgetPrivate> d_ptr;
-
-private:
-  Q_DECLARE_PRIVATE(ctkDirectoryListWidget);
-  Q_DISABLE_COPY(ctkDirectoryListWidget);
-};
-
-#endif

+ 713 - 0
Libs/Widgets/ctkPathListButtonsWidget.cpp

@@ -0,0 +1,713 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) University College London.
+
+  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.apache.org/licenses/LICENSE-2.0.txt
+
+  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.
+
+=========================================================================*/
+
+#include <iostream>
+
+// Qt includes
+#include <QDebug>
+#include <QFileDialog>
+#include <QSortFilterProxyModel>
+#include <QFileSystemModel>
+#include <QFileInfo>
+#include <QMessageBox>
+
+// CTK includes
+#include "ctkPathListButtonsWidget.h"
+#include "ctkPathListButtonsWidget_p.h"
+
+//-----------------------------------------------------------------------------
+// ctkPathListButtonsWidgetPrivate methods
+
+//-----------------------------------------------------------------------------
+ctkPathListButtonsWidgetPrivate::~ctkPathListButtonsWidgetPrivate()
+{
+}
+
+//-----------------------------------------------------------------------------
+ctkPathListButtonsWidgetPrivate::ctkPathListButtonsWidgetPrivate(ctkPathListButtonsWidget& object)
+  : QObject(&object)
+  , q_ptr(&object)
+  , PathListWidget(NULL)
+{
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidgetPrivate::init()
+{
+  Q_Q(ctkPathListButtonsWidget);
+  this->setupUi(q);
+
+  q->unsetIconAddFilesButton();
+  q->unsetIconAddDirectoryButton();
+  q->unsetIconRemoveButton();
+  q->unsetIconEditButton();
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidgetPrivate::setupUi(QWidget * widget)
+{
+  this->Ui_ctkPathListButtonsWidget::setupUi(widget);
+
+  connect(this->AddFilesButton, SIGNAL(clicked()), SLOT(on_AddFilesButton_clicked()));
+  connect(this->AddDirectoryButton, SIGNAL(clicked()), SLOT(on_AddDirButton_clicked()));
+  connect(this->RemoveButton, SIGNAL(clicked()), SLOT(on_RemoveButton_clicked()));
+  connect(this->EditButton, SIGNAL(clicked()), SLOT(on_EditButton_clicked()));
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidgetPrivate::on_AddFilesButton_clicked()
+{
+  if (!this->PathListWidget) return;
+
+  QStringList paths = this->openAddFilesDialog(true);
+  this->addPathsWithWarningMessage(paths);
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidgetPrivate::on_AddDirButton_clicked()
+{
+  if (!this->PathListWidget) return;
+
+  QStringList paths = this->openAddDirDialog();
+  this->addPathsWithWarningMessage(paths);
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidgetPrivate::on_RemoveButton_clicked()
+{
+  if (!this->PathListWidget) return;
+
+  this->PathListWidget->removeSelectedPaths();
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidgetPrivate::on_EditButton_clicked()
+{
+  Q_Q(ctkPathListButtonsWidget);
+
+  if (!this->PathListWidget) return;
+
+  QString currentPath = this->PathListWidget->currentPath(true);
+
+  QStringList paths;
+  if (this->PathListWidget->isFile(currentPath))
+  {
+    paths = this->openAddFilesDialog(false);
+  }
+  else
+  {
+    paths = this->openAddDirDialog();
+  }
+
+  if (!paths.isEmpty())
+  {
+    if (!this->PathListWidget->editPath(currentPath, paths.front()))
+    {
+      QMessageBox::information(q, tr("Editing the path failed"),
+                               QString(tr("Failed to change path:\n\n%1\n\nto path\n\n%2\n\nPlease check your permissions."))
+                               .arg(currentPath).arg(paths.front()));
+    }
+  }
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidgetPrivate::on_PathListWidget_selectionChanged(const QItemSelection &selected,
+                                                                         const QItemSelection &deselected)
+{
+  Q_UNUSED(selected)
+  Q_UNUSED(deselected)
+
+  bool hasSelection = this->PathListWidget->selectionModel()->hasSelection();
+  this->EditButton->setEnabled(hasSelection);
+  this->RemoveButton->setEnabled(hasSelection);
+}
+
+//-----------------------------------------------------------------------------
+QStringList ctkPathListButtonsWidgetPrivate::openAddFilesDialog(bool multiple)
+{
+  Q_Q(ctkPathListButtonsWidget);
+
+  if (!this->PathListWidget) return QStringList();
+
+  QString caption;
+  if (multiple)
+  {
+    caption = tr("Select one or more files");
+  }
+  else
+  {
+    caption = tr("Select a file");
+  }
+
+  QFileDialog fileDialog(q, caption);
+  fileDialog.setReadOnly(true);
+
+  if (multiple)
+  {
+    fileDialog.setFileMode(QFileDialog::ExistingFiles);
+  }
+  else
+  {
+    fileDialog.setFileMode(QFileDialog::ExistingFile);
+  }
+
+  QString currentPath = this->PathListWidget->currentPath(true);
+  currentPath = currentPath.left(currentPath.lastIndexOf('/') + 1);
+  if (!currentPath.isEmpty())
+  {
+    fileDialog.setDirectory(currentPath);
+  }
+
+  // We use a proxy model as a workaround for the broken QFileDialog::setFilter() method.
+  // See for example https://bugreports.qt-project.org/browse/QTBUG-10244
+  class FileFilterProxyModel : public QSortFilterProxyModel
+  {
+  public:
+    FileFilterProxyModel(ctkPathListWidget::PathOptions fileOptions)
+      : FileOptions(fileOptions)
+    {}
+
+  protected:
+    virtual bool filterAcceptsRow(int sourceRow, const QModelIndex & sourceParent) const
+    {
+      QModelIndex sourceIndex = sourceModel()->index(sourceRow, 0, sourceParent);
+      QFileSystemModel* fileModel = qobject_cast<QFileSystemModel*>(sourceModel());
+
+      QFileInfo fileInfo = fileModel->fileInfo(sourceIndex);
+
+      if(fileInfo.isFile())
+      {
+        if (FileOptions.testFlag(ctkPathListWidget::Readable) &&
+            !fileInfo.isReadable())
+        {
+          return false;
+        }
+        if (FileOptions.testFlag(ctkPathListWidget::Writable) &&
+            !fileInfo.isWritable())
+        {
+          return false;
+        }
+        if (FileOptions.testFlag(ctkPathListWidget::Executable)&&
+            !fileInfo.isExecutable())
+        {
+          return false;
+        }
+        return true;
+      }
+      else
+      {
+        // Show all readable directories
+        return fileInfo.isReadable();
+      }
+    }
+
+  private:
+    ctkPathListWidget::PathOptions FileOptions;
+  };
+
+  fileDialog.setProxyModel(new FileFilterProxyModel(this->PathListWidget->fileOptions()));
+
+  if (fileDialog.exec() == QDialog::Accepted)
+  {
+    return fileDialog.selectedFiles();
+  }
+  return QStringList();
+}
+
+//-----------------------------------------------------------------------------
+QStringList ctkPathListButtonsWidgetPrivate::openAddDirDialog()
+{
+  Q_Q(ctkPathListButtonsWidget);
+
+  if (!this->PathListWidget) return QStringList();
+
+  QString caption = tr("Select a directory");
+  QFileDialog fileDialog(q, caption);
+
+  fileDialog.setFileMode(QFileDialog::Directory);
+  fileDialog.setOption(QFileDialog::ShowDirsOnly);
+
+  QString currentPath = this->PathListWidget->currentPath(true);
+  if (!currentPath.isEmpty())
+  {
+    fileDialog.setDirectory(currentPath);
+  }
+
+  // We use a proxy model as a workaround for the broken QFileDialog::setFilter() method.
+  // See for example https://bugreports.qt-project.org/browse/QTBUG-10244
+  class DirFilterProxyModel : public QSortFilterProxyModel
+  {
+  public:
+    DirFilterProxyModel(ctkPathListWidget::PathOptions dirOptions)
+      : DirOptions(dirOptions)
+    {}
+
+  protected:
+    virtual bool filterAcceptsRow(int sourceRow, const QModelIndex & sourceParent) const
+    {
+      QModelIndex sourceIndex = sourceModel()->index(sourceRow, 0, sourceParent);
+      QFileSystemModel* fileModel = qobject_cast<QFileSystemModel*>(sourceModel());
+
+      QFileInfo fileInfo = fileModel->fileInfo(sourceIndex);
+
+      if (DirOptions.testFlag(ctkPathListWidget::Readable) &&
+          !fileInfo.isReadable())
+      {
+        return false;
+      }
+      // Do not check for the Writable flag, since it makes navigation from
+      // non-writable folders to writable sub-folders hard.
+//      if (DirOptions.testFlag(ctkPathListWidget::Writable) &&
+//          !fileInfo.isWritable())
+//      {
+//        return false;
+//      }
+      return true;
+    }
+
+  private:
+    ctkPathListWidget::PathOptions DirOptions;
+  };
+
+  fileDialog.setProxyModel(new DirFilterProxyModel(this->PathListWidget->directoryOptions()));
+
+  if (fileDialog.exec() == QDialog::Accepted)
+  {
+    return fileDialog.selectedFiles();
+  }
+  return QStringList();
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidgetPrivate::addPathsWithWarningMessage(const QStringList& paths)
+{
+  Q_Q(ctkPathListButtonsWidget);
+
+  QStringList addedPaths = this->PathListWidget->addPaths(paths);
+  if (addedPaths != paths)
+  {
+    QString problematicPaths;
+    foreach(const QString& path, paths)
+    {
+      if (!addedPaths.contains(path))
+      {
+        problematicPaths += path + '\n';
+      }
+    }
+    QMessageBox::information(q, tr("Adding paths failed"),
+                             QString(tr("Failed to add the following paths:\n\n%1\nPlease check your permissions."))
+                             .arg(problematicPaths));
+  }
+}
+
+
+//-----------------------------------------------------------------------------
+// ctkPathListButtonsWidget methods
+
+//-----------------------------------------------------------------------------
+ctkPathListButtonsWidget::~ctkPathListButtonsWidget()
+{
+}
+
+void ctkPathListButtonsWidget::init(ctkPathListWidget *pathListWidget)
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->PathListWidget = pathListWidget;
+
+  if (d->PathListWidget->selectionModel()->selectedIndexes().isEmpty())
+  {
+    d->RemoveButton->setEnabled(false);
+    d->EditButton->setEnabled(false);
+  }
+
+  switch(d->PathListWidget->mode())
+  {
+  case ctkPathListWidget::FilesOnly:
+    d->AddDirectoryButton->setVisible(false);
+    break;
+  case ctkPathListWidget::DirectoriesOnly:
+    d->AddFilesButton->setVisible(false);
+    break;
+  default:
+    break;
+  }
+
+  connect(d->PathListWidget->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
+          d, SLOT(on_PathListWidget_selectionChanged(QItemSelection,QItemSelection)));
+  connect(d->PathListWidget, SIGNAL(pathActivated(QString)), d, SLOT(on_EditButton_clicked()));
+}
+
+//-----------------------------------------------------------------------------
+ctkPathListButtonsWidget::ctkPathListButtonsWidget(QWidget* newParent)
+  : Superclass(newParent)
+  , d_ptr(new ctkPathListButtonsWidgetPrivate(*this))
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->init();
+}
+
+//-----------------------------------------------------------------------------
+bool ctkPathListButtonsWidget::isAddFilesButtonVisible() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->AddFilesButton->isVisible();
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::setAddFilesButtonVisible(bool visible)
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->AddFilesButton->setVisible(visible);
+}
+
+//-----------------------------------------------------------------------------
+bool ctkPathListButtonsWidget::isAddDirectoryButtonVisible() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->AddDirectoryButton->isVisible();
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::setAddDirectoryButtonVisible(bool visible)
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->AddDirectoryButton->setVisible(visible);
+}
+
+//-----------------------------------------------------------------------------
+bool ctkPathListButtonsWidget::isRemoveButtonVisible() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->RemoveButton->isVisible();
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::setRemoveButtonVisible(bool visible)
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->RemoveButton->setVisible(visible);
+}
+
+//-----------------------------------------------------------------------------
+bool ctkPathListButtonsWidget::isEditButtonVisible() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->EditButton->isVisible();
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::setEditButtonVisible(bool visible)
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->EditButton->setVisible(visible);
+}
+
+//-----------------------------------------------------------------------------
+QString ctkPathListButtonsWidget::textAddFilesButton() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->AddFilesButton->text();
+}
+
+//-----------------------------------------------------------------------------
+QString ctkPathListButtonsWidget::textAddDirectoryButton() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->AddDirectoryButton->text();
+}
+
+//-----------------------------------------------------------------------------
+QString ctkPathListButtonsWidget::textRemoveButton() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->RemoveButton->text();
+}
+
+//-----------------------------------------------------------------------------
+QString ctkPathListButtonsWidget::textEditButton() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->EditButton->text();
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::setTextAddFilesButton(const QString& text)
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->AddFilesButton->setText(text);
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::setTextAddDirectoryButton(const QString& text)
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->AddDirectoryButton->setText(text);
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::setTextRemoveButton(const QString& text)
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->RemoveButton->setText(text);
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::setTextEditButton(const QString& text)
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->EditButton->setText(text);
+}
+
+//-----------------------------------------------------------------------------
+QString ctkPathListButtonsWidget::toolTipAddFilesButton() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->AddFilesButton->toolTip();
+}
+
+//-----------------------------------------------------------------------------
+QString ctkPathListButtonsWidget::toolTipAddDirectoryButton() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->AddDirectoryButton->toolTip();
+}
+
+//-----------------------------------------------------------------------------
+QString ctkPathListButtonsWidget::toolTipRemoveButton() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->RemoveButton->toolTip();
+}
+
+//-----------------------------------------------------------------------------
+QString ctkPathListButtonsWidget::toolTipEditButton() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->EditButton->toolTip();
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::setToolTipAddFilesButton(const QString& toolTip)
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->AddFilesButton->setToolTip(toolTip);
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::setToolTipAddDirectoryButton(const QString& toolTip)
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->AddDirectoryButton->setToolTip(toolTip);
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::setToolTipRemoveButton(const QString& toolTip)
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->RemoveButton->setToolTip(toolTip);
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::setToolTipEditButton(const QString& toolTip)
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->EditButton->setToolTip(toolTip);
+}
+
+//-----------------------------------------------------------------------------
+QIcon ctkPathListButtonsWidget::iconAddFilesButton() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->AddFilesButton->icon();
+}
+
+//-----------------------------------------------------------------------------
+QIcon ctkPathListButtonsWidget::iconAddDirectoryButton() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->AddDirectoryButton->icon();
+}
+
+//-----------------------------------------------------------------------------
+QIcon ctkPathListButtonsWidget::iconRemoveButton() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->RemoveButton->icon();
+}
+
+//-----------------------------------------------------------------------------
+QIcon ctkPathListButtonsWidget::iconEditButton() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->EditButton->icon();
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::setIconAddFilesButton(const QIcon& icon)
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->AddFilesButton->setIcon(icon);
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::setIconAddDirectoryButton(const QIcon& icon)
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->AddDirectoryButton->setIcon(icon);
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::setIconRemoveButton(const QIcon& icon)
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->RemoveButton->setIcon(icon);
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::setIconEditButton(const QIcon& icon)
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->EditButton->setIcon(icon);
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::unsetIconAddFilesButton()
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->AddFilesButton->setIcon(QIcon(":/Icons/plus.png"));
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::unsetIconAddDirectoryButton()
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->AddDirectoryButton->setIcon(QIcon(":/Icons/plus.png"));
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::unsetIconRemoveButton()
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->RemoveButton->setIcon(QIcon(":/Icons/minus.png"));
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::unsetIconEditButton()
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->EditButton->setIcon(QIcon(":/Icons/edit.png"));
+}
+
+//-----------------------------------------------------------------------------
+bool ctkPathListButtonsWidget::isButtonsAutoRaise() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->AddFilesButton->autoRaise();
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::setButtonsAutoRaise(bool autoRaise)
+{
+  Q_D(ctkPathListButtonsWidget);
+  d->AddFilesButton->setAutoRaise(autoRaise);
+  d->AddDirectoryButton->setAutoRaise(autoRaise);
+  d->RemoveButton->setAutoRaise(autoRaise);
+  d->EditButton->setAutoRaise(autoRaise);
+}
+
+//-----------------------------------------------------------------------------
+int ctkPathListButtonsWidget::buttonSpacing() const
+{
+  return this->layout()->spacing();
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::setButtonSpacing(int spacing)
+{
+  this->layout()->setSpacing(spacing);
+}
+
+//-----------------------------------------------------------------------------
+Qt::Orientation ctkPathListButtonsWidget::orientation() const
+{
+  return qobject_cast<QVBoxLayout*>(this->layout()) ? Qt::Vertical : Qt::Horizontal;
+}
+
+//-----------------------------------------------------------------------------
+void ctkPathListButtonsWidget::setOrientation(Qt::Orientation orientation)
+{
+  QVBoxLayout* verticalLayout = qobject_cast<QVBoxLayout*>(this->layout());
+  if (verticalLayout && orientation == Qt::Vertical)
+  {
+    return;
+  }
+
+  QLayout* oldLayout = this->layout();
+  QLayout* newLayout = NULL;
+  if (orientation == Qt::Vertical)
+  {
+    newLayout = new QVBoxLayout;
+  }
+  else
+  {
+    newLayout = new QHBoxLayout;
+  }
+  newLayout->setContentsMargins(0,0,0,0);
+  newLayout->setSpacing(oldLayout->spacing());
+
+  QLayoutItem* item = 0;
+  while((item = oldLayout->takeAt(0)))
+  {
+    if (item->widget())
+    {
+      newLayout->addWidget(item->widget());
+    }
+  }
+  delete oldLayout;
+  this->setLayout(newLayout);
+}
+
+//-----------------------------------------------------------------------------
+QToolButton *ctkPathListButtonsWidget::buttonAddFiles() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->AddFilesButton;
+}
+
+//-----------------------------------------------------------------------------
+QToolButton *ctkPathListButtonsWidget::buttonAddDirectory() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->AddDirectoryButton;
+}
+
+//-----------------------------------------------------------------------------
+QToolButton *ctkPathListButtonsWidget::buttonEdit() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->EditButton;
+}
+
+//-----------------------------------------------------------------------------
+QToolButton *ctkPathListButtonsWidget::buttonRemove() const
+{
+  Q_D(const ctkPathListButtonsWidget);
+  return d->RemoveButton;
+}

+ 157 - 0
Libs/Widgets/ctkPathListButtonsWidget.h

@@ -0,0 +1,157 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) University College London.
+
+  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.apache.org/licenses/LICENSE-2.0.txt
+
+  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.
+
+=========================================================================*/
+
+#ifndef __ctkPathListButtonsWidget_h
+#define __ctkPathListButtonsWidget_h
+
+// Qt includes
+#include <QWidget>
+
+
+// QtGUI includes
+#include "ctkWidgetsExport.h"
+#include "ctkPathListWidget.h"
+
+class ctkPathListButtonsWidgetPrivate;
+
+class QToolButton;
+
+
+/// \ingroup Widgets
+///
+/// \brief A widget with add, remove and edit buttons to be used together with ctkPathListWidget.
+///
+/// This widget should be initialized with a ctkPathListWidget instance in order to work properly.
+///
+/// \sa init(ctkPathListWidget*)
+///
+/// \author m.clarkson@ucl.ac.uk
+/// \author s.zelzer@dkfz-heidelberg.de
+///
+class CTK_WIDGETS_EXPORT ctkPathListButtonsWidget : public QWidget
+{
+  Q_OBJECT
+
+  Q_PROPERTY(bool showAddFilesButton READ isAddFilesButtonVisible WRITE setAddFilesButtonVisible)
+  Q_PROPERTY(bool showAddDirectoryButton READ isAddDirectoryButtonVisible WRITE setAddDirectoryButtonVisible)
+  Q_PROPERTY(bool showRemoveButton READ isRemoveButtonVisible WRITE setRemoveButtonVisible)
+  Q_PROPERTY(bool showEditButton READ isEditButtonVisible WRITE setEditButtonVisible)
+
+  Q_PROPERTY(QString textAddFilesButton READ textAddFilesButton WRITE setTextAddFilesButton)
+  Q_PROPERTY(QString textAddDirectoryButton READ textAddDirectoryButton WRITE setTextAddDirectoryButton)
+  Q_PROPERTY(QString textRemoveButton READ textRemoveButton WRITE setTextRemoveButton)
+  Q_PROPERTY(QString textEditButton READ textEditButton WRITE setTextEditButton)
+
+  Q_PROPERTY(QString toolTipAddFilesButton READ toolTipAddFilesButton WRITE setToolTipAddFilesButton)
+  Q_PROPERTY(QString toolTipAddDirectoryButton READ toolTipAddDirectoryButton WRITE setToolTipAddDirectoryButton)
+  Q_PROPERTY(QString toolTipRemoveButton READ toolTipRemoveButton WRITE setToolTipRemoveButton)
+  Q_PROPERTY(QString toolTipEditButton READ toolTipEditButton WRITE setToolTipEditButton)
+
+  Q_PROPERTY(QIcon iconAddFilesButton READ iconAddFilesButton WRITE setIconAddFilesButton RESET unsetIconAddFilesButton)
+  Q_PROPERTY(QIcon iconAddDirectoryButton READ iconAddDirectoryButton WRITE setIconAddDirectoryButton RESET unsetIconAddDirectoryButton)
+  Q_PROPERTY(QIcon iconRemoveButton READ iconRemoveButton WRITE setIconRemoveButton RESET unsetIconRemoveButton)
+  Q_PROPERTY(QIcon iconEditButton READ iconEditButton WRITE setIconEditButton RESET unsetIconEditButton)
+
+  Q_PROPERTY(bool buttonsAutoRaise READ isButtonsAutoRaise WRITE setButtonsAutoRaise)
+  Q_PROPERTY(int buttonSpacing READ buttonSpacing WRITE setButtonSpacing)
+
+  Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation)
+
+public:
+
+  /// Superclass typedef
+  typedef QWidget Superclass;
+
+  ctkPathListButtonsWidget(QWidget* parent = 0);
+  virtual ~ctkPathListButtonsWidget();
+
+  /// Initialize this widget with a ctkPathListWidget.
+  void init(ctkPathListWidget* pathListWidget);
+
+  bool isAddFilesButtonVisible() const;
+  void setAddFilesButtonVisible(bool visible);
+
+  bool isAddDirectoryButtonVisible() const;
+  void setAddDirectoryButtonVisible(bool visible);
+
+  bool isRemoveButtonVisible() const;
+  void setRemoveButtonVisible(bool visible);
+
+  bool isEditButtonVisible() const;
+  void setEditButtonVisible(bool visible);
+
+  QString textAddFilesButton() const;
+  QString textAddDirectoryButton() const;
+  QString textRemoveButton() const;
+  QString textEditButton() const;
+
+  void setTextAddFilesButton(const QString& text);
+  void setTextAddDirectoryButton(const QString& text);
+  void setTextRemoveButton(const QString& text);
+  void setTextEditButton(const QString& text);
+
+  QString toolTipAddFilesButton() const;
+  QString toolTipAddDirectoryButton() const;
+  QString toolTipRemoveButton() const;
+  QString toolTipEditButton() const;
+
+  void setToolTipAddFilesButton(const QString& toolTip);
+  void setToolTipAddDirectoryButton(const QString& toolTip);
+  void setToolTipRemoveButton(const QString& toolTip);
+  void setToolTipEditButton(const QString& toolTip);
+
+  QIcon iconAddFilesButton() const;
+  QIcon iconAddDirectoryButton() const;
+  QIcon iconRemoveButton() const;
+  QIcon iconEditButton() const;
+
+  void setIconAddFilesButton(const QIcon& icon);
+  void setIconAddDirectoryButton(const QIcon& icon);
+  void setIconRemoveButton(const QIcon& icon);
+  void setIconEditButton(const QIcon& icon);
+
+  void unsetIconAddFilesButton();
+  void unsetIconAddDirectoryButton();
+  void unsetIconRemoveButton();
+  void unsetIconEditButton();
+
+  bool isButtonsAutoRaise() const;
+  void setButtonsAutoRaise(bool autoRaise);
+
+  int buttonSpacing() const;
+  void setButtonSpacing(int spacing);
+
+  Qt::Orientation orientation() const;
+  void setOrientation(Qt::Orientation orientation);
+
+  QToolButton* buttonAddFiles() const;
+  QToolButton* buttonAddDirectory() const;
+  QToolButton* buttonEdit() const;
+  QToolButton* buttonRemove() const;
+
+protected:
+  QScopedPointer<ctkPathListButtonsWidgetPrivate> d_ptr;
+
+private:
+  Q_DECLARE_PRIVATE(ctkPathListButtonsWidget)
+  Q_DISABLE_COPY(ctkPathListButtonsWidget)
+};
+
+#endif

+ 33 - 19
Libs/Widgets/ctkDirectoryListWidget_p.h

@@ -18,43 +18,57 @@
 
 =========================================================================*/
 
-#ifndef __ctkDirectoryListWidget_p_h
-#define __ctkDirectoryListWidget_p_h
+#ifndef __ctkPathListButtonsWidget_p_h
+#define __ctkPathListButtonsWidget_p_h
 
 // Qt includes
 #include <QObject>
-#include <QStringList>
 
 // CTK includes
-#include "ctkDirectoryListWidget.h"
-#include "ui_ctkDirectoryListWidget.h"
+#include "ui_ctkPathListButtonsWidget.h"
 
-//-----------------------------------------------------------------------------
-/// \ingroup Widgets
+class ctkPathListButtonsWidget;
+class ctkPathListWidget;
+
+class QFileDialog;
 
-class ctkDirectoryListWidgetPrivate : public QObject, public Ui_ctkDirectoryListWidget
+//-----------------------------------------------------------------------------
+class ctkPathListButtonsWidgetPrivate : public QObject, public Ui_ctkPathListButtonsWidget
 {
   Q_OBJECT
-  Q_DECLARE_PUBLIC(ctkDirectoryListWidget);
+  Q_DECLARE_PUBLIC(ctkPathListButtonsWidget)
+
 protected:
-  ctkDirectoryListWidget* const q_ptr;
+
+  ctkPathListButtonsWidget* const q_ptr;
+
 public:
-  explicit ctkDirectoryListWidgetPrivate(ctkDirectoryListWidget& object);
-  virtual ~ctkDirectoryListWidgetPrivate();
+
+  explicit ctkPathListButtonsWidgetPrivate(ctkPathListButtonsWidget& object);
+  virtual ~ctkPathListButtonsWidgetPrivate();
 
   void init();
   void setupUi(QWidget * parent);
 
-  void setDirectoryList(const QStringList& list);
-  QStringList directoryList() const;
-
 public Q_SLOTS:
-  void onAddClicked();
-  void onRemoveClicked();
-  void onExpandClicked(bool);
-  void onDirectoryListChanged();
+
+  void on_AddFilesButton_clicked();
+  void on_AddDirButton_clicked();
+  void on_RemoveButton_clicked();
+  void on_EditButton_clicked();
+
+  void on_PathListWidget_selectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
 
 public:
+
+  ctkPathListWidget* PathListWidget;
+
+private:
+
+  QStringList openAddFilesDialog(bool multiple = true);
+  QStringList openAddDirDialog();
+
+  void addPathsWithWarningMessage(const QStringList& paths);
 };
 
 #endif

+ 813 - 0
Libs/Widgets/ctkPathListWidget.cpp

@@ -0,0 +1,813 @@
+/*=========================================================================
+
+  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.apache.org/licenses/LICENSE-2.0.txt
+
+  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.
+
+  This file was originally developed by Jean-Christophe Fillion-Robin, Kitware Inc.
+  and was partially funded by NIH grant 3P41RR013218-12S1
+
+=========================================================================*/
+
+// Qt includes
+#include <QFileInfo>
+#include <QHBoxLayout>
+#include <QListView>
+#include <QStandardItemModel>
+#include <QApplication>
+
+// QtGUI includes
+#include "ctkPathListWidget.h"
+
+// --------------------------------------------------------------------------
+// ctkPathListWidgetPrivate
+
+//-----------------------------------------------------------------------------
+class ctkPathListWidgetPrivate
+{
+  Q_DECLARE_PUBLIC(ctkPathListWidget)
+
+protected:
+  ctkPathListWidget* const q_ptr;
+
+public:
+
+  enum PathType {
+    Unknown,
+    File,
+    Directory
+  };
+
+  ctkPathListWidgetPrivate(ctkPathListWidget& object);
+
+  void _q_emitPathClicked(const QModelIndex &index);
+  void _q_emitPathDoubleClicked(const QModelIndex &index);
+  void _q_emitPathActivated(const QModelIndex &index);
+  void _q_emitCurrentPathChanged(const QModelIndex &current, const QModelIndex &previous);
+
+  bool addPath(const QString& path);
+  bool removePath(const QString& path);
+
+  void fileOptionsChanged();
+  void directoryOptionsChanged();
+
+  PathType pathType(const QString& absolutePath) const;
+
+  bool isValidPath(const QString& absoluteFilePath, PathType pathType) const;
+  bool isValidFile(const QString& absoluteFilePath) const;
+  bool isValidDir(const QString& absoluteDirPath) const;
+
+  QStandardItemModel PathListModel;
+  ctkPathListWidget::Mode Mode;
+  ctkPathListWidget::PathOptions FileOptions;
+  ctkPathListWidget::PathOptions DirectoryOptions;
+  QIcon FileIcon;
+  QIcon DirectoryIcon;
+};
+
+// --------------------------------------------------------------------------
+// ctkPathListWidgetPrivate methods
+
+#include "moc_ctkPathListWidget.h"
+
+// --------------------------------------------------------------------------
+ctkPathListWidgetPrivate::ctkPathListWidgetPrivate(ctkPathListWidget& object)
+  : q_ptr(&object)
+  , Mode(ctkPathListWidget::Any)
+  , FileOptions(ctkPathListWidget::Exists | ctkPathListWidget::Readable)
+  , DirectoryOptions(ctkPathListWidget::Exists | ctkPathListWidget::Readable)
+{
+}
+
+// --------------------------------------------------------------------------
+void ctkPathListWidgetPrivate::_q_emitPathClicked(const QModelIndex &index)
+{
+  Q_Q(ctkPathListWidget);
+  emit q->pathClicked(this->PathListModel.data(index, ctkPathListWidget::AbsolutePathRole).toString());
+}
+
+// --------------------------------------------------------------------------
+void ctkPathListWidgetPrivate::_q_emitPathDoubleClicked(const QModelIndex &index)
+{
+  Q_Q(ctkPathListWidget);
+  emit q->pathDoubleClicked(this->PathListModel.data(index, ctkPathListWidget::AbsolutePathRole).toString());
+}
+
+// --------------------------------------------------------------------------
+void ctkPathListWidgetPrivate::_q_emitPathActivated(const QModelIndex &index)
+{
+  Q_Q(ctkPathListWidget);
+  emit q->pathActivated(this->PathListModel.data(index, ctkPathListWidget::AbsolutePathRole).toString());
+}
+
+// --------------------------------------------------------------------------
+void ctkPathListWidgetPrivate::_q_emitCurrentPathChanged(const QModelIndex &current, const QModelIndex &previous)
+{
+  Q_Q(ctkPathListWidget);
+  QString currentPath = this->PathListModel.data(current, ctkPathListWidget::AbsolutePathRole).toString();
+  QString previousPath = this->PathListModel.data(previous, ctkPathListWidget::AbsolutePathRole).toString();
+  emit q->currentPathChanged(currentPath, previousPath);
+}
+
+// --------------------------------------------------------------------------
+bool ctkPathListWidgetPrivate::addPath(const QString& path)
+{
+  Q_Q(ctkPathListWidget);
+  QString absolutePath = QFileInfo(path).absoluteFilePath();
+  if (q->contains(absolutePath))
+  {
+    return false;
+  }
+
+  PathType pathType = this->pathType(absolutePath);
+  if (!this->isValidPath(absolutePath, pathType))
+  {
+    return false;
+  }
+  QStandardItem * item = new QStandardItem(path);
+  item->setData(QVariant(absolutePath), Qt::ToolTipRole);
+  item->setData(QVariant(absolutePath), ctkPathListWidget::AbsolutePathRole);
+  if (pathType == File && !this->FileIcon.isNull())
+  {
+    item->setData(this->FileIcon, Qt::DecorationRole);
+  }
+  else if (pathType == Directory && !this->DirectoryIcon.isNull())
+  {
+    item->setData(this->DirectoryIcon, Qt::DecorationRole);
+  }
+  this->PathListModel.appendRow(item);
+  return true;
+}
+
+// --------------------------------------------------------------------------
+bool ctkPathListWidgetPrivate::removePath(const QString& path)
+{
+  QString absolutePath = QFileInfo(path).absoluteFilePath();
+  QModelIndexList foundIndices = this->PathListModel.match(this->PathListModel.index(0, 0),
+                                                           ctkPathListWidget::AbsolutePathRole,
+                                                           absolutePath);
+  Q_ASSERT(foundIndices.count() < 2);
+  if (!foundIndices.isEmpty())
+  {
+    this->PathListModel.removeRow(foundIndices.front().row());
+    return true;
+  }
+  return false;
+}
+
+// --------------------------------------------------------------------------
+void ctkPathListWidgetPrivate::fileOptionsChanged()
+{
+  QStringList removedPaths;
+  for(int i = 0; i < this->PathListModel.rowCount();)
+  {
+    QModelIndex index = this->PathListModel.index(i, 0);
+    QString filePath = this->PathListModel.data(index, ctkPathListWidget::AbsolutePathRole).toString();
+    if (!this->isValidFile(filePath))
+    {
+      this->PathListModel.removeRow(i);
+      removedPaths << filePath;
+    }
+    else
+    {
+      ++i;
+    }
+  }
+
+  if (!removedPaths.empty())
+  {
+    Q_Q(ctkPathListWidget);
+    emit q->pathsChanged(QStringList(), removedPaths);
+  }
+}
+
+// --------------------------------------------------------------------------
+void ctkPathListWidgetPrivate::directoryOptionsChanged()
+{
+  QStringList removedPaths;
+  for(int i = 0; i < this->PathListModel.rowCount();)
+  {
+    QModelIndex index = this->PathListModel.index(i, 0);
+    QString dirPath = this->PathListModel.data(index, ctkPathListWidget::AbsolutePathRole).toString();
+    if (!this->isValidDir(dirPath))
+    {
+      this->PathListModel.removeRow(i);
+      removedPaths << dirPath;
+    }
+    else
+    {
+      ++i;
+    }
+  }
+
+  if (!removedPaths.empty())
+  {
+    Q_Q(ctkPathListWidget);
+    emit q->pathsChanged(QStringList(), removedPaths);
+  }
+}
+
+// --------------------------------------------------------------------------
+ctkPathListWidgetPrivate::PathType ctkPathListWidgetPrivate::pathType(const QString& absolutePath) const
+{
+  QFileInfo fileInfo(absolutePath);
+  if (fileInfo.exists())
+  {
+    if (fileInfo.isFile())
+    {
+      return File;
+    }
+    else if (fileInfo.isDir())
+    {
+      return Directory;
+    }
+    return Unknown;
+  }
+  // Check if path is a file or directory by looking for a trailing slash
+  else if (absolutePath.endsWith('/'))
+  {
+    return Directory;
+  }
+  else
+  {
+    return File;
+  }
+}
+
+// --------------------------------------------------------------------------
+bool ctkPathListWidgetPrivate::isValidPath(const QString& absoluteFilePath, PathType pathType) const
+{
+  switch (pathType)
+  {
+  case Unknown:
+    if (this->Mode == ctkPathListWidget::Any)
+    {
+      return true;
+    }
+    return false;
+  case File:
+    return this->isValidFile(absoluteFilePath);
+  case Directory:
+    return this->isValidDir(absoluteFilePath);
+  default:
+    return false;
+  }
+}
+
+// --------------------------------------------------------------------------
+bool ctkPathListWidgetPrivate::isValidFile(const QString& absoluteFilePath) const
+{
+  if (this->Mode == ctkPathListWidget::DirectoriesOnly)
+  {
+    return false;
+  }
+
+  if (this->FileOptions.testFlag(ctkPathListWidget::None))
+  {
+    return true;
+  }
+  else
+  {
+    QFileInfo fileInfo(absoluteFilePath);
+    if (fileInfo.exists())
+    {
+      if (!fileInfo.isFile())
+      {
+        return false;
+      }
+      if (this->FileOptions.testFlag(ctkPathListWidget::Readable) &&
+          !fileInfo.isReadable())
+      {
+        return false;
+      }
+      if (this->FileOptions.testFlag(ctkPathListWidget::Writable) &&
+          !fileInfo.isWritable())
+      {
+        return false;
+      }
+      if (this->FileOptions.testFlag(ctkPathListWidget::Executable) &&
+          !fileInfo.isExecutable())
+      {
+        return false;
+      }
+      return true;
+    }
+    else
+    {
+      return !this->FileOptions.testFlag(ctkPathListWidget::Exists);
+    }
+  }
+}
+
+// --------------------------------------------------------------------------
+bool ctkPathListWidgetPrivate::isValidDir(const QString& absoluteDirPath) const
+{
+  if (this->Mode == ctkPathListWidget::FilesOnly)
+  {
+    return false;
+  }
+
+  if (this->DirectoryOptions.testFlag(ctkPathListWidget::None))
+  {
+    return true;
+  }
+  else
+  {
+    QFileInfo fileInfo(absoluteDirPath);
+    if (fileInfo.exists())
+    {
+      if (!fileInfo.isDir())
+      {
+        return false;
+      }
+      if (this->DirectoryOptions.testFlag(ctkPathListWidget::Readable) &&
+          !fileInfo.isReadable())
+      {
+        return false;
+      }
+      if (this->DirectoryOptions.testFlag(ctkPathListWidget::Writable) &&
+          !fileInfo.isWritable())
+      {
+        return false;
+      }
+      if (this->DirectoryOptions.testFlag(ctkPathListWidget::Executable) &&
+          !fileInfo.isExecutable())
+      {
+        return false;
+      }
+      return true;
+    }
+    else
+    {
+      return !this->FileOptions.testFlag(ctkPathListWidget::Exists);
+    }
+  }
+}
+
+// --------------------------------------------------------------------------
+// ctkPathListWidget methods
+
+// --------------------------------------------------------------------------
+ctkPathListWidget::ctkPathListWidget(QWidget* _parent)
+  : Superclass(_parent)
+  , d_ptr(new ctkPathListWidgetPrivate(*this))
+{
+  Q_D(ctkPathListWidget);
+
+  this->setSelectionBehavior(QAbstractItemView::SelectRows);
+  this->setSelectionMode(QAbstractItemView::ExtendedSelection);
+  this->setEditTriggers(QAbstractItemView::NoEditTriggers);
+
+  this->unsetFileIcon();
+  this->unsetDirectoryIcon();
+
+  QListView::setModel(&d->PathListModel);
+
+  // signals
+  this->connect(this, SIGNAL(clicked(QModelIndex)), SLOT(_q_emitPathClicked(QModelIndex)));
+  this->connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(_q_emitPathDoubleClicked(QModelIndex)));
+  this->connect(this, SIGNAL(activated(QModelIndex)), SLOT(_q_emitPathActivated(QModelIndex)));
+  this->connect(this->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
+                SLOT(_q_emitCurrentPathChanged(QModelIndex,QModelIndex)));
+}
+
+// --------------------------------------------------------------------------
+ctkPathListWidget::~ctkPathListWidget()
+{
+}
+
+// --------------------------------------------------------------------------
+ctkPathListWidget::Mode ctkPathListWidget::mode() const
+{
+  Q_D(const ctkPathListWidget);
+  return d->Mode;
+}
+
+// --------------------------------------------------------------------------
+QIcon ctkPathListWidget::fileIcon() const
+{
+  Q_D(const ctkPathListWidget);
+  return d->FileIcon;
+}
+
+// --------------------------------------------------------------------------
+void ctkPathListWidget::setFileIcon(const QIcon& icon)
+{
+  Q_D(ctkPathListWidget);
+  d->FileIcon = icon;
+}
+
+// --------------------------------------------------------------------------
+void ctkPathListWidget::unsetFileIcon()
+{
+  Q_D(ctkPathListWidget);
+  d->FileIcon = QApplication::style()->standardIcon(QStyle::SP_FileIcon);
+}
+
+// --------------------------------------------------------------------------
+QIcon ctkPathListWidget::directoryIcon() const
+{
+  Q_D(const ctkPathListWidget);
+  return d->DirectoryIcon;
+}
+
+// --------------------------------------------------------------------------
+void ctkPathListWidget::setDirectoryIcon(const QIcon& icon)
+{
+  Q_D(ctkPathListWidget);
+  d->DirectoryIcon = icon;
+}
+
+// --------------------------------------------------------------------------
+void ctkPathListWidget::unsetDirectoryIcon()
+{
+  Q_D(ctkPathListWidget);
+  d->DirectoryIcon = QApplication::style()->standardIcon(QStyle::SP_DirIcon);
+}
+
+// --------------------------------------------------------------------------
+ctkPathListWidget::PathOptions ctkPathListWidget::fileOptions() const
+{
+  Q_D(const ctkPathListWidget);
+  return d->FileOptions;
+}
+
+// --------------------------------------------------------------------------
+void ctkPathListWidget::setFileOptions(PathOptions fileOptions)
+{
+  Q_D(ctkPathListWidget);
+  if (d->FileOptions != fileOptions)
+  {
+    d->FileOptions = fileOptions;
+    d->fileOptionsChanged();
+  }
+}
+
+// --------------------------------------------------------------------------
+ctkPathListWidget::PathOptions ctkPathListWidget::directoryOptions() const
+{
+  Q_D(const ctkPathListWidget);
+  return d->DirectoryOptions;
+}
+
+// --------------------------------------------------------------------------
+void ctkPathListWidget::setDirectoryOptions(PathOptions directoryOptions)
+{
+  Q_D(ctkPathListWidget);
+  if (d->DirectoryOptions != directoryOptions)
+  {
+    d->DirectoryOptions = directoryOptions;
+    d->directoryOptionsChanged();
+  }
+}
+
+// --------------------------------------------------------------------------
+QStringList ctkPathListWidget::files(bool absolutePath) const
+{
+  Q_D(const ctkPathListWidget);
+  QStringList fileList;
+  int role = Qt::DisplayRole;
+  if (absolutePath)
+  {
+    role = ctkPathListWidget::AbsolutePathRole;
+  }
+  for(int i = 0; i < d->PathListModel.rowCount(); ++i)
+  {
+    QString filePath = d->PathListModel.data(d->PathListModel.index(i, 0), role).toString();
+    if (d->pathType(filePath) == ctkPathListWidgetPrivate::File)
+    {
+      fileList << filePath;
+    }
+  }
+  return fileList;
+}
+
+// --------------------------------------------------------------------------
+QStringList ctkPathListWidget::directories(bool absolutePath) const
+{
+  Q_D(const ctkPathListWidget);
+  QStringList pathList;
+  int role = Qt::DisplayRole;
+  if (absolutePath)
+  {
+    role = ctkPathListWidget::AbsolutePathRole;
+  }
+  for(int i = 0; i < d->PathListModel.rowCount(); ++i)
+  {
+    QString dirPath = d->PathListModel.data(d->PathListModel.index(i, 0), role).toString();
+    if (d->pathType(dirPath) == ctkPathListWidgetPrivate::Directory)
+    {
+      pathList << dirPath;
+    }
+  }
+  return pathList;
+}
+
+// --------------------------------------------------------------------------
+QStringList ctkPathListWidget::paths(bool absolutePath)const
+{
+  Q_D(const ctkPathListWidget);
+  QStringList pathList;
+  int role = Qt::DisplayRole;
+  if (absolutePath)
+    {
+    role = ctkPathListWidget::AbsolutePathRole;
+    }
+  for(int i = 0; i < d->PathListModel.rowCount(); ++i)
+    {
+    pathList << d->PathListModel.data(d->PathListModel.index(i, 0), role).toString();
+    }
+  return pathList;
+}
+
+// --------------------------------------------------------------------------
+QStringList ctkPathListWidget::selectedPaths(bool absolutePath)const
+{
+  Q_D(const ctkPathListWidget);
+  QStringList pathList;
+  int role = Qt::DisplayRole;
+  if (absolutePath)
+    {
+    role = ctkPathListWidget::AbsolutePathRole;
+    }
+  QModelIndexList selectedIndexes = this->selectionModel()->selectedRows();
+  foreach(const QModelIndex& index, selectedIndexes)
+    {
+    pathList << d->PathListModel.data(index, role).toString();
+    }
+  return pathList;
+}
+
+// --------------------------------------------------------------------------
+QString ctkPathListWidget::currentPath(bool absolutePath) const
+{
+  Q_D(const ctkPathListWidget);
+
+  QModelIndex currentIndex = this->currentIndex();
+  if (!currentIndex.isValid())
+  {
+    return QString();
+  }
+
+  int role = absolutePath ? static_cast<int>(AbsolutePathRole)
+                          : static_cast<int>(Qt::DisplayRole);
+  return d->PathListModel.data(currentIndex, role).toString();
+}
+
+// --------------------------------------------------------------------------
+int ctkPathListWidget::count() const
+{
+  return this->model()->rowCount();
+}
+
+// --------------------------------------------------------------------------
+QString ctkPathListWidget::path(int row) const
+{
+  Q_D(const ctkPathListWidget);
+  if (row < 0 || row >= count())
+  {
+    return QString();
+  }
+  return d->PathListModel.data(d->PathListModel.index(row, 0), AbsolutePathRole).toString();
+}
+
+// --------------------------------------------------------------------------
+QString ctkPathListWidget::pathAt(const QPoint& point) const
+{
+  Q_D(const ctkPathListWidget);
+  return d->PathListModel.data(indexAt(point), AbsolutePathRole).toString();
+}
+
+// --------------------------------------------------------------------------
+int ctkPathListWidget::row(const QString& path) const
+{
+  Q_D(const ctkPathListWidget);
+  QModelIndexList result = d->PathListModel.match(d->PathListModel.index(0,0), AbsolutePathRole,
+                                                  QFileInfo(path).absoluteFilePath(), 1, Qt::MatchExactly);
+  Q_ASSERT(result.count() < 2);
+  if (!result.isEmpty())
+  {
+    return result.front().row();
+  }
+  return -1;
+}
+
+// --------------------------------------------------------------------------
+bool ctkPathListWidget::editPath(const QString &oldPath, const QString &newPath)
+{
+  Q_D(ctkPathListWidget);
+
+  QString oldAbsolutePath = QFileInfo(oldPath).absoluteFilePath();
+  QModelIndexList matched = d->PathListModel.match(d->PathListModel.index(0,0), AbsolutePathRole,
+                                                   oldAbsolutePath, 1, Qt::MatchExactly);
+  Q_ASSERT(matched.size() < 2);
+  if (matched.isEmpty())
+  {
+    return false;
+  }
+  return this->editPath(matched.front(), newPath);
+}
+
+// --------------------------------------------------------------------------
+bool ctkPathListWidget::editPath(const QModelIndex &index, const QString &newPath)
+{
+  Q_D(ctkPathListWidget);
+
+  if (!index.isValid())
+  {
+    return false;
+  }
+
+  QString oldAbsolutePath = d->PathListModel.data(index, AbsolutePathRole).toString();
+  ctkPathListWidgetPrivate::PathType oldPathType = d->pathType(oldAbsolutePath);
+  ctkPathListWidgetPrivate::PathType newPathType = d->pathType(newPath);
+  if (oldPathType != newPathType)
+  {
+    return false;
+  }
+
+  if (!d->isValidPath(newPath, newPathType))
+  {
+    return false;
+  }
+
+  QString newAbsolutePath = QFileInfo(newPath).absoluteFilePath();
+  d->PathListModel.setData(index, newPath, Qt::DisplayRole);
+  d->PathListModel.setData(index, newAbsolutePath, AbsolutePathRole);
+
+  emit this->pathsChanged(QStringList(newAbsolutePath), QStringList(oldAbsolutePath));
+  return true;
+}
+
+// --------------------------------------------------------------------------
+bool ctkPathListWidget::isFile(const QString &path) const
+{
+  Q_D(const ctkPathListWidget);
+  return d->pathType(path) == ctkPathListWidgetPrivate::File;
+}
+
+// --------------------------------------------------------------------------
+bool ctkPathListWidget::isDirectory(const QString &path) const
+{
+  Q_D(const ctkPathListWidget);
+  return d->pathType(path) == ctkPathListWidgetPrivate::Directory;
+}
+
+// --------------------------------------------------------------------------
+void ctkPathListWidget::setMode(ctkPathListWidget::Mode mode)
+{
+  Q_D(ctkPathListWidget);
+  d->Mode = mode;
+}
+
+// --------------------------------------------------------------------------
+bool ctkPathListWidget::contains(const QString& path)const
+{
+  Q_D(const ctkPathListWidget);
+  QString absolutePath = QFileInfo(path).absoluteFilePath();
+  QModelIndexList foundIndexes = d->PathListModel.match(
+        d->PathListModel.index(0, 0), ctkPathListWidget::AbsolutePathRole,
+        QVariant(absolutePath), 1, Qt::MatchExactly);
+  Q_ASSERT(foundIndexes.size() < 2);
+  return (foundIndexes.size() != 0);
+}
+
+// --------------------------------------------------------------------------
+bool ctkPathListWidget::addPath(const QString& path)
+{
+  return !this->addPaths(QStringList() << path).empty();
+}
+
+// --------------------------------------------------------------------------
+QStringList ctkPathListWidget::addPaths(const QStringList &paths)
+{
+  Q_D(ctkPathListWidget);
+
+  QStringList addedPaths;
+  foreach(const QString& path, paths)
+  {
+    if (d->addPath(path))
+    {
+      addedPaths << QFileInfo(path).absoluteFilePath();
+    }
+  }
+
+  if (!addedPaths.empty())
+  {
+    emit this->pathsChanged(addedPaths, QStringList());
+  }
+  return addedPaths;
+}
+
+// --------------------------------------------------------------------------
+bool ctkPathListWidget::removePath(const QString& path)
+{
+  return !this->removePaths(QStringList() << path).empty();
+}
+
+// --------------------------------------------------------------------------
+QStringList ctkPathListWidget::removePaths(const QStringList &paths)
+{
+  Q_D(ctkPathListWidget);
+
+  QStringList removedPaths;
+  foreach(const QString& path, paths)
+  {
+    if (d->removePath(path))
+    {
+      removedPaths << QFileInfo(path).absoluteFilePath();
+    }
+  }
+
+  if (!removedPaths.empty())
+  {
+    emit this->pathsChanged(QStringList(), removedPaths);
+  }
+  return removedPaths;
+}
+
+// --------------------------------------------------------------------------
+void ctkPathListWidget::removeSelectedPaths()
+{
+  Q_D(ctkPathListWidget);
+
+  QModelIndexList selectedIndexes = this->selectionModel()->selectedRows();
+  if (selectedIndexes.empty()) return;
+
+  QStringList removedPaths;
+  while(selectedIndexes.count() > 0)
+  {
+    removedPaths << d->PathListModel.data(selectedIndexes.front(), AbsolutePathRole).toString();
+    d->PathListModel.removeRow(selectedIndexes.front().row());
+    selectedIndexes = this->selectionModel()->selectedRows();
+  }
+
+  emit this->pathsChanged(QStringList(), removedPaths);
+}
+
+// --------------------------------------------------------------------------
+void ctkPathListWidget::clear()
+{
+  Q_D(ctkPathListWidget);
+  if (d->PathListModel.rowCount() == 0) return;
+
+  QStringList removedPaths = this->paths(true);
+  d->PathListModel.clear();
+  emit this->pathsChanged(QStringList(), removedPaths);
+}
+
+// --------------------------------------------------------------------------
+void ctkPathListWidget::setPaths(const QStringList& paths)
+{
+  Q_D(ctkPathListWidget);
+
+  QStringList addedPaths;
+  QStringList removedPaths;
+
+  QStringList absolutePaths;
+  foreach(const QString& path, paths)
+  {
+    absolutePaths << QFileInfo(path).absoluteFilePath();
+  }
+
+  foreach(const QString& path, this->paths(true))
+  {
+    if (!absolutePaths.contains(path) && d->removePath(path))
+    {
+      removedPaths << path;
+    }
+  }
+
+  for(int i = 0; i < paths.count(); ++i)
+  {
+    if (!this->contains(paths[i]) && d->addPath(paths[i]))
+    {
+      addedPaths << absolutePaths[i];
+    }
+  }
+
+  if (addedPaths.isEmpty() && removedPaths.empty())
+  {
+    return;
+  }
+
+  emit this->pathsChanged(addedPaths, removedPaths);
+}
+
+// --------------------------------------------------------------------------
+void ctkPathListWidget::setModel(QAbstractItemModel*)
+{
+  Q_ASSERT(!"ctkPathListWidget::setModel() - Changing the model of the ctkPathListWidget is not allowed.");
+}

+ 307 - 0
Libs/Widgets/ctkPathListWidget.h

@@ -0,0 +1,307 @@
+/*=========================================================================
+
+  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.apache.org/licenses/LICENSE-2.0.txt
+
+  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.
+
+  This file was originally developed by Jean-Christophe Fillion-Robin, Kitware Inc.
+  and was partially funded by NIH grant 3P41RR013218-12S1
+
+=========================================================================*/
+
+#ifndef __ctkPathListWidget_h
+#define __ctkPathListWidget_h
+
+// Qt includes
+#include <QListView>
+
+// QtGUI includes
+#include "ctkWidgetsExport.h"
+
+class ctkPathListWidgetPrivate;
+
+/// \ingroup Widgets
+///
+/// \brief The ctkPathListWidget lists files and/or directories.
+///
+/// The ctkPathListWidget is a QListView sub-class tailored specifically for displaying
+/// lists of file and/or directory entries. A \e path denotes either a file or a directory.
+/// Paths can be relative or absolute and the range of valid paths can be constrained
+/// by setting file and directory options.
+///
+class CTK_WIDGETS_EXPORT ctkPathListWidget : public QListView
+{
+  Q_OBJECT
+
+  /// The current list of paths.
+  Q_PROPERTY(QStringList paths READ paths WRITE setPaths NOTIFY pathsChanged)
+
+  /// The mode for this ctkPathListWidget.
+  Q_PROPERTY(Mode mode READ mode WRITE setMode)
+
+  /// Constraints on the file type.
+  Q_PROPERTY(PathOptions fileOptions READ fileOptions WRITE setFileOptions)
+
+  /// Constraints on the directory type.
+  Q_PROPERTY(PathOptions directoryOptions READ directoryOptions WRITE setDirectoryOptions)
+
+  /// The icon to be shown for a file entry.
+  Q_PROPERTY(QIcon fileIcon READ fileIcon WRITE setFileIcon RESET unsetFileIcon)
+
+  /// The icon to be shown for a directory entry.
+  Q_PROPERTY(QIcon directoryIcon READ directoryIcon WRITE setDirectoryIcon RESET unsetDirectoryIcon)
+
+  Q_FLAGS(PathOption PathOptions)
+  Q_ENUMS(Mode)
+
+public:
+
+  enum
+  {
+    /// A role for getting the absolute path from items in this list views model.
+    AbsolutePathRole = Qt::UserRole + 1
+  };
+
+  /// Describes constraints on paths.
+  enum PathOption
+  {
+    /// No constraints
+    None       = 0x00,
+    /// The path must exist in the file system.
+    Exists     = 0x01,
+    /// The path must be readable by the current user.
+    Readable   = 0x02,
+    /// The path must be writable by the current user.
+    Writable   = 0x04,
+    /// The path must be executable by the current user.
+    Executable = 0x08
+  };
+  Q_DECLARE_FLAGS(PathOptions, PathOption)
+
+  enum Mode
+  {
+    /// Allow all paths.
+    Any = 0,
+    /// Allow only file entries.
+    FilesOnly,
+    /// Allow only directory entries.
+    DirectoriesOnly
+  };
+
+  /// Superclass typedef
+  typedef QListView Superclass;
+
+  /// Constructor
+  explicit ctkPathListWidget(QWidget* parent = 0);
+
+  /// Destructor
+  virtual ~ctkPathListWidget();
+
+  /// \return The current widget mode.
+  Mode mode() const;
+
+  /// \return The QIcon used for file entries.
+  QIcon fileIcon() const;
+
+  /// Sets a QIcon to be used for file entries.
+  /// \param icon The new file entry icon.
+  void setFileIcon(const QIcon& icon);
+
+  /// Un-set any custom file icon.
+  void unsetFileIcon();
+
+  /// \return The QIcon used for directory entries.
+  QIcon directoryIcon() const;
+
+  /// Sets a QIcon to be used for directory entries.
+  /// \param icon The new directory entry icon.
+  void setDirectoryIcon(const QIcon& icon);
+
+  /// Un-set any custom directory icon.
+  void unsetDirectoryIcon();
+
+  /// \return The file entry constraints.
+  PathOptions fileOptions() const;
+
+  /// Set new file entry constraints.
+  /// \param fileOptions The file entry constraints.
+  void setFileOptions(PathOptions fileOptions);
+
+  /// \return The directory entry constraints.
+  PathOptions directoryOptions() const;
+
+  /// Set new directory entry constraints.
+  /// \param directoryOptions The directory entry constraints.
+  void setDirectoryOptions(PathOptions directoryOptions);
+
+  /// Checks if an entry with the given \a path already exists.
+  /// \return <code>true</code> if the \a path has already been added, <code>false</code> otherwise.
+  bool contains(const QString& path)const;
+
+  /// Get all file entries.
+  /// \param absolutePath If <code>true</code>, resolve all entries to absolute paths.
+  /// \return A list of all file entries.
+  QStringList files(bool absolutePath = false) const;
+
+  /// Get all directory entries.
+  /// \param absolutePath If <code>true</code>, resolve all entries to absolute paths.
+  /// \return A list of all directory entries.
+  QStringList directories(bool absolutePath = false) const;
+
+  /// Get all path entries.
+  /// \param absolutePath If <code>true</code>, resolve all entries to absolute paths.
+  /// \return A list of all entries.
+  QStringList paths(bool absolutePath = false) const;
+
+  /// Get all selected path entries.
+  /// \param absolutePath If <code>true</code>, resolve all entries to absolute paths.
+  /// \return A list of all selected entries.
+  QStringList selectedPaths(bool absolutePath = false) const;
+
+  /// Get the currently focused path entry.
+  /// \param absolutePath If <code>true</code>, resolve all entries to absolute paths.
+  /// \return The focused path entry or a null QString if no entry is focused.
+  QString currentPath(bool absolutePath = false) const;
+
+  /// \return The current entry count.
+  int count() const;
+
+  /// \return The absolute path for \a row or a null QString if \a row is out of range.
+  QString path(int row) const;
+
+  /// \return The absolute path for the entry located at the point \a point (in the
+  ///         widget coordinate system) or a null QString if no entry could be found for \a point.
+  QString pathAt(const QPoint& point) const;
+
+  /// \see pathAt(const QPoint&)
+  QString pathAt(int x, int y) const { return pathAt(QPoint(x, y)); }
+
+  /// \return The row number for the given \a path or -1 if \a path is not in the list of current
+  ///         entries.
+  int row(const QString& path) const;
+
+  /// Changes \a oldPath to the new value given by \a newPath. Does nothing if \a oldPath is not
+  /// in the list or \a newPath does not fullfill the current path options (constraints).
+  /// \param oldPath The path to be edited.
+  /// \param newPath The new path replacing \a oldPath.
+  /// \return <code>true</code> if the old path was successfully changed, <code>false</code> otherwise.
+  bool editPath(const QString& oldPath, const QString& newPath);
+
+  /// Changes the path value of \a index to \a newPath.
+  /// \param index The model index for which the path will be changed.
+  /// \param newPath The new path replacing the path value of \a index.
+  ///
+  /// \sa editPath(const QString&, const QString&)
+  bool editPath(const QModelIndex& index, const QString& newPath);
+
+  /// \return Returns <code>true</code> if the given path is treated as a file,
+  ///         <code>false</code> otherwise.
+  bool isFile(const QString& path) const;
+
+  /// \return Returns <code>true</code> if the given path is treated as a directory,
+  ///         <code>false</code> otherwise.
+  bool isDirectory(const QString& path) const;
+
+public Q_SLOTS:
+
+  /// Set the mode for controlling the path type.
+  /// \param mode The path mode.
+  void setMode(Mode mode);
+
+  /// Depending on the mode and path constraints, add \a path to the entry list and emit signal pathListChanged().
+  /// \param path The path to add.
+  /// \return <code>true</code> if the path was added, <code>false</code> otherwise.
+  ///
+  /// \sa pathListChanged()
+  bool addPath(const QString& path);
+
+  /// Depending on the mode and path constraints, add \a paths to the entry list and emit signal pathListChanged().
+  /// \param paths The paths to add.
+  /// \return The absolute paths of all added entries from \a paths.
+  ///
+  /// \sa pathListChanged()
+  QStringList addPaths(const QStringList& paths);
+
+  /// Remove all entries and set all valid entries in \a paths as the current list.
+  /// The signal pathListChanged() is emitted if the old list of paths is
+  /// different from the provided one.
+  /// \param paths The new path list.
+  ///
+  /// \sa addPaths(), pathListChanged()
+  void setPaths(const QStringList& paths);
+
+  /// Remove \a path from the list.
+  /// The signal pathListChanged() is emitted if the path was in the list.
+  /// \param path The path to remove.
+  /// \return <code>true</code> if \a path was removed, <code>false</code> otherwise.
+  ///
+  /// \sa pathListChanged()
+  bool removePath(const QString& path);
+
+  /// Remove \a paths from the list.
+  /// \param paths The paths to remove.
+  /// \return The absolute paths of all removed entries from \a paths.
+  QStringList removePaths(const QStringList& paths);
+
+  /// Remove all currently selected paths from the list.
+  void removeSelectedPaths();
+
+  /// Remove all paths from the list.
+  void clear();
+
+Q_SIGNALS:
+
+  /// This signal is emitted when paths are added or removed to the list.
+  /// \param added The newly added absolute paths.
+  /// \param removed The removed absolute paths.
+  void pathsChanged(const QStringList& added, const QStringList& removed);
+
+  /// The user clicked on a path entry.
+  void pathClicked(const QString& absolutePath);
+
+  /// The user double-clicked on a path entry.
+  void pathDoubleClicked(const QString& absolutePath);
+
+  /// This signal is emitted when the \a absolutePath entry is activated. The entry is activated when the user
+  /// clicks or double clicks on it, depending on the system configuration. It is also activated
+  /// when the user presses the activation key (on Windows and X11 this is the Return key, on
+  /// Mac OS X it is Ctrl+0).
+  void pathActivated(const QString& absolutePath);
+
+  /// This signal is emitted whenever the current item changes.
+  /// \param currentAbsolutePath The new current path entry.
+  /// \param previousAbsolutePath The path entry that previously had the focus.
+  void currentPathChanged(const QString& currentAbsolutePath, const QString& previousAbsolutePath);
+
+protected:
+  QScopedPointer<ctkPathListWidgetPrivate> d_ptr;
+
+private:
+
+  void setModel(QAbstractItemModel *model);
+
+  Q_DECLARE_PRIVATE(ctkPathListWidget)
+  Q_DISABLE_COPY(ctkPathListWidget)
+
+  Q_PRIVATE_SLOT(d_func(), void _q_emitPathClicked(const QModelIndex& index))
+  Q_PRIVATE_SLOT(d_func(), void _q_emitPathDoubleClicked(const QModelIndex& index))
+  Q_PRIVATE_SLOT(d_func(), void _q_emitPathActivated(const QModelIndex& index))
+  Q_PRIVATE_SLOT(d_func(), void _q_emitCurrentPathChanged(const QModelIndex &previous, const QModelIndex &current))
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(ctkPathListWidget::PathOptions)
+
+#endif
+