1 /*=========================================================================
2
3 Library: CTK
4
5 Copyright (c) Brigham and Women's Hospital (BWH).
6
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
10
11 http://www.apache.org/licenses/LICENSE-2.0.txt
12
13 Unless required by applicable law or agreed to in writing, software
14 distributed under the License is distributed on an "AS IS" BASIS,
15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 See the License for the specific language governing permissions and
17 limitations under the License.
18
19 =========================================================================*/
20
21 // ctkDICOMWidgets includes
22 #include "ctkDICOMObjectListWidget.h"
23 #include "ui_ctkDICOMObjectListWidget.h"
24
25 // Qt includes
26 #include <QApplication>
27 #include <QClipboard>
28 #include <QDesktopServices>
29 #include <QSortFilterProxyModel>
30 #include <QString>
31 #include <QStringList>
32 #include <QUrl>
33
34 //CTK includes
35 #include <ctkDICOMObjectModel.h>
36 #include <ctkLogger.h>
37 static ctkLogger logger("org.commontk.DICOM.Widgets.ctkDICOMObjectListWidget");
38
39 class qRecursiveTreeProxyFilter : public QSortFilterProxyModel
40 {
41 public:
qRecursiveTreeProxyFilter(QObject * parent=NULL)42 qRecursiveTreeProxyFilter(QObject *parent = NULL):
43 QSortFilterProxyModel(parent)
44 {
45 }
46
filterAcceptsRow(int sourceRow,const QModelIndex & sourceParent) const47 bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
48 {
49 if (filterRegExp().isEmpty())
50 {
51 return true;
52 }
53 QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
54 return filterAcceptsIndex(index);
55 }
56
57 private:
filterAcceptsIndex(const QModelIndex index) const58 bool filterAcceptsIndex(const QModelIndex index) const
59 {
60 // Accept item if its tag, attribute, or value text matches
61 if ((sourceModel()->data(sourceModel()->index(index.row(), ctkDICOMObjectModel::TagColumn,
62 index.parent()), Qt::DisplayRole).toString().contains(filterRegExp()))
63 || (sourceModel()->data(sourceModel()->index(index.row(), ctkDICOMObjectModel::AttributeColumn,
64 index.parent()), Qt::DisplayRole).toString().contains(filterRegExp()))
65 || (sourceModel()->data(sourceModel()->index(index.row(), ctkDICOMObjectModel::ValueColumn,
66 index.parent()), Qt::DisplayRole).toString().contains(filterRegExp())))
67 {
68 return true;
69 }
70 // Accept item if any child matches
71 for (int row = 0; row < sourceModel()->rowCount(index); row++)
72 {
73 QModelIndex childIndex = sourceModel()->index(row, 0, index);
74 if (!childIndex.isValid())
75 {
76 break;
77 }
78 if (filterAcceptsIndex(childIndex))
79 {
80 return true;
81 }
82 }
83 return false;
84 }
85 };
86
87 //----------------------------------------------------------------------------
88 class ctkDICOMObjectListWidgetPrivate: public Ui_ctkDICOMObjectListWidget
89 {
90 public:
91 ctkDICOMObjectListWidgetPrivate();
92 ~ctkDICOMObjectListWidgetPrivate();
93 void populateDICOMObjectTreeView(const QString& fileName);
94 void setPathLabel(const QString& currentFile);
95 QString dicomObjectModelAsString(QAbstractItemModel* dicomObjectModel, QModelIndex parent = QModelIndex(), int indent = 0, QString rowPrefix = QString());
96 void setFilterExpressionInModel(qRecursiveTreeProxyFilter* filterModel, const QString& expr);
97
98 QString endOfLine;
99 QString currentFile;
100 QStringList fileList;
101 ctkDICOMObjectModel* dicomObjectModel;
102 qRecursiveTreeProxyFilter* filterModel;
103 QString filterExpression;
104 };
105
106 //----------------------------------------------------------------------------
107 // ctkDICOMObjectListWidgetPrivate methods
108
109 //----------------------------------------------------------------------------
ctkDICOMObjectListWidgetPrivate()110 ctkDICOMObjectListWidgetPrivate::ctkDICOMObjectListWidgetPrivate()
111 {
112 #ifdef WIN32
113 this->endOfLine = "\r\n";
114 #else
115 this->endOfLine = "\n";
116 #endif
117 this->dicomObjectModel = 0;
118 this->filterModel = 0;
119 }
120
121 //----------------------------------------------------------------------------
~ctkDICOMObjectListWidgetPrivate()122 ctkDICOMObjectListWidgetPrivate::~ctkDICOMObjectListWidgetPrivate()
123 {
124 }
125
126 //----------------------------------------------------------------------------
setFilterExpressionInModel(qRecursiveTreeProxyFilter * filterModel,const QString & expr)127 void ctkDICOMObjectListWidgetPrivate::setFilterExpressionInModel(qRecursiveTreeProxyFilter* filterModel, const QString& expr)
128 {
129 const QString regexpPrefix("regexp:");
130 if (expr.startsWith(regexpPrefix))
131 {
132 filterModel->setFilterCaseSensitivity(Qt::CaseSensitive);
133 filterModel->setFilterRegExp(expr.right(expr.length() - regexpPrefix.length()));
134 }
135 else
136 {
137 filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
138 filterModel->setFilterWildcard(expr);
139 }
140 }
141
142 //----------------------------------------------------------------------------
populateDICOMObjectTreeView(const QString & fileName)143 void ctkDICOMObjectListWidgetPrivate::populateDICOMObjectTreeView(const QString& fileName)
144 {
145 this->dicomObjectModel->setFile(fileName);
146 this->filterModel->invalidate();
147 this->dcmObjectTreeView->setModel(this->filterModel);
148 this->dcmObjectTreeView->expandAll();
149 }
150
151 // --------------------------------------------------------------------------
setPathLabel(const QString & currentFile)152 void ctkDICOMObjectListWidgetPrivate::setPathLabel(const QString& currentFile)
153 {
154 currentPathLabel->setText(currentFile);
155 }
156
157 // --------------------------------------------------------------------------
dicomObjectModelAsString(QAbstractItemModel * aDicomObjectModel,QModelIndex parent,int indent,QString rowPrefix)158 QString ctkDICOMObjectListWidgetPrivate::dicomObjectModelAsString(QAbstractItemModel* aDicomObjectModel, QModelIndex parent /*=QModelIndex()*/, int indent /*=0*/, QString rowPrefix /*=QString()*/)
159 {
160 QString dump;
161 QString indentString(indent, '\t'); // add tab characters, (indent) number of times
162 for (int r = 0; r < aDicomObjectModel->rowCount(parent); ++r)
163 {
164 for (int c = 0; c < aDicomObjectModel->columnCount(); ++c)
165 {
166 QModelIndex index = aDicomObjectModel->index(r, c, parent);
167 QString name = aDicomObjectModel->data(index).toString();
168 if (c == 0)
169 {
170 // Replace round brackets by square brackets.
171 // If the text is copied into Excel, Excel would recognize tag (0008,0012)
172 // as a negative number (-80,012). Instead, [0008,0012] is displayed fine.
173 name.replace('(', '[');
174 name.replace(')', ']');
175 dump += rowPrefix + indentString + name;
176 }
177 else
178 {
179 dump += "\t" + name;
180 }
181 }
182 dump += endOfLine;
183 // Print children
184 QModelIndex index0 = aDicomObjectModel->index(r, 0, parent);
185 if (aDicomObjectModel->hasChildren(index0))
186 {
187 dump += dicomObjectModelAsString(aDicomObjectModel, index0, indent + 1, rowPrefix);
188 }
189 }
190 return dump;
191 }
192
193 //----------------------------------------------------------------------------
194 // ctkDICOMObjectListWidget methods
195
196 //----------------------------------------------------------------------------
ctkDICOMObjectListWidget(QWidget * _parent)197 ctkDICOMObjectListWidget::ctkDICOMObjectListWidget(QWidget* _parent):Superclass(_parent),
198 d_ptr(new ctkDICOMObjectListWidgetPrivate)
199 {
200 Q_D(ctkDICOMObjectListWidget);
201
202 d->setupUi(this);
203
204 d->metadataSearchBox->setAlwaysShowClearIcon(true);
205 d->metadataSearchBox->setShowSearchIcon(true);
206
207 d->dicomObjectModel = new ctkDICOMObjectModel(this);
208 d->filterModel = new qRecursiveTreeProxyFilter(this);
209 d->filterModel->setSourceModel(d->dicomObjectModel);
210
211 d->fileSliderWidget->setMaximum(1);
212 d->fileSliderWidget->setMinimum(1);
213 d->fileSliderWidget->setPageStep(1);
214
215 d->currentPathLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
216 connect(d->fileSliderWidget, SIGNAL(valueChanged(double)), this, SLOT(updateWidget()));
217 connect(d->dcmObjectTreeView, SIGNAL(doubleClicked(const QModelIndex&)),
218 this, SLOT(itemDoubleClicked(const QModelIndex&)));
219 connect(d->copyPathPushButton , SIGNAL(clicked(bool)),this, SLOT(copyPath()));
220
221 connect(d->expandAllPushButton, SIGNAL(clicked(bool)), d->dcmObjectTreeView, SLOT(expandAll()));
222 connect(d->collapseAllPushButton, SIGNAL(clicked(bool)), d->dcmObjectTreeView, SLOT(collapseAll()));
223 connect(d->copyMetadataPushButton, SIGNAL(clicked(bool)), this, SLOT(copyMetadata()));
224 connect(d->copyAllFilesMetadataPushButton, SIGNAL(clicked(bool)), this, SLOT(copyAllFilesMetadata()));
225
226 QObject::connect(d->metadataSearchBox, SIGNAL(textChanged(QString)), this, SLOT(setFilterExpression(QString)));
227 QObject::connect(d->metadataSearchBox, SIGNAL(textChanged(QString)), this, SLOT(onFilterChanged()));
228 }
229
230 //----------------------------------------------------------------------------
~ctkDICOMObjectListWidget()231 ctkDICOMObjectListWidget::~ctkDICOMObjectListWidget()
232 {
233 Q_D(ctkDICOMObjectListWidget);
234 d->dicomObjectModel->deleteLater();
235 d->filterModel->deleteLater();
236 }
237
238 //----------------------------------------------------------------------------
setCurrentFile(const QString & newFileName)239 void ctkDICOMObjectListWidget::setCurrentFile(const QString& newFileName)
240 {
241 Q_D(ctkDICOMObjectListWidget);
242 d->setPathLabel(newFileName);
243 }
244
245 // --------------------------------------------------------------------------
setFileList(const QStringList & fileList)246 void ctkDICOMObjectListWidget::setFileList(const QStringList& fileList)
247 {
248 Q_D(ctkDICOMObjectListWidget);
249 d->fileList = fileList;
250 if (d->fileList.size() > 0)
251 {
252 d->currentFile = d->fileList[0];
253
254 d->populateDICOMObjectTreeView(d->currentFile);
255 d->fileSliderWidget->setMaximum(fileList.size());
256 d->fileSliderWidget->setSuffix(QString(" / %1").arg(fileList.size()));
257 for (int columnIndex = 0; columnIndex < d->dicomObjectModel->columnCount(); ++columnIndex)
258 {
259 d->dcmObjectTreeView->resizeColumnToContents(columnIndex);
260 }
261 }
262 else
263 {
264 d->currentFile.clear();
265 d->dicomObjectModel->clear();
266 }
267
268 d->setPathLabel(d->currentFile);
269 d->fileSliderWidget->setVisible(d->fileList.size() > 1);
270 }
271
272 // --------------------------------------------------------------------------
currentFile()273 QString ctkDICOMObjectListWidget::currentFile()
274 {
275 Q_D(ctkDICOMObjectListWidget);
276 return d->currentFile;
277 }
278
279 // --------------------------------------------------------------------------
fileList()280 QStringList ctkDICOMObjectListWidget::fileList()
281 {
282 Q_D(ctkDICOMObjectListWidget);
283 return d->fileList;
284 }
285
286 // --------------------------------------------------------------------------
openLookupUrl(QString tag)287 void ctkDICOMObjectListWidget::openLookupUrl(QString tag)
288 {
289 QString lookupUrl = "http://dicomlookup.com/lookup.asp?sw=Tnumber&q=" + tag;
290 QUrl url(lookupUrl);
291 QDesktopServices::openUrl(url);
292 }
293
294 // --------------------------------------------------------------------------
itemDoubleClicked(const QModelIndex & index)295 void ctkDICOMObjectListWidget::itemDoubleClicked(const QModelIndex& index)
296 {
297 Q_D(ctkDICOMObjectListWidget);
298 QModelIndex tagIndex = d->filterModel->index(index.row(), 0, index.parent());
299 QString tag = d->filterModel->data(tagIndex).toString();
300 openLookupUrl(tag);
301 }
302
303 // --------------------------------------------------------------------------
updateWidget()304 void ctkDICOMObjectListWidget::updateWidget()
305 {
306 Q_D(ctkDICOMObjectListWidget);
307 d->currentFile = d->fileList[static_cast<int>(d->fileSliderWidget->value())-1];
308 d->setPathLabel(d->currentFile);
309 d->populateDICOMObjectTreeView(d->currentFile);
310 }
311
312 // --------------------------------------------------------------------------
copyPath()313 void ctkDICOMObjectListWidget::copyPath()
314 {
315 Q_D(ctkDICOMObjectListWidget);
316 QClipboard *clipboard = QApplication::clipboard();
317 clipboard->setText(d->currentFile);
318 }
319
320 // --------------------------------------------------------------------------
metadataAsText(bool allFiles)321 QString ctkDICOMObjectListWidget::metadataAsText(bool allFiles /*=false*/)
322 {
323 Q_D(ctkDICOMObjectListWidget);
324 QString metadata;
325 if (allFiles)
326 {
327 foreach(QString fileName, d->fileList)
328 {
329 // copy metadata of all files
330
331 ctkDICOMObjectModel* aDicomObjectModel = new ctkDICOMObjectModel();
332 aDicomObjectModel->setFile(fileName);
333
334 qRecursiveTreeProxyFilter* afilterModel = new qRecursiveTreeProxyFilter();
335 afilterModel->setSourceModel(aDicomObjectModel);
336 d->setFilterExpressionInModel(afilterModel, d->filterExpression);
337
338 QString thisFileMetadata = d->dicomObjectModelAsString(afilterModel, QModelIndex(), 0, fileName + "\t");
339
340 if (!thisFileMetadata.isEmpty())
341 {
342 metadata += thisFileMetadata;
343 }
344 else
345 {
346 metadata += fileName + "\t(none)" + d->endOfLine;
347 }
348
349 delete afilterModel;
350 delete aDicomObjectModel;
351 }
352 }
353 else
354 {
355 // single file
356 metadata = d->dicomObjectModelAsString(d->filterModel);
357 }
358 return metadata;
359 }
360
361 // --------------------------------------------------------------------------
copyMetadata()362 void ctkDICOMObjectListWidget::copyMetadata()
363 {
364 QClipboard *clipboard = QApplication::clipboard();
365 clipboard->setText(metadataAsText());
366 }
367
368 // --------------------------------------------------------------------------
copyAllFilesMetadata()369 void ctkDICOMObjectListWidget::copyAllFilesMetadata()
370 {
371 QApplication::setOverrideCursor(QCursor(Qt::BusyCursor));
372 QClipboard *clipboard = QApplication::clipboard();
373 clipboard->setText(metadataAsText(true));
374 QApplication::restoreOverrideCursor();
375 }
376
377 //------------------------------------------------------------------------------
onFilterChanged()378 void ctkDICOMObjectListWidget::onFilterChanged()
379 {
380 Q_D(ctkDICOMObjectListWidget);
381
382 // Change the searchbox background to yellow
383 // if there are no matches
384 bool showWarning = (d->filterModel->rowCount() == 0 &&
385 d->dicomObjectModel->rowCount() != 0);
386 QPalette palette;
387 if (showWarning)
388 {
389 palette.setColor(QPalette::Base, Qt::yellow);
390 }
391 else
392 {
393 palette.setColor(QPalette::Base, Qt::white);
394 }
395 d->metadataSearchBox->setPalette(palette);
396 }
397
398 //------------------------------------------------------------------------------
setFilterExpression(const QString & expr)399 void ctkDICOMObjectListWidget::setFilterExpression(const QString& expr)
400 {
401 Q_D(ctkDICOMObjectListWidget);
402 d->filterExpression = expr;
403 d->setFilterExpressionInModel(d->filterModel, expr);
404 }
405
406 //------------------------------------------------------------------------------
filterExpression()407 QString ctkDICOMObjectListWidget::filterExpression()
408 {
409 Q_D(ctkDICOMObjectListWidget);
410 return d->filterExpression;
411 }
412