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 {
|
||||
|
||||
#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
|
||||
|
||||
bool FileFilters::Add(const char* filter_name, std::initializer_list<const char*> il) {
|
||||
|
@ -16,7 +39,8 @@ namespace YYCC::DialogHelper {
|
|||
// assign filter patterns
|
||||
FilterModes modes;
|
||||
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
|
||||
|
@ -29,8 +53,7 @@ namespace YYCC::DialogHelper {
|
|||
|
||||
bool FileFilters::Generate(WinFileFilters& win_result) const {
|
||||
// clear Windows oriented data
|
||||
win_result.m_WinDataStruct.reset();
|
||||
win_result.m_WinFilters.clear();
|
||||
win_result.Clear();
|
||||
|
||||
// build new Windows oriented string vector first
|
||||
for (const auto& it : m_Filters) {
|
||||
|
@ -72,7 +95,57 @@ namespace YYCC::DialogHelper {
|
|||
#pragma region File Dialog
|
||||
|
||||
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
|
||||
|
@ -86,7 +159,20 @@ namespace YYCC::DialogHelper {
|
|||
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>
|
||||
bool CommonFileDialog(const FileDialog& params, std::vector<std::string>& ret) {
|
||||
|
@ -117,22 +203,16 @@ namespace YYCC::DialogHelper {
|
|||
CLSCTX_INPROC_SERVER,
|
||||
IID_PPV_ARGS(&_pfd)
|
||||
);
|
||||
if (!SUCCEEDED(hr)) return false;
|
||||
if (FAILED(hr)) return false;
|
||||
// create memory-safe dialog pointer
|
||||
SmartFileDialogPtr pfd(
|
||||
_pfd,
|
||||
[](IFileDialog* instance) -> void {
|
||||
if (instance != nullptr)
|
||||
instance->Release();
|
||||
}
|
||||
);
|
||||
SmartIFileDialog pfd(_pfd);
|
||||
|
||||
// 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;
|
||||
if (FAILED(hr)) return false;
|
||||
// modify options
|
||||
switch (EDialogType) {
|
||||
// We want user only can pick file system files: FOS_FORCEFILESYSTEM.
|
||||
|
@ -162,98 +242,109 @@ namespace YYCC::DialogHelper {
|
|||
}
|
||||
// set folder dialog options
|
||||
hr = pfd->SetOptions(dwFlags);
|
||||
if (!SUCCEEDED(hr)) return false;
|
||||
if (FAILED(hr)) return false;
|
||||
|
||||
// set title
|
||||
std::wstring wtitle, winit_filename, winit_directory;
|
||||
if (params.GetTitle() != nullptr) {
|
||||
EncodingHelper::UTF8ToWchar(params.GetTitle(), wtitle);
|
||||
// build Windows used file dialog parameters
|
||||
WinFileDialog win_params;
|
||||
if (!params.Generate(win_params))
|
||||
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
|
||||
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;
|
||||
const auto& file_filters = win_params.GetFileTypes();
|
||||
hr = pfd->SetFileTypes(file_filters.GetFilterCount(), file_filters.GetFilterSpecs());
|
||||
if (FAILED(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;
|
||||
hr = pfd->SetFileTypeIndex(win_params.GetDefaultFileTypeIndex());
|
||||
if (FAILED(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;
|
||||
// show the dialog
|
||||
hr = pfd->Show(win_params.HasOwner() ? win_params.GetOwner() : nullptr);
|
||||
if (FAILED(hr)) return false;
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// obtain result when user click "OK" button.
|
||||
switch (EDialogType) {
|
||||
case CommonFileDialogType::OpenFile:
|
||||
case CommonFileDialogType::OpenFolder:
|
||||
case CommonFileDialogType::SaveFile:
|
||||
{
|
||||
// obtain one file entry
|
||||
IShellItem* _item;
|
||||
hr = pfd->GetResult(&_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));
|
||||
}
|
||||
break;
|
||||
case CommonFileDialogType::OpenMultipleFiles:
|
||||
{
|
||||
// try casting file dialog to file open dialog
|
||||
// Ref: https://learn.microsoft.com/en-us/windows/win32/learnwin32/asking-an-object-for-an-interface
|
||||
IFileOpenDialog* _pfod = nullptr;
|
||||
hr = pfd->QueryInterface(IID_PPV_ARGS(&_pfod));
|
||||
if (FAILED(hr)) return false;
|
||||
SmartIFileOpenDialog pfod(_pfod);
|
||||
|
||||
// obtain multiple file entires
|
||||
IShellItemArray* _items;
|
||||
hr = pfod->GetResults(&_items);
|
||||
if (FAILED(hr)) return false;
|
||||
SmartIShellItemArray result_items(_items);
|
||||
|
||||
// analyze file entries
|
||||
// get array count first
|
||||
DWORD result_items_count = 0u;
|
||||
hr = result_items->GetCount(&result_items_count);
|
||||
if (FAILED(hr)) return false;
|
||||
// iterate array
|
||||
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
|
||||
|
@ -285,159 +376,6 @@ namespace YYCC::DialogHelper {
|
|||
|
||||
#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
|
||||
|
|
|
@ -10,17 +10,50 @@
|
|||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include <shlobj_core.h>
|
||||
#include <commdlg.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
|
||||
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
|
||||
* @details THis class is specific for Windows use, not user oriented.
|
||||
*/
|
||||
class WinFileFilters {
|
||||
friend class FileFilters;
|
||||
friend class WinFileDialog;
|
||||
public:
|
||||
WinFileFilters() : m_WinFilters(), m_WinDataStruct(nullptr) {}
|
||||
|
||||
|
@ -38,12 +71,17 @@ namespace YYCC::DialogHelper {
|
|||
|
||||
std::vector<WinFilterPair> m_WinFilters;
|
||||
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.
|
||||
* @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.
|
||||
* @details This class is user oriented. User can use function manipulate file types
|
||||
* and final generation function will produce Windows-understood data struct from this.
|
||||
*/
|
||||
class FileFilters {
|
||||
public:
|
||||
|
@ -86,46 +124,69 @@ namespace YYCC::DialogHelper {
|
|||
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 {
|
||||
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)
|
||||
{}
|
||||
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 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(); }
|
||||
bool HasInitFileName() const { return !m_WinInitFileName.empty(); }
|
||||
bool HasInitFileName() const { return m_HasInitFileName; }
|
||||
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:
|
||||
HWND m_WinOwner;
|
||||
|
||||
WinFileFilters m_WinFileTypes;
|
||||
/**
|
||||
* @brief The default selected file type in dialog
|
||||
* @remarks This is 1-based index according to Windows specification.
|
||||
*/
|
||||
UINT m_WinDefaultFileTypeIndex;
|
||||
|
||||
bool m_HasTitle, m_HasInitFileName;
|
||||
std::wstring m_WinTitle, m_WinInitFileName;
|
||||
SmartIShellItem m_WinInitDirectory;
|
||||
|
||||
using SmartShellItem = std::unique_ptr<IShellItem, std::function<void(IShellItem*)>>;
|
||||
SmartShellItem m_WinInitDirectory;
|
||||
void Clear() {
|
||||
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 {
|
||||
public:
|
||||
FileDialog() :
|
||||
m_Owner(NULL), m_Title(),
|
||||
m_Owner(NULL),
|
||||
m_FileTypes(),
|
||||
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 SetTitle(const char* title) {
|
||||
|
@ -135,7 +196,7 @@ namespace YYCC::DialogHelper {
|
|||
FileFilters& ConfigreFileTypes() {
|
||||
return m_FileTypes;
|
||||
}
|
||||
void SetDefaultFileTypeIndex(UINT idx) { m_DefaultFileTypeIndex = idx; }
|
||||
void SetDefaultFileTypeIndex(size_t idx) { m_DefaultFileTypeIndex = idx; }
|
||||
void SetInitFileName(const char* init_filename) {
|
||||
if (m_HasInitFileName = init_filename != nullptr)
|
||||
m_InitFileName = init_filename;
|
||||
|
@ -145,21 +206,29 @@ namespace YYCC::DialogHelper {
|
|||
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;
|
||||
|
||||
protected:
|
||||
HWND m_Owner;
|
||||
|
||||
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
|
||||
* @brief The default selected file type 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;
|
||||
size_t m_DefaultFileTypeIndex;
|
||||
};
|
||||
|
||||
bool OpenFileDialog(const FileDialog& params, std::string& ret);
|
||||
|
|
|
@ -53,15 +53,33 @@ namespace Testbench {
|
|||
}
|
||||
|
||||
static void DialogTestbench() {
|
||||
YYCC::DialogHelper::FileFilters test;
|
||||
test.Add("Microsoft Word (*.docx; *.doc)", {"*.docx", "*.doc"});
|
||||
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 (*.*)", {"*.*"});
|
||||
std::string ret;
|
||||
std::vector<std::string> rets;
|
||||
|
||||
YYCC::DialogHelper::WinFileFilters win_file_filters;
|
||||
bool ret = test.Generate(win_file_filters);
|
||||
YYCC::DialogHelper::FileDialog params;
|
||||
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