|
@@ -26,6 +26,7 @@
|
|
|
#include <QApplication>
|
|
|
#include <QClipboard>
|
|
|
#include <QDesktopServices>
|
|
|
+#include <QSortFilterProxyModel>
|
|
|
#include <QString>
|
|
|
#include <QStringList>
|
|
|
#include <QUrl>
|
|
@@ -35,6 +36,54 @@
|
|
|
#include <ctkLogger.h>
|
|
|
static ctkLogger logger("org.commontk.DICOM.Widgets.ctkDICOMObjectListWidget");
|
|
|
|
|
|
+class qRecursiveTreeProxyFilter : public QSortFilterProxyModel
|
|
|
+{
|
|
|
+public:
|
|
|
+ qRecursiveTreeProxyFilter(QObject *parent = NULL):
|
|
|
+ QSortFilterProxyModel(parent)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
|
|
+ {
|
|
|
+ if (filterRegExp().isEmpty())
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
|
|
+ return filterAcceptsIndex(index);
|
|
|
+ }
|
|
|
+
|
|
|
+private:
|
|
|
+ bool filterAcceptsIndex(const QModelIndex index) const
|
|
|
+ {
|
|
|
+ // Accept item if its tag, attribute, or value text matches
|
|
|
+ if ((sourceModel()->data(sourceModel()->index(index.row(), ctkDICOMObjectModel::TagColumn,
|
|
|
+ index.parent()), Qt::DisplayRole).toString().contains(filterRegExp()))
|
|
|
+ || (sourceModel()->data(sourceModel()->index(index.row(), ctkDICOMObjectModel::AttributeColumn,
|
|
|
+ index.parent()), Qt::DisplayRole).toString().contains(filterRegExp()))
|
|
|
+ || (sourceModel()->data(sourceModel()->index(index.row(), ctkDICOMObjectModel::ValueColumn,
|
|
|
+ index.parent()), Qt::DisplayRole).toString().contains(filterRegExp())))
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ // Accept item if any child matches
|
|
|
+ for (int row = 0; row < sourceModel()->rowCount(index); row++)
|
|
|
+ {
|
|
|
+ QModelIndex childIndex = sourceModel()->index(row, 0, index);
|
|
|
+ if (!childIndex.isValid())
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (filterAcceptsIndex(childIndex))
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
//----------------------------------------------------------------------------
|
|
|
class ctkDICOMObjectListWidgetPrivate: public Ui_ctkDICOMObjectListWidget
|
|
|
{
|
|
@@ -43,11 +92,13 @@ public:
|
|
|
~ctkDICOMObjectListWidgetPrivate();
|
|
|
void populateDICOMObjectTreeView(const QString& fileName);
|
|
|
void setPathLabel(const QString& currentFile);
|
|
|
- QString dicomObjectModelAsString(QModelIndex parent = QModelIndex(), int indent = 0);
|
|
|
+ QString dicomObjectModelAsString(QAbstractItemModel* dicomObjectModel, QModelIndex parent = QModelIndex(), int indent = 0, QString rowPrefix = QString());
|
|
|
|
|
|
+ QString endOfLine;
|
|
|
QString currentFile;
|
|
|
QStringList fileList;
|
|
|
ctkDICOMObjectModel* dicomObjectModel;
|
|
|
+ qRecursiveTreeProxyFilter* filterModel;
|
|
|
};
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
@@ -56,6 +107,13 @@ public:
|
|
|
//----------------------------------------------------------------------------
|
|
|
ctkDICOMObjectListWidgetPrivate::ctkDICOMObjectListWidgetPrivate()
|
|
|
{
|
|
|
+#ifdef WIN32
|
|
|
+ this->endOfLine = "\r\n";
|
|
|
+#else
|
|
|
+ this->endOfLine = "\n";
|
|
|
+#endif
|
|
|
+ this->dicomObjectModel = 0;
|
|
|
+ this->filterModel = 0;
|
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
@@ -67,7 +125,8 @@ ctkDICOMObjectListWidgetPrivate::~ctkDICOMObjectListWidgetPrivate()
|
|
|
void ctkDICOMObjectListWidgetPrivate::populateDICOMObjectTreeView(const QString& fileName)
|
|
|
{
|
|
|
this->dicomObjectModel->setFile(fileName);
|
|
|
- this->dcmObjectTreeView->setModel(this->dicomObjectModel);
|
|
|
+ this->filterModel->invalidate();
|
|
|
+ this->dcmObjectTreeView->setModel(this->filterModel);
|
|
|
this->dcmObjectTreeView->expandAll();
|
|
|
}
|
|
|
|
|
@@ -78,22 +137,16 @@ void ctkDICOMObjectListWidgetPrivate::setPathLabel(const QString& currentFile)
|
|
|
}
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
|
-QString ctkDICOMObjectListWidgetPrivate::dicomObjectModelAsString(QModelIndex parent /*=QModelIndex()*/, int indent /*=0*/)
|
|
|
+QString ctkDICOMObjectListWidgetPrivate::dicomObjectModelAsString(QAbstractItemModel* aDicomObjectModel, QModelIndex parent /*=QModelIndex()*/, int indent /*=0*/, QString rowPrefix /*=QString()*/)
|
|
|
{
|
|
|
QString dump;
|
|
|
QString indentString(indent, '\t'); // add tab characters, (indent) number of times
|
|
|
-#ifdef WIN32
|
|
|
- QString newLine = "\r\n";
|
|
|
-#else
|
|
|
- QString newLine = "\n";
|
|
|
-#endif
|
|
|
- for (int r = 0; r < this->dicomObjectModel->rowCount(parent); ++r)
|
|
|
+ for (int r = 0; r < aDicomObjectModel->rowCount(parent); ++r)
|
|
|
{
|
|
|
- dump += indentString;
|
|
|
- for (int c = 0; c < this->dicomObjectModel->columnCount(); ++c)
|
|
|
+ for (int c = 0; c < aDicomObjectModel->columnCount(); ++c)
|
|
|
{
|
|
|
- QModelIndex index = this->dicomObjectModel->index(r, c, parent);
|
|
|
- QString name = this->dicomObjectModel->data(index).toString();
|
|
|
+ QModelIndex index = aDicomObjectModel->index(r, c, parent);
|
|
|
+ QString name = aDicomObjectModel->data(index).toString();
|
|
|
if (c == 0)
|
|
|
{
|
|
|
// Replace round brackets by square brackets.
|
|
@@ -101,20 +154,19 @@ QString ctkDICOMObjectListWidgetPrivate::dicomObjectModelAsString(QModelIndex pa
|
|
|
// as a negative number (-80,012). Instead, [0008,0012] is displayed fine.
|
|
|
name.replace('(', '[');
|
|
|
name.replace(')', ']');
|
|
|
- dump += name;
|
|
|
+ dump += rowPrefix + indentString + name;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
dump += "\t" + name;
|
|
|
}
|
|
|
-
|
|
|
}
|
|
|
- dump += newLine;
|
|
|
- // here is your applicable code
|
|
|
- QModelIndex index0 = this->dicomObjectModel->index(r, 0, parent);
|
|
|
- if (this->dicomObjectModel->hasChildren(index0))
|
|
|
+ dump += endOfLine;
|
|
|
+ // Print children
|
|
|
+ QModelIndex index0 = aDicomObjectModel->index(r, 0, parent);
|
|
|
+ if (aDicomObjectModel->hasChildren(index0))
|
|
|
{
|
|
|
- dump += dicomObjectModelAsString(index0, indent+1);
|
|
|
+ dump += dicomObjectModelAsString(aDicomObjectModel, index0, indent + 1, rowPrefix);
|
|
|
}
|
|
|
}
|
|
|
return dump;
|
|
@@ -124,22 +176,39 @@ QString ctkDICOMObjectListWidgetPrivate::dicomObjectModelAsString(QModelIndex pa
|
|
|
// ctkDICOMObjectListWidget methods
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
-ctkDICOMObjectListWidget::ctkDICOMObjectListWidget(QWidget* _parent):Superclass(_parent),
|
|
|
+ctkDICOMObjectListWidget::ctkDICOMObjectListWidget(QWidget* _parent):Superclass(_parent),
|
|
|
d_ptr(new ctkDICOMObjectListWidgetPrivate)
|
|
|
{
|
|
|
Q_D(ctkDICOMObjectListWidget);
|
|
|
|
|
|
d->setupUi(this);
|
|
|
+
|
|
|
+ d->metadataSearchBox->setAlwaysShowClearIcon(true);
|
|
|
+ d->metadataSearchBox->setShowSearchIcon(true);
|
|
|
+
|
|
|
d->dicomObjectModel = new ctkDICOMObjectModel(this);
|
|
|
+ d->filterModel = new qRecursiveTreeProxyFilter(this);
|
|
|
+ d->filterModel->setSourceModel(d->dicomObjectModel);
|
|
|
+ d->filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
|
|
|
|
|
|
+ d->fileSliderWidget->setMaximum(1);
|
|
|
+ d->fileSliderWidget->setMinimum(1);
|
|
|
d->fileSliderWidget->setPageStep(1);
|
|
|
|
|
|
d->currentPathLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
|
|
connect(d->fileSliderWidget, SIGNAL(valueChanged(double)), this, SLOT(updateWidget()));
|
|
|
- connect(d->dcmObjectTreeView, SIGNAL(doubleClicked(const QModelIndex&))
|
|
|
- ,this, SLOT(openLookupUrl(const QModelIndex&)));
|
|
|
+ connect(d->dcmObjectTreeView, SIGNAL(doubleClicked(const QModelIndex&)),
|
|
|
+ this, SLOT(itemDoubleClicked(const QModelIndex&)));
|
|
|
connect(d->copyPathPushButton , SIGNAL(clicked(bool)),this, SLOT(copyPath()));
|
|
|
+
|
|
|
+ connect(d->expandAllPushButton, SIGNAL(clicked(bool)), d->dcmObjectTreeView, SLOT(expandAll()));
|
|
|
+ connect(d->collapseAllPushButton, SIGNAL(clicked(bool)), d->dcmObjectTreeView, SLOT(collapseAll()));
|
|
|
connect(d->copyMetadataPushButton, SIGNAL(clicked(bool)), this, SLOT(copyMetadata()));
|
|
|
+ connect(d->copyAllFilesMetadataPushButton, SIGNAL(clicked(bool)), this, SLOT(copyAllFilesMetadata()));
|
|
|
+
|
|
|
+ QObject::connect(d->metadataSearchBox, SIGNAL(textChanged(QString)),
|
|
|
+ d->filterModel, SLOT(setFilterWildcard(QString)));
|
|
|
+ QObject::connect(d->metadataSearchBox, SIGNAL(textChanged(QString)), this, SLOT(onFilterChanged()));
|
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
@@ -147,6 +216,7 @@ ctkDICOMObjectListWidget::~ctkDICOMObjectListWidget()
|
|
|
{
|
|
|
Q_D(ctkDICOMObjectListWidget);
|
|
|
d->dicomObjectModel->deleteLater();
|
|
|
+ d->filterModel->deleteLater();
|
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
@@ -164,11 +234,25 @@ void ctkDICOMObjectListWidget::setFileList(const QStringList& fileList)
|
|
|
if (d->fileList.size() > 0)
|
|
|
{
|
|
|
d->currentFile = d->fileList[0];
|
|
|
- d->setPathLabel(d->currentFile );
|
|
|
- d->populateDICOMObjectTreeView(d->currentFile );
|
|
|
- d->fileSliderWidget->setMaximum(fileList.size()-1);
|
|
|
+
|
|
|
+ d->populateDICOMObjectTreeView(d->currentFile);
|
|
|
+ d->fileSliderWidget->setMaximum(fileList.size());
|
|
|
+ d->fileSliderWidget->setSuffix(QString(" / %1").arg(fileList.size()));
|
|
|
+ for (int columnIndex = 0; columnIndex < d->dicomObjectModel->columnCount(); ++columnIndex)
|
|
|
+ {
|
|
|
+ d->dcmObjectTreeView->resizeColumnToContents(columnIndex);
|
|
|
+ }
|
|
|
}
|
|
|
+ else
|
|
|
+ {
|
|
|
+ d->currentFile.clear();
|
|
|
+ d->dicomObjectModel->clear();
|
|
|
+ }
|
|
|
+
|
|
|
+ d->setPathLabel(d->currentFile);
|
|
|
+ d->fileSliderWidget->setVisible(d->fileList.size() > 1);
|
|
|
}
|
|
|
+
|
|
|
// --------------------------------------------------------------------------
|
|
|
QString ctkDICOMObjectListWidget::currentFile()
|
|
|
{
|
|
@@ -182,29 +266,34 @@ QStringList ctkDICOMObjectListWidget::fileList()
|
|
|
Q_D(ctkDICOMObjectListWidget);
|
|
|
return d->fileList;
|
|
|
}
|
|
|
-// --------------------------------------------------------------------------
|
|
|
|
|
|
-void ctkDICOMObjectListWidget::openLookupUrl(const QModelIndex& index)
|
|
|
+// --------------------------------------------------------------------------
|
|
|
+void ctkDICOMObjectListWidget::openLookupUrl(QString tag)
|
|
|
{
|
|
|
- if (index.column() == 0)
|
|
|
- {
|
|
|
- QVariant data = index.data();
|
|
|
- QString lookupUrl = "http://dicomlookup.com/lookup.asp?sw=Tnumber&q="+data.toString();
|
|
|
- QUrl url(lookupUrl);
|
|
|
- QDesktopServices::openUrl(url);
|
|
|
- }
|
|
|
+ QString lookupUrl = "http://dicomlookup.com/lookup.asp?sw=Tnumber&q=" + tag;
|
|
|
+ QUrl url(lookupUrl);
|
|
|
+ QDesktopServices::openUrl(url);
|
|
|
}
|
|
|
+
|
|
|
// --------------------------------------------------------------------------
|
|
|
+void ctkDICOMObjectListWidget::itemDoubleClicked(const QModelIndex& index)
|
|
|
+{
|
|
|
+ Q_D(ctkDICOMObjectListWidget);
|
|
|
+ QModelIndex tagIndex = d->filterModel->index(index.row(), 0, index.parent());
|
|
|
+ QString tag = d->filterModel->data(tagIndex).toString();
|
|
|
+ openLookupUrl(tag);
|
|
|
+}
|
|
|
|
|
|
+// --------------------------------------------------------------------------
|
|
|
void ctkDICOMObjectListWidget::updateWidget()
|
|
|
{
|
|
|
Q_D(ctkDICOMObjectListWidget);
|
|
|
- d->currentFile = d->fileList[static_cast<int>(d->fileSliderWidget->value())];
|
|
|
+ d->currentFile = d->fileList[static_cast<int>(d->fileSliderWidget->value())-1];
|
|
|
d->setPathLabel(d->currentFile);
|
|
|
d->populateDICOMObjectTreeView(d->currentFile);
|
|
|
}
|
|
|
-// --------------------------------------------------------------------------
|
|
|
|
|
|
+// --------------------------------------------------------------------------
|
|
|
void ctkDICOMObjectListWidget::copyPath()
|
|
|
{
|
|
|
Q_D(ctkDICOMObjectListWidget);
|
|
@@ -213,18 +302,82 @@ void ctkDICOMObjectListWidget::copyPath()
|
|
|
}
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
|
-
|
|
|
-QString ctkDICOMObjectListWidget::metadataAsText()
|
|
|
+QString ctkDICOMObjectListWidget::metadataAsText(bool allFiles /*=false*/)
|
|
|
{
|
|
|
Q_D(ctkDICOMObjectListWidget);
|
|
|
- return d->dicomObjectModelAsString();
|
|
|
+ QString metadata;
|
|
|
+ if (allFiles)
|
|
|
+ {
|
|
|
+ foreach(QString fileName, d->fileList)
|
|
|
+ {
|
|
|
+ // copy metadata of all files
|
|
|
+
|
|
|
+ ctkDICOMObjectModel* aDicomObjectModel = new ctkDICOMObjectModel();
|
|
|
+ aDicomObjectModel->setFile(fileName);
|
|
|
+
|
|
|
+ qRecursiveTreeProxyFilter* afilterModel = new qRecursiveTreeProxyFilter();
|
|
|
+ afilterModel->setSourceModel(aDicomObjectModel);
|
|
|
+ afilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
|
|
|
+ afilterModel->setFilterWildcard(d->metadataSearchBox->text());
|
|
|
+
|
|
|
+ QString thisFileMetadata = d->dicomObjectModelAsString(afilterModel, QModelIndex(), 0, fileName + "\t");
|
|
|
+
|
|
|
+ if (!thisFileMetadata.isEmpty())
|
|
|
+ {
|
|
|
+ metadata += thisFileMetadata;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ metadata += fileName + "\t(none)" + d->endOfLine;
|
|
|
+ }
|
|
|
+
|
|
|
+ delete afilterModel;
|
|
|
+ delete aDicomObjectModel;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // single file
|
|
|
+ metadata = d->dicomObjectModelAsString(d->filterModel);
|
|
|
+ }
|
|
|
+ return metadata;
|
|
|
}
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
|
-
|
|
|
void ctkDICOMObjectListWidget::copyMetadata()
|
|
|
{
|
|
|
Q_D(ctkDICOMObjectListWidget);
|
|
|
QClipboard *clipboard = QApplication::clipboard();
|
|
|
clipboard->setText(metadataAsText());
|
|
|
}
|
|
|
+
|
|
|
+// --------------------------------------------------------------------------
|
|
|
+void ctkDICOMObjectListWidget::copyAllFilesMetadata()
|
|
|
+{
|
|
|
+ Q_D(ctkDICOMObjectListWidget);
|
|
|
+ QApplication::setOverrideCursor(QCursor(Qt::BusyCursor));
|
|
|
+ QClipboard *clipboard = QApplication::clipboard();
|
|
|
+ clipboard->setText(metadataAsText(true));
|
|
|
+ QApplication::restoreOverrideCursor();
|
|
|
+}
|
|
|
+
|
|
|
+//------------------------------------------------------------------------------
|
|
|
+void ctkDICOMObjectListWidget::onFilterChanged()
|
|
|
+{
|
|
|
+ Q_D(ctkDICOMObjectListWidget);
|
|
|
+
|
|
|
+ // Change the searchbox background to yellow
|
|
|
+ // if there are no matches
|
|
|
+ bool showWarning = (d->filterModel->rowCount() == 0 &&
|
|
|
+ d->dicomObjectModel->rowCount() != 0);
|
|
|
+ QPalette palette;
|
|
|
+ if (showWarning)
|
|
|
+ {
|
|
|
+ palette.setColor(QPalette::Base, Qt::yellow);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ palette.setColor(QPalette::Base, Qt::white);
|
|
|
+ }
|
|
|
+ d->metadataSearchBox->setPalette(palette);
|
|
|
+}
|