瀏覽代碼

XNAT tree model and example application

Miklos Espak 11 年之前
父節點
當前提交
64e3132bb2

+ 39 - 0
Applications/ctkXnatTreeBrowser/CMakeLists.txt

@@ -0,0 +1,39 @@
+project(ctkXnatTreeBrowser)
+
+#
+# See CTK/CMake/ctkMacroBuildApp.cmake for details
+#
+
+set(KIT_SRCS
+  ctkXnatTreeBrowserMain.cpp
+  ctkXnatTreeBrowserMainWindow.cpp
+)
+
+# Headers that should run through moc
+set(KIT_MOC_SRCS
+  ctkXnatTreeBrowserMainWindow.h
+)
+
+# UI files
+set(KIT_UI_FORMS
+  ctkXnatTreeBrowserMainWindow.ui
+)
+
+# Resources
+set(KIT_resources
+)
+
+# Target libraries - See CMake/ctkFunctionGetTargetLibraries.cmake
+# The following macro will read the target libraries from the file 'target_libraries.cmake'
+ctkFunctionGetTargetLibraries(KIT_target_libraries)
+
+ctkMacroBuildApp(
+  NAME ${PROJECT_NAME}
+  SRCS ${KIT_SRCS}
+  MOC_SRCS ${KIT_MOC_SRCS}
+  UI_FORMS ${KIT_UI_FORMS}
+  TARGET_LIBRARIES ${KIT_target_libraries}
+  RESOURCES ${KIT_resources}
+  )
+
+target_link_libraries(${PROJECT_NAME} ${QT_LIBRARIES})

+ 37 - 0
Applications/ctkXnatTreeBrowser/ctkXnatTreeBrowserMain.cpp

@@ -0,0 +1,37 @@
+/*=============================================================================
+
+  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
+
+  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 "ctkXnatTreeBrowserMainWindow.h"
+
+int main(int argc, char** argv)
+{
+  QApplication myApp(argc, argv);
+  myApp.setOrganizationName("CommonTK");
+  myApp.setApplicationName("XnatTreeBrowser");
+
+  ctkXnatTreeBrowserMainWindow mainWindow;
+  mainWindow.show();
+
+  return myApp.exec();
+}

+ 78 - 0
Applications/ctkXnatTreeBrowser/ctkXnatTreeBrowserMainWindow.cpp

@@ -0,0 +1,78 @@
+/*=============================================================================
+
+  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
+
+  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 "ctkXnatTreeBrowserMainWindow.h"
+#include "ui_ctkXnatTreeBrowserMainWindow.h"
+
+#include "ctkXnatLoginDialog.h"
+#include "ctkXnatTreeModel.h"
+#include "ctkXnatConnection.h"
+#include "ctkXnatServer.h"
+#include "ctkXnatProject.h"
+
+#include <QDebug>
+
+ctkXnatTreeBrowserMainWindow::ctkXnatTreeBrowserMainWindow(QWidget *parent) :
+  QMainWindow(parent),
+  ui(new Ui::ctkXnatTreeBrowserMainWindow),
+  xnatConnection(0),
+  treeModel(new ctkXnatTreeModel())
+{
+  ui->setupUi(this);
+
+  ui->treeView->setModel(treeModel);
+
+  connect(ui->loginButton, SIGNAL(clicked()), SLOT(loginButtonPushed()));
+}
+
+ctkXnatTreeBrowserMainWindow::~ctkXnatTreeBrowserMainWindow()
+{
+  delete treeModel;
+  delete ui;
+}
+
+void ctkXnatTreeBrowserMainWindow::loginButtonPushed()
+{
+  if (xnatConnection)
+  {
+    delete xnatConnection;
+    xnatConnection = NULL;
+    ui->loginButton->setText("Login");
+    ui->loginLabel->setText("Disconnected");
+  }
+  else
+  {
+    ctkXnatLoginDialog loginDialog(xnatConnectionFactory);
+    if (loginDialog.exec() == QDialog::Accepted)
+    {
+      xnatConnection = loginDialog.getConnection();
+      if (xnatConnection)
+      {
+        ui->loginButton->setText("Logout");
+        ui->loginLabel->setText(QString("Connected: %1").arg(xnatConnection->url()));
+
+        ctkXnatServer::Pointer server = xnatConnection->server();
+        treeModel->addServer(server);
+        ui->treeView->reset();
+      }
+    }
+  }
+}

+ 58 - 0
Applications/ctkXnatTreeBrowser/ctkXnatTreeBrowserMainWindow.h

@@ -0,0 +1,58 @@
+/*=============================================================================
+
+  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
+
+  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 CTKXNATTREEBROWSERMAINWINDOW_H
+#define CTKXNATTREEBROWSERMAINWINDOW_H
+
+#include <QMainWindow>
+
+class QModelIndex;
+
+#include "ctkXnatConnectionFactory.h"
+
+class ctkXnatConnection;
+class ctkXnatTreeModel;
+
+namespace Ui {
+class ctkXnatTreeBrowserMainWindow;
+}
+
+class ctkXnatTreeBrowserMainWindow : public QMainWindow
+{
+  Q_OBJECT
+
+public:
+  explicit ctkXnatTreeBrowserMainWindow(QWidget *parent = 0);
+  ~ctkXnatTreeBrowserMainWindow();
+
+private Q_SLOTS:
+
+  void loginButtonPushed();
+
+private:
+  Ui::ctkXnatTreeBrowserMainWindow* ui;
+
+  ctkXnatConnection* xnatConnection;
+  ctkXnatTreeModel* treeModel;
+  ctkXnatConnectionFactory xnatConnectionFactory;
+};
+
+#endif

+ 101 - 0
Applications/ctkXnatTreeBrowser/ctkXnatTreeBrowserMainWindow.ui

@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ctkXnatTreeBrowserMainWindow</class>
+ <widget class="QMainWindow" name="ctkXnatTreeBrowserMainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>377</width>
+    <height>325</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QVBoxLayout" name="verticalLayout_3">
+    <item>
+     <layout class="QHBoxLayout" name="horizontalLayout_2">
+      <item>
+       <widget class="QPushButton" name="loginButton">
+        <property name="text">
+         <string>Login</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="loginLabel">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="text">
+         <string>Not connected</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </item>
+    <item>
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <item>
+       <widget class="QTreeView" name="treeView"/>
+      </item>
+     </layout>
+    </item>
+    <item>
+     <widget class="QGroupBox" name="detailsGroupBox">
+      <property name="title">
+       <string>Details</string>
+      </property>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>377</width>
+     <height>22</height>
+    </rect>
+   </property>
+   <widget class="QMenu" name="menu_File">
+    <property name="title">
+     <string>&amp;File</string>
+    </property>
+    <addaction name="action_Quit"/>
+   </widget>
+   <addaction name="menu_File"/>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+  <action name="action_Quit">
+   <property name="text">
+    <string>&amp;Quit</string>
+   </property>
+  </action>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>action_Quit</sender>
+   <signal>triggered()</signal>
+   <receiver>ctkXnatBrowserMainWindow</receiver>
+   <slot>close()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>-1</x>
+     <y>-1</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>399</x>
+     <y>299</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>

+ 9 - 0
Applications/ctkXnatTreeBrowser/target_libraries.cmake

@@ -0,0 +1,9 @@
+#
+# See CMake/ctkFunctionGetTargetLibraries.cmake
+#
+# This file should list the libraries required to build the current CTK application.
+#
+
+set(target_libraries
+  CTKXNATWidgets
+  )

+ 4 - 0
CMakeLists.txt

@@ -593,6 +593,10 @@ ctk_app_option(ctkXNATBrowser
                "Build the XNAT Browser application" OFF
                CTK_BUILD_EXAMPLES)
 
+ctk_app_option(ctkXnatTreeBrowser
+               "Build the XNAT Tree Browser application" OFF
+               CTK_BUILD_EXAMPLES)
+
 # Save the set of enabled apps in a cache file
 set(_enabled_apps)
 foreach(_app ${CTK_APPS})

+ 15 - 0
Libs/XNAT/Core/ctkXnatConnection.cpp

@@ -47,6 +47,7 @@
 class ctkXnatConnectionPrivate
 {
 public:
+  QString profileName;
   QString url;
   QString userName;
   QString password;
@@ -94,6 +95,19 @@ void ctkXnatConnection::progress(QUuid queryId, double progress)
   qDebug() << "progress:" << (progress * 100.0) << "%";
 }
 
+QString ctkXnatConnection::profileName() const
+{
+  Q_D(const ctkXnatConnection);
+  return d->profileName;
+}
+
+void ctkXnatConnection::setProfileName(const QString& profileName)
+{
+  Q_D(ctkXnatConnection);
+  d->profileName = profileName;
+  d->server->setProperty("name", profileName);
+}
+
 QString ctkXnatConnection::url() const
 {
   Q_D(const ctkXnatConnection);
@@ -105,6 +119,7 @@ void ctkXnatConnection::setUrl(const QString& url)
   Q_D(ctkXnatConnection);
   d->url = url;
   d->xnat->setServerUrl(d->url);
+  d->server->setProperty("ID", url);
 }
 
 QString ctkXnatConnection::userName() const

+ 3 - 0
Libs/XNAT/Core/ctkXnatConnection.h

@@ -59,6 +59,9 @@ public:
 
   void createConnections();
 
+  QString profileName() const;
+  void setProfileName(const QString& profileName);
+
   QString url() const;
   void setUrl(const QString& url);
 

+ 13 - 3
Libs/XNAT/Core/ctkXnatObject.cpp

@@ -24,7 +24,6 @@
 
 #include "ctkXnatServer.h"
 #include <QVariant>
-#include <QDebug>
 
 ctkXnatObject::~ctkXnatObject()
 {
@@ -86,12 +85,23 @@ void ctkXnatObject::reset()
   Q_D(ctkXnatObject);
   //d->properties.clear();
   d->children.clear();
+  d->fetched = false;
+}
+
+bool ctkXnatObject::isFetched() const
+{
+  Q_D(const ctkXnatObject);
+  return d->fetched;
 }
 
 void ctkXnatObject::fetch()
 {
-  this->reset();
-  this->fetchImpl();
+  Q_D(ctkXnatObject);
+  if (!d->fetched)
+  {
+    this->fetchImpl();
+    d->fetched = true;
+  }
 }
 
 void ctkXnatObject::download(const QString& /*zipFilename*/)

+ 2 - 0
Libs/XNAT/Core/ctkXnatObject.h

@@ -55,6 +55,8 @@ public:
 
   void addChild(Pointer& child);
 
+  bool isFetched() const;
+
   virtual void reset();
   void fetch();
 

+ 1 - 0
Libs/XNAT/Core/ctkXnatObjectPrivate.cpp

@@ -24,6 +24,7 @@
 #include <QString>
 
 ctkXnatObjectPrivate::ctkXnatObjectPrivate()
+: fetched(false)
 {
 }
 

+ 2 - 0
Libs/XNAT/Core/ctkXnatObjectPrivate.h

@@ -49,6 +49,8 @@ private:
   QList<QSharedPointer<ctkXnatObject> > children;
   QMap<QString,QString> properties;
 
+  bool fetched;
+
   QWeakPointer<ctkXnatObject> parent;
 };
 

+ 1 - 1
Libs/XNAT/Core/ctkXnatServer.cpp

@@ -64,7 +64,7 @@ void ctkXnatServer::fetchImpl()
   Q_D(ctkXnatObject);
   qDebug() << "Starting to fetch projects...";
   ctkXnatObject::Pointer self = d->selfPtr;
-  return getConnection()->fetch(self.staticCast<ctkXnatServer>());
+  this->getConnection()->fetch(self.staticCast<ctkXnatServer>());
 }
 
 ctkXnatConnection* ctkXnatServer::getConnection() const

+ 4 - 1
Libs/XNAT/Widgets/CMakeLists.txt

@@ -9,14 +9,17 @@ set(KIT_export_directive "CTK_XNAT_WIDGETS_EXPORT")
 set(KIT_SRCS
   ctkXnatLoginDialog.cpp
   ctkXnatLoginProfile.cpp
+  ctkXnatTreeItem.cpp
   ctkXnatProjectListModel.cpp
+  ctkXnatTreeModel.cpp
   ctkXnatSettings.cpp
-  )
+)
 
 # Files which should be processed by Qts moc
 set(KIT_MOC_SRCS
   ctkXnatLoginDialog.h
   ctkXnatProjectListModel.h
+  ctkXnatTreeModel.h
 )
 
 

+ 1 - 0
Libs/XNAT/Widgets/ctkXnatLoginDialog.cpp

@@ -186,6 +186,7 @@ void ctkXnatLoginDialog::accept()
     {
     d->Connection = d->Factory.makeConnection(url.toAscii().constData(), userName.toAscii().constData(),
                                         password.toAscii().constData());
+    d->Connection->setProfileName(ui->edtProfileName->text());
     }
   catch (ctkXnatException& e)
     {

+ 89 - 0
Libs/XNAT/Widgets/ctkXnatTreeItem.cpp

@@ -0,0 +1,89 @@
+/*=============================================================================
+
+  Plugin: org.commontk.xnat
+
+  Copyright (c) University College London,
+    Centre for Medical Image Computing
+
+  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
+
+  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 "ctkXnatTreeItem.h"
+
+#include "ctkXnatObject.h"
+#include "ctkXnatTreeItem.h"
+
+ctkXnatTreeItem::ctkXnatTreeItem()
+: m_XnatObject(0)
+, m_ParentItem(0)
+{
+}
+
+ctkXnatTreeItem::ctkXnatTreeItem(ctkXnatObject::Pointer xnatObject, ctkXnatTreeItem* parentItem)
+: m_XnatObject(xnatObject)
+, m_ParentItem(parentItem)
+{
+}
+
+ctkXnatTreeItem::~ctkXnatTreeItem()
+{
+  qDeleteAll(m_ChildItems);
+}
+
+ctkXnatObject::Pointer ctkXnatTreeItem::xnatObject() const
+{
+  return m_XnatObject;
+}
+
+void ctkXnatTreeItem::appendChild(ctkXnatTreeItem* item)
+{
+  m_ChildItems.append(item);
+  item->m_ParentItem = this;
+}
+
+void ctkXnatTreeItem::removeChildren()
+{
+  qDeleteAll(m_ChildItems);
+}
+
+ctkXnatTreeItem* ctkXnatTreeItem::child(int row)
+{
+  return m_ChildItems.value(row);
+}
+
+int ctkXnatTreeItem::childCount() const
+{
+  return m_ChildItems.count();
+}
+
+int ctkXnatTreeItem::row() const
+{
+  return m_ParentItem->m_ChildItems.indexOf(const_cast<ctkXnatTreeItem*>(this));
+}
+
+int ctkXnatTreeItem::columnCount() const
+{
+  return 1;
+}
+
+QVariant ctkXnatTreeItem::data(int column) const
+{
+  Q_UNUSED(column);
+  return m_XnatObject->getName();
+}
+
+ctkXnatTreeItem* ctkXnatTreeItem::parent()
+{
+  return m_ParentItem;
+}

+ 60 - 0
Libs/XNAT/Widgets/ctkXnatTreeItem.h

@@ -0,0 +1,60 @@
+/*=============================================================================
+
+  Plugin: org.commontk.xnat
+
+  Copyright (c) University College London,
+    Centre for Medical Image Computing
+
+  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
+
+  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 ctkXnatTreeItem_h
+#define ctkXnatTreeItem_h
+
+#include "ctkXnatObject.h"
+
+#include <QList>
+#include <QVariant>
+
+class ctkXnatTreeItem;
+
+class ctkXnatTreeItem
+{
+public:
+
+  explicit ctkXnatTreeItem();
+  explicit ctkXnatTreeItem(ctkXnatObject::Pointer xnatObject, ctkXnatTreeItem* parentItem = 0);
+  virtual ~ctkXnatTreeItem();
+
+  ctkXnatObject::Pointer xnatObject() const;
+
+  void appendChild(ctkXnatTreeItem* child);
+  void removeChildren();
+
+  ctkXnatTreeItem* child(int row);
+  int childCount() const;
+  int columnCount() const;
+  QVariant data(int column) const;
+  int row() const;
+  ctkXnatTreeItem* parent();
+
+private:
+
+  ctkXnatObject::Pointer m_XnatObject;
+
+  ctkXnatTreeItem* m_ParentItem;
+  QList<ctkXnatTreeItem*> m_ChildItems;
+};
+
+#endif

+ 206 - 0
Libs/XNAT/Widgets/ctkXnatTreeModel.cpp

@@ -0,0 +1,206 @@
+/*=============================================================================
+
+  Plugin: org.commontk.xnat
+
+  Copyright (c) University College London,
+    Centre for Medical Image Computing
+
+  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
+
+  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 "ctkXnatTreeModel.h"
+
+#include "ctkXnatException.h"
+#include "ctkXnatObject.h"
+#include "ctkXnatSubject.h"
+
+#include <QDebug>
+#include <QList>
+
+ctkXnatTreeModel::ctkXnatTreeModel()
+: m_RootItem(new ctkXnatTreeItem())
+{
+}
+
+ctkXnatTreeModel::~ctkXnatTreeModel()
+{
+  delete m_RootItem;
+}
+
+// returns name (project, subject, etc.) for row and column of
+//   parent in index if role is Qt::DisplayRole
+QVariant ctkXnatTreeModel::data(const QModelIndex& index, int role) const
+{
+  if (!index.isValid())
+  {
+    return QVariant();
+  }
+
+  if (role == Qt::TextAlignmentRole)
+  {
+    return QVariant(int(Qt::AlignTop | Qt::AlignLeft));
+  }
+  else if (role == Qt::DisplayRole)
+  {
+    ctkXnatObject::Pointer xnatObject = this->itemAt(index)->xnatObject();
+
+    QString displayData = xnatObject->getName();
+    if (displayData.isEmpty())
+    {
+      displayData = xnatObject->getId();
+    }
+    return displayData;
+  }
+  else if (role == Qt::ToolTipRole)
+  {
+    ctkXnatObject::Pointer xnatObject = this->itemAt(index)->xnatObject();
+
+    return xnatObject->getDescription();
+  }
+
+  return QVariant();
+}
+
+QModelIndex ctkXnatTreeModel::index(int row, int column, const QModelIndex& index) const
+{
+  if (!this->hasIndex(row, column, index))
+  {
+    return QModelIndex();
+  }
+
+  ctkXnatTreeItem* item;
+  if (!index.isValid())
+  {
+    item = m_RootItem;
+  }
+  else
+  {
+    item = this->itemAt(index);
+  }
+
+  ctkXnatTreeItem* childItem = item->child(row);
+
+  if (childItem)
+  {
+    return this->createIndex(row, column, childItem);
+  }
+
+  return QModelIndex();
+}
+
+QModelIndex ctkXnatTreeModel::parent(const QModelIndex& index) const
+{
+  if (!index.isValid())
+  {
+    return QModelIndex();
+  }
+
+  ctkXnatTreeItem* item = this->itemAt(index);
+  ctkXnatTreeItem* parentItem = item->parent();
+
+  if (parentItem == m_RootItem)
+  {
+    return QModelIndex();
+  }
+
+  return this->createIndex(parentItem->row(), 0, parentItem);
+}
+
+int ctkXnatTreeModel::rowCount(const QModelIndex& index) const
+{
+  if (index.column() > 0)
+  {
+    return 0;
+  }
+
+  ctkXnatTreeItem* item;
+  if (!index.isValid())
+  {
+    item = m_RootItem;
+  }
+  else
+  {
+    item = this->itemAt(index);
+  }
+
+  return item->childCount();
+}
+
+int ctkXnatTreeModel::columnCount(const QModelIndex& index) const
+{
+  Q_UNUSED(index);
+  return 1;
+}
+
+// defer request for children until actually needed by QTreeView object
+bool ctkXnatTreeModel::hasChildren(const QModelIndex& index) const
+{
+  if (!index.isValid())
+  {
+    return m_RootItem->childCount() > 0;
+  }
+
+  ctkXnatTreeItem* item = this->itemAt(index);
+  ctkXnatObject::Pointer xnatObject = item->xnatObject();
+  return !xnatObject->isFetched() || (item->childCount() > 0);
+}
+
+bool ctkXnatTreeModel::canFetchMore(const QModelIndex& index) const
+{
+  if (!index.isValid())
+  {
+    return false;
+  }
+
+  ctkXnatTreeItem* item = this->itemAt(index);
+
+  ctkXnatObject::Pointer xnatObject = item->xnatObject();
+
+  return !xnatObject->isFetched();
+}
+
+void ctkXnatTreeModel::fetchMore(const QModelIndex& index)
+{
+  if (!index.isValid())
+  {
+    return;
+  }
+
+  ctkXnatTreeItem* item = this->itemAt(index);
+
+  ctkXnatObject::Pointer xnatObject = item->xnatObject();
+
+  xnatObject->fetch();
+
+  QList<ctkXnatObject::Pointer> children = xnatObject->getChildren();
+  if (!children.isEmpty())
+  {
+    beginInsertRows(index, 0, children.size() - 1);
+    foreach (ctkXnatObject::Pointer child, children)
+    {
+      item->appendChild(new ctkXnatTreeItem(child, item));
+    }
+    endInsertRows();
+  }
+}
+
+void ctkXnatTreeModel::addServer(ctkXnatServer::Pointer server)
+{
+  m_RootItem->appendChild(new ctkXnatTreeItem(server, m_RootItem));
+}
+
+ctkXnatTreeItem* ctkXnatTreeModel::itemAt(const QModelIndex& index) const
+{
+  return static_cast<ctkXnatTreeItem*>(index.internalPointer());
+}

+ 59 - 0
Libs/XNAT/Widgets/ctkXnatTreeModel.h

@@ -0,0 +1,59 @@
+/*=============================================================================
+
+  Plugin: org.commontk.xnat
+
+  Copyright (c) University College London,
+    Centre for Medical Image Computing
+
+  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
+
+  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 ctkXnatTreeModel_h
+#define ctkXnatTreeModel_h
+
+#include "ctkXNATWidgetsExport.h"
+
+#include <QAbstractItemModel>
+
+#include "ctkXnatTreeItem.h"
+#include "ctkXnatServer.h"
+
+
+class CTK_XNAT_WIDGETS_EXPORT ctkXnatTreeModel : public QAbstractItemModel
+{
+  Q_OBJECT
+
+public:
+  explicit ctkXnatTreeModel();
+  virtual ~ctkXnatTreeModel();
+
+  virtual QVariant data(const QModelIndex& index, int role) const;
+  virtual QModelIndex parent(const QModelIndex& child) const;
+  virtual QModelIndex index(int row, int column, const QModelIndex& parent) const;
+  virtual int rowCount(const QModelIndex& parent) const;
+  virtual int columnCount(const QModelIndex& parent) const;
+  virtual bool hasChildren(const QModelIndex& parent) const;
+  virtual bool canFetchMore(const QModelIndex& parent) const;
+  virtual void fetchMore(const QModelIndex& parent);
+
+  void addServer(ctkXnatServer::Pointer server);
+
+private:
+
+  ctkXnatTreeItem* itemAt(const QModelIndex& index) const;
+
+  ctkXnatTreeItem* m_RootItem;
+};
+
+#endif