Medical Imaging Interaction Toolkit  2016.11.0
Medical Imaging Interaction Toolkit
QmitkIOUtil.cpp
Go to the documentation of this file.
1 /*===================================================================
2 
3 The Medical Imaging Interaction Toolkit (MITK)
4 
5 Copyright (c) German Cancer Research Center,
6 Division of Medical and Biological Informatics.
7 All rights reserved.
8 
9 This software is distributed WITHOUT ANY WARRANTY; without
10 even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 A PARTICULAR PURPOSE.
12 
13 See LICENSE.txt or http://www.mitk.org for details.
14 
15 ===================================================================*/
16 
17 #include "QmitkIOUtil.h"
18 
19 #include "mitkCoreServices.h"
20 #include "mitkCustomMimeType.h"
21 #include "mitkFileReaderRegistry.h"
22 #include "mitkFileWriterRegistry.h"
23 #include "mitkIMimeTypeProvider.h"
24 #include "mitkMimeType.h"
25 #include <mitkCoreObjectFactory.h>
26 #include <mitkIOUtil.h>
27 
30 
31 // QT
32 #include <QDebug>
33 #include <QFileDialog>
34 #include <QMessageBox>
35 #include <QSet>
36 
37 // ITK
38 #include <itksys/SystemTools.hxx>
39 
40 #include <algorithm>
41 
42 struct QmitkIOUtil::Impl
43 {
44  struct ReaderOptionsDialogFunctor : public ReaderOptionsFunctorBase
45  {
46  virtual bool operator()(LoadInfo &loadInfo) override
47  {
48  QmitkFileReaderOptionsDialog dialog(loadInfo);
49  if (dialog.exec() == QDialog::Accepted)
50  {
51  return !dialog.ReuseOptions();
52  }
53  else
54  {
55  loadInfo.m_Cancel = true;
56  return true;
57  }
58  }
59  };
60 
61  struct WriterOptionsDialogFunctor : public WriterOptionsFunctorBase
62  {
63  virtual bool operator()(SaveInfo &saveInfo) override
64  {
65  QmitkFileWriterOptionsDialog dialog(saveInfo);
66  if (dialog.exec() == QDialog::Accepted)
67  {
68  return !dialog.ReuseOptions();
69  }
70  else
71  {
72  saveInfo.m_Cancel = true;
73  return true;
74  }
75  }
76  };
77 };
78 
79 struct MimeTypeComparison : public std::unary_function<mitk::MimeType, bool>
80 {
81  MimeTypeComparison(const std::string &mimeTypeName) : m_Name(mimeTypeName) {}
82  bool operator()(const mitk::MimeType &mimeType) const { return mimeType.GetName() == m_Name; }
83  const std::string m_Name;
84 };
85 
87 {
88  QString filters;
89 
91  std::vector<std::string> categories = mimeTypeProvider->GetCategories();
92  for (std::vector<std::string>::iterator cat = categories.begin(); cat != categories.end(); ++cat)
93  {
94  QSet<QString> filterExtensions;
95  std::vector<mitk::MimeType> mimeTypes = mimeTypeProvider->GetMimeTypesForCategory(*cat);
96  for (std::vector<mitk::MimeType>::iterator mt = mimeTypes.begin(); mt != mimeTypes.end(); ++mt)
97  {
98  std::vector<std::string> extensions = mt->GetExtensions();
99  for (std::vector<std::string>::iterator ext = extensions.begin(); ext != extensions.end(); ++ext)
100  {
101  filterExtensions << QString::fromStdString(*ext);
102  }
103  }
104 
105  QString filter = QString::fromStdString(*cat) + " (";
106  foreach (const QString &extension, filterExtensions)
107  {
108  filter += "*." + extension + " ";
109  }
110  filter = filter.replace(filter.size() - 1, 1, ')');
111  filters += ";;" + filter;
112  }
113  filters.prepend("All (*)");
114  return filters;
115 }
116 
117 QList<mitk::BaseData::Pointer> QmitkIOUtil::Load(const QStringList &paths, QWidget *parent)
118 {
119  std::vector<LoadInfo> loadInfos;
120  foreach (const QString &file, paths)
121  {
122  loadInfos.push_back(LoadInfo(file.toStdString()));
123  }
124 
125  Impl::ReaderOptionsDialogFunctor optionsCallback;
126  std::string errMsg = Load(loadInfos, NULL, NULL, &optionsCallback);
127  if (!errMsg.empty())
128  {
129  QMessageBox::warning(parent, "Error reading files", QString::fromStdString(errMsg));
130  mitkThrow() << errMsg;
131  }
132 
133  QList<mitk::BaseData::Pointer> qResult;
134  for (std::vector<LoadInfo>::const_iterator iter = loadInfos.begin(), iterEnd = loadInfos.end(); iter != iterEnd;
135  ++iter)
136  {
137  for (const auto &elem : iter->m_Output)
138  {
139  qResult << elem;
140  }
141  }
142  return qResult;
143 }
144 
146  mitk::DataStorage &storage,
147  QWidget *parent)
148 {
149  std::vector<LoadInfo> loadInfos;
150  foreach (const QString &file, paths)
151  {
152  loadInfos.push_back(LoadInfo(QFile::encodeName(file).constData()));
153  }
154 
156  Impl::ReaderOptionsDialogFunctor optionsCallback;
157  std::string errMsg = Load(loadInfos, nodeResult, &storage, &optionsCallback);
158  if (!errMsg.empty())
159  {
160  QMessageBox::warning(parent, "Error reading files", QString::fromStdString(errMsg));
161  }
162  return nodeResult;
163 }
164 
165 QList<mitk::BaseData::Pointer> QmitkIOUtil::Load(const QString &path, QWidget *parent)
166 {
167  QStringList paths;
168  paths << path;
169  return Load(paths, parent);
170 }
171 
173  mitk::DataStorage &storage,
174  QWidget *parent)
175 {
176  QStringList paths;
177  paths << path;
178  return Load(paths, storage, parent);
179 }
180 
181 QString QmitkIOUtil::Save(const mitk::BaseData *data,
182  const QString &defaultBaseName,
183  const QString &defaultPath,
184  QWidget *parent)
185 {
186  std::vector<const mitk::BaseData *> dataVector;
187  dataVector.push_back(data);
188  QStringList defaultBaseNames;
189  defaultBaseNames.push_back(defaultBaseName);
190  return Save(dataVector, defaultBaseNames, defaultPath, parent).back();
191 }
192 
193 QStringList QmitkIOUtil::Save(const std::vector<const mitk::BaseData *> &data,
194  const QStringList &defaultBaseNames,
195  const QString &defaultPath,
196  QWidget *parent)
197 {
198  QStringList fileNames;
199  QString currentPath = defaultPath;
200 
201  std::vector<SaveInfo> saveInfos;
202 
203  int counter = 0;
204  for (std::vector<const mitk::BaseData *>::const_iterator dataIter = data.begin(), dataIterEnd = data.end();
205  dataIter != dataIterEnd;
206  ++dataIter, ++counter)
207  {
208  SaveInfo saveInfo(*dataIter, mitk::MimeType(), std::string());
209 
210  SaveFilter filters(saveInfo);
211 
212  // If there is only the "__all__" filter string, it means there is no writer for this base data
213  if (filters.Size() < 2)
214  {
215  QMessageBox::warning(
216  parent,
217  "Saving not possible",
218  QString("No writer available for type \"%1\"").arg(QString::fromStdString((*dataIter)->GetNameOfClass())));
219  continue;
220  }
221 
222  // Construct a default path and file name
223  QString filterString = filters.ToString();
224  QString selectedFilter = filters.GetDefaultFilter();
225  QString fileName = currentPath;
226  QString dialogTitle = "Save " + QString::fromStdString((*dataIter)->GetNameOfClass());
227  if (counter < defaultBaseNames.size())
228  {
229  dialogTitle += " \"" + defaultBaseNames[counter] + "\"";
230  fileName += QDir::separator() + defaultBaseNames[counter];
231  // We do not append an extension to the file name by default. The extension
232  // is chosen by the user by either selecting a filter or writing the
233  // extension in the file name himself (in the file save dialog).
234  /*
235  QString defaultExt = filters.GetDefaultExtension();
236  if (!defaultExt.isEmpty())
237  {
238  fileName += "." + defaultExt;
239  }
240  */
241  }
242 
243  // Ask the user for a file name
244  QString nextName = QFileDialog::getSaveFileName(parent, dialogTitle, fileName, filterString, &selectedFilter);
245 
246  if (nextName.isEmpty())
247  {
248  // We stop asking for further file names, but we still save the
249  // data where the user already confirmed the save dialog.
250  break;
251  }
252 
253  fileName = nextName;
254  std::string stdFileName = QFile::encodeName(fileName).constData();
255  QFileInfo fileInfo(fileName);
256  currentPath = fileInfo.absolutePath();
257  QString suffix = fileInfo.completeSuffix();
258  mitk::MimeType filterMimeType = filters.GetMimeTypeForFilter(selectedFilter);
259  mitk::MimeType selectedMimeType;
260 
261  if (fileInfo.exists() && !fileInfo.isFile())
262  {
263  QMessageBox::warning(parent, "Saving not possible", QString("The path \"%1\" is not a file").arg(fileName));
264  continue;
265  }
266 
267  // Theoretically, the user could have entered an extension that does not match the selected filter
268  // The extension then has prioritry over the filter
269  // Check if one of the available mime-types match the filename
270  std::vector<mitk::MimeType> filterMimeTypes = filters.GetMimeTypes();
271  for (std::vector<mitk::MimeType>::const_iterator mimeTypeIter = filterMimeTypes.begin(),
272  mimeTypeIterEnd = filterMimeTypes.end();
273  mimeTypeIter != mimeTypeIterEnd;
274  ++mimeTypeIter)
275  {
276  if (mimeTypeIter->MatchesExtension(stdFileName))
277  {
278  selectedMimeType = *mimeTypeIter;
279  break;
280  }
281  }
282 
283  if (!selectedMimeType.IsValid())
284  {
285  // The file name either does not contain an extension or the
286  // extension is unknown.
287 
288  // If the file already exists, we stop here because we are unable
289  // to (over)write the file without adding a custom suffix. If the file
290  // does not exist, we add the default extension from the currently
291  // selected filter. If the "All" filter was selected, we only add the
292  // default extensions if the file name itself does not already contain
293  // an extension.
294  if (!fileInfo.exists())
295  {
296  if (filterMimeType == SaveFilter::ALL_MIMETYPE())
297  {
298  if (suffix.isEmpty())
299  {
300  // Use the highest ranked mime-type from the list
301  selectedMimeType = filters.GetDefaultMimeType();
302  }
303  }
304  else
305  {
306  selectedMimeType = filterMimeType;
307  }
308 
309  if (selectedMimeType.IsValid())
310  {
311  suffix = QString::fromStdString(selectedMimeType.GetExtensions().front());
312  fileName += "." + suffix;
313  stdFileName = QFile::encodeName(fileName).constData();
314  // We changed the file name (added a suffix) so ask in case
315  // the file aready exists.
316  fileInfo = QFileInfo(fileName);
317  if (fileInfo.exists())
318  {
319  if (!fileInfo.isFile())
320  {
321  QMessageBox::warning(
322  parent, "Saving not possible", QString("The path \"%1\" is not a file").arg(fileName));
323  continue;
324  }
325  if (QMessageBox::question(
326  parent,
327  "Replace File",
328  QString("A file named \"%1\" already exists. Do you want to replace it?").arg(fileName)) ==
329  QMessageBox::No)
330  {
331  continue;
332  }
333  }
334  }
335  }
336  }
337 
338  if (!selectedMimeType.IsValid())
339  {
340  // The extension/filename is not valid (no mime-type found), bail out
341  QMessageBox::warning(
342  parent, "Saving not possible", QString("No mime-type available which can handle \"%1\".").arg(fileName));
343  continue;
344  }
345 
346  if (!QFileInfo(fileInfo.absolutePath()).isWritable())
347  {
348  QMessageBox::warning(parent, "Saving not possible", QString("The path \"%1\" is not writable").arg(fileName));
349  continue;
350  }
351 
352  fileNames.push_back(fileName);
353  saveInfo.m_Path = stdFileName;
354  saveInfo.m_MimeType = selectedMimeType;
355  // pre-select the best writer for the chosen mime-type
356  saveInfo.m_WriterSelector.Select(selectedMimeType.GetName());
357  saveInfos.push_back(saveInfo);
358  }
359 
360  if (!saveInfos.empty())
361  {
362  Impl::WriterOptionsDialogFunctor optionsCallback;
363  std::string errMsg = Save(saveInfos, &optionsCallback);
364  if (!errMsg.empty())
365  {
366  QMessageBox::warning(parent, "Error writing files", QString::fromStdString(errMsg));
367  mitkThrow() << errMsg;
368  }
369  }
370 
371  return fileNames;
372 }
373 
374 void QmitkIOUtil::SaveBaseDataWithDialog(mitk::BaseData *data, std::string fileName, QWidget * /*parent*/)
375 {
376  Save(data, fileName);
377 }
378 
379 void QmitkIOUtil::SaveSurfaceWithDialog(mitk::Surface::Pointer surface, std::string fileName, QWidget * /*parent*/)
380 {
381  Save(surface, fileName);
382 }
383 
384 void QmitkIOUtil::SaveImageWithDialog(mitk::Image::Pointer image, std::string fileName, QWidget * /*parent*/)
385 {
386  Save(image, fileName);
387 }
388 
389 void QmitkIOUtil::SavePointSetWithDialog(mitk::PointSet::Pointer pointset, std::string fileName, QWidget * /*parent*/)
390 {
391  Save(pointset, fileName);
392 }
393 
394 struct QmitkIOUtil::SaveFilter::Impl
395 {
396  Impl(const mitk::IOUtil::SaveInfo &saveInfo) : m_SaveInfo(saveInfo)
397  {
398  // Add an artifical filter for "All"
399  m_MimeTypes.push_back(ALL_MIMETYPE());
400  m_FilterStrings.push_back("All (*.*)");
401 
402  // Get all writers and their mime types for the given base data type
403  // (this is sorted already)
404  std::vector<mitk::MimeType> mimeTypes = saveInfo.m_WriterSelector.GetMimeTypes();
405 
406  for (std::vector<mitk::MimeType>::const_reverse_iterator iter = mimeTypes.rbegin(), iterEnd = mimeTypes.rend();
407  iter != iterEnd;
408  ++iter)
409  {
410  QList<QString> filterExtensions;
411  mitk::MimeType mimeType = *iter;
412  std::vector<std::string> extensions = mimeType.GetExtensions();
413  for (auto &extension : extensions)
414  {
415  filterExtensions << QString::fromStdString(extension);
416  }
417  if (m_DefaultExtension.isEmpty())
418  {
419  m_DefaultExtension = QString::fromStdString(extensions.front());
420  }
421 
422  QString filter = QString::fromStdString(mimeType.GetComment()) + " (";
423  foreach (const QString &extension, filterExtensions)
424  {
425  filter += "*." + extension + " ";
426  }
427  filter = filter.replace(filter.size() - 1, 1, ')');
428  m_MimeTypes.push_back(mimeType);
429  m_FilterStrings.push_back(filter);
430  }
431  }
432 
433  const mitk::IOUtil::SaveInfo m_SaveInfo;
434  std::vector<mitk::MimeType> m_MimeTypes;
435  QStringList m_FilterStrings;
436  QString m_DefaultExtension;
437 };
438 
440 {
441  static mitk::CustomMimeType allMimeType(std::string("__all__"));
442  return mitk::MimeType(allMimeType, -1, -1);
443 }
444 
446 {
447 }
448 
449 QmitkIOUtil::SaveFilter::SaveFilter(const SaveInfo &saveInfo) : d(new Impl(saveInfo))
450 {
451 }
452 
454 {
455  d.reset(new Impl(*other.d));
456  return *this;
457 }
458 
459 std::vector<mitk::MimeType> QmitkIOUtil::SaveFilter::GetMimeTypes() const
460 {
461  return d->m_MimeTypes;
462 }
463 
464 QString QmitkIOUtil::SaveFilter::GetFilterForMimeType(const std::string &mimeType) const
465 {
466  std::vector<mitk::MimeType>::const_iterator iter =
467  std::find_if(d->m_MimeTypes.begin(), d->m_MimeTypes.end(), MimeTypeComparison(mimeType));
468  if (iter == d->m_MimeTypes.end())
469  {
470  return QString();
471  }
472  int index = static_cast<int>(iter - d->m_MimeTypes.begin());
473  if (index < 0 || index >= d->m_FilterStrings.size())
474  {
475  return QString();
476  }
477  return d->m_FilterStrings[index];
478 }
479 
481 {
482  int index = d->m_FilterStrings.indexOf(filter);
483  if (index < 0)
484  {
485  return mitk::MimeType();
486  }
487  return d->m_MimeTypes[index];
488 }
489 
491 {
492  if (d->m_FilterStrings.size() > 1)
493  {
494  return d->m_FilterStrings.at(1);
495  }
496  else if (d->m_FilterStrings.size() > 0)
497  {
498  return d->m_FilterStrings.front();
499  }
500  return QString();
501 }
502 
504 {
505  return d->m_DefaultExtension;
506 }
507 
509 {
510  if (d->m_MimeTypes.size() > 1)
511  {
512  return d->m_MimeTypes.at(1);
513  }
514  else if (d->m_MimeTypes.size() > 0)
515  {
516  return d->m_MimeTypes.front();
517  }
518  return mitk::MimeType();
519 }
520 
522 {
523  return d->m_FilterStrings.join(";;");
524 }
525 
527 {
528  return d->m_FilterStrings.size();
529 }
530 
532 {
533  return d->m_FilterStrings.isEmpty();
534 }
535 
536 bool QmitkIOUtil::SaveFilter::ContainsMimeType(const std::string &mimeType)
537 {
538  return std::find_if(d->m_MimeTypes.begin(), d->m_MimeTypes.end(), MimeTypeComparison(mimeType)) !=
539  d->m_MimeTypes.end();
540 }
QString GetDefaultExtension() const
Data management class that handles 'was created by' relations.
itk::SmartPointer< Self > Pointer
FileWriterSelector m_WriterSelector
Contains a set of IFileWriter objects.
Definition: mitkIOUtil.h:74
static IMimeTypeProvider * GetMimeTypeProvider(us::ModuleContext *context=us::GetModuleContext())
Get an IMimeTypeProvider instance.
static void SavePointSetWithDialog(mitk::PointSet::Pointer pointset, std::string fileName="", QWidget *parent=NULL)
SavePointSetWithDialog Convenience method to save a pointset with a Qt dialog.
Base of all data objects.
Definition: mitkBaseData.h:39
bool IsValid() const
static mitk::MimeType ALL_MIMETYPE()
bool Select(const std::string &mimeType)
QString GetFilterForMimeType(const std::string &mimeType) const
bool ContainsMimeType(const std::string &mimeType)
std::vector< std::string > GetExtensions() const
std::string GetName() const
The CustomMimeType class represents a custom mime-type which may be registered as a service object...
MimeType m_MimeType
The selected mime-type, used to restrict results from FileWriterSelector.
Definition: mitkIOUtil.h:76
static QString GetFileOpenFilterString()
GetFilterString.
Definition: QmitkIOUtil.cpp:86
virtual std::vector< MimeType > GetMimeTypesForCategory(const std::string &category) const =0
mitk::MimeType GetMimeTypeForFilter(const QString &filter) const
#define mitkThrow()
virtual std::vector< std::string > GetCategories() const =0
Get a sorted and unique list of mime-type categories.
static void SaveImageWithDialog(mitk::Image::Pointer image, std::string fileName="", QWidget *parent=NULL)
SaveImageWithDialog Convenience method to save an image with a Qt dialog.
static QList< mitk::BaseData::Pointer > Load(const QStringList &paths, QWidget *parent=NULL)
Loads the specified files.
std::vector< MimeType > GetMimeTypes() const
static void SaveBaseDataWithDialog(mitk::BaseData *data, std::string fileName, QWidget *parent=NULL)
SaveBaseDataWithDialog Convenience method to save any data with a Qt dialog.
SaveFilter & operator=(const SaveFilter &other)
std::vector< mitk::MimeType > GetMimeTypes() const
The MimeType class represens a registered mime-type. It is an immutable wrapper for mitk::CustomMimeT...
Definition: mitkMimeType.h:45
QString GetDefaultFilter() const
SaveFilter(const SaveFilter &other)
QString ToString() const
static void SaveSurfaceWithDialog(mitk::Surface::Pointer surface, std::string fileName="", QWidget *parent=NULL)
SaveSurfaceWithDialog Convenience method to save a surface with a Qt dialog.
mitk::MimeType GetDefaultMimeType() const
std::string m_Path
The path to write the BaseData object to.
Definition: mitkIOUtil.h:78
static QString Save(const mitk::BaseData *data, const QString &defaultBaseName, const QString &defaultPath=QString(), QWidget *parent=NULL)
std::string GetComment() const
A RAII helper class for core service objects.
static itkEventMacro(BoundingShapeInteractionEvent, itk::AnyEvent) class MITKBOUNDINGSHAPE_EXPORT BoundingShapeInteractor Pointer New()
Basic interaction methods for mitk::GeometryData.