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