feat: finish dialog helper.
- finish dialof helper. - dialog helper still nned some annotation.
This commit is contained in:
parent
a9059013f0
commit
cebe2f004d
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user