/* * FileDialog.cpp * -------------- * Purpose: File and folder selection dialogs implementation. * Notes : (currently none) * Authors: OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #include "stdafx.h" #include "FileDialog.h" #include "Mainfrm.h" #include "InputHandler.h" OPENMPT_NAMESPACE_BEGIN class CFileDialogEx : public CFileDialog { public: CFileDialogEx(bool bOpenFileDialog, LPCTSTR lpszDefExt, LPCTSTR lpszFileName, DWORD dwFlags, LPCTSTR lpszFilter, CWnd *pParentWnd, DWORD dwSize, BOOL bVistaStyle, bool preview) : CFileDialog(bOpenFileDialog ? TRUE : FALSE, lpszDefExt, lpszFileName, dwFlags, lpszFilter, pParentWnd, dwSize, bVistaStyle) , m_fileNameBuf(65536) , doPreview(preview) , played(false) { // MFC's filename buffer is way too small for multi-selections of a large number of files. _tcsncpy(m_fileNameBuf.data(), lpszFileName, m_fileNameBuf.size()); m_fileNameBuf.back() = '\0'; m_ofn.lpstrFile = m_fileNameBuf.data(); m_ofn.nMaxFile = mpt::saturate_cast(m_fileNameBuf.size()); } ~CFileDialogEx() { if(played) { CMainFrame::GetMainFrame()->StopPreview(); } } #if NTDDI_VERSION >= NTDDI_VISTA // 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. // Avoid this by re-implementing our own version which doesn't throw. void AddPlace(const mpt::PathString &path) { if(m_bVistaStyle && path.IsDirectory()) { CComPtr shellItem; HRESULT hr = SHCreateItemFromParsingName(path.ToWide().c_str(), nullptr, IID_IShellItem, reinterpret_cast(&shellItem)); if(SUCCEEDED(hr)) { static_cast(m_pIFileDialog)->AddPlace(shellItem, FDAP_TOP); } } } #endif protected: std::vector m_fileNameBuf; CString oldName; bool doPreview, played; void OnFileNameChange() override { if(doPreview) { CString name = GetPathName(); if(!name.IsEmpty() && name != oldName) { oldName = name; if(CMainFrame::GetMainFrame()->PlaySoundFile(mpt::PathString::FromCString(name), NOTE_MIDDLEC)) { played = true; } } } CFileDialog::OnFileNameChange(); } }; // Display the file dialog. bool FileDialog::Show(CWnd *parent) { m_filenames.clear(); // First, set up the dialog... CFileDialogEx dlg(m_load, m_defaultExtension.empty() ? nullptr : m_defaultExtension.c_str(), m_defaultFilename.c_str(), OFN_EXPLORER | OFN_NOCHANGEDIR | OFN_HIDEREADONLY | OFN_ENABLESIZING | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | (m_multiSelect ? OFN_ALLOWMULTISELECT : 0) | (m_load ? 0 : (OFN_OVERWRITEPROMPT | OFN_NOREADONLYRETURN)), m_extFilter.c_str(), parent != nullptr ? parent : CMainFrame::GetMainFrame(), 0, (mpt::OS::Windows::IsWine() || mpt::OS::Windows::Version::Current().IsBefore(mpt::OS::Windows::Version::WinVista)) ? FALSE : TRUE, m_preview && TrackerSettings::Instance().previewInFileDialogs); OPENFILENAME &ofn = dlg.GetOFN(); ofn.nFilterIndex = m_filterIndex != nullptr ? *m_filterIndex : 0; if(!m_workingDirectory.empty()) { ofn.lpstrInitialDir = m_workingDirectory.c_str(); } #if NTDDI_VERSION >= NTDDI_VISTA const auto places = { &TrackerSettings::Instance().PathPluginPresets, &TrackerSettings::Instance().PathPlugins, &TrackerSettings::Instance().PathSamples, &TrackerSettings::Instance().PathInstruments, &TrackerSettings::Instance().PathSongs, }; for(const auto place : places) { dlg.AddPlace(place->GetDefaultDir()); } for(const auto &place : m_places) { dlg.AddPlace(place); } #endif // Do it! BypassInputHandler bih; if(dlg.DoModal() != IDOK) { return false; } // Retrieve variables if(m_filterIndex != nullptr) *m_filterIndex = ofn.nFilterIndex; if(m_multiSelect) { #if NTDDI_VERSION >= NTDDI_VISTA // Multiple files might have been selected if(CComPtr shellItems = dlg.GetResults(); shellItems != nullptr) { // Using the old-style GetNextPathName doesn't work properly when the user performs a search and selects files from different folders. // Hence we use that only as a fallback. DWORD numItems = 0; shellItems->GetCount(&numItems); for(DWORD i = 0; i < numItems; i++) { CComPtr shellItem; shellItems->GetItemAt(i, &shellItem); LPWSTR filePath = nullptr; if(HRESULT hr = shellItem->GetDisplayName(SIGDN_FILESYSPATH, &filePath); SUCCEEDED(hr)) { m_filenames.push_back(mpt::PathString::FromWide(filePath)); ::CoTaskMemFree(filePath); } } } else #endif { POSITION pos = dlg.GetStartPosition(); while(pos != nullptr) { m_filenames.push_back(mpt::PathString::FromCString(dlg.GetNextPathName(pos))); } } } else { // Only one file m_filenames.push_back(mpt::PathString::FromCString(dlg.GetPathName())); } if(m_filenames.empty()) { return false; } m_workingDirectory = m_filenames.front().AsNative().substr(0, ofn.nFileOffset); m_extension = m_filenames.front().AsNative().substr(ofn.nFileExtension); return true; } // Helper callback to set start path. int CALLBACK BrowseForFolder::BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM /*lParam*/, LPARAM lpData) { if(uMsg == BFFM_INITIALIZED && lpData != NULL) { const BrowseForFolder *that = reinterpret_cast(lpData); SendMessage(hwnd, BFFM_SETSELECTION, TRUE, reinterpret_cast(that->m_workingDirectory.AsNative().c_str())); } return 0; } // Display the folder dialog. bool BrowseForFolder::Show(CWnd *parent) { // Note: MFC's CFolderPickerDialog won't work on pre-Vista systems, as it tries to use OPENFILENAME. BypassInputHandler bih; TCHAR path[MAX_PATH]; BROWSEINFO bi; MemsetZero(bi); bi.hwndOwner = (parent != nullptr ? parent : theApp.m_pMainWnd)->m_hWnd; if(!m_caption.IsEmpty()) bi.lpszTitle = m_caption; bi.pszDisplayName = path; bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI; bi.lpfn = BrowseCallbackProc; bi.lParam = reinterpret_cast(this); LPITEMIDLIST pid = SHBrowseForFolder(&bi); bool success = pid != nullptr && SHGetPathFromIDList(pid, path); CoTaskMemFree(pid); if(success) { m_workingDirectory = mpt::PathString::FromNative(path); } return success; } OPENMPT_NAMESPACE_END