ctkDICOMObjectListWidget.cpp 14 KB

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