1
0

FileDialog.cpp 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. /*
  2. * FileDialog.cpp
  3. * --------------
  4. * Purpose: File and folder selection dialogs implementation.
  5. * Notes : (currently none)
  6. * Authors: OpenMPT Devs
  7. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  8. */
  9. #include "stdafx.h"
  10. #include "FileDialog.h"
  11. #include "Mainfrm.h"
  12. #include "InputHandler.h"
  13. OPENMPT_NAMESPACE_BEGIN
  14. class CFileDialogEx : public CFileDialog
  15. {
  16. public:
  17. CFileDialogEx(bool bOpenFileDialog,
  18. LPCTSTR lpszDefExt,
  19. LPCTSTR lpszFileName,
  20. DWORD dwFlags,
  21. LPCTSTR lpszFilter,
  22. CWnd *pParentWnd,
  23. DWORD dwSize,
  24. BOOL bVistaStyle,
  25. bool preview)
  26. : CFileDialog(bOpenFileDialog ? TRUE : FALSE, lpszDefExt, lpszFileName, dwFlags, lpszFilter, pParentWnd, dwSize, bVistaStyle)
  27. , m_fileNameBuf(65536)
  28. , doPreview(preview)
  29. , played(false)
  30. {
  31. // MFC's filename buffer is way too small for multi-selections of a large number of files.
  32. _tcsncpy(m_fileNameBuf.data(), lpszFileName, m_fileNameBuf.size());
  33. m_fileNameBuf.back() = '\0';
  34. m_ofn.lpstrFile = m_fileNameBuf.data();
  35. m_ofn.nMaxFile = mpt::saturate_cast<DWORD>(m_fileNameBuf.size());
  36. }
  37. ~CFileDialogEx()
  38. {
  39. if(played)
  40. {
  41. CMainFrame::GetMainFrame()->StopPreview();
  42. }
  43. }
  44. #if NTDDI_VERSION >= NTDDI_VISTA
  45. // MFC's AddPlace() is declared as throw() but can in fact throw if any of the COM calls fail, e.g. because the place does not exist.
  46. // Avoid this by re-implementing our own version which doesn't throw.
  47. void AddPlace(const mpt::PathString &path)
  48. {
  49. if(m_bVistaStyle && path.IsDirectory())
  50. {
  51. CComPtr<IShellItem> shellItem;
  52. HRESULT hr = SHCreateItemFromParsingName(path.ToWide().c_str(), nullptr, IID_IShellItem, reinterpret_cast<void **>(&shellItem));
  53. if(SUCCEEDED(hr))
  54. {
  55. static_cast<IFileDialog*>(m_pIFileDialog)->AddPlace(shellItem, FDAP_TOP);
  56. }
  57. }
  58. }
  59. #endif
  60. protected:
  61. std::vector<TCHAR> m_fileNameBuf;
  62. CString oldName;
  63. bool doPreview, played;
  64. void OnFileNameChange() override
  65. {
  66. if(doPreview)
  67. {
  68. CString name = GetPathName();
  69. if(!name.IsEmpty() && name != oldName)
  70. {
  71. oldName = name;
  72. if(CMainFrame::GetMainFrame()->PlaySoundFile(mpt::PathString::FromCString(name), NOTE_MIDDLEC))
  73. {
  74. played = true;
  75. }
  76. }
  77. }
  78. CFileDialog::OnFileNameChange();
  79. }
  80. };
  81. // Display the file dialog.
  82. bool FileDialog::Show(CWnd *parent)
  83. {
  84. m_filenames.clear();
  85. // First, set up the dialog...
  86. CFileDialogEx dlg(m_load,
  87. m_defaultExtension.empty() ? nullptr : m_defaultExtension.c_str(),
  88. m_defaultFilename.c_str(),
  89. OFN_EXPLORER | OFN_NOCHANGEDIR | OFN_HIDEREADONLY | OFN_ENABLESIZING | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | (m_multiSelect ? OFN_ALLOWMULTISELECT : 0) | (m_load ? 0 : (OFN_OVERWRITEPROMPT | OFN_NOREADONLYRETURN)),
  90. m_extFilter.c_str(),
  91. parent != nullptr ? parent : CMainFrame::GetMainFrame(),
  92. 0,
  93. (mpt::OS::Windows::IsWine() || mpt::OS::Windows::Version::Current().IsBefore(mpt::OS::Windows::Version::WinVista)) ? FALSE : TRUE,
  94. m_preview && TrackerSettings::Instance().previewInFileDialogs);
  95. OPENFILENAME &ofn = dlg.GetOFN();
  96. ofn.nFilterIndex = m_filterIndex != nullptr ? *m_filterIndex : 0;
  97. if(!m_workingDirectory.empty())
  98. {
  99. ofn.lpstrInitialDir = m_workingDirectory.c_str();
  100. }
  101. #if NTDDI_VERSION >= NTDDI_VISTA
  102. const auto places =
  103. {
  104. &TrackerSettings::Instance().PathPluginPresets,
  105. &TrackerSettings::Instance().PathPlugins,
  106. &TrackerSettings::Instance().PathSamples,
  107. &TrackerSettings::Instance().PathInstruments,
  108. &TrackerSettings::Instance().PathSongs,
  109. };
  110. for(const auto place : places)
  111. {
  112. dlg.AddPlace(place->GetDefaultDir());
  113. }
  114. for(const auto &place : m_places)
  115. {
  116. dlg.AddPlace(place);
  117. }
  118. #endif
  119. // Do it!
  120. BypassInputHandler bih;
  121. if(dlg.DoModal() != IDOK)
  122. {
  123. return false;
  124. }
  125. // Retrieve variables
  126. if(m_filterIndex != nullptr)
  127. *m_filterIndex = ofn.nFilterIndex;
  128. if(m_multiSelect)
  129. {
  130. #if NTDDI_VERSION >= NTDDI_VISTA
  131. // Multiple files might have been selected
  132. if(CComPtr<IShellItemArray> shellItems = dlg.GetResults(); shellItems != nullptr)
  133. {
  134. // Using the old-style GetNextPathName doesn't work properly when the user performs a search and selects files from different folders.
  135. // Hence we use that only as a fallback.
  136. DWORD numItems = 0;
  137. shellItems->GetCount(&numItems);
  138. for(DWORD i = 0; i < numItems; i++)
  139. {
  140. CComPtr<IShellItem> shellItem;
  141. shellItems->GetItemAt(i, &shellItem);
  142. LPWSTR filePath = nullptr;
  143. if(HRESULT hr = shellItem->GetDisplayName(SIGDN_FILESYSPATH, &filePath); SUCCEEDED(hr))
  144. {
  145. m_filenames.push_back(mpt::PathString::FromWide(filePath));
  146. ::CoTaskMemFree(filePath);
  147. }
  148. }
  149. } else
  150. #endif
  151. {
  152. POSITION pos = dlg.GetStartPosition();
  153. while(pos != nullptr)
  154. {
  155. m_filenames.push_back(mpt::PathString::FromCString(dlg.GetNextPathName(pos)));
  156. }
  157. }
  158. } else
  159. {
  160. // Only one file
  161. m_filenames.push_back(mpt::PathString::FromCString(dlg.GetPathName()));
  162. }
  163. if(m_filenames.empty())
  164. {
  165. return false;
  166. }
  167. m_workingDirectory = m_filenames.front().AsNative().substr(0, ofn.nFileOffset);
  168. m_extension = m_filenames.front().AsNative().substr(ofn.nFileExtension);
  169. return true;
  170. }
  171. // Helper callback to set start path.
  172. int CALLBACK BrowseForFolder::BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM /*lParam*/, LPARAM lpData)
  173. {
  174. if(uMsg == BFFM_INITIALIZED && lpData != NULL)
  175. {
  176. const BrowseForFolder *that = reinterpret_cast<BrowseForFolder *>(lpData);
  177. SendMessage(hwnd, BFFM_SETSELECTION, TRUE, reinterpret_cast<LPARAM>(that->m_workingDirectory.AsNative().c_str()));
  178. }
  179. return 0;
  180. }
  181. // Display the folder dialog.
  182. bool BrowseForFolder::Show(CWnd *parent)
  183. {
  184. // Note: MFC's CFolderPickerDialog won't work on pre-Vista systems, as it tries to use OPENFILENAME.
  185. BypassInputHandler bih;
  186. TCHAR path[MAX_PATH];
  187. BROWSEINFO bi;
  188. MemsetZero(bi);
  189. bi.hwndOwner = (parent != nullptr ? parent : theApp.m_pMainWnd)->m_hWnd;
  190. if(!m_caption.IsEmpty()) bi.lpszTitle = m_caption;
  191. bi.pszDisplayName = path;
  192. bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI;
  193. bi.lpfn = BrowseCallbackProc;
  194. bi.lParam = reinterpret_cast<LPARAM>(this);
  195. LPITEMIDLIST pid = SHBrowseForFolder(&bi);
  196. bool success = pid != nullptr && SHGetPathFromIDList(pid, path);
  197. CoTaskMemFree(pid);
  198. if(success)
  199. {
  200. m_workingDirectory = mpt::PathString::FromNative(path);
  201. }
  202. return success;
  203. }
  204. OPENMPT_NAMESPACE_END