refactor: migrate windows specific content.
- move com, dialog and winfct function into new place. - add testbench for com and winfct. - dialog now still not working.
This commit is contained in:
@ -15,6 +15,9 @@ PRIVATE
|
||||
yycc/string/op.cpp
|
||||
yycc/patch/fopen.cpp
|
||||
yycc/rust/panic.cpp
|
||||
yycc/windows/com.cpp
|
||||
#yycc/windows/dialog.cpp
|
||||
yycc/windows/winfct.cpp
|
||||
yycc/encoding/stl.cpp
|
||||
yycc/encoding/windows.cpp
|
||||
yycc/encoding/iconv.cpp
|
||||
@ -52,6 +55,9 @@ FILES
|
||||
yycc/rust/result.hpp
|
||||
yycc/windows/import_guard_head.hpp
|
||||
yycc/windows/import_guard_tail.hpp
|
||||
yycc/windows/com.hpp
|
||||
#yycc/windows/dialog.hpp
|
||||
yycc/windows/winfct.hpp
|
||||
yycc/constraint.hpp
|
||||
yycc/constraint/builder.hpp
|
||||
yycc/encoding/stl.hpp
|
||||
@ -115,7 +121,8 @@ PUBLIC
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/utf-8>
|
||||
# Order preprocessor conformance mode (fix __VA_OPT__ error in MSVC)
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/Zc:preprocessor>
|
||||
|
||||
# Resolve MSVC __cplusplus macro value error.
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/Zc:__cplusplus>
|
||||
)
|
||||
|
||||
# Fix GCC std::stacktrace link error
|
||||
|
@ -1,48 +0,0 @@
|
||||
#include "COMHelper.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
namespace YYCC::COMHelper {
|
||||
|
||||
/**
|
||||
* @brief The guard for initialize COM environment.
|
||||
* @details This class will try initializing COM environment by calling CoInitialize when constructing,
|
||||
* and it also will try uninitializing COM environment when destructing.
|
||||
* If initialization failed, uninitialization will not be executed.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsInitialized() const {
|
||||
return m_HasInit;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool m_HasInit;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The instance of COM environment guard.
|
||||
* @details Dialog related function need COM environment,
|
||||
* so we need initializing COM environment when loading this module,
|
||||
* and uninitializing COM environment when we no longer use this module.
|
||||
* So we use a static instance in here.
|
||||
* And make it be const so no one can change it.
|
||||
*/
|
||||
static const ComGuard c_ComGuard {};
|
||||
|
||||
bool IsInitialized() {
|
||||
return c_ComGuard.IsInitialized();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,71 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include <shlobj_core.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
|
||||
/**
|
||||
* @brief Windows COM related types and checker.
|
||||
* @details
|
||||
* This namespace is Windows specific.
|
||||
* In other platforms, this whole namespace will be unavailable.
|
||||
*
|
||||
* See also \ref com_helper.
|
||||
*/
|
||||
namespace YYCC::COMHelper {
|
||||
|
||||
/// @brief C++ standard deleter for every COM interfaces inheriting IUnknown.
|
||||
class ComPtrDeleter {
|
||||
public:
|
||||
ComPtrDeleter() {}
|
||||
void operator() (IUnknown* com_ptr) {
|
||||
if (com_ptr != nullptr) {
|
||||
com_ptr->Release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Smart unique pointer of \c IFileDialog
|
||||
using SmartIFileDialog = std::unique_ptr<IFileDialog, ComPtrDeleter>;
|
||||
/// @brief Smart unique pointer of \c IFileOpenDialog
|
||||
using SmartIFileOpenDialog = std::unique_ptr<IFileOpenDialog, ComPtrDeleter>;
|
||||
/// @brief Smart unique pointer of \c IShellItem
|
||||
using SmartIShellItem = std::unique_ptr<IShellItem, ComPtrDeleter>;
|
||||
/// @brief Smart unique pointer of \c IShellItemArray
|
||||
using SmartIShellItemArray = std::unique_ptr<IShellItemArray, ComPtrDeleter>;
|
||||
/// @brief Smart unique pointer of \c IShellFolder
|
||||
using SmartIShellFolder = std::unique_ptr<IShellFolder, ComPtrDeleter>;
|
||||
|
||||
/// @brief C++ standard deleter for almost raw pointer used in COM which need to be free by CoTaskMemFree()
|
||||
class CoTaskMemDeleter {
|
||||
public:
|
||||
CoTaskMemDeleter() {}
|
||||
void operator() (void* com_ptr) {
|
||||
if (com_ptr != nullptr) {
|
||||
CoTaskMemFree(com_ptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Smart unique pointer of COM created \c WCHAR sequence.
|
||||
using SmartLPWSTR = std::unique_ptr<std::remove_pointer_t<LPWSTR>, CoTaskMemDeleter>;
|
||||
|
||||
/**
|
||||
* @brief Check whether COM environment has been initialized.
|
||||
* @return True if it is, otherwise false.
|
||||
* @remarks
|
||||
* This function will call corresponding function of COM Guard.
|
||||
* Do not remove this function and you must preserve at least one reference to this function in final program.
|
||||
* Some compiler will try to drop COM Guard in final program if no reference to it and it will cause the initialization of COM environment failed.
|
||||
* This is the reason why I order you do the things said above.
|
||||
*/
|
||||
bool IsInitialized();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -143,16 +143,17 @@ namespace yycc::encoding::windows {
|
||||
// Due to the shitty design of mbrtoc16, it forcely assume that passed string is null-terminated.
|
||||
// And the third argument should >= 1.
|
||||
// However, our given string is string view which do not have null-terminated guaranteen.
|
||||
//
|
||||
//
|
||||
// So we manually check whether we have reach the tail of string and simulate a fake null terminal.
|
||||
// If string is still processing, we pass given string.
|
||||
// If we have reach the tail of string, we pass our homemade NULL_TERMINAL to this function to make it works normally.
|
||||
//
|
||||
//
|
||||
// This is a stupid polyfill, however, it I do not do this,
|
||||
// there is a bug that the second part of surrogate pair will be dropped in final string,
|
||||
// if there is a Unicode character located at the tail of string which need surrogate pair to be presented.
|
||||
static const char NULL_TERMINAL = '\0';
|
||||
while (size_t rc = std::mbrtoc16(&c16, (ptr < end ? ptr : &NULL_TERMINAL), (ptr < end ? end - ptr : sizeof(NULL_TERMINAL)), &state)) {
|
||||
while (
|
||||
size_t rc = std::mbrtoc16(&c16, (ptr < end ? ptr : &NULL_TERMINAL), (ptr < end ? end - ptr : sizeof(NULL_TERMINAL)), &state)) {
|
||||
if (rc == (size_t) -1) return std::unexpected(ConvError::EncodeUtf8);
|
||||
else if (rc == (size_t) -2) return std::unexpected(ConvError::IncompleteUtf8);
|
||||
else if (rc == (size_t) -3) dst.push_back(c16); // from earlier surrogate pair
|
||||
@ -174,7 +175,7 @@ namespace yycc::encoding::windows {
|
||||
size_t rc = 1; // Assign it to ONE to avoid mismatching surrogate pair checker when string is empty.
|
||||
for (char16_t c : src) {
|
||||
rc = std::c16rtomb(mbout, c, &state);
|
||||
|
||||
|
||||
if (rc == (size_t) -1) return std::unexpected(ConvError::InvalidUtf16);
|
||||
else dst.append(reinterpret_cast<char8_t*>(mbout), rc);
|
||||
}
|
||||
|
46
src/yycc/windows/com.cpp
Normal file
46
src/yycc/windows/com.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
#include "com.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
|
||||
namespace yycc::windows::com {
|
||||
|
||||
/**
|
||||
* @brief The guard for initialize COM environment.
|
||||
* @details This class will try initializing COM environment by calling CoInitialize when constructing,
|
||||
* and it also will try uninitializing COM environment when destructing.
|
||||
* If initialization failed, uninitialization will not be executed.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsInitialized() const { return m_HasInit; }
|
||||
|
||||
protected:
|
||||
bool m_HasInit;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The instance of COM environment guard.
|
||||
* @details Dialog related function need COM environment,
|
||||
* so we need initializing COM environment when loading this module,
|
||||
* and uninitializing COM environment when we no longer use this module.
|
||||
* So we use a static instance in here.
|
||||
* And make it be const so no one can change it.
|
||||
*/
|
||||
static const ComGuard COM_GUARD{};
|
||||
|
||||
bool is_initialized() {
|
||||
return COM_GUARD.IsInitialized();
|
||||
}
|
||||
|
||||
} // namespace yycc::windows::com
|
||||
|
||||
#endif
|
70
src/yycc/windows/com.hpp
Normal file
70
src/yycc/windows/com.hpp
Normal file
@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
#include "../macro/os_detector.hpp"
|
||||
#include "../macro/stl_detector.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "import_guard_head.hpp"
|
||||
#include <Windows.h>
|
||||
#include <shlobj_core.h>
|
||||
#include "import_guard_tail.hpp"
|
||||
|
||||
/**
|
||||
* @brief Windows COM related types and checker.
|
||||
* @details
|
||||
* This namespace is Windows specific.
|
||||
* In other platforms, this whole namespace will be unavailable.
|
||||
*/
|
||||
namespace yycc::windows::com {
|
||||
|
||||
/// @brief C++ standard deleter for every COM interfaces inheriting IUnknown.
|
||||
class ComPtrDeleter {
|
||||
public:
|
||||
ComPtrDeleter() {}
|
||||
void operator()(IUnknown* com_ptr) {
|
||||
if (com_ptr != nullptr) {
|
||||
com_ptr->Release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Smart unique pointer of \c IFileDialog
|
||||
using SmartIFileDialog = std::unique_ptr<IFileDialog, ComPtrDeleter>;
|
||||
/// @brief Smart unique pointer of \c IFileOpenDialog
|
||||
using SmartIFileOpenDialog = std::unique_ptr<IFileOpenDialog, ComPtrDeleter>;
|
||||
/// @brief Smart unique pointer of \c IShellItem
|
||||
using SmartIShellItem = std::unique_ptr<IShellItem, ComPtrDeleter>;
|
||||
/// @brief Smart unique pointer of \c IShellItemArray
|
||||
using SmartIShellItemArray = std::unique_ptr<IShellItemArray, ComPtrDeleter>;
|
||||
/// @brief Smart unique pointer of \c IShellFolder
|
||||
using SmartIShellFolder = std::unique_ptr<IShellFolder, ComPtrDeleter>;
|
||||
|
||||
/// @brief C++ standard deleter for almost raw pointer used in COM which need to be free by CoTaskMemFree()
|
||||
class CoTaskMemDeleter {
|
||||
public:
|
||||
CoTaskMemDeleter() {}
|
||||
void operator()(void* com_ptr) {
|
||||
if (com_ptr != nullptr) {
|
||||
CoTaskMemFree(com_ptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Smart unique pointer of COM created \c WCHAR sequence.
|
||||
using SmartLPWSTR = std::unique_ptr<std::remove_pointer_t<LPWSTR>, CoTaskMemDeleter>;
|
||||
|
||||
/**
|
||||
* @brief Check whether COM environment has been initialized.
|
||||
* @return True if it is, otherwise false.
|
||||
* @remarks
|
||||
* This function will call corresponding function of COM Guard.
|
||||
* Do not remove this function and you must preserve at least one reference to this function in final program.
|
||||
* Some compiler will try to drop COM Guard in final program if no reference to it and it will cause the initialization of COM environment failed.
|
||||
* This is the reason why I order you do the things said above.
|
||||
*/
|
||||
bool is_initialized();
|
||||
|
||||
} // namespace yycc::windows::com
|
||||
|
||||
#endif
|
348
src/yycc/windows/dialog.cpp
Normal file
348
src/yycc/windows/dialog.cpp
Normal file
@ -0,0 +1,348 @@
|
||||
#include "dialog.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
|
||||
namespace yycc::windows::dialog {
|
||||
|
||||
#pragma region FileFilters
|
||||
|
||||
bool FileFilters::Add(const yycc_char8_t* filter_name, std::initializer_list<const yycc_char8_t*> il) {
|
||||
// assign filter name
|
||||
if (filter_name == nullptr) return false;
|
||||
FilterName name(filter_name);
|
||||
|
||||
// assign filter patterns
|
||||
FilterModes modes;
|
||||
for (const yycc_char8_t* pattern : il) {
|
||||
if (pattern != nullptr) modes.emplace_back(yycc_u8string(pattern));
|
||||
}
|
||||
|
||||
// check filter patterns
|
||||
if (modes.empty()) return false;
|
||||
|
||||
// add into pairs and return
|
||||
m_Filters.emplace_back(std::make_pair(std::move(name), std::move(modes)));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileFilters::Generate(WinFileFilters& win_result) const {
|
||||
// clear Windows oriented data
|
||||
win_result.Clear();
|
||||
|
||||
// build new Windows oriented string vector first
|
||||
for (const auto& it : m_Filters) {
|
||||
// convert name to wchar
|
||||
WinFileFilters::WinFilterName name;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(it.first, name)) return false;
|
||||
|
||||
// convert pattern and join them
|
||||
const auto& filter_modes = it.second;
|
||||
yycc_u8string joined_modes(YYCC::StringHelper::Join(filter_modes.begin(), filter_modes.end(), YYCC_U8(";")));
|
||||
WinFileFilters::WinFilterModes modes;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(joined_modes, modes)) return false;
|
||||
|
||||
// append new pair
|
||||
win_result.m_WinFilters.emplace_back(std::make_pair(name, modes));
|
||||
}
|
||||
|
||||
// check filter size
|
||||
// if it overflow the maximum value, return false
|
||||
size_t count = win_result.m_WinFilters.size();
|
||||
if (count > std::numeric_limits<UINT>::max()) return false;
|
||||
|
||||
// create new win data struct
|
||||
// and assign string pointer from internal built win string vector.
|
||||
win_result.m_WinDataStruct.reset(new COMDLG_FILTERSPEC[count]);
|
||||
for (size_t i = 0u; i < count; ++i) {
|
||||
win_result.m_WinDataStruct[i].pszName = win_result.m_WinFilters[i].first.c_str();
|
||||
win_result.m_WinDataStruct[i].pszSpec = win_result.m_WinFilters[i].second.c_str();
|
||||
}
|
||||
|
||||
// everything is okey
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region File Dialog
|
||||
|
||||
bool FileDialog::Generate(WinFileDialog& win_result) const {
|
||||
// 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, win_result.m_WinTitle)) return false;
|
||||
win_result.m_HasTitle = true;
|
||||
}
|
||||
if (m_HasInitFileName) {
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(m_InitFileName, 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, 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 region Windows Dialog Code
|
||||
|
||||
enum class CommonFileDialogType { OpenFile, OpenMultipleFiles, SaveFile, OpenFolder };
|
||||
|
||||
/**
|
||||
* @brief Extract display name from given IShellItem*.
|
||||
* @param item[in] The pointer to IShellItem for extracting.
|
||||
* @param ret[out] Extracted display name container.
|
||||
* @return True if success, otherwise false.
|
||||
* @remarks This is an assist function of CommonFileDialog.
|
||||
*/
|
||||
static bool ExtractDisplayName(IShellItem* item, yycc_u8string& ret) {
|
||||
// fetch display name from IShellItem*
|
||||
LPWSTR _name;
|
||||
HRESULT hr = item->GetDisplayName(SIGDN_FILESYSPATH, &_name);
|
||||
if (FAILED(hr)) return false;
|
||||
COMHelper::SmartLPWSTR display_name(_name);
|
||||
|
||||
// convert result
|
||||
if (!YYCC::EncodingHelper::WcharToUTF8(display_name.get(), ret)) return false;
|
||||
|
||||
// finished
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief General file dialog.
|
||||
* @param params[in] User specified parameter controlling the behavior of this file dialog,
|
||||
* including title, file types and etc.
|
||||
* @param ret[out] The path to user selected files or folders.
|
||||
* For multiple selection, the count of items >= 1. For other scenario, the count of item is 1.
|
||||
* @return True if success, otherwise false (input parameters is wrong or user click "Cancel" in popup window).
|
||||
* @remarks This function is the real underlying function of all dialog functions.
|
||||
*/
|
||||
template<CommonFileDialogType EDialogType>
|
||||
static bool CommonFileDialog(const FileDialog& params, std::vector<yycc_u8string>& ret) {
|
||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog
|
||||
// prepare result variable
|
||||
HRESULT hr;
|
||||
|
||||
// check whether COM environment has been initialized
|
||||
if (!COMHelper::IsInitialized()) return false;
|
||||
|
||||
// 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 (FAILED(hr)) return false;
|
||||
// create memory-safe dialog pointer
|
||||
COMHelper::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 (FAILED(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 (FAILED(hr)) return false;
|
||||
|
||||
// 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
|
||||
if constexpr (EDialogType != CommonFileDialogType::OpenFolder) {
|
||||
// set file types list
|
||||
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
|
||||
hr = pfd->SetFileTypeIndex(win_params.GetDefaultFileTypeIndex());
|
||||
if (FAILED(hr)) return false;
|
||||
}
|
||||
|
||||
// show the dialog
|
||||
hr = pfd->Show(win_params.HasOwner() ? win_params.GetOwner() : nullptr);
|
||||
if (FAILED(hr)) return false;
|
||||
|
||||
// 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;
|
||||
COMHelper::SmartIShellItem result_item(_item);
|
||||
|
||||
// extract display name
|
||||
yycc_u8string 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;
|
||||
COMHelper::SmartIFileOpenDialog pfod(_pfod);
|
||||
|
||||
// obtain multiple file entires
|
||||
IShellItemArray* _items;
|
||||
hr = pfod->GetResults(&_items);
|
||||
if (FAILED(hr)) return false;
|
||||
COMHelper::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;
|
||||
COMHelper::SmartIShellItem result_item(_item);
|
||||
|
||||
// extract display name
|
||||
yycc_u8string result_name;
|
||||
if (!ExtractDisplayName(result_item.get(), result_name)) return false;
|
||||
|
||||
// append result
|
||||
ret.emplace_back(std::move(result_name));
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// everything is okey
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Wrapper Functions
|
||||
|
||||
bool OpenFileDialog(const FileDialog& params, yycc_u8string& ret) {
|
||||
std::vector<yycc_u8string> cache;
|
||||
bool isok = CommonFileDialog<CommonFileDialogType::OpenFile>(params, cache);
|
||||
if (isok) ret = cache.front();
|
||||
return isok;
|
||||
}
|
||||
bool OpenMultipleFileDialog(const FileDialog& params, std::vector<yycc_u8string>& ret) {
|
||||
return CommonFileDialog<CommonFileDialogType::OpenMultipleFiles>(params, ret);
|
||||
}
|
||||
bool SaveFileDialog(const FileDialog& params, yycc_u8string& ret) {
|
||||
std::vector<yycc_u8string> cache;
|
||||
bool isok = CommonFileDialog<CommonFileDialogType::SaveFile>(params, cache);
|
||||
if (isok) ret = cache.front();
|
||||
return isok;
|
||||
}
|
||||
|
||||
bool OpenFolderDialog(const FileDialog& params, yycc_u8string& ret) {
|
||||
std::vector<yycc_u8string> cache;
|
||||
bool isok = CommonFileDialog<CommonFileDialogType::OpenFolder>(params, cache);
|
||||
if (isok) ret = cache.front();
|
||||
return isok;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
}
|
||||
|
||||
#endif
|
304
src/yycc/windows/dialog.hpp
Normal file
304
src/yycc/windows/dialog.hpp
Normal file
@ -0,0 +1,304 @@
|
||||
#pragma once
|
||||
#include "../macro/os_detector.hpp"
|
||||
#include "../macro/stl_detector.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
|
||||
#include "../macro/class_copy_move.hpp"
|
||||
#include "com.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <initializer_list>
|
||||
|
||||
#include "import_guard_head.hpp"
|
||||
#include <Windows.h>
|
||||
#include <shlobj_core.h>
|
||||
#include "import_guard_tail.hpp"
|
||||
|
||||
/**
|
||||
* @brief The namespace providing Windows universal dialog features.
|
||||
* @details
|
||||
* This namespace only available on Windows platform.
|
||||
* See also \ref dialog_helper.
|
||||
*/
|
||||
namespace yycc::windows::dialog {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief The class representing the file types region in file dialog.
|
||||
* @details
|
||||
* This class is served for Windows used.
|
||||
* Programmer should \b not create this class manually.
|
||||
*/
|
||||
class WinFileFilters {
|
||||
friend class FileFilters;
|
||||
friend class WinFileDialog;
|
||||
|
||||
public:
|
||||
WinFileFilters() : m_WinFilters(), m_WinDataStruct(nullptr) {}
|
||||
YYCC_DELETE_COPY_MOVE(WinFileFilters)
|
||||
|
||||
/// @brief Get the count of available file filters
|
||||
UINT GetFilterCount() const { return static_cast<UINT>(m_WinFilters.size()); }
|
||||
/// @brief Get pointer to Windows used file filters declarations
|
||||
const COMDLG_FILTERSPEC* GetFilterSpecs() const { return m_WinDataStruct.get(); }
|
||||
|
||||
protected:
|
||||
using WinFilterModes = std::wstring;
|
||||
using WinFilterName = std::wstring;
|
||||
using WinFilterPair = std::pair<WinFilterName, WinFilterModes>;
|
||||
|
||||
std::vector<WinFilterPair> m_WinFilters;
|
||||
std::unique_ptr<COMDLG_FILTERSPEC[]> m_WinDataStruct;
|
||||
|
||||
/// @brief Clear all current file filters
|
||||
void Clear() {
|
||||
m_WinDataStruct.reset();
|
||||
m_WinFilters.clear();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The class representing the file types region in file dialog.
|
||||
* @details
|
||||
* This class is served for programmer using.
|
||||
* But you don't need create it on your own.
|
||||
* You can simply fetch it by FileDialog::ConfigreFileTypes ,
|
||||
* because this class is a part of FileDialog.
|
||||
*/
|
||||
class FileFilters {
|
||||
public:
|
||||
FileFilters() : m_Filters() {}
|
||||
YYCC_DELETE_COPY_MOVE(FileFilters)
|
||||
|
||||
/**
|
||||
* @brief Add a filter pair in file types list.
|
||||
* @param[in] filter_name The friendly name of the filter.
|
||||
* @param[in] il
|
||||
* A C++ initialize list containing acceptable file filter pattern.
|
||||
* Every entries must be `const yycc_char8_t*` representing a single filter pattern.
|
||||
* The list at least should have one valid pattern.
|
||||
* This function will not validate these filter patterns, so please write them carefully.
|
||||
* @return True if added success, otherwise false.
|
||||
* @remarks
|
||||
* This function allow you register multiple filter patterns for single friendly name.
|
||||
* For example: <TT>Add(u8"Microsoft Word (*.doc; *.docx)", {u8"*.doc", u8"*.docx"})</TT>
|
||||
*/
|
||||
bool Add(const yycc_char8_t* filter_name, std::initializer_list<const yycc_char8_t*> il);
|
||||
/**
|
||||
* @brief Get the count of added filter pairs.
|
||||
* @return The count of already added filter pairs.
|
||||
*/
|
||||
size_t Count() const { return m_Filters.size(); }
|
||||
|
||||
/// @brief Clear filter pairs for following re-use.
|
||||
void Clear() { m_Filters.clear(); }
|
||||
|
||||
/**
|
||||
* @brief Generate Windows dialog system used data struct.
|
||||
* @param[out] win_result The class receiving the generated filter data struct.
|
||||
* @return True if generation success, otherwise false.
|
||||
* @remarks
|
||||
* Programmer should not call this function,
|
||||
* this function is used as YYCC internal code.
|
||||
*/
|
||||
bool Generate(WinFileFilters& win_result) const;
|
||||
|
||||
protected:
|
||||
using FilterModes = std::vector<std::u8string>;
|
||||
using FilterName = std::u8string;
|
||||
using FilterPair = std::pair<FilterName, FilterModes>;
|
||||
|
||||
std::vector<FilterPair> m_Filters;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief The class representing the file dialog.
|
||||
* @details
|
||||
* This class is served for Windows used.
|
||||
* Programmer should \b not create this class manually.
|
||||
*/
|
||||
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) {}
|
||||
YYCC_DELETE_COPY_MOVE(WinFileDialog)
|
||||
|
||||
/// @brief Get whether this dialog has owner.
|
||||
bool HasOwner() const { return m_WinOwner != NULL; }
|
||||
/// @brief Get the \c HWND of dialog owner.
|
||||
HWND GetOwner() const { return m_WinOwner; }
|
||||
|
||||
/// @brief Get the struct holding Windows used file filters data.
|
||||
const WinFileFilters& GetFileTypes() const { return m_WinFileTypes; }
|
||||
/// @brief Get the index of default selected file filter.
|
||||
UINT GetDefaultFileTypeIndex() const { return m_WinDefaultFileTypeIndex; }
|
||||
|
||||
/// @brief Get whether dialog has custom title.
|
||||
bool HasTitle() const { return m_HasTitle; }
|
||||
/// @brief Get custom title of dialog.
|
||||
const wchar_t* GetTitle() const { return m_WinTitle.c_str(); }
|
||||
/// @brief Get whether dialog has custom initial file name.
|
||||
bool HasInitFileName() const { return m_HasInitFileName; }
|
||||
/// @brief Get custom initial file name of dialog
|
||||
const wchar_t* GetInitFileName() const { return m_WinInitFileName.c_str(); }
|
||||
|
||||
/// @brief Get whether dialog has custom initial directory.
|
||||
bool HasInitDirectory() const { return m_WinInitDirectory.get() != nullptr; }
|
||||
/// @brief Get custom initial directory of dialog.
|
||||
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.
|
||||
* In other words, when generating this struct from FileDialog to this struct this field should plus 1.
|
||||
* Because the same field located in FileDialog is 0-based index.
|
||||
*/
|
||||
UINT m_WinDefaultFileTypeIndex;
|
||||
bool m_HasTitle, m_HasInitFileName;
|
||||
std::wstring m_WinTitle, m_WinInitFileName;
|
||||
com::SmartIShellItem m_WinInitDirectory;
|
||||
|
||||
/// @brief Clear all data and reset them to default value.
|
||||
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 representing the file dialog.
|
||||
* @details
|
||||
* This class is served for programming using to describe every aspectes of the dialog.
|
||||
* For how to use this struct, see \ref dialog_helper.
|
||||
*/
|
||||
class FileDialog {
|
||||
public:
|
||||
FileDialog() :
|
||||
m_Owner(NULL), m_FileTypes(), m_DefaultFileTypeIndex(0u), m_Title(), m_InitFileName(), m_InitDirectory(), m_HasTitle(false),
|
||||
m_HasInitFileName(false), m_HasInitDirectory(false) {}
|
||||
YYCC_DELETE_COPY_MOVE(FileDialog)
|
||||
|
||||
/**
|
||||
* @brief Set the owner of dialog.
|
||||
* @param[in] owner The \c HWND pointing to the owner of dialog, or NULL to remove owner.
|
||||
*/
|
||||
void SetOwner(HWND owner) { m_Owner = owner; }
|
||||
/**
|
||||
* @brief Set custom title of dialog
|
||||
* @param[in] title The string pointer to custom title, or nullptr to remove it.
|
||||
*/
|
||||
void SetTitle(const yycc_char8_t* title) {
|
||||
if (m_HasTitle = title != nullptr) m_Title = title;
|
||||
}
|
||||
/**
|
||||
* @brief Fetch the struct describing file filters for future configuration.
|
||||
* @return The reference to the struct describing file filters.
|
||||
*/
|
||||
FileFilters& ConfigreFileTypes() { return m_FileTypes; }
|
||||
/**
|
||||
* @brief Set the index of default selected file filter.
|
||||
* @param[in] idx
|
||||
* The index to default one.
|
||||
* This must be a valid index in file filters.
|
||||
*/
|
||||
void SetDefaultFileTypeIndex(size_t idx) { m_DefaultFileTypeIndex = idx; }
|
||||
/**
|
||||
* @brief Set the initial file name of dialog
|
||||
* @details If set, the file name will always be same one when opening dialog.
|
||||
* @param[in] init_filename String pointer to initial file name, or nullptr to remove it.
|
||||
*/
|
||||
void SetInitFileName(const yycc_char8_t* init_filename) {
|
||||
if (m_HasInitFileName = init_filename != nullptr) m_InitFileName = init_filename;
|
||||
}
|
||||
/**
|
||||
* @brief Set the initial directory of dialog
|
||||
* @details If set, the opended directory will always be the same one when opening dialog
|
||||
* @param[in] init_dir
|
||||
* String pointer to initial directory.
|
||||
* Invalid path or nullptr will remove this feature.
|
||||
*/
|
||||
void SetInitDirectory(const yycc_char8_t* init_dir) {
|
||||
if (m_HasInitDirectory = init_dir != nullptr) m_InitDirectory = init_dir;
|
||||
}
|
||||
|
||||
/// @brief Clear file dialog parameters for following re-use.
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate Windows dialog system used data struct.
|
||||
* @param[out] win_result The class receiving the generated filter data struct.
|
||||
* @return True if generation is success, otherwise false.
|
||||
* @remarks
|
||||
* Programmer should not call this function.
|
||||
* This function is used as YYCC internal code.
|
||||
*/
|
||||
bool Generate(WinFileDialog& win_result) const;
|
||||
|
||||
protected:
|
||||
HWND m_Owner;
|
||||
bool m_HasTitle, m_HasInitFileName, m_HasInitDirectory;
|
||||
std::u8string m_Title, m_InitFileName, m_InitDirectory;
|
||||
FileFilters m_FileTypes;
|
||||
/**
|
||||
* @brief The default selected file type in dialog
|
||||
* @remarks
|
||||
* The index Windows used is 1-based index.
|
||||
* But for universal experience, we order this is 0-based index.
|
||||
* And do convertion when generating Windows used struct.
|
||||
*/
|
||||
size_t m_DefaultFileTypeIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Open the dialog which order user select single file to open.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @param[out] ret Full path to user selected file.
|
||||
* @return False if user calcel the operation or something went wrong, otherwise true.
|
||||
*/
|
||||
bool OpenFileDialog(const FileDialog& params, std::u8string& ret);
|
||||
/**
|
||||
* @brief Open the dialog which order user select multiple file to open.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @param[out] ret The list of full path of user selected files.
|
||||
* @return False if user calcel the operation or something went wrong, otherwise true.
|
||||
*/
|
||||
bool OpenMultipleFileDialog(const FileDialog& params, std::vector<std::u8string>& ret);
|
||||
/**
|
||||
* @brief Open the dialog which order user select single file to save.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @param[out] ret Full path to user selected file.
|
||||
* @return False if user calcel the operation or something went wrong, otherwise true.
|
||||
*/
|
||||
bool SaveFileDialog(const FileDialog& params, std::u8string& ret);
|
||||
/**
|
||||
* @brief Open the dialog which order user select single directory to open.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @param[out] ret Full path to user selected directory.
|
||||
* @return False if user calcel the operation or something went wrong, otherwise true.
|
||||
*/
|
||||
bool OpenFolderDialog(const FileDialog& params, std::u8string& ret);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
158
src/yycc/windows/winfct.cpp
Normal file
158
src/yycc/windows/winfct.cpp
Normal file
@ -0,0 +1,158 @@
|
||||
#include "winfct.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#include "../encoding/windows.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
#if defined(YYCC_STL_MSSTL)
|
||||
#include "com.hpp"
|
||||
#endif
|
||||
|
||||
#define ENC ::yycc::encoding::windows
|
||||
#define COM ::yycc::windows::com
|
||||
|
||||
namespace yycc::windows::winfct {
|
||||
|
||||
WinFctResult<HMODULE> get_current_module() {
|
||||
// Reference: https://stackoverflow.com/questions/557081/how-do-i-get-the-hmodule-for-the-currently-executing-code
|
||||
HMODULE hModule = NULL;
|
||||
BOOL rv = ::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
|
||||
| GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, // get address but do not inc ref counter.
|
||||
(LPCWSTR) get_current_module,
|
||||
&hModule);
|
||||
if (rv) return hModule;
|
||||
else return std::unexpected(WinFctError::Backend);
|
||||
}
|
||||
|
||||
WinFctResult<std::u8string> get_temp_directory() {
|
||||
// create wchar buffer for receiving the temp path.
|
||||
std::wstring wpath(MAX_PATH + 1u, L'\0');
|
||||
DWORD expected_size;
|
||||
|
||||
// fetch temp folder
|
||||
while (true) {
|
||||
if ((expected_size = ::GetTempPathW(static_cast<DWORD>(wpath.size()), wpath.data())) == 0) {
|
||||
// failed, return
|
||||
return std::unexpected(WinFctError::Backend);
|
||||
}
|
||||
|
||||
if (expected_size > static_cast<DWORD>(wpath.size())) {
|
||||
// buffer is too short, need enlarge and do fetching again
|
||||
wpath.resize(expected_size);
|
||||
} else {
|
||||
// ok. shrink to real length. break while
|
||||
wpath.resize(expected_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// convert to utf8 and return
|
||||
return ENC::to_utf8(wpath).transform_error([](auto err) { return WinFctError::Encoding; });
|
||||
}
|
||||
|
||||
WinFctResult<std::u8string> get_module_file_name(HINSTANCE hModule) {
|
||||
// create wchar buffer for receiving the temp path.
|
||||
std::wstring wpath(MAX_PATH + 1u, L'\0');
|
||||
DWORD copied_size;
|
||||
|
||||
while (true) {
|
||||
if ((copied_size = ::GetModuleFileNameW(hModule, wpath.data(), static_cast<DWORD>(wpath.size()))) == 0) {
|
||||
// failed, return
|
||||
return std::unexpected(WinFctError::Backend);
|
||||
}
|
||||
|
||||
// check insufficient buffer
|
||||
if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
|
||||
// buffer is not enough, enlarge it and try again.
|
||||
wpath.resize(wpath.size() + MAX_PATH);
|
||||
} else {
|
||||
// ok. shrink to real length. break while
|
||||
wpath.resize(copied_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// convert to utf8 and return
|
||||
return ENC::to_utf8(wpath).transform_error([](auto err) { return WinFctError::Encoding; });
|
||||
}
|
||||
|
||||
bool is_valid_code_page(UINT code_page) {
|
||||
CPINFOEXW cpinfo;
|
||||
return ::GetCPInfoExW(code_page, 0, &cpinfo);
|
||||
}
|
||||
|
||||
WinFctResult<void> copy_file(const std::u8string_view& lpExistingFileName, const std::u8string_view& lpNewFileName, BOOL bFailIfExists) {
|
||||
auto wExistingFileName = ENC::to_wchar(lpExistingFileName);
|
||||
auto wNewFileName = ENC::to_wchar(lpNewFileName);
|
||||
if (!(wExistingFileName.has_value() && wNewFileName.has_value())) {
|
||||
return std::unexpected(WinFctError::Encoding);
|
||||
}
|
||||
|
||||
if (!::CopyFileW(wExistingFileName.value().c_str(), wNewFileName.value().c_str(), bFailIfExists)) {
|
||||
return std::unexpected(WinFctError::Backend);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
WinFctResult<void> move_file(const std::u8string_view& lpExistingFileName, const std::u8string_view& lpNewFileName) {
|
||||
auto wExistingFileName = ENC::to_wchar(lpExistingFileName);
|
||||
auto wNewFileName = ENC::to_wchar(lpNewFileName);
|
||||
if (!(wExistingFileName.has_value() && wNewFileName.has_value())) {
|
||||
return std::unexpected(WinFctError::Encoding);
|
||||
}
|
||||
|
||||
if (!::MoveFileW(wExistingFileName.value().c_str(), wNewFileName.value().c_str())) {
|
||||
return std::unexpected(WinFctError::Backend);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
WinFctResult<void> delete_file(const std::u8string_view& lpFileName) {
|
||||
auto wFileName = ENC::to_wchar(lpFileName);
|
||||
if (!wFileName.has_value()) {
|
||||
return std::unexpected(WinFctError::Encoding);
|
||||
}
|
||||
|
||||
if (!::DeleteFileW(wFileName.value().c_str())) {
|
||||
return std::unexpected(WinFctError::Backend);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
#if defined(YYCC_STL_MSSTL)
|
||||
|
||||
WinFctResult<std::u8string> get_known_path(KnownDirectory path_type) {
|
||||
// check whether com initialized
|
||||
if (!COM::is_initialized()) return std::unexpected(WinFctError::NoCom);
|
||||
|
||||
// get folder id according to type
|
||||
const KNOWNFOLDERID* pId;
|
||||
switch (path_type) {
|
||||
case KnownDirectory::LocalAppData:
|
||||
pId = &FOLDERID_LocalAppData;
|
||||
break;
|
||||
case KnownDirectory::AppData:
|
||||
pId = &FOLDERID_RoamingAppData;
|
||||
break;
|
||||
default:
|
||||
throw std::logic_error("unknow known directory type");
|
||||
}
|
||||
|
||||
// fetch path
|
||||
LPWSTR raw_known_path;
|
||||
HRESULT hr = SHGetKnownFolderPath(*pId, KF_FLAG_CREATE, NULL, &raw_known_path);
|
||||
if (FAILED(hr)) return std::unexpected(WinFctError::Backend);
|
||||
COM::SmartLPWSTR known_path(raw_known_path);
|
||||
|
||||
// convert to utf8 and return
|
||||
return ENC::to_utf8(known_path.get()).transform_error([](auto err) { return WinFctError::Encoding; });
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace yycc::windows::winfct
|
||||
|
||||
#endif
|
116
src/yycc/windows/winfct.hpp
Normal file
116
src/yycc/windows/winfct.hpp
Normal file
@ -0,0 +1,116 @@
|
||||
#pragma once
|
||||
#include "../macro/os_detector.hpp"
|
||||
#include "../macro/stl_detector.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <expected>
|
||||
|
||||
#include "import_guard_head.hpp"
|
||||
#include <Windows.h>
|
||||
#include "import_guard_tail.hpp"
|
||||
|
||||
namespace yycc::windows::winfct {
|
||||
|
||||
/// @brief All errors occur in this module.
|
||||
enum class WinFctError {
|
||||
Backend, ///< Error occurs when calling Win32 functions.
|
||||
Encoding, ///< Can not perform encoding convertion.
|
||||
NoCom, ///< No COM environment.
|
||||
};
|
||||
|
||||
/// @brief The result type used in this module.
|
||||
template<typename T>
|
||||
using WinFctResult = std::expected<T, WinFctError>;
|
||||
|
||||
/**
|
||||
* @brief Get Windows used HANDLE for current module.
|
||||
* @details
|
||||
* If your target is EXE, the current module simply is your program self.
|
||||
* However, if your target is DLL, the current module is your DLL, not the EXE loading your DLL.
|
||||
*
|
||||
* This function is frequently used by DLL.
|
||||
* Because some design need the HANDLE of current module, not the host EXE loading your DLL.
|
||||
* For example, you may want to get the path of your built DLL, or fetch resources from your DLL at runtime,
|
||||
* then you should pass current module HANDLE, not NULL or the HANDLE of EXE.
|
||||
* @return A Windows HANDLE pointing to current module, or error occurs.
|
||||
*/
|
||||
WinFctResult<HMODULE> get_current_module();
|
||||
|
||||
/**
|
||||
* @brief Get path to Windows temporary directory.
|
||||
* @details Windows temporary directory usually is the target of \%TEMP\%.
|
||||
* @return Fetched UTF8 encoded path to Windows temporary directory, or error occurs.
|
||||
*/
|
||||
WinFctResult<std::u8string> get_temp_directory();
|
||||
|
||||
/**
|
||||
* @brief Get the file name of given module HANDLE
|
||||
* @param[in] hModule
|
||||
* The HANDLE to the module where you want to get file name.
|
||||
* It is same as the HANDLE parameter of Win32 \c GetModuleFileName.
|
||||
* @param[out] ret The variable receiving UTF8 encoded file name of given module.
|
||||
* @return Fetched UTF8 encoded file name of given module, or error occurs.
|
||||
*/
|
||||
WinFctResult<std::u8string> get_module_file_name(HINSTANCE hModule);
|
||||
|
||||
/**
|
||||
* @brief Check whether given code page number is a valid one.
|
||||
* @param[in] code_page The code page number.
|
||||
* @return True if it is valid, otherwise false.
|
||||
*/
|
||||
bool is_valid_code_page(UINT code_page);
|
||||
|
||||
/**
|
||||
* @brief Copies an existing file to a new file.
|
||||
* @param[in] lpExistingFileName The name of an existing file.
|
||||
* @param[in] lpNewFileName The name of the new file.
|
||||
* @param[in] bFailIfExists
|
||||
* If this parameter is TRUE and the new file specified by \c lpNewFileName already exists, the function fails.
|
||||
* If this parameter is FALSE and the new file already exists, the function overwrites the existing file and succeeds.
|
||||
* @return Nothing or error occurs. If function failed with backend error, caller can call \c GetLastError for more details.
|
||||
* @remarks Same as Windows \c CopyFile: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-copyfilew
|
||||
*/
|
||||
WinFctResult<void> copy_file(const std::u8string_view& lpExistingFileName, const std::u8string_view& lpNewFileName, BOOL bFailIfExists);
|
||||
|
||||
/**
|
||||
* @brief Moves an existing file or a directory, including its children.
|
||||
* @param[in] lpExistingFileName The current name of the file or directory on the local computer.
|
||||
* @param[in] lpNewFileName
|
||||
* The new name for the file or directory. The new name must not already exist.
|
||||
* A new file may be on a different file system or drive. A new directory must be on the same drive.
|
||||
* @return Nothing or error occurs. If function failed with backend error, caller can call \c GetLastError for more details.
|
||||
* @remarks Same as Windows \c MoveFile: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefilew
|
||||
*/
|
||||
WinFctResult<void> move_file(const std::u8string_view& lpExistingFileName, const std::u8string_view& lpNewFileName);
|
||||
|
||||
/**
|
||||
* @brief Deletes an existing file.
|
||||
* @param[in] lpFileName The name of the file to be deleted.
|
||||
* @return Nothing or error occurs. If function failed with backend error, caller can call \c GetLastError for more details.
|
||||
* @remarks Same as Windows \c DeleteFile: https://learn.microsoft.com/e-us/windows/win32/api/winbase/nf-winbase-deletefile
|
||||
*/
|
||||
WinFctResult<void> delete_file(const std::u8string_view& lpFileName);
|
||||
|
||||
#if defined(YYCC_STL_MSSTL)
|
||||
|
||||
/// @brief The known directory type in Windows.
|
||||
enum class KnownDirectory {
|
||||
LocalAppData, ///< The path \%LOCALAPPDATA\% pointed.
|
||||
AppData, ///< The path \%APPDATA\% pointed.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get the path to \%LOCALAPPDATA\%.
|
||||
* @details \%LOCALAPPDATA\% usually was used as putting local app data files
|
||||
* @param[out] ret The variable receiving UTF8 encoded path to LOCALAPPDATA.
|
||||
* @return True if success, otherwise false.
|
||||
*/
|
||||
WinFctResult<std::u8string> get_known_path(KnownDirectory path_type);
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -22,6 +22,9 @@ PRIVATE
|
||||
yycc/encoding/stl.cpp
|
||||
yycc/encoding/windows.cpp
|
||||
yycc/encoding/iconv.cpp
|
||||
yycc/windows/com.cpp
|
||||
#yycc/windows/dialog.cpp
|
||||
yycc/windows/winfct.cpp
|
||||
|
||||
yycc/carton/pycodec.cpp
|
||||
)
|
||||
|
16
testbench/yycc/windows/com.cpp
Normal file
16
testbench/yycc/windows/com.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/windows/com.hpp>
|
||||
|
||||
#define COM ::yycc::windows::com
|
||||
|
||||
namespace yycctest::windows::com {
|
||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
|
||||
TEST(WindowsCom, IsInitialized) {
|
||||
// COM environment should always be ready.
|
||||
EXPECT_TRUE(COM::is_initialized());
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
16
testbench/yycc/windows/dialog.cpp
Normal file
16
testbench/yycc/windows/dialog.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/windows/dialog.hpp>
|
||||
|
||||
#define DIALOG ::yycc::windows::dialog
|
||||
|
||||
namespace yycctest::windows::dialog {
|
||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
|
||||
|
||||
// TEST(WindowsDialog, Normal) {
|
||||
|
||||
// }
|
||||
|
||||
#endif
|
||||
}
|
48
testbench/yycc/windows/winfct.cpp
Normal file
48
testbench/yycc/windows/winfct.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/windows/winfct.hpp>
|
||||
|
||||
#define WINFCT ::yycc::windows::winfct
|
||||
|
||||
namespace yycctest::windows::winfct {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
TEST(WindowsWinFct, GetCurrentModule) {
|
||||
auto rv = WINFCT::get_current_module();
|
||||
EXPECT_TRUE(rv.has_value());
|
||||
}
|
||||
|
||||
TEST(WindowsWinFct, GetTempDirectory) {
|
||||
auto rv = WINFCT::get_temp_directory();
|
||||
EXPECT_TRUE(rv.has_value());
|
||||
}
|
||||
|
||||
TEST(WindowsWinFct, GetModuleFileName) {
|
||||
auto handle = WINFCT::get_current_module();
|
||||
ASSERT_TRUE(handle.has_value());
|
||||
|
||||
auto rv = WINFCT::get_module_file_name(handle.value());
|
||||
EXPECT_TRUE(rv.has_value());
|
||||
}
|
||||
|
||||
TEST(WindowsWinFct, IsValidCodePage) {
|
||||
EXPECT_TRUE(WINFCT::is_valid_code_page(437));
|
||||
EXPECT_TRUE(WINFCT::is_valid_code_page(65001));
|
||||
|
||||
EXPECT_FALSE(WINFCT::is_valid_code_page(6161));
|
||||
}
|
||||
|
||||
#if defined(YYCC_STL_MSSTL)
|
||||
|
||||
TEST(WindowsWinFct, GetKnownPath) {
|
||||
auto rv = WINFCT::get_known_path(WINFCT::KnownDirectory::LocalAppData);
|
||||
EXPECT_TRUE(rv.has_value());
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// YYC MARK:
|
||||
// I can't test CopyFile, MoveFile and DeleteFile.
|
||||
|
||||
#endif
|
||||
} // namespace yycctest::windows::winfct
|
Reference in New Issue
Block a user