瀏覽代碼

Cleanup and add test to ctkDICOMServerNodeWidget

Julien Finet 14 年之前
父節點
當前提交
48bd15382d

+ 66 - 119
Libs/DICOM/Widgets/Resources/UI/ctkDICOMServerNodeWidget.ui

@@ -6,147 +6,94 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>585</width>
-    <height>544</height>
+    <width>504</width>
+    <height>347</height>
    </rect>
   </property>
   <property name="windowTitle">
-   <string>Form</string>
+   <string>Server List</string>
   </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
-   <item>
-    <widget class="QFrame" name="frame_2">
-     <property name="frameShape">
-      <enum>QFrame::StyledPanel</enum>
-     </property>
-     <property name="frameShadow">
-      <enum>QFrame::Raised</enum>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0">
+    <widget class="QLabel" name="CallingAETitleLabel">
+     <property name="text">
+      <string>Calling AETitle</string>
      </property>
-     <layout class="QHBoxLayout" name="horizontalLayout_2">
-      <item>
-       <widget class="QLabel" name="label_2">
-        <property name="text">
-         <string>Calling AETitle</string>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <widget class="QLineEdit" name="CallingAETitle"/>
-      </item>
-     </layout>
     </widget>
    </item>
-   <item>
-    <widget class="QLabel" name="label">
+   <item row="0" column="1" colspan="3">
+    <widget class="QLineEdit" name="CallingAETitle"/>
+   </item>
+   <item row="2" column="0" colspan="2">
+    <widget class="QPushButton" name="AddButton">
      <property name="text">
-      <string>Servers</string>
+      <string>Add Server</string>
      </property>
     </widget>
    </item>
-   <item>
-    <widget class="QScrollArea" name="scrollArea">
-     <property name="widgetResizable">
-      <bool>true</bool>
+   <item row="3" column="0">
+    <widget class="QLabel" name="StorageAETitleLabel">
+     <property name="text">
+      <string>Storage AETitle</string>
      </property>
-     <widget class="QWidget" name="scrollAreaWidgetContents">
-      <property name="geometry">
-       <rect>
-        <x>0</x>
-        <y>0</y>
-        <width>565</width>
-        <height>342</height>
-       </rect>
-      </property>
-      <layout class="QVBoxLayout" name="verticalLayout_2">
-       <item>
-        <widget class="QTableWidget" name="NodeTable">
-         <attribute name="horizontalHeaderStretchLastSection">
-          <bool>true</bool>
-         </attribute>
-         <attribute name="verticalHeaderStretchLastSection">
-          <bool>false</bool>
-         </attribute>
-         <column>
-          <property name="text">
-           <string>Name</string>
-          </property>
-         </column>
-         <column>
-          <property name="text">
-           <string>AETitle</string>
-          </property>
-         </column>
-         <column>
-          <property name="text">
-           <string>Address</string>
-          </property>
-         </column>
-         <column>
-          <property name="text">
-           <string>Port</string>
-          </property>
-         </column>
-        </widget>
-       </item>
-      </layout>
-     </widget>
     </widget>
    </item>
-   <item>
-    <widget class="QFrame" name="frame">
-     <property name="frameShape">
-      <enum>QFrame::StyledPanel</enum>
-     </property>
-     <property name="frameShadow">
-      <enum>QFrame::Raised</enum>
+   <item row="3" column="1">
+    <widget class="QLineEdit" name="StorageAETitle"/>
+   </item>
+   <item row="3" column="2">
+    <widget class="QLabel" name="StoratePortLabel">
+     <property name="text">
+      <string>Storage Port</string>
      </property>
-     <layout class="QHBoxLayout" name="horizontalLayout">
-      <item>
-       <widget class="QPushButton" name="AddButton">
-        <property name="text">
-         <string>Add Server</string>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <widget class="QPushButton" name="RemoveButton">
-        <property name="text">
-         <string>Remove Server</string>
-        </property>
-       </widget>
-      </item>
-     </layout>
     </widget>
    </item>
-   <item>
-    <widget class="QFrame" name="frame_3">
-     <property name="frameShape">
-      <enum>QFrame::StyledPanel</enum>
+   <item row="3" column="3">
+    <widget class="QLineEdit" name="StoragePort"/>
+   </item>
+   <item row="2" column="2" colspan="2">
+    <widget class="QPushButton" name="RemoveButton">
+     <property name="text">
+      <string>Remove Server</string>
      </property>
-     <property name="frameShadow">
-      <enum>QFrame::Raised</enum>
+    </widget>
+   </item>
+   <item row="1" column="0" colspan="4">
+    <widget class="QGroupBox" name="ServersGroupBox">
+     <property name="title">
+      <string>Servers</string>
      </property>
-     <layout class="QHBoxLayout" name="horizontalLayout_3">
-      <item>
-       <widget class="QLabel" name="label_3">
-        <property name="text">
-         <string>Storage AETitle</string>
-        </property>
-       </widget>
-      </item>
+     <layout class="QVBoxLayout" name="verticalLayout">
       <item>
-       <widget class="QLineEdit" name="StorageAETitle"/>
-      </item>
-      <item>
-       <widget class="QLabel" name="label_4">
-        <property name="text">
-         <string>Storage Port</string>
-        </property>
+       <widget class="QTableWidget" name="NodeTable">
+        <attribute name="horizontalHeaderStretchLastSection">
+         <bool>true</bool>
+        </attribute>
+        <attribute name="verticalHeaderStretchLastSection">
+         <bool>false</bool>
+        </attribute>
+        <column>
+         <property name="text">
+          <string>Name</string>
+         </property>
+        </column>
+        <column>
+         <property name="text">
+          <string>AETitle</string>
+         </property>
+        </column>
+        <column>
+         <property name="text">
+          <string>Address</string>
+         </property>
+        </column>
+        <column>
+         <property name="text">
+          <string>Port</string>
+         </property>
+        </column>
        </widget>
       </item>
-      <item>
-       <widget class="QLineEdit" name="StoragePort"/>
-      </item>
      </layout>
     </widget>
    </item>

+ 1 - 0
Libs/DICOM/Widgets/Testing/Cpp/CMakeLists.txt

@@ -9,6 +9,7 @@ CREATE_TEST_SOURCELIST(Tests ${KIT}CppTests.cpp
   ctkDICOMModelTest2.cpp
   ctkDICOMQueryRetrieveWidgetTest1.cpp
   ctkDICOMQueryResultsTabWidgetTest1.cpp
+  ctkDICOMServerNodeWidgetTest1.cpp
   ctkDICOMThumbnailWidgetTest1.cpp
   )
 

+ 149 - 0
Libs/DICOM/Widgets/Testing/Cpp/ctkDICOMServerNodeWidgetTest1.cpp

@@ -0,0 +1,149 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.commontk.org/LICENSE
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+// Qt includes
+#include <QApplication>
+#include <QTimer>
+#include <QVariant>
+
+// ctkDICOMCore includes
+#include "ctkDICOMServerNodeWidget.h"
+
+// STD includes
+#include <iostream>
+
+int ctkDICOMServerNodeWidgetTest1( int argc, char * argv [] )
+{
+  QApplication app(argc, argv);
+
+  ctkDICOMServerNodeWidget widget;
+  if (widget.callingAETitle() != "FINDSCU")
+    {
+    std::cout << "ctkDICOMServerNodeWidget::callingAETitle() failed:"
+              << qPrintable(widget.callingAETitle()) << " instead of FINDSCU"
+              << std::endl;
+    return EXIT_FAILURE;
+    }
+  if (widget.storageAETitle() != "CTKSTORE")
+    {
+    std::cout << "ctkDICOMServerNodeWidget::storageAETitle() failed:"
+              << qPrintable(widget.storageAETitle()) << " instead of CTKSTORE"
+              << std::endl;
+    return EXIT_FAILURE;
+    }
+  if (widget.storagePort() != 11112)
+    {
+    std::cout << "ctkDICOMServerNodeWidget::storagePort() failed:"
+              << widget.storagePort() << " instead of 11112"
+              << std::endl;
+    return EXIT_FAILURE;
+    }
+  QMap<QString,QVariant> parameters;
+  parameters["CallingAETitle"] = QVariant(widget.callingAETitle());
+  parameters["StorageAETitle"] = QVariant(widget.storageAETitle());
+  parameters["StoragePort"] = QVariant(widget.storagePort());
+  if (widget.parameters() != parameters)
+    {
+    std::cout << "ctkDICOMServerNodeWidget::parameters() failed." << std::endl;
+    return EXIT_FAILURE;
+    }
+  if (widget.serverNodes().count() != 1)
+    {
+    std::cout << "ctkDICOMServerNodeWidget::serverNodes() failed:"
+              << widget.serverNodes().count() << std::endl;
+    return EXIT_FAILURE;
+    }
+  if (widget.selectedServerNodes().count() != 1)
+    {
+    std::cout << "ctkDICOMServerNodeWidget::selectedServerNodes() failed:"
+              << widget.selectedServerNodes().count() << std::endl;
+    return EXIT_FAILURE;
+    }
+  if (widget.serverNodeParameters("").count() != 0)
+    {
+    std::cout << "ctkDICOMServerNodeWidget::serverNodeParameters("") failed:"
+              << widget.serverNodeParameters("").count() << std::endl;
+    return EXIT_FAILURE;
+    }
+  if (widget.serverNodeParameters(-1).count() != 0 ||
+      widget.serverNodeParameters(0).count() != 5 || // 5 per node
+      widget.serverNodeParameters(1).count() != 0)
+    {
+    std::cout << "ctkDICOMServerNodeWidget::serverNodeParameters() failed:"
+              << widget.serverNodeParameters(-1).count() << " "
+              << widget.serverNodeParameters(0).count() << " "
+              << widget.serverNodeParameters(1).count() << std::endl;
+    return EXIT_FAILURE;
+    }
+  // Add an empty row and make it current
+  widget.addServerNode();
+  if (widget.serverNodes().count() != 2)
+    {
+    std::cout << "ctkDICOMServerNodeWidget::addServerNode() failed:"
+              << widget.serverNodes().count() << std::endl;
+    return EXIT_FAILURE;
+    }
+  widget.removeCurrentServerNode();
+  if (widget.serverNodes().count() != 1)
+    {
+    std::cout << "ctkDICOMServerNodeWidget::removeCurrentServerNode() failed:"
+              << widget.serverNodes().count() << std::endl;
+    return EXIT_FAILURE;
+    }
+  QMap<QString, QVariant> serverNode;
+  serverNode["Name"] = QString("TestName");
+  serverNode["CheckState"] = Qt::Unchecked;
+  serverNode["AETitle"] = QString("TestAETitle");
+  serverNode["Address"] = QString("TestAddress");
+  serverNode["Port"] = 12345;
+  widget.addServerNode(serverNode);
+  if (widget.serverNodes().count() != 2 ||
+      widget.serverNodes()[1] != "TestName" ||
+      widget.selectedServerNodes().count() != 1 ||
+      widget.serverNodeParameters("TestName") != serverNode ||
+      widget.serverNodeParameters(1) != serverNode)
+    {
+     std::cout << "ctkDICOMServerNodeWidget::addServerNode() failed:"
+              << widget.serverNodes().count() << " "
+              << (widget.serverNodes().count() > 1?
+                    qPrintable(widget.serverNodes()[1]) : "none") << " "
+              << widget.selectedServerNodes().count() << " "
+              << (widget.serverNodeParameters("TestName") == serverNode) << " "
+              << (widget.serverNodeParameters(1) == serverNode) << std::endl;
+    return EXIT_FAILURE;
+    }
+  widget.removeCurrentServerNode();
+  if (widget.serverNodes().count() != 1)
+    {
+    std::cout << "ctkDICOMServerNodeWidget::removeCurrentServerNode() failed:"
+              << widget.serverNodes().count() << std::endl;
+    return EXIT_FAILURE;
+    }
+  widget.saveSettings();
+  widget.readSettings();
+  widget.show();
+
+  if (argc <= 1 || QString(argv[1]) != "-I")
+    {
+    QTimer::singleShot(200, &app, SLOT(quit()));
+    }
+
+  return app.exec();
+}

+ 4 - 3
Libs/DICOM/Widgets/ctkDICOMQueryRetrieveWidget.cpp

@@ -176,13 +176,14 @@ void ctkDICOMQueryRetrieveWidget::query()
   progress.setWindowModality(Qt::WindowModal);
   progress.setMinimumDuration(0);
   progress.setValue(0);
-  foreach (d->CurrentServer, d->ServerNodeWidget->checkedNodes())
+  foreach (d->CurrentServer, d->ServerNodeWidget->selectedServerNodes())
     {
     if (progress.wasCanceled())
       {
       break;
       }
-    QMap<QString, QVariant> parameters = d->ServerNodeWidget->nodeParameters(d->CurrentServer);
+    QMap<QString, QVariant> parameters =
+      d->ServerNodeWidget->serverNodeParameters(d->CurrentServer);
     // if we are here it's because the server node was checked
     Q_ASSERT(parameters["CheckState"] == Qt::Checked );
     // create a query for the current server
@@ -294,7 +295,7 @@ void ctkDICOMQueryRetrieveWidget::onQueryProgressChanged(int value)
     {
     return;
     }
-  QStringList servers = d->ServerNodeWidget->checkedNodes();
+  QStringList servers = d->ServerNodeWidget->selectedServerNodes();
   int serverIndex = servers.indexOf(d->CurrentServer);
   if (serverIndex < 0)
     {

+ 123 - 85
Libs/DICOM/Widgets/ctkDICOMServerNodeWidget.cpp

@@ -78,14 +78,16 @@ ctkDICOMServerNodeWidget::ctkDICOMServerNodeWidget(QWidget* parentWidget)
     this, SLOT(saveSettings()));
   connect(d->StoragePort, SIGNAL(textChanged(const QString&)),
     this, SLOT(saveSettings()));
+
   connect(d->AddButton, SIGNAL(clicked()),
-    this, SLOT(addNode()));
+    this, SLOT(addServerNode()));
   connect(d->RemoveButton, SIGNAL(clicked()),
-    this, SLOT(removeNode()));
+    this, SLOT(removeCurrentServerNode()));
+
   connect(d->NodeTable, SIGNAL(cellChanged(int,int)),
-    this, SLOT(onCellChanged(int,int)));
+    this, SLOT(saveSettings()));
   connect(d->NodeTable, SIGNAL(currentItemChanged(QTableWidgetItem*, QTableWidgetItem*)),
-    this, SLOT(onCurrentItemChanged(QTableWidgetItem*, QTableWidgetItem*)));
+    this, SLOT(updateRemoveButtonEnableState()));
 }
 
 //----------------------------------------------------------------------------
@@ -93,45 +95,57 @@ ctkDICOMServerNodeWidget::~ctkDICOMServerNodeWidget()
 {
 }
 
-
 //----------------------------------------------------------------------------
-void ctkDICOMServerNodeWidget::addNode()
+int ctkDICOMServerNodeWidget::addServerNode()
 {
   Q_D(ctkDICOMServerNodeWidget);
+  const int rowCount = d->NodeTable->rowCount();
+  d->NodeTable->setRowCount( rowCount + 1 );
+
+  QTableWidgetItem* newItem = new QTableWidgetItem;
+  newItem->setCheckState( Qt::Unchecked );
+  d->NodeTable->setItem(rowCount, NameColumn, newItem);
 
-  d->NodeTable->setRowCount( d->NodeTable->rowCount() + 1 );
+  d->NodeTable->setCurrentCell(rowCount, NameColumn);
+  // The old rowCount becomes the added row index
+  return rowCount;
 }
 
 //----------------------------------------------------------------------------
-void ctkDICOMServerNodeWidget::removeNode()
+int ctkDICOMServerNodeWidget::addServerNode(const QMap<QString, QVariant>& node)
 {
   Q_D(ctkDICOMServerNodeWidget);
-
-  d->NodeTable->removeRow( d->NodeTable->currentRow() );
-  d->RemoveButton->setEnabled(false);
-  this->saveSettings();
+  const int row = this->addServerNode();
+  
+  QTableWidgetItem *newItem;
+  newItem = new QTableWidgetItem( node["Name"].toString() );
+  newItem->setCheckState( Qt::CheckState(node["CheckState"].toInt()) );
+  d->NodeTable->setItem(row, NameColumn, newItem);
+  newItem = new QTableWidgetItem( node["AETitle"].toString() );
+  d->NodeTable->setItem(row, AETitleColumn, newItem);
+  newItem = new QTableWidgetItem( node["Address"].toString() );
+  d->NodeTable->setItem(row, AddressColumn, newItem);
+  newItem = new QTableWidgetItem( node["Port"].toString() );
+  d->NodeTable->setItem(row, PortColumn, newItem);
+  return row;
 }
 
 //----------------------------------------------------------------------------
-void ctkDICOMServerNodeWidget::onCellChanged(int row, int column)
+void ctkDICOMServerNodeWidget::removeCurrentServerNode()
 {
-  Q_UNUSED(row);
-  Q_UNUSED(column);
+  Q_D(ctkDICOMServerNodeWidget);
+
+  d->NodeTable->removeRow( d->NodeTable->currentRow() );
 
   this->saveSettings();
+  this->updateRemoveButtonEnableState();
 }
 
 //----------------------------------------------------------------------------
-void ctkDICOMServerNodeWidget::onCurrentItemChanged(QTableWidgetItem* current, QTableWidgetItem *previous)
+void ctkDICOMServerNodeWidget::updateRemoveButtonEnableState()
 {
-  Q_UNUSED(current);
-  Q_UNUSED(previous);
-
   Q_D(ctkDICOMServerNodeWidget);
-  if (d->NodeTable->rowCount() > 1)
-  {
-    d->RemoveButton->setEnabled(true);
-  }
+  d->RemoveButton->setEnabled(d->NodeTable->rowCount() > 0);
 }
 
 //----------------------------------------------------------------------------
@@ -146,23 +160,12 @@ void ctkDICOMServerNodeWidget::saveSettings()
   settings.setValue("ServerNodeCount", rowCount);
   for (int row = 0; row < rowCount; ++row)
     {
-    QMap<QString, QVariant> node;
-    for (int k = 0; k < columnCount; ++k)
-      {
-      if (!d->NodeTable->item(row,k))
-        {
-        continue;
-        }
-      QString label = d->NodeTable->horizontalHeaderItem(k)->text();
-      node[label] = d->NodeTable->item(row, k)->data(Qt::DisplayRole);
-      }
-    node["CheckState"] = d->NodeTable->item(row,0) ?
-      d->NodeTable->item(row,0)->checkState() : Qt::Unchecked;
+    QMap<QString, QVariant> node = this->serverNodeParameters(row);
     settings.setValue(QString("ServerNodes/%1").arg(row), QVariant(node));
     }
-  settings.setValue("CallingAETitle", d->CallingAETitle->text());
-  settings.setValue("StorageAETitle", d->StorageAETitle->text());
-  settings.setValue("StoragePort", d->StoragePort->text());
+  settings.setValue("CallingAETitle", this->callingAETitle());
+  settings.setValue("StorageAETitle", this->storageAETitle());
+  settings.setValue("StoragePort", this->storagePort());
   settings.sync();
 }
 
@@ -171,6 +174,8 @@ void ctkDICOMServerNodeWidget::readSettings()
 {
   Q_D(ctkDICOMServerNodeWidget);
 
+  d->NodeTable->setRowCount(0);
+
   QSettings settings;
 
   QMap<QString, QVariant> node;
@@ -180,114 +185,147 @@ void ctkDICOMServerNodeWidget::readSettings()
     d->StorageAETitle->setText("CTKSTORE");
     d->StoragePort->setText("11112");
     d->CallingAETitle->setText("FINDSCU");
-    d->NodeTable->setRowCount(1);
-    d->NodeTable->setItem(0, NameColumn, new QTableWidgetItem("ExampleHost"));
-    d->NodeTable->item(0, NameColumn)->setCheckState( Qt::Checked );
-    d->NodeTable->setItem(0, AETitleColumn, new QTableWidgetItem("ANY-SCP"));
-    d->NodeTable->setItem(0, AddressColumn, new QTableWidgetItem("localhost"));
-    d->NodeTable->setItem(0, PortColumn, new QTableWidgetItem("11112"));
+
+    QMap<QString, QVariant> defaultServerNode;
+    defaultServerNode["Name"] = QString("ExampleHost");
+    defaultServerNode["CheckState"] = Qt::Checked;
+    defaultServerNode["AETitle"] = QString("ANY-SCP");
+    defaultServerNode["Address"] = QString("localhost");
+    defaultServerNode["Port"] = QString("11112");
+    this->addServerNode(defaultServerNode);
     return;
     }
 
   d->StorageAETitle->setText(settings.value("StorageAETitle").toString());
   d->StoragePort->setText(settings.value("StoragePort").toString());
   d->CallingAETitle->setText(settings.value("CallingAETitle").toString());
+
   const int count = settings.value("ServerNodeCount").toInt();
-  d->NodeTable->setRowCount(count);
-  for (int row = 0; row < count; row++)
+  for (int row = 0; row < count; ++row)
     {
     node = settings.value(QString("ServerNodes/%1").arg(row)).toMap();
-    QTableWidgetItem *newItem;
-    newItem = new QTableWidgetItem( node["Name"].toString() );
-    newItem->setCheckState( Qt::CheckState(node["CheckState"].toInt()) );
-    d->NodeTable->setItem(row, NameColumn, newItem);
-    newItem = new QTableWidgetItem( node["AETitle"].toString() );
-    d->NodeTable->setItem(row, AETitleColumn, newItem);
-    newItem = new QTableWidgetItem( node["Address"].toString() );
-    d->NodeTable->setItem(row, AddressColumn, newItem);
-    newItem = new QTableWidgetItem( node["Port"].toString() );
-    d->NodeTable->setItem(row, PortColumn, newItem);
+    this->addServerNode(node);
     }
 }
 
 //----------------------------------------------------------------------------
-QString ctkDICOMServerNodeWidget::callingAETitle()
+QString ctkDICOMServerNodeWidget::callingAETitle()const
 {
-  Q_D(ctkDICOMServerNodeWidget);
-
+  Q_D(const ctkDICOMServerNodeWidget);
   return d->CallingAETitle->text();
 }
 
 //----------------------------------------------------------------------------
-QMap<QString,QVariant> ctkDICOMServerNodeWidget::parameters()
+QString ctkDICOMServerNodeWidget::storageAETitle()const
 {
-  Q_D(ctkDICOMServerNodeWidget);
+  Q_D(const ctkDICOMServerNodeWidget);
+  return d->StorageAETitle->text();
+}
+
+//----------------------------------------------------------------------------
+int ctkDICOMServerNodeWidget::storagePort()const
+{
+  Q_D(const ctkDICOMServerNodeWidget);
+  bool ok = false;
+  int port = d->StoragePort->text().toInt(&ok);
+  Q_ASSERT(ok);
+  return port;
+}
+
+//----------------------------------------------------------------------------
+QMap<QString,QVariant> ctkDICOMServerNodeWidget::parameters()const
+{
+  Q_D(const ctkDICOMServerNodeWidget);
 
   QMap<QString, QVariant> parameters;
 
-  parameters["CallingAETitle"] = d->CallingAETitle->text();
-  parameters["StorageAETitle"] = d->StorageAETitle->text();
-  parameters["StoragePort"] = d->StoragePort->text();
+  parameters["CallingAETitle"] = this->callingAETitle();
+  parameters["StorageAETitle"] = this->storageAETitle();
+  parameters["StoragePort"] = this->storagePort();
 
   return parameters;
 }
 
 //----------------------------------------------------------------------------
-QStringList ctkDICOMServerNodeWidget::nodes()const
+QStringList ctkDICOMServerNodeWidget::serverNodes()const
 {
   Q_D(const ctkDICOMServerNodeWidget);
 
-  const int count = d->NodeTable->rowCount();
   QStringList nodes;
-  for (int row = 0; row < count; row++)
+  const int count = d->NodeTable->rowCount();
+  for (int row = 0; row < count; ++row)
     {
-    nodes << d->NodeTable->item(row,NameColumn)->text();
+    QTableWidgetItem* item = d->NodeTable->item(row,NameColumn);
+    nodes << (item ? item->text() : QString(""));
     }
+  // If there are duplicates, serverNodeParameters(QString) will behave
+  // strangely
+  Q_ASSERT(nodes.removeDuplicates() == 0);
   return nodes;
 }
 
 //----------------------------------------------------------------------------
-QStringList ctkDICOMServerNodeWidget::checkedNodes()const
+QStringList ctkDICOMServerNodeWidget::selectedServerNodes()const
 {
   Q_D(const ctkDICOMServerNodeWidget);
 
-  const int count = d->NodeTable->rowCount();
   QStringList nodes;
-  for (int row = 0; row < count; row++)
+  const int count = d->NodeTable->rowCount();
+  for (int row = 0; row < count; ++row)
     {
-    QTableWidgetItem* item = d->NodeTable->item(row,NameColumn);
+    QTableWidgetItem* item = d->NodeTable->item(row, NameColumn);
     if (item && item->checkState() == Qt::Checked)
       {
       nodes << item->text();
       }
     }
+  // If there are duplicates, serverNodeParameters(QString) will behave
+  // strangely
+  Q_ASSERT(nodes.removeDuplicates() == 0);
   return nodes;
 }
 
 //----------------------------------------------------------------------------
-QMap<QString, QVariant> ctkDICOMServerNodeWidget::nodeParameters(const QString &node)const
+QMap<QString, QVariant> ctkDICOMServerNodeWidget::serverNodeParameters(const QString &node)const
 {
   Q_D(const ctkDICOMServerNodeWidget);
 
   QMap<QString, QVariant> parameters;
 
-  int count = d->NodeTable->rowCount();
-  QStringList keys;
-  keys << "Name" << "AETitle" << "Address" << "Port";
+  const int count = d->NodeTable->rowCount();
   for (int row = 0; row < count; row++)
-  {
-    if ( d->NodeTable->item(row,0)->text() == node )
     {
-      for (int k = 0; k < keys.size(); ++k)
+    if ( d->NodeTable->item(row,0)->text() == node )
       {
-        if ( d->NodeTable->item(row,k) )
-        {
-          parameters[keys.at(k)] = d->NodeTable->item(row,k)->text();
-        }
-        parameters["CheckState"] = d->NodeTable->item(row,0)->checkState();
+      // TBD: not sure what it means to merge parameters
+      parameters.unite(this->serverNodeParameters(row));
       }
     }
-  }
 
   return parameters;
 }
+
+//----------------------------------------------------------------------------
+QMap<QString, QVariant> ctkDICOMServerNodeWidget::serverNodeParameters(int row)const
+{
+  Q_D(const ctkDICOMServerNodeWidget);
+
+  QMap<QString, QVariant> node;
+  if (row < 0 || row >= d->NodeTable->rowCount())
+    {
+    return node;
+    }
+  const int columnCount = d->NodeTable->columnCount();
+  for (int column = 0; column < columnCount; ++column)
+    {
+    if (!d->NodeTable->item(row, column))
+      {
+      continue;
+      }
+    QString label = d->NodeTable->horizontalHeaderItem(column)->text();
+    node[label] = d->NodeTable->item(row, column)->data(Qt::DisplayRole);
+    }
+  node["CheckState"] = d->NodeTable->item(row, NameColumn) ?
+    d->NodeTable->item(row,0)->checkState() : Qt::Unchecked;
+  return node;
+}

+ 29 - 10
Libs/DICOM/Widgets/ctkDICOMServerNodeWidget.h

@@ -40,22 +40,41 @@ public:
   explicit ctkDICOMServerNodeWidget(QWidget* parent=0);
   virtual ~ctkDICOMServerNodeWidget();
 
-  QString                callingAETitle();
-  QMap<QString,QVariant> parameters();
-
-  QStringList            nodes()const;
-  QStringList            checkedNodes()const;
-  QMap<QString,QVariant> nodeParameters(const QString &node)const ;
+  /// "FINDSCU" by default
+  QString                callingAETitle()const;
+  /// "CTKSTORE" by default
+  QString                storageAETitle()const;
+  /// 11112 by default
+  int                    storagePort()const;
+  /// Utility function that returns the callingAETitle, storageAETitle and
+  /// storagePort in a map
+  QMap<QString,QVariant> parameters()const;
+
+  /// Return the list of server names
+  QStringList            serverNodes()const;
+  /// Return the list of selected(checked) server names 
+  QStringList            selectedServerNodes()const;
+  /// Return all the information associated to a server defined by its name
+  QMap<QString,QVariant> serverNodeParameters(const QString &serverNode)const;
+  QMap<QString,QVariant> serverNodeParameters(int row)const;
+  
+  /// Add a server node with the given parameters
+  /// Return the row index added into the table
+  int addServerNode(const QMap<QString, QVariant>& parameters);
 
 public slots:
-  void addNode ();
-  void removeNode ();
-  void onCellChanged (int row, int column);
-  void onCurrentItemChanged(QTableWidgetItem* current, QTableWidgetItem *previous);
+  /// Add an empty server node and make it current
+  /// Return the row index added into the table
+  int addServerNode();
+  /// Remove the current row (different from the checked rows)
+  void removeCurrentServerNode();
 
   void readSettings();
   void saveSettings();
 
+protected slots:
+  void updateRemoveButtonEnableState();
+
 protected:
   QScopedPointer<ctkDICOMServerNodeWidgetPrivate> d_ptr;
   enum ServerColumns{