Fraxinus  2023.01.05-dev+develop.0da12
An IGT application
cxImportWidget.cpp
Go to the documentation of this file.
1 /*=========================================================================
2 This file is part of CustusX, an Image Guided Therapy Application.
3 
4 Copyright (c) SINTEF Department of Medical Technology.
5 All rights reserved.
6 
7 CustusX is released under a BSD 3-Clause license.
8 
9 See Lisence.txt (https://github.com/SINTEFMedtek/CustusX/blob/master/License.txt) for details.
10 =========================================================================*/
11 
12 #include "cxImportWidget.h"
13 
14 #include <QTreeWidgetItem>
15 
16 #include <QFileDialog>
17 #include <QPushButton>
18 #include <QLabel>
19 #include <QTableWidget>
20 #include <QListView>
21 #include <QHeaderView>
22 #include <QApplication>
23 #include <QDesktopWidget>
24 #include <QStackedWidget>
25 #include <QProgressDialog>
26 #include "cxForwardDeclarations.h"
28 #include "cxVisServices.h"
29 #include "cxLogger.h"
30 #include "cxPatientModelService.h"
31 #include "cxProfile.h"
32 #include "cxImportDataTypeWidget.h"
34 #include "cxImage.h"
35 #include "cxMesh.h"
36 #include "cxPointMetric.h"
37 #include "cxUtilHelpers.h"
38 
39 namespace cx
40 {
41 /*
42  * TODOS:
43  * * implement coordinate system guess
44  * * implement parent guess
45  * * implement auto RAS to LPS conversion
46  */
47 
48 
50  mImportDataTypeWidget(widget),
51  QDialog(parent)
52 {
53  this->setAttribute(Qt::WA_DeleteOnClose);
54 
55  this->setWindowTitle("Select data for import (Click on a line)");
56  QVBoxLayout* layout = new QVBoxLayout(this);
57  QTableWidget* tableWidget = mImportDataTypeWidget->getSimpleTableWidget();
58 
59  layout->addWidget(tableWidget, 1);
60  connect(tableWidget, &QTableWidget::currentCellChanged, this, &SimpleImportDataDialog::tableItemSelected);
61 
62  QPushButton* cancelButton = new QPushButton("Cancel", this);
63  layout->addStretch();
64  QHBoxLayout* buttonLayout = new QHBoxLayout();
65  buttonLayout->addWidget(cancelButton);
66  layout->addLayout(buttonLayout);
67 
68  connect(cancelButton, &QPushButton::clicked, this, &SimpleImportDataDialog::cancelClicked);
69  cancelButton->setFocus();
70 }
71 
72 void SimpleImportDataDialog::tableItemSelected(int currentRow, int currentColumn, int previousRow, int previousColumn)
73 {
74  int filenameColoumn = 1;
75  QString fullfilename = mImportDataTypeWidget->getSimpleTableWidget()->item(currentRow, filenameColoumn)->text();
76  std::vector<DataPtr> datas = mImportDataTypeWidget->getDatas();
77  DataPtr selected;
78  for (std::vector<DataPtr>::iterator it = datas.begin(); it != datas.end(); ++it)
79  {
80  if((*it)->getName() == fullfilename)
81  selected = *it;
82  }
83  if(selected)
84  {
85  datas = std::vector<DataPtr>();
86  datas.push_back(selected);
87  mImportDataTypeWidget->setData(datas);
88  }
89  accept();
90 }
91 
92 void SimpleImportDataDialog::cancelClicked()
93 {
94  mImportDataTypeWidget->setData(std::vector<DataPtr>());
95  reject();
96 }
97 
99  BaseWidget(NULL, "import_widget", "Import"),
100  mSelectedIndexInTable(0),
101  mFileManager(filemanager),
102  mVisServices(services)
103 {
104  //see https://wiki.qt.io/How_to_Use_QTableWidget
105  mTableWidget = new QTableWidget();
106  mTableWidget->setRowCount(0);
107  mTableWidget->setColumnCount(3);
108  mTableHeader<<""<<"Status"<<"Filename";
109  mTableWidget->setHorizontalHeaderLabels(mTableHeader);
110  mTableWidget->horizontalHeader()->setStretchLastSection(true);
111  mTableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
112  mTableWidget->verticalHeader()->setVisible(false);
113  mTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
114  mTableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
115  mTableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
116  mTableWidget->setShowGrid(false);
117  mTableWidget->setStyleSheet("QTableView {selection-background-color: #ACCEF7;}");
118  mTableWidget->setGeometry(QApplication::desktop()->screenGeometry());
119  connect(mTableWidget, &QTableWidget::currentCellChanged, this, &ImportWidget::tableItemSelected);
120 
121  mStackedWidget = new QStackedWidget;
122 
123  mTopLayout = new QVBoxLayout();
124  this->setLayout(mTopLayout);
125  QPushButton *addMoreFilesButton = new QPushButton("Add more files...");
126  QHBoxLayout *buttonLayout = new QHBoxLayout();
127  QPushButton *importButton = new QPushButton("Import");
128  QPushButton *cancelButton = new QPushButton("Cancel");
129  buttonLayout->addWidget(importButton);
130  buttonLayout->addWidget(cancelButton);
131  buttonLayout->addWidget(addMoreFilesButton);
132  buttonLayout->addStretch();
133  mTopLayout->addLayout(buttonLayout);
134  mTopLayout->addWidget(new QLabel("Supports: "+this->generateFileTypeFilter()));
135  mTopLayout->addWidget(mTableWidget);
136  mTopLayout->addWidget(mStackedWidget);
137  mTopLayout->addStretch();
138 
139  connect(addMoreFilesButton, &QPushButton::clicked, this, &ImportWidget::addMoreFilesButtonClicked);
140  connect(importButton, &QPushButton::clicked, this, &ImportWidget::importButtonClicked);
141  connect(cancelButton, &QPushButton::clicked, this, &ImportWidget::cancelButtonClicked);
142  connect(this, &ImportWidget::finishedImporting, this, &ImportWidget::cleanUpAfterImport);
143 
144  QAction* addMoreFilesButtonClickedAction = new QAction("AddMoreFilesButtonClickedAction", this);
145  this->addAction(addMoreFilesButtonClickedAction);
146  connect(addMoreFilesButtonClickedAction, &QAction::triggered, this, &ImportWidget::addMoreFilesButtonClicked);
147 
148  QAction* addFilesForImportWithDialogAction = new QAction("AddFilesForImportWithDialogAction", this);
149  this->addAction(addFilesForImportWithDialogAction);
150  connect(addFilesForImportWithDialogAction, &QAction::triggered, this, &ImportWidget::addFilesForImportWithDialogTriggerend);
151 
152  QAction* importButtonClickedAction = new QAction("ImportButtonClickedAction", this);
153  this->addAction(importButtonClickedAction);
154  connect(importButtonClickedAction, &QAction::triggered, this, &ImportWidget::importButtonClicked);
155 }
156 
157 int ImportWidget::insertDataIntoTable(QString fullfilename, std::vector<DataPtr> data)
158 {
159  int newRowIndex = mTableWidget->rowCount();
160  mTableWidget->setRowCount(newRowIndex+1);
161  mTableWidget->selectRow(mSelectedIndexInTable);
162  QString status;
163  bool allDataOk = true;
164  for(unsigned i=0; i<data.size(); ++i)
165  {
166  if(!data[i])
167  allDataOk = false;
168  }
169  status = allDataOk ? "ok" : "error";
170  QFileInfo fileInfo(fullfilename);
171  QString filename = fileInfo.fileName();
172 
173  QIcon trashcan(":/icons/open_icon_library/edit-delete-2.png");
174  QPushButton *removeButton = new QPushButton(trashcan,"");
175  connect(removeButton, &QPushButton::clicked, this, &ImportWidget::removeRowFromTableAndRemoveFilenameFromImportList);
176 
177  mTableWidget->setCellWidget(newRowIndex, 0, removeButton);
178  mTableWidget->setItem(newRowIndex, 1, new QTableWidgetItem(status));
179  QTableWidgetItem *filenameItem = new QTableWidgetItem(filename);
180  filenameItem->setData(Qt::ToolTipRole, fullfilename);
181  mTableWidget->setItem(newRowIndex, 2, filenameItem);
182 
183  return newRowIndex;
184 }
185 
186 void ImportWidget::addFilesForImportWithDialogTriggerend()
187 {
188  ImportDataTypeWidget* widget = this->addMoreFilesButtonClicked();
189  if(widget && widget->getDatas().size() > 0)
190  {
191  SimpleImportDataDialog* dialog = new SimpleImportDataDialog(widget, this);
192  dialog->exec();
193  }
194 }
195 
196 ImportDataTypeWidget* ImportWidget::addMoreFilesButtonClicked()
197 {
198  QStringList filenames = this->openFileBrowserForSelectingFiles();
199 
200  bool addedDICOM = false;
201 
202  ImportDataTypeWidget *widget = NULL;
203  for(int i = 0; i < filenames.size(); ++i)
204  {
205  QString filename = filenames[i];
206  QString fileType = mFileManager->getFileReaderName(filename);
207  if(fileType == "DICOMReader")
208  {
209  //Only allow adding one dicom series to prevent duplicates, and long processing time
210  if(addedDICOM)
211  {
212  //If import of multiple series is something we need:
213  //All DICOM files can be added to a QStringList before sending them to the reader.
214  //Then the reader can determine which files are from the same/different series
215  CX_LOG_WARNING() << "Import of multiple DICOM series at once is not supported. Skipping series based on file: " << filename;
216  //continue;//Need to remove?
217  }
218  addedDICOM = true;
219  }
220 
221  mFileNames.push_back(filename);
222 
223  std::vector<DataPtr> newData = mFileManager->read(filename);
224  if(newData.size() > 0)
225  {
226  int index = this->insertDataIntoTable(filename, newData);
227  widget = new ImportDataTypeWidget(this, mVisServices, newData, mParentCandidates, filename);
228  mStackedWidget->insertWidget(index, widget);
229  mNotImportedData.insert(mNotImportedData.end(), newData.begin(), newData.end());//Update mNotImportedData with new data
230  }
231  }
232 
233  this->generateParentCandidates();
234  return widget;
235 }
236 
237 void ImportWidget::showProgressDialog(QProgressDialog &progress)
238 {
239  progress.setWindowModality(Qt::WindowModal);
240  progress.setMinimumDuration(0);
241  //It seems like QProgressDialog won't show correctly for only a few steps, or for a long processing time
242  //This helps start the progress bar
243  for(int i = 0; i < 100; ++i)
244  {
245  progress.setValue(i);
246  sleep_ms(1);
247  }
248 }
249 
250 void ImportWidget::importButtonClicked()
251 {
252  this->clearData();
253  emit readyToImport();
254  emit finishedImporting();
255 
256  mVisServices->session()->save();//AutoSave
257 }
258 
259 void ImportWidget::cancelButtonClicked()
260 {
261  this->clearData();
262  emit finishedImporting();
263 }
264 
265 void ImportWidget::clearData()
266 {
267  mParentCandidates.clear();
268  mNotImportedData.clear();
269 }
270 
271 void ImportWidget::removeWidget(QWidget *widget)
272 {
273  mTopLayout->removeWidget(widget);
274  widget->deleteLater();
275 }
276 
277 void ImportWidget::removeRowFromTableAndRemoveFilenameFromImportList()
278 {
279  QPushButton *button = qobject_cast<QPushButton*>(QObject::sender());
280  int rowindex = ImportDataTypeWidget::findRowIndexContainingButton(button, mTableWidget);
281  int filenamecoloumn = 2;
282  QString fullfilename = mTableWidget->item(rowindex, filenamecoloumn)->data(Qt::ToolTipRole).toString();
283  if(rowindex != -1)
284  mTableWidget->removeRow(rowindex);
285  int numberOfRemovedEntries = mFileNames.removeAll(fullfilename);
286 
287  QWidget *widgetToRemove = mStackedWidget->widget(rowindex);
288  mStackedWidget->removeWidget(widgetToRemove);
289 }
290 
291 void ImportWidget::tableItemSelected(int currentRow, int currentColumn, int previousRow, int previousColumn)
292 {
293  if(currentRow == mSelectedIndexInTable)
294  return;
295 
296  mStackedWidget->setCurrentIndex(currentRow);
297  mSelectedIndexInTable = currentRow;
298 }
299 
300 void ImportWidget::cleanUpAfterImport()
301 {
302  //clear data
303  //mData.clear();
304  mFileNames.clear();
305 
306  //reset table
307  mTableWidget->clear();
308  mTableWidget->setRowCount(0);
309  mTableWidget->setHorizontalHeaderLabels(mTableHeader);
310  //clear the stacked widget
311  for(int i=mStackedWidget->count()-1; i>=0; --i)
312  {
313  QWidget *widgetToRemove = mStackedWidget->widget(i);
314  mStackedWidget->removeWidget(widgetToRemove);
315  delete widgetToRemove;
316  }
317 }
318 
319 QStringList ImportWidget::openFileBrowserForSelectingFiles()
320 {
321  QString file_type_filter = generateFileTypeFilter();
322 
323  QFileDialog dialog(this->parentWidget(), QString(tr("Select Medical Image file(s)/folder(s) for import")), QDir::homePath(), tr(file_type_filter.toStdString().c_str()));
324  dialog.setFileMode(QFileDialog::Directory);
325 
326  // Select multiple files and directories at the same time in QFileDialog
327  //https://www.qtcentre.org/threads/43841-QFileDialog-to-select-files-AND-folders
328  dialog.setOption(QFileDialog::DontUseNativeDialog, true);
329  QListView *l = dialog.findChild<QListView*>("listView");
330  if (l)
331  l->setSelectionMode(QAbstractItemView::MultiSelection);
332  QTreeView *t = dialog.findChild<QTreeView*>();
333  if (t)
334  t->setSelectionMode(QAbstractItemView::MultiSelection);
335 
336  QStringList fileName;
337  if (dialog.exec())
338  fileName = dialog.selectedFiles();
339  fileName = removeDirIfSubdirIsIncluded(fileName);
340 
341  if (fileName.empty())
342  {
343  report("Import canceled");
344  }
345 
346  return fileName;
347 }
348 
349 QStringList ImportWidget::removeDirIfSubdirIsIncluded(QStringList importFiles)
350 {
351  QStringList retval;
352  QStringList allDirs;
353  for(int i = 0; i < importFiles.size(); ++i)
354  {
355  if(!QFileInfo(importFiles[i]).isDir())
356  retval << importFiles[i];//Insert all files
357  else
358  allDirs << importFiles[i];
359  }
360 
361  QStringList removeDirs;
362  for(int i = 0; i < importFiles.size(); ++i)
363  {
364  for(int j = 0; j < allDirs.size(); ++j)
365  {
366  if(i != j)
367  if(importFiles[i].contains(allDirs[j]))
368  removeDirs << allDirs[j];
369  }
370  }
371  for(int i = 0; i < allDirs.size(); ++i)
372  {
373  bool addDir = true;
374  for(int j = 0; j < removeDirs.size(); ++j)
375  {
376  if(allDirs[i] == removeDirs[j])
377  addDir = false;
378  }
379  if(addDir)
380  retval << allDirs[i];
381  }
382  return retval;
383 }
384 
385 QString ImportWidget::generateFileTypeFilter() const
386 {
387  QString file_type_filter;
388  std::vector<FileReaderWriterServicePtr> mesh_readers = mVisServices->file()->getImportersForDataType(Mesh::getTypeName());
389  std::vector<FileReaderWriterServicePtr> image_readers = mVisServices->file()->getImportersForDataType(Image::getTypeName());
390  std::vector<FileReaderWriterServicePtr> point_metric_readers = mVisServices->file()->getImportersForDataType(PointMetric::getTypeName());
391  std::vector<FileReaderWriterServicePtr> readers;
392  readers.insert( readers.end(), mesh_readers.begin(), mesh_readers.end() );
393  readers.insert( readers.end(), image_readers.begin(), image_readers.end() );
394  readers.insert( readers.end(), point_metric_readers.begin(), point_metric_readers.end() );
395 
396  file_type_filter = "Image/Mesh/PointMetrics (";
397  for(unsigned i=0; i<readers.size(); ++i)
398  {
399  QString suffix = readers[i]->getFileSuffix();
400  if(!suffix.isEmpty())
401  file_type_filter.append("*."+suffix+" ");
402  }
403  while(file_type_filter.endsWith( ' ' ))
404  file_type_filter.chop(1);
405  file_type_filter.append(")");
406 
407  return file_type_filter;
408 }
409 
410 void ImportWidget::generateParentCandidates()
411 {
412  for(unsigned i=0; i<mNotImportedData.size(); ++i)
413  {
414  if(!mNotImportedData[i])
415  {
416  CX_LOG_WARNING() << "ImportWidget::generateParentCandidates(): No data";
417  continue;
418  }
419  if(mNotImportedData[i]->getType() != PointMetric::getTypeName())
420  mParentCandidates.push_back(mNotImportedData[i]);
421  }
422  std::map<QString, DataPtr> loadedData = mVisServices->patient()->getDatas();
423  std::map<QString, DataPtr>::iterator it = loadedData.begin();
424  for(; it!=loadedData.end(); ++it)
425  {
426  mParentCandidates.push_back(it->second);
427  }
429 }
430 
431 
432 }
boost::shared_ptr< class FileManagerService > FileManagerServicePtr
ImportWidget(FileManagerServicePtr filemanager, VisServicesPtr services)
boost::shared_ptr< class VisServices > VisServicesPtr
Definition: cxMainWindow.h:40
boost::shared_ptr< class Data > DataPtr
static int findRowIndexContainingButton(QPushButton *button, QTableWidget *tableWidget)
static QString getTypeName()
Definition: cxImage.h:126
std::vector< DataPtr > getDatas()
SimpleImportDataDialog(ImportDataTypeWidget *widget, QWidget *parent=NULL)
void parentCandidatesUpdated()
Interface for QWidget which handles widgets uniformly for the system.
Definition: cxBaseWidget.h:88
void report(QString msg)
Definition: cxLogger.cpp:69
#define CX_LOG_WARNING
Definition: cxLogger.h:98
static QString getTypeName()
Definition: cxPointMetric.h:58
void finishedImporting()
static QString getTypeName()
Definition: cxMesh.h:67
void setData(std::vector< DataPtr > datas)
void sleep_ms(int ms)
Namespace for all CustusX production code.