feat: finish dialog helper.

- finish dialof helper.
- dialog helper still nned some annotation.
This commit is contained in:
yyc12345 2024-05-27 14:27:11 +08:00
parent a9059013f0
commit cebe2f004d
3 changed files with 299 additions and 274 deletions

View File

@ -6,6 +6,29 @@
namespace YYCC::DialogHelper { namespace YYCC::DialogHelper {
#pragma region COM Guard
class ComGuard {
public:
ComGuard() : m_HasInit(false) {
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (SUCCEEDED(hr)) m_HasInit = true;
}
~ComGuard() {
if (m_HasInit) {
CoUninitialize();
}
}
protected:
bool m_HasInit;
};
static const ComGuard c_ComGuard;
#pragma endregion
#pragma region FileFilters #pragma region FileFilters
bool FileFilters::Add(const char* filter_name, std::initializer_list<const char*> il) { bool FileFilters::Add(const char* filter_name, std::initializer_list<const char*> il) {
@ -16,7 +39,8 @@ namespace YYCC::DialogHelper {
// assign filter patterns // assign filter patterns
FilterModes modes; FilterModes modes;
for (const char* pattern : il) { for (const char* pattern : il) {
if (pattern != nullptr) modes.emplace_back(std::string(pattern)); if (pattern != nullptr)
modes.emplace_back(std::string(pattern));
} }
// check filter patterns // check filter patterns
@ -29,8 +53,7 @@ namespace YYCC::DialogHelper {
bool FileFilters::Generate(WinFileFilters& win_result) const { bool FileFilters::Generate(WinFileFilters& win_result) const {
// clear Windows oriented data // clear Windows oriented data
win_result.m_WinDataStruct.reset(); win_result.Clear();
win_result.m_WinFilters.clear();
// build new Windows oriented string vector first // build new Windows oriented string vector first
for (const auto& it : m_Filters) { for (const auto& it : m_Filters) {
@ -72,7 +95,57 @@ namespace YYCC::DialogHelper {
#pragma region File Dialog #pragma region File Dialog
bool FileDialog::Generate(WinFileDialog& win_result) const { bool FileDialog::Generate(WinFileDialog& win_result) const {
return false; // clear Windows oriented data
win_result.Clear();
// set owner
win_result.m_WinOwner = m_Owner;
// build file filters
if (!m_FileTypes.Generate(win_result.m_WinFileTypes))
return false;
// check default file type index
// check value overflow (comparing with >= because we need plus 1 for file type index later)
if (m_DefaultFileTypeIndex >= std::numeric_limits<UINT>::max())
return false;
// check invalid index (overflow the length or registered file types if there is file type)
if (m_FileTypes.Count() != 0u && m_DefaultFileTypeIndex >= m_FileTypes.Count())
return false;
// set index with additional plus according to Windows specification.
win_result.m_WinDefaultFileTypeIndex = static_cast<UINT>(m_DefaultFileTypeIndex + 1);
// build title and init file name
if (m_HasTitle) {
if (!YYCC::EncodingHelper::UTF8ToWchar(m_Title.c_str(), win_result.m_WinTitle))
return false;
win_result.m_HasTitle = true;
}
if (m_HasInitFileName) {
if (!YYCC::EncodingHelper::UTF8ToWchar(m_InitFileName.c_str(), win_result.m_WinInitFileName))
return false;
win_result.m_HasInitFileName = true;
}
// fetch init directory
if (m_HasInitDirectory) {
// convert to wpath
std::wstring w_init_directory;
if (!YYCC::EncodingHelper::UTF8ToWchar(m_InitDirectory.c_str(), w_init_directory))
return false;
// fetch IShellItem*
// Ref: https://stackoverflow.com/questions/76306324/how-to-set-default-folder-for-ifileopendialog-interface
IShellItem* init_directory = NULL;
HRESULT hr = SHCreateItemFromParsingName(w_init_directory.c_str(), NULL, IID_PPV_ARGS(&init_directory));
if (FAILED(hr)) return false;
// assign IShellItem*
win_result.m_WinInitDirectory.reset(init_directory);
}
// everything is okey
return true;
} }
#pragma endregion #pragma endregion
@ -86,7 +159,20 @@ namespace YYCC::DialogHelper {
OpenFolder OpenFolder
}; };
using SmartFileDialogPtr = std::unique_ptr<IFileDialog, std::function<void(IFileDialog*)>>; bool ExtractDisplayName(IShellItem* item, std::string& ret) {
// fetch display name from IShellItem*
WCHAR* _name;
HRESULT hr = item->GetDisplayName(SIGDN_FILESYSPATH, &_name);
if (FAILED(hr)) return false;
SmartLPWSTR display_name(_name);
// convert result
if (!YYCC::EncodingHelper::WcharToUTF8(display_name.get(), ret))
return false;
// finished
return true;
}
template<CommonFileDialogType EDialogType> template<CommonFileDialogType EDialogType>
bool CommonFileDialog(const FileDialog& params, std::vector<std::string>& ret) { bool CommonFileDialog(const FileDialog& params, std::vector<std::string>& ret) {
@ -117,22 +203,16 @@ namespace YYCC::DialogHelper {
CLSCTX_INPROC_SERVER, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&_pfd) IID_PPV_ARGS(&_pfd)
); );
if (!SUCCEEDED(hr)) return false; if (FAILED(hr)) return false;
// create memory-safe dialog pointer // create memory-safe dialog pointer
SmartFileDialogPtr pfd( SmartIFileDialog pfd(_pfd);
_pfd,
[](IFileDialog* instance) -> void {
if (instance != nullptr)
instance->Release();
}
);
// set options for dialog // set options for dialog
// before setting, always get the options first in order. // before setting, always get the options first in order.
// not to override existing options. // not to override existing options.
DWORD dwFlags; DWORD dwFlags;
hr = pfd->GetOptions(&dwFlags); hr = pfd->GetOptions(&dwFlags);
if (!SUCCEEDED(hr)) return false; if (FAILED(hr)) return false;
// modify options // modify options
switch (EDialogType) { switch (EDialogType) {
// We want user only can pick file system files: FOS_FORCEFILESYSTEM. // We want user only can pick file system files: FOS_FORCEFILESYSTEM.
@ -162,98 +242,109 @@ namespace YYCC::DialogHelper {
} }
// set folder dialog options // set folder dialog options
hr = pfd->SetOptions(dwFlags); hr = pfd->SetOptions(dwFlags);
if (!SUCCEEDED(hr)) return false; if (FAILED(hr)) return false;
// set title // build Windows used file dialog parameters
std::wstring wtitle, winit_filename, winit_directory; WinFileDialog win_params;
if (params.GetTitle() != nullptr) { if (!params.Generate(win_params))
EncodingHelper::UTF8ToWchar(params.GetTitle(), wtitle); return false;
// setup title and init file name
if (win_params.HasTitle()) {
hr = pfd->SetTitle(win_params.GetTitle());
if (FAILED(hr)) return false;
}
if (win_params.HasInitFileName()) {
hr = pfd->SetFileName(win_params.GetInitFileName());
if (FAILED(hr)) return false;
}
// setup init directory
if (win_params.HasInitDirectory()) {
hr = pfd->SetFolder(win_params.GetInitDirectory());
} }
// set file types and default file index when we picking file // set file types and default file index when we picking file
WinFileFilters win_file_filters;
if constexpr (EDialogType != CommonFileDialogType::OpenFolder) { 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 // set file types list
hr = pfd->SetFileTypes(win_file_filters.GetFilterCount(), win_file_filters.GetFilterSpecs()); const auto& file_filters = win_params.GetFileTypes();
if (!SUCCEEDED(hr)) return false; hr = pfd->SetFileTypes(file_filters.GetFilterCount(), file_filters.GetFilterSpecs());
if (FAILED(hr)) return false;
// set default file type index // set default file type index
// Windows order this is 1-based index. hr = pfd->SetFileTypeIndex(win_params.GetDefaultFileTypeIndex());
// We plus 1 for it because we used is 0-based index. if (FAILED(hr)) return false;
hr = pfd->SetFileTypeIndex(params.GetDefaultFileTypeIndex() + 1);
if (!SUCCEEDED(hr)) return false;
} }
// CoCreate the File Open Dialog object. // show the dialog
IFileDialog* pfd = NULL; hr = pfd->Show(win_params.HasOwner() ? win_params.GetOwner() : nullptr);
HRESULT hr = CoCreateInstance( if (FAILED(hr)) return false;
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 // obtain result when user click "OK" button.
// not to override existing options. switch (EDialogType) {
hr = pfd->GetOptions(&dwFlags); case CommonFileDialogType::OpenFile:
if (SUCCEEDED(hr)) { case CommonFileDialogType::OpenFolder:
// In this case, get shell items only for file system items. case CommonFileDialogType::SaveFile:
hr = pfd->SetOptions(dwFlags | FOS_FORCEFILESYSTEM); {
if (SUCCEEDED(hr)) { // obtain one file entry
// Set the file types to display only. IShellItem* _item;
// Notice that this is a 1-based array. hr = pfd->GetResult(&_item);
hr = pfd->SetFileTypes(ARRAYSIZE(c_rgSaveTypes), c_rgSaveTypes); if (FAILED(hr)) return false;
if (SUCCEEDED(hr)) { SmartIShellItem result_item(_item);
// Set the selected file type index to Word Docs for this example.
hr = pfd->SetFileTypeIndex(INDEX_WORDDOC); // extract display name
if (SUCCEEDED(hr)) { std::string result_name;
// Set the default extension to be ".doc" file. if (!ExtractDisplayName(result_item.get(), result_name))
hr = pfd->SetDefaultExtension(L"doc;docx"); return false;
if (SUCCEEDED(hr)) {
// Show the dialog // append result
hr = pfd->Show(NULL); ret.emplace_back(std::move(result_name));
if (SUCCEEDED(hr)) { }
// Obtain the result once the user clicks break;
// the 'Open' button. case CommonFileDialogType::OpenMultipleFiles:
// The result is an IShellItem object. {
IShellItem* psiResult; // try casting file dialog to file open dialog
hr = pfd->GetResult(&psiResult); // Ref: https://learn.microsoft.com/en-us/windows/win32/learnwin32/asking-an-object-for-an-interface
if (SUCCEEDED(hr)) { IFileOpenDialog* _pfod = nullptr;
// We are just going to print out the hr = pfd->QueryInterface(IID_PPV_ARGS(&_pfod));
// name of the file for sample sake. if (FAILED(hr)) return false;
PWSTR pszFilePath = NULL; SmartIFileOpenDialog pfod(_pfod);
hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH,
&pszFilePath); // obtain multiple file entires
if (SUCCEEDED(hr)) { IShellItemArray* _items;
TaskDialog(NULL, hr = pfod->GetResults(&_items);
NULL, if (FAILED(hr)) return false;
L"CommonFileDialogApp", SmartIShellItemArray result_items(_items);
pszFilePath,
NULL, // analyze file entries
TDCBF_OK_BUTTON, // get array count first
TD_INFORMATION_ICON, DWORD result_items_count = 0u;
NULL); hr = result_items->GetCount(&result_items_count);
CoTaskMemFree(pszFilePath); if (FAILED(hr)) return false;
} // iterate array
psiResult->Release(); for (DWORD i = 0u; i < result_items_count; ++i) {
} // fetch item by index
} IShellItem* _item;;
} hr = result_items->GetItemAt(i, &_item);
} if (FAILED(hr)) return false;
} SmartIShellItem result_item(_item);
// extract display name
std::string result_name;
if (!ExtractDisplayName(result_item.get(), result_name))
return false;
// append result
ret.emplace_back(std::move(result_name));
} }
} }
pfd->Release(); break;
default:
return false;
} }
return SUCCEEDED(hr);
// everything is okey
return true;
} }
#pragma endregion #pragma endregion
@ -285,159 +376,6 @@ namespace YYCC::DialogHelper {
#pragma endregion #pragma endregion
//template<bool TIsOpen, bool TMultiSelection>
//bool GeneralFileDialog(const FileDialogParameter& params, std::vector<std::string>& ret) {
// // make sure multi-selection only available in open mode
// static_assert(TIsOpen && (!TIsOpen && TMultiSelection == false));
// // build filter
// std::wstring w_filter;
// for (const auto& filter_pair : params.m_Filter) {
// w_filter += EncodingHelper::UTF8ToWchar(filter_pair.first.c_str());
// w_filter += L'\0';
// w_filter += EncodingHelper::UTF8ToWchar(filter_pair.second.c_str());
// w_filter += L'\0';
// }
// // build title
// std::wstring w_title(EncodingHelper::UTF8ToWchar(params.m_Title.c_str()));
// // build default extension
// std::wstring w_default_ext(EncodingHelper::UTF8ToWchar(params.m_DefaultExtension.c_str()));
// // build initial directory
// std::wstring w_init_dir(EncodingHelper::UTF8ToWchar(params.m_InitialDirectory.c_str()));
// // prepare file name receiver and preset it as initial file name
// std::wstring path_receiver(EncodingHelper::UTF8ToWchar(params.m_InitialFileName.c_str()));
// path_receiver.resize(std::max(MAX_PATH, path_receiver.size()), L'\0');
// // prepare the common part of file dialog struct
// OPENFILENAMEW dialog_param;
// ZeroMemory(&dialog_param, sizeof(OPENFILENAMEW));
// dialog_param.lStructSize = sizeof(OPENFILENAMEW);
// // dialog owner
// dialog_param.hwndOwner = params.m_Owner;
// // if no filter, we pass NULL
// dialog_param.lpstrFilter = w_filter.empty() ? NULL : w_filter.c_str();
// // no record to user selected filter
// dialog_param.lpstrCustomFilter = NULL;
// dialog_param.nMaxCustFilter = 0;
// dialog_param.nFilterIndex = 0;
// // path receiver, also is init filename
// dialog_param.lpstrFile = path_receiver.data();
// dialog_param.nMaxFile = static_caast<DWORD>(path_receiver.size());
// // no selected file infos
// dialog_param.lpstrFileTitle = NULL;
// dialog_param.nMaxFileTitle = 0;
// // initial directory
// dialog_param.lpstrInitialDir = w_init_dir.empty() ? NULL : w_init_dir.c_str();
// // dialog title
// dialog_param.lpstrTitle = w_title.empty() ? NULL : w_title.c_str();
// // setup basic flags
// dialog_param.Flags = OFN_EXPLORER;
// // default extension
// dialog_param.lpstrDefExt = w_default_ext.empty() ? NULL : w_default_ext.c_str();
// BOOL status;
// if constexpr (TIsOpen) {
// // multi-selection need add special multi-selection flags
// if constexpr (TMultiSelection) {
// dialog_param.Flags |= OFN_ALLOWMULTISELECT;
// }
// // call browser
// status = GetOpenFileNameW(&dialog_param);
// // only process result when success
// if (status) {
// if constexpr (TMultiSelection) {
// // get directory part, copy from start to dialog param specified offset
// std::wstring w_directory_part(path_receiver.c_str(), dialog_param.nFileOffset);
// // get file names part one by one
// size_t filename_cursor = dialog_param.nFileOffset;
// while (path_receiver[filename_cursor] != L'\0') {
// // init wstring from given offset
// std::wstring w_filename_part(path_receiver.c_str() + filename_cursor);
// // get eaten chars from result and increase to cursor
// filename_cursor += w_filename_part.size() + 1u;
// // combine 2 parts and insert into list
// ret.emplace_back(std::string(EncodingHelper::WcharToUTF8(w_directory_part + w_filename_part)));
// }
// } else {
// ret.emplace_back(std::string(EncodingHelper::WcharToUTF8(path_receiver.c_str())));
// }
// }
// } else {
// // call browser
// status = GetSaveFileNameW(&dialog_param);
// // only process result when success
// if (status) {
// ret.emplace_back(std::string(EncodingHelper::WcharToUTF8(path_receiver.c_str())));
// }
// }
// // if failed, clear result
// // and return result
// if (!status) {
// ret.clear();
// }
// return status == TRUE;
//}
//bool OpenFileDialog(const FileDialogParameter& params, std::string& ret) {
// std::vector<std::string> cache;
// bool isok = GeneralFileDialog<true, false>(params, cache);
// if (isok) ret = cache.front();
// return isok;
//}
//bool OpenMultipleFileDialog(const FileDialogParameter& params, std::vector<std::string>& ret) {
// return GeneralFileDialog<true, true>(params, ret);
//}
//bool SaveFileDialog(const FileDialogParameter& params, std::string& ret) {
// std::vector<std::string> cache;
// bool isok = GeneralFileDialog<false, false>(params, cache);
// if (isok) ret = cache.front();
// return isok;
//}
//bool OpenFolderDialog(const FolderDialogParameter& params, std::string& ret) {
// // create wchar string cache for windows W-tail function
// std::wstring w_title(EncodingHelper::UTF8ToWchar(params.m_Title.c_str()));
// // create buffer for receiving selected folder
// // initialize with MAX_PATH length and content is filled with zero.
// std::wstring path_receiver(MAX_PATH, L'\0');
// // prepare folder foalog struct
// BROWSEINFOW dialog_param = { 0 };
// dialog_param.hwndOwner = params.m_Owner;
// dialog_param.pidlRoot = nullptr;
// dialog_param.pszDisplayName = path_receiver.data();
// dialog_param.lpszTitle = w_title.c_str();
// dialog_param.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI;
// dialog_param.lpfn = nullptr;
// // call browser
// PIDLIST_ABSOLUTE place = SHBrowseForFolderW(&dialog_param);
// // if browser failed, return.
// if (place == nullptr) {
// ret.clear();
// return false;
// }
// // get path from browser result
// BOOL status;
// if (status = SHGetPathFromIDListW(place, path_receiver.data())) {
// EncodingHelper::WcharToUTF8(path_receiver.c_str(), ret);
// } else {
// ret.clear();
// }
// // clear browser result and return
// CoTaskMemFree(place);
// return status == TRUE;
//}
} }
#endif #endif

View File

@ -10,17 +10,50 @@
#include "WinImportPrefix.hpp" #include "WinImportPrefix.hpp"
#include <Windows.h> #include <Windows.h>
#include <shlobj_core.h> #include <shlobj_core.h>
#include <commdlg.h>
#include "WinImportSuffix.hpp" #include "WinImportSuffix.hpp"
namespace YYCC::DialogHelper { namespace YYCC::DialogHelper {
#pragma region COM Pointer Management
class ComPtrDeleter {
public:
ComPtrDeleter() {}
void operator() (IUnknown* com_ptr) {
if (com_ptr != nullptr) {
com_ptr->Release();
}
}
};
using SmartIFileDialog = std::unique_ptr<IFileDialog, ComPtrDeleter>;
using SmartIFileOpenDialog = std::unique_ptr<IFileOpenDialog, ComPtrDeleter>;
using SmartIShellItem = std::unique_ptr<IShellItem, ComPtrDeleter>;
using SmartIShellItemArray = std::unique_ptr<IShellItemArray, ComPtrDeleter>;
using SmartIShellFolder = std::unique_ptr<IShellFolder, ComPtrDeleter>;
template<typename _Ty>
class CoTaskMemDeleter {
public:
CoTaskMemDeleter() {}
void operator() (_Ty* com_ptr) {
if (com_ptr != nullptr) {
CoTaskMemFree(com_ptr);
}
}
};
using SmartLPWSTR = std::unique_ptr<WCHAR, CoTaskMemDeleter<WCHAR>>;
#pragma endregion
/** /**
* @brief The class represent the file types region in file dialog * @brief The class represent the file types region in file dialog
* @details THis class is specific for Windows use, not user oriented. * @details THis class is specific for Windows use, not user oriented.
*/ */
class WinFileFilters { class WinFileFilters {
friend class FileFilters; friend class FileFilters;
friend class WinFileDialog;
public: public:
WinFileFilters() : m_WinFilters(), m_WinDataStruct(nullptr) {} WinFileFilters() : m_WinFilters(), m_WinDataStruct(nullptr) {}
@ -38,12 +71,17 @@ namespace YYCC::DialogHelper {
std::vector<WinFilterPair> m_WinFilters; std::vector<WinFilterPair> m_WinFilters;
std::unique_ptr<COMDLG_FILTERSPEC[]> m_WinDataStruct; std::unique_ptr<COMDLG_FILTERSPEC[]> m_WinDataStruct;
void Clear() {
m_WinDataStruct.reset();
m_WinFilters.clear();
}
}; };
/** /**
* @brief The class represent the file types region in file dialog. * @brief The class represent the file types region in file dialog.
* @details THis class is user oriented. User can use function manipulate file types * @details This class is user oriented. User can use function manipulate file types
* and final fialog function will produce Windows-understood data struct from this. * and final generation function will produce Windows-understood data struct from this.
*/ */
class FileFilters { class FileFilters {
public: public:
@ -86,46 +124,69 @@ namespace YYCC::DialogHelper {
std::vector<FilterPair> m_Filters; std::vector<FilterPair> m_Filters;
}; };
/**
* @brief The class represent the file dialog
* @details THis class is specific for Windows use, not user oriented.
*/
class WinFileDialog { class WinFileDialog {
friend class FileDialog; friend class FileDialog;
public: public:
WinFileDialog() : WinFileDialog() :
m_WinOwner(NULL), m_WinOwner(NULL),
m_WinFileTypes(), m_WinDefaultFileTypeIndex(0u), m_WinFileTypes(), m_WinDefaultFileTypeIndex(0u),
m_HasTitle(false), m_HasInitFileName(false), m_WinTitle(), m_WinInitFileName(), m_HasTitle(false), m_HasInitFileName(false), m_WinTitle(), m_WinInitFileName(),
m_WinInitDirectory(nullptr) m_WinInitDirectory(nullptr) {}
{}
bool HasOwner() const { return m_WinOwner != NULL; } bool HasOwner() const { return m_WinOwner != NULL; }
HWND GetOwner() const { return m_WinOwner; } HWND GetOwner() const { return m_WinOwner; }
bool HasTitle() const { return !m_WinTitle.empty(); } const WinFileFilters& GetFileTypes() const { return m_WinFileTypes; }
UINT GetDefaultFileTypeIndex() const { return m_WinDefaultFileTypeIndex; }
bool HasTitle() const { return m_HasTitle; }
const wchar_t* GetTitle() const { return m_WinTitle.c_str(); } const wchar_t* GetTitle() const { return m_WinTitle.c_str(); }
bool HasInitFileName() const { return !m_WinInitFileName.empty(); } bool HasInitFileName() const { return m_HasInitFileName; }
const wchar_t* GetInitFileName() const { return m_WinInitFileName.c_str(); } 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(); } bool HasInitDirectory() const { return m_WinInitDirectory.get() != nullptr; }
IShellItem* GetInitDirectory() const { return m_WinInitDirectory.get(); }
protected: protected:
HWND m_WinOwner; HWND m_WinOwner;
WinFileFilters m_WinFileTypes; WinFileFilters m_WinFileTypes;
/**
* @brief The default selected file type in dialog
* @remarks This is 1-based index according to Windows specification.
*/
UINT m_WinDefaultFileTypeIndex; UINT m_WinDefaultFileTypeIndex;
bool m_HasTitle, m_HasInitFileName; bool m_HasTitle, m_HasInitFileName;
std::wstring m_WinTitle, m_WinInitFileName; std::wstring m_WinTitle, m_WinInitFileName;
SmartIShellItem m_WinInitDirectory;
using SmartShellItem = std::unique_ptr<IShellItem, std::function<void(IShellItem*)>>; void Clear() {
SmartShellItem m_WinInitDirectory; m_WinOwner = nullptr;
m_WinFileTypes.Clear();
m_WinDefaultFileTypeIndex = 0u;
m_HasTitle = m_HasInitFileName = false;
m_WinTitle.clear();
m_WinInitFileName.clear();
m_WinInitDirectory.reset();
}
}; };
/**
* @brief The class represent the file dialog.
* @details This class is user oriented. User can use function manipulate file dialog properties
* and final generation function will produce Windows-understood data struct from this.
*/
class FileDialog { class FileDialog {
public: public:
FileDialog() : FileDialog() :
m_Owner(NULL), m_Title(), m_Owner(NULL),
m_FileTypes(), m_FileTypes(),
m_DefaultFileTypeIndex(0u), m_DefaultFileTypeIndex(0u),
m_InitFileName(), m_InitDirectory() {} m_Title(), m_InitFileName(), m_InitDirectory(),
m_HasTitle(false), m_HasInitFileName(false), m_HasInitDirectory(false) {}
void SetOwner(HWND owner) { m_Owner = owner; } void SetOwner(HWND owner) { m_Owner = owner; }
void SetTitle(const char* title) { void SetTitle(const char* title) {
@ -135,7 +196,7 @@ namespace YYCC::DialogHelper {
FileFilters& ConfigreFileTypes() { FileFilters& ConfigreFileTypes() {
return m_FileTypes; return m_FileTypes;
} }
void SetDefaultFileTypeIndex(UINT idx) { m_DefaultFileTypeIndex = idx; } void SetDefaultFileTypeIndex(size_t idx) { m_DefaultFileTypeIndex = idx; }
void SetInitFileName(const char* init_filename) { void SetInitFileName(const char* init_filename) {
if (m_HasInitFileName = init_filename != nullptr) if (m_HasInitFileName = init_filename != nullptr)
m_InitFileName = init_filename; m_InitFileName = init_filename;
@ -145,21 +206,29 @@ namespace YYCC::DialogHelper {
m_InitDirectory = init_dir; m_InitDirectory = init_dir;
} }
void Clear() {
m_Owner = nullptr;
m_HasTitle = m_HasInitFileName = m_HasInitDirectory = false;
m_Title.clear();
m_InitFileName.clear();
m_InitDirectory.clear();
m_FileTypes.Clear();
m_DefaultFileTypeIndex = 0u;
}
bool Generate(WinFileDialog& win_result) const; bool Generate(WinFileDialog& win_result) const;
protected: protected:
HWND m_Owner; HWND m_Owner;
bool m_HasTitle, m_HasInitFileName, m_HasInitDirectory; bool m_HasTitle, m_HasInitFileName, m_HasInitDirectory;
std::string m_Title, m_InitFileName, m_InitDirectory; std::string m_Title, m_InitFileName, m_InitDirectory;
FileFilters m_FileTypes; FileFilters m_FileTypes;
/** /**
* @brief The default file type selected in dialog * @brief The default selected file type in dialog
* @remarks Although Windows notice that this is a 1-based index, * @remarks Although Windows notice that this is a 1-based index,
* but for universal experience, we order this is 0-based index. * but for universal experience, we order this is 0-based index.
*/ */
UINT m_DefaultFileTypeIndex; size_t m_DefaultFileTypeIndex;
}; };
bool OpenFileDialog(const FileDialog& params, std::string& ret); bool OpenFileDialog(const FileDialog& params, std::string& ret);

View File

@ -53,15 +53,33 @@ namespace Testbench {
} }
static void DialogTestbench() { static void DialogTestbench() {
YYCC::DialogHelper::FileFilters test; std::string ret;
test.Add("Microsoft Word (*.docx; *.doc)", {"*.docx", "*.doc"}); std::vector<std::string> rets;
test.Add("Microsoft Excel (*.xlsx; *.xls)", {"*.xlsx", "*.xls"});
test.Add("Microsoft PowerPoint (*.pptx; *.ppt)", {"*.pptx", "*.ppt"});
test.Add("Text File (*.*)", {"*.txt"});
test.Add("All Files (*.*)", {"*.*"});
YYCC::DialogHelper::WinFileFilters win_file_filters; YYCC::DialogHelper::FileDialog params;
bool ret = test.Generate(win_file_filters); auto& filters = params.ConfigreFileTypes();
filters.Add("Microsoft Word (*.docx; *.doc)", {"*.docx", "*.doc"});
filters.Add("Microsoft Excel (*.xlsx; *.xls)", {"*.xlsx", "*.xls"});
filters.Add("Microsoft PowerPoint (*.pptx; *.ppt)", {"*.pptx", "*.ppt"});
filters.Add("Text File (*.txt)", {"*.txt"});
filters.Add("All Files (*.*)", {"*.*"});
params.SetDefaultFileTypeIndex(0u);
if (YYCC::DialogHelper::OpenFileDialog(params, ret)) {
YYCC::TerminalHelper::FPrintf(stdout, u8"Open File: %s\n", ret.c_str());
}
if (YYCC::DialogHelper::OpenMultipleFileDialog(params, rets)) {
YYCC::TerminalHelper::FPuts(u8"Open Multiple Files:\n", stdout);
for (const auto& item : rets) {
YYCC::TerminalHelper::FPrintf(stdout, u8"\t%s\n", item.c_str());
}
}
if (YYCC::DialogHelper::SaveFileDialog(params, ret)) {
YYCC::TerminalHelper::FPrintf(stdout, u8"Save File: %s\n", ret.c_str());
}
params.Clear();
if (YYCC::DialogHelper::OpenFolderDialog(params, ret)) {
YYCC::TerminalHelper::FPrintf(stdout, u8"Open Folder: %s\n", ret.c_str());
}
} }
} }