Browse Source

Merge branch '199-create-a-path-list-widget'

* 199-create-a-path-list-widget:
  Reworked ctkDirectoryListWidget for improved flexibility.
  Changed size of vertical spacer in ctkDirectoryListWidget group box to get better layout when expand/un-expand
  Changed ctkDirectoryListWidgetPrivate::onExpandClicked to take bool, and toggle visibility of group box
  Updated copyright info in ctkDirectoryListView and ctkDirectoryListWidget
  Connect directoryListChanged signal
  Added ctkDirectoryListWidget
  Added ctkDirectoryListView
Sascha Zelzer 12 years ago
parent
commit
0911bbd4f4

+ 12 - 0
Libs/Widgets/CMakeLists.txt

@@ -108,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
@@ -226,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
@@ -261,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
@@ -269,6 +280,7 @@ 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

+ 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

+ 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"

+ 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

+ 74 - 0
Libs/Widgets/ctkPathListButtonsWidget_p.h

@@ -0,0 +1,74 @@
+/*=========================================================================
+
+  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_p_h
+#define __ctkPathListButtonsWidget_p_h
+
+// Qt includes
+#include <QObject>
+
+// CTK includes
+#include "ui_ctkPathListButtonsWidget.h"
+
+class ctkPathListButtonsWidget;
+class ctkPathListWidget;
+
+class QFileDialog;
+
+//-----------------------------------------------------------------------------
+class ctkPathListButtonsWidgetPrivate : public QObject, public Ui_ctkPathListButtonsWidget
+{
+  Q_OBJECT
+  Q_DECLARE_PUBLIC(ctkPathListButtonsWidget)
+
+protected:
+
+  ctkPathListButtonsWidget* const q_ptr;
+
+public:
+
+  explicit ctkPathListButtonsWidgetPrivate(ctkPathListButtonsWidget& object);
+  virtual ~ctkPathListButtonsWidgetPrivate();
+
+  void init();
+  void setupUi(QWidget * parent);
+
+public Q_SLOTS:
+
+  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
+