diff --git a/src/DialogHelper.cpp b/src/DialogHelper.cpp index eab41f7..c0544af 100644 --- a/src/DialogHelper.cpp +++ b/src/DialogHelper.cpp @@ -69,9 +69,219 @@ namespace YYCC::DialogHelper { #pragma endregion -#pragma region FileDialog +#pragma region File Dialog + bool FileDialog::Generate(WinFileDialog& win_result) const { + return false; + } +#pragma endregion + +#pragma region Windows Dialog Code + + enum class CommonFileDialogType { + OpenFile, + OpenMultipleFiles, + SaveFile, + OpenFolder + }; + + using SmartFileDialogPtr = std::unique_ptr>; + + template + bool CommonFileDialog(const FileDialog& params, std::vector& ret) { + // Reference: https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog + // prepare result variable + HRESULT hr; + + // create file dialog instance + // fetch dialog CLSID first + CLSID dialog_clsid; + switch (EDialogType) { + case CommonFileDialogType::OpenFile: + case CommonFileDialogType::OpenMultipleFiles: + case CommonFileDialogType::OpenFolder: + dialog_clsid = CLSID_FileOpenDialog; + break; + case CommonFileDialogType::SaveFile: + dialog_clsid = CLSID_FileSaveDialog; + break; + default: + return false; + } + // create raw dialog pointer + IFileDialog* _pfd = nullptr; + hr = CoCreateInstance( + dialog_clsid, + NULL, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&_pfd) + ); + if (!SUCCEEDED(hr)) return false; + // create memory-safe dialog pointer + SmartFileDialogPtr pfd( + _pfd, + [](IFileDialog* instance) -> void { + if (instance != nullptr) + instance->Release(); + } + ); + + // set options for dialog + // before setting, always get the options first in order. + // not to override existing options. + DWORD dwFlags; + hr = pfd->GetOptions(&dwFlags); + if (!SUCCEEDED(hr)) return false; + // modify options + switch (EDialogType) { + // We want user only can pick file system files: FOS_FORCEFILESYSTEM. + // Open dialog default: FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR + // Save dialog default: FOS_OVERWRITEPROMPT | FOS_NOREADONLYRETURN | FOS_PATHMUSTEXIST | FOS_NOCHANGEDIR + // Pick folder: FOS_PICKFOLDERS + case CommonFileDialogType::OpenFile: + dwFlags |= FOS_FORCEFILESYSTEM; + dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR; + break; + case CommonFileDialogType::OpenMultipleFiles: + dwFlags |= FOS_FORCEFILESYSTEM; + dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR; + dwFlags |= FOS_ALLOWMULTISELECT; + break; + case CommonFileDialogType::SaveFile: + dwFlags |= FOS_FORCEFILESYSTEM; + dwFlags |= FOS_OVERWRITEPROMPT | FOS_NOREADONLYRETURN | FOS_PATHMUSTEXIST | FOS_NOCHANGEDIR; + break; + case CommonFileDialogType::OpenFolder: + dwFlags |= FOS_FORCEFILESYSTEM; + dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR; + dwFlags |= FOS_PICKFOLDERS; + break; + default: + return false; + } + // set folder dialog options + hr = pfd->SetOptions(dwFlags); + if (!SUCCEEDED(hr)) return false; + + // set title + std::wstring wtitle, winit_filename, winit_directory; + if (params.GetTitle() != nullptr) { + EncodingHelper::UTF8ToWchar(params.GetTitle(), wtitle); + } + + // set file types and default file index when we picking file + WinFileFilters win_file_filters; + if constexpr (EDialogType != CommonFileDialogType::OpenFolder) { + // generate data from user specified file filters + const auto& file_filters = params.GetFileTypes(); + if (!file_filters.Generate(win_file_filters)) + return false; + + // set file types list + hr = pfd->SetFileTypes(win_file_filters.GetFilterCount(), win_file_filters.GetFilterSpecs()); + if (!SUCCEEDED(hr)) return false; + + // set default file type index + // Windows order this is 1-based index. + // We plus 1 for it because we used is 0-based index. + hr = pfd->SetFileTypeIndex(params.GetDefaultFileTypeIndex() + 1); + if (!SUCCEEDED(hr)) return false; + } + + // CoCreate the File Open Dialog object. + IFileDialog* pfd = NULL; + HRESULT hr = CoCreateInstance( + CLSID_FileOpenDialog, + NULL, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&pfd) + ); + if (SUCCEEDED(hr)) { + // Set the options on the dialog. + DWORD dwFlags; + + // Before setting, always get the options first in order + // not to override existing options. + hr = pfd->GetOptions(&dwFlags); + if (SUCCEEDED(hr)) { + // In this case, get shell items only for file system items. + hr = pfd->SetOptions(dwFlags | FOS_FORCEFILESYSTEM); + if (SUCCEEDED(hr)) { + // Set the file types to display only. + // Notice that this is a 1-based array. + hr = pfd->SetFileTypes(ARRAYSIZE(c_rgSaveTypes), c_rgSaveTypes); + if (SUCCEEDED(hr)) { + // Set the selected file type index to Word Docs for this example. + hr = pfd->SetFileTypeIndex(INDEX_WORDDOC); + if (SUCCEEDED(hr)) { + // Set the default extension to be ".doc" file. + hr = pfd->SetDefaultExtension(L"doc;docx"); + if (SUCCEEDED(hr)) { + // Show the dialog + hr = pfd->Show(NULL); + if (SUCCEEDED(hr)) { + // Obtain the result once the user clicks + // the 'Open' button. + // The result is an IShellItem object. + IShellItem* psiResult; + hr = pfd->GetResult(&psiResult); + if (SUCCEEDED(hr)) { + // We are just going to print out the + // name of the file for sample sake. + PWSTR pszFilePath = NULL; + hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH, + &pszFilePath); + if (SUCCEEDED(hr)) { + TaskDialog(NULL, + NULL, + L"CommonFileDialogApp", + pszFilePath, + NULL, + TDCBF_OK_BUTTON, + TD_INFORMATION_ICON, + NULL); + CoTaskMemFree(pszFilePath); + } + psiResult->Release(); + } + } + } + } + } + } + } + pfd->Release(); + } + return SUCCEEDED(hr); + } + +#pragma endregion + +#pragma region Wrapper Functions + + bool OpenFileDialog(const FileDialog& params, std::string& ret) { + std::vector cache; + bool isok = CommonFileDialog(params, cache); + if (isok) ret = cache.front(); + return isok; + } + bool OpenMultipleFileDialog(const FileDialog& params, std::vector& ret) { + return CommonFileDialog(params, ret); + } + bool SaveFileDialog(const FileDialog& params, std::string& ret) { + std::vector cache; + bool isok = CommonFileDialog(params, cache); + if (isok) ret = cache.front(); + return isok; + } + + bool OpenFolderDialog(const FileDialog& params, std::string& ret) { + std::vector cache; + bool isok = CommonFileDialog(params, cache); + if (isok) ret = cache.front(); + return isok; + } #pragma endregion diff --git a/src/DialogHelper.hpp b/src/DialogHelper.hpp index 8d3f93e..81a154f 100644 --- a/src/DialogHelper.hpp +++ b/src/DialogHelper.hpp @@ -47,7 +47,7 @@ namespace YYCC::DialogHelper { */ class FileFilters { public: - FileFilters() : m_Filters(){} + FileFilters() : m_Filters() {} /** * @brief Add a filter pair in file types list. @@ -86,6 +86,39 @@ namespace YYCC::DialogHelper { std::vector m_Filters; }; + class WinFileDialog { + friend class FileDialog; + public: + WinFileDialog() : + m_WinOwner(NULL), + m_WinFileTypes(), m_WinDefaultFileTypeIndex(0u), + m_HasTitle(false), m_HasInitFileName(false), m_WinTitle(), m_WinInitFileName(), + m_WinInitDirectory(nullptr) + {} + + bool HasOwner() const { return m_WinOwner != NULL; } + HWND GetOwner() const { return m_WinOwner; } + + bool HasTitle() const { return !m_WinTitle.empty(); } + const wchar_t* GetTitle() const { return m_WinTitle.c_str(); } + bool HasInitFileName() const { return !m_WinInitFileName.empty(); } + const wchar_t* GetInitFileName() const { return m_WinInitFileName.c_str(); } + bool HasInitDirectory() const { return m_WinInitDirectory != nullptr;} + const IShellItem* GetInitDirectory() const { return m_WinInitDirectory.get(); } + + protected: + HWND m_WinOwner; + + WinFileFilters m_WinFileTypes; + UINT m_WinDefaultFileTypeIndex; + + bool m_HasTitle, m_HasInitFileName; + std::wstring m_WinTitle, m_WinInitFileName; + + using SmartShellItem = std::unique_ptr>; + SmartShellItem m_WinInitDirectory; + }; + class FileDialog { public: FileDialog() : @@ -95,52 +128,38 @@ namespace YYCC::DialogHelper { m_InitFileName(), m_InitDirectory() {} void SetOwner(HWND owner) { m_Owner = owner; } - HWND GetOwner() const { return m_Owner; } - void SetTitle(const char* title) { - if (title == nullptr) m_Title.clear(); - else m_Title = title; + if (m_HasTitle = title != nullptr) + m_Title = title; } - const char* GetTitle() const { - if (m_Title.empty()) return nullptr; - else return m_Title.c_str(); - } - - FileFilters& GetFileTypes() { + FileFilters& ConfigreFileTypes() { return m_FileTypes; } - const FileFilters& GetFileTypes() const { - return m_FileTypes; - } - void SetDefaultFileTypeIndex(UINT idx) { m_DefaultFileTypeIndex = idx; } - UINT GetDefaultFileTypeIndex() const { return m_DefaultFileTypeIndex; } - void SetInitFileName(const char* init_filename) { - if (init_filename == nullptr) m_InitFileName.clear(); - else m_InitFileName = init_filename; + if (m_HasInitFileName = init_filename != nullptr) + m_InitFileName = init_filename; } - const char* GetInitFileName() const { - if (m_InitFileName.empty()) return nullptr; - else return m_InitFileName.c_str(); - } - void SetInitDirectory(const char* init_dir) { - if (init_dir == nullptr) m_InitDirectory.clear(); - else m_InitDirectory = init_dir; - } - const char* GetInitDirectory() const { - if (m_InitDirectory.empty()) return nullptr; - else return m_InitDirectory.c_str(); + if (m_HasInitDirectory = init_dir != nullptr) + m_InitDirectory = init_dir; } - private: + bool Generate(WinFileDialog& win_result) const; + + protected: HWND m_Owner; - std::string m_Title; + + bool m_HasTitle, m_HasInitFileName, m_HasInitDirectory; + std::string m_Title, m_InitFileName, m_InitDirectory; + FileFilters m_FileTypes; + /** + * @brief The default file type selected in dialog + * @remarks Although Windows notice that this is a 1-based index, + * but for universal experience, we order this is 0-based index. + */ UINT m_DefaultFileTypeIndex; - std::string m_InitFileName; - std::string m_InitDirectory; }; bool OpenFileDialog(const FileDialog& params, std::string& ret);