Compare commits
8 Commits
050bed400d
...
master
Author | SHA1 | Date | |
---|---|---|---|
ccd0219ead | |||
4bfba6f243 | |||
9e994dd4f0 | |||
d6034f8cb0 | |||
0694d923f3 | |||
580b096cb3 | |||
f9365481b9 | |||
15aade052f |
@ -300,6 +300,9 @@ StatementMacros:
|
||||
- YYCC_DEFAULT_COPY
|
||||
- YYCC_DEFAULT_MOVE
|
||||
- YYCC_DEFAULT_COPY_MOVE
|
||||
- YYCC_DECL_COPY
|
||||
- YYCC_DECL_MOVE
|
||||
- YYCC_DECL_COPY_MOVE
|
||||
TableGenBreakInsideDAGArg: DontBreak
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
|
@ -13,12 +13,14 @@ PRIVATE
|
||||
# Sources
|
||||
yycc/string/reinterpret.cpp
|
||||
yycc/string/op.cpp
|
||||
yycc/string/stream.cpp
|
||||
yycc/patch/fopen.cpp
|
||||
yycc/rust/panic.cpp
|
||||
yycc/rust/env.cpp
|
||||
yycc/windows/com.cpp
|
||||
#yycc/windows/dialog.cpp
|
||||
yycc/windows/dialog.cpp
|
||||
yycc/windows/winfct.cpp
|
||||
yycc/windows/console.cpp
|
||||
yycc/encoding/stl.cpp
|
||||
yycc/encoding/windows.cpp
|
||||
yycc/encoding/iconv.cpp
|
||||
@ -27,6 +29,7 @@ PRIVATE
|
||||
yycc/carton/termcolor.cpp
|
||||
yycc/carton/wcwidth.cpp
|
||||
yycc/carton/tabulate.cpp
|
||||
yycc/carton/csconsole.cpp
|
||||
)
|
||||
target_sources(YYCCommonplace
|
||||
PUBLIC
|
||||
@ -45,6 +48,7 @@ FILES
|
||||
yycc/flag_enum.hpp
|
||||
yycc/string/reinterpret.hpp
|
||||
yycc/string/op.hpp
|
||||
yycc/string/stream.hpp
|
||||
yycc/patch/ptr_pad.hpp
|
||||
yycc/patch/fopen.hpp
|
||||
yycc/num/parse.hpp
|
||||
@ -61,8 +65,9 @@ FILES
|
||||
yycc/windows/import_guard_head.hpp
|
||||
yycc/windows/import_guard_tail.hpp
|
||||
yycc/windows/com.hpp
|
||||
#yycc/windows/dialog.hpp
|
||||
yycc/windows/dialog.hpp
|
||||
yycc/windows/winfct.hpp
|
||||
yycc/windows/console.hpp
|
||||
yycc/constraint.hpp
|
||||
yycc/constraint/builder.hpp
|
||||
yycc/encoding/stl.hpp
|
||||
@ -73,6 +78,7 @@ FILES
|
||||
yycc/carton/termcolor.hpp
|
||||
yycc/carton/wcwidth.hpp
|
||||
yycc/carton/tabulate.hpp
|
||||
yycc/carton/csconsole.hpp
|
||||
)
|
||||
# Setup header infomations
|
||||
target_include_directories(YYCCommonplace
|
||||
|
@ -1,282 +0,0 @@
|
||||
#include "ConsoleHelper.hpp"
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "StringHelper.hpp"
|
||||
#include <iostream>
|
||||
|
||||
// Include Windows used headers in Windows.
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
#endif
|
||||
|
||||
namespace YYCC::ConsoleHelper {
|
||||
|
||||
#pragma region Windows Specific Functions
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
static bool RawEnableColorfulConsole(FILE* fs) {
|
||||
if (!_isatty(_fileno(fs))) return false;
|
||||
|
||||
HANDLE h_output;
|
||||
DWORD dw_mode;
|
||||
|
||||
h_output = (HANDLE)_get_osfhandle(_fileno(fs));
|
||||
if (!GetConsoleMode(h_output, &dw_mode)) return false;
|
||||
if (!SetConsoleMode(h_output, dw_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
Reference:
|
||||
* https://stackoverflow.com/questions/45575863/how-to-print-utf-8-strings-to-stdcout-on-windows
|
||||
* https://stackoverflow.com/questions/69830460/reading-utf-8-input
|
||||
|
||||
There is 3 way to make Windows console enable UTF8 mode.
|
||||
|
||||
First one is calling SetConsoleCP and SetConsoleOutputCP.
|
||||
The side effect of this is std::cin and std::cout is broken,
|
||||
however there is a patch for this issue.
|
||||
|
||||
Second one is calling _set_mode with _O_U8TEXT or _O_U16TEXT to enable Unicode mode for Windows console.
|
||||
This also have side effect which is stronger than first one.
|
||||
All puts family functions (ASCII-based output functions) will throw assertion exception.
|
||||
You only can use putws family functions (wide-char-based output functions).
|
||||
However these functions can not be used without calling _set_mode in Windows design.
|
||||
|
||||
There still is another method, using WriteConsoleW directly visiting console.
|
||||
This function family can output correct string without calling any extra functions!
|
||||
This method is what we adopted.
|
||||
*/
|
||||
|
||||
template<bool _bIsConsole>
|
||||
static yycc_u8string WinConsoleRead(HANDLE hStdIn) {
|
||||
using _TChar = std::conditional_t<_bIsConsole, wchar_t, char>;
|
||||
|
||||
// Prepare an internal buffer because the read data may not be fully used.
|
||||
// For example, we may read x\ny in a single calling but after processing \n, this function will return
|
||||
// so y will temporarily stored in this internal buffer for next using.
|
||||
// Thus this function is not thread safe.
|
||||
static std::basic_string<_TChar> internal_buffer;
|
||||
// create return value buffer
|
||||
std::basic_string<_TChar> return_buffer;
|
||||
|
||||
// Prepare some variables
|
||||
DWORD dwReadNumberOfChars;
|
||||
_TChar szReadChars[64];
|
||||
size_t eol_pos;
|
||||
|
||||
// try fetching EOL
|
||||
while (true) {
|
||||
// if internal buffer is empty,
|
||||
// try fetching it.
|
||||
if (internal_buffer.empty()) {
|
||||
// console and non-console use different method to read.
|
||||
if constexpr (_bIsConsole) {
|
||||
// console handle, use ReadConsoleW.
|
||||
// read from console, the read data is wchar based
|
||||
if (!ReadConsoleW(hStdIn, szReadChars, sizeof(szReadChars) / sizeof(_TChar), &dwReadNumberOfChars, NULL))
|
||||
break;
|
||||
} else {
|
||||
// anything else, use ReadFile instead.
|
||||
// the read data is utf8 based
|
||||
if (!ReadFile(hStdIn, szReadChars, sizeof(szReadChars), &dwReadNumberOfChars, NULL))
|
||||
break;
|
||||
}
|
||||
|
||||
// send to internal buffer
|
||||
if (dwReadNumberOfChars == 0) break;
|
||||
internal_buffer.append(szReadChars, dwReadNumberOfChars);
|
||||
}
|
||||
|
||||
// try finding EOL in internal buffer
|
||||
if constexpr (std::is_same_v<_TChar, char>) eol_pos = internal_buffer.find_first_of('\n');
|
||||
else eol_pos = internal_buffer.find_first_of(L'\n');
|
||||
// check finding result
|
||||
if (eol_pos == std::wstring::npos) {
|
||||
// the whole string do not include EOL, fully appended to return value
|
||||
return_buffer += internal_buffer;
|
||||
internal_buffer.clear();
|
||||
// need more data, continue while
|
||||
} else {
|
||||
// split result
|
||||
// push into result and remain some in internal buffer.
|
||||
return_buffer.append(internal_buffer, 0u, eol_pos);
|
||||
internal_buffer.erase(0u, eol_pos + 1u); // +1 because EOL take one place.
|
||||
// break while mean success finding
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// post-process for return value
|
||||
yycc_u8string real_return_buffer;
|
||||
if constexpr (_bIsConsole) {
|
||||
// console mode need convert wchar to utf8
|
||||
YYCC::EncodingHelper::WcharToUTF8(return_buffer, real_return_buffer);
|
||||
} else {
|
||||
// non-console just copt the result
|
||||
real_return_buffer = EncodingHelper::ToUTF8(return_buffer);
|
||||
}
|
||||
// every mode need delete \r words
|
||||
YYCC::StringHelper::Replace(real_return_buffer, YYCC_U8("\r"), YYCC_U8(""));
|
||||
// return value
|
||||
return real_return_buffer;
|
||||
}
|
||||
|
||||
static void WinConsoleWrite(const yycc_u8string& strl, bool to_stderr) {
|
||||
// Prepare some Win32 variables
|
||||
// fetch stdout handle first
|
||||
HANDLE hStdOut = GetStdHandle(to_stderr ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE);
|
||||
DWORD dwConsoleMode;
|
||||
DWORD dwWrittenNumberOfChars;
|
||||
|
||||
// if stdout was redirected, this handle may point to a file handle or anything else,
|
||||
// WriteConsoleW can not write data into such scenario, so we need check whether this handle is console handle
|
||||
if (GetConsoleMode(hStdOut, &dwConsoleMode)) {
|
||||
// console handle, use WriteConsoleW.
|
||||
// convert utf8 string to wide char first
|
||||
std::wstring wstrl(YYCC::EncodingHelper::UTF8ToWchar(strl));
|
||||
size_t wstrl_size = wstrl.size();
|
||||
// write string with size check
|
||||
if (wstrl_size <= std::numeric_limits<DWORD>::max()) {
|
||||
WriteConsoleW(hStdOut, wstrl.c_str(), static_cast<DWORD>(wstrl_size), &dwWrittenNumberOfChars, NULL);
|
||||
}
|
||||
} else {
|
||||
// anything else, use WriteFile instead.
|
||||
// WriteFile do not need extra convertion, because it is direct writing.
|
||||
// check whether string length is overflow
|
||||
size_t strl_size = strl.size() * sizeof(yycc_u8string::value_type);
|
||||
// write string with size check
|
||||
if (strl_size <= std::numeric_limits<DWORD>::max()) {
|
||||
WriteFile(hStdOut, strl.c_str(), static_cast<DWORD>(strl_size), &dwWrittenNumberOfChars, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#pragma endregion
|
||||
|
||||
bool EnableColorfulConsole() {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
bool ret = true;
|
||||
ret &= RawEnableColorfulConsole(stdout);
|
||||
ret &= RawEnableColorfulConsole(stderr);
|
||||
return ret;
|
||||
|
||||
#else
|
||||
|
||||
// just return true and do nothing
|
||||
return true;
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
yycc_u8string ReadLine() {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
// get stdin mode
|
||||
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
|
||||
// use different method to get according to whether stdin is redirected
|
||||
DWORD dwConsoleMode;
|
||||
if (GetConsoleMode(hStdIn, &dwConsoleMode)) {
|
||||
return WinConsoleRead<true>(hStdIn);
|
||||
} else {
|
||||
return WinConsoleRead<false>(hStdIn);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// in linux, directly use C++ function to fetch.
|
||||
std::string cmd;
|
||||
if (std::getline(std::cin, cmd).fail()) cmd.clear();
|
||||
return EncodingHelper::ToUTF8(cmd);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
template<bool bNeedFmt, bool bIsErr, bool bHasEOL>
|
||||
static void RawWrite(const yycc_char8_t* u8_fmt, va_list argptr) {
|
||||
// Buiild string need to be written first
|
||||
// If no format string or plain string for writing, return.
|
||||
if (u8_fmt == nullptr) return;
|
||||
// Build or simply copy string
|
||||
yycc_u8string strl;
|
||||
if constexpr (bNeedFmt) {
|
||||
// treat as format string
|
||||
va_list argcpy;
|
||||
va_copy(argcpy, argptr);
|
||||
strl = YYCC::StringHelper::VPrintf(u8_fmt, argcpy);
|
||||
va_end(argcpy);
|
||||
} else {
|
||||
// treat as plain string
|
||||
strl = u8_fmt;
|
||||
}
|
||||
// Checkout whether add EOL
|
||||
if constexpr (bHasEOL) {
|
||||
strl += YYCC_U8("\n");
|
||||
}
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
// call Windows specific writer
|
||||
WinConsoleWrite(strl, bIsErr);
|
||||
#else
|
||||
// in linux, directly use C function to write.
|
||||
std::fputs(EncodingHelper::ToOrdinary(strl.c_str()), bIsErr ? stderr : stdout);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Format(const yycc_char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
RawWrite<true, false, false>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void FormatLine(const yycc_char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
RawWrite<true, false, true>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void Write(const yycc_char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
RawWrite<false, false, false>(u8_strl, empty);
|
||||
}
|
||||
|
||||
void WriteLine(const yycc_char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
RawWrite<false, false, true>(u8_strl, empty);
|
||||
}
|
||||
|
||||
void ErrFormat(const yycc_char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
RawWrite<true, true, false>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void ErrFormatLine(const yycc_char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
RawWrite<true, true, true>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void ErrWrite(const yycc_char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
RawWrite<false, true, false>(u8_strl, empty);
|
||||
}
|
||||
|
||||
void ErrWriteLine(const yycc_char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
RawWrite<false, true, true>(u8_strl, empty);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,92 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* @brief The helper providing universal C\# style console function and other console related stuff
|
||||
* @details
|
||||
* For how to utilize this functions provided by this namespace, please view \ref console_helper.
|
||||
*/
|
||||
namespace YYCC::ConsoleHelper {
|
||||
|
||||
/**
|
||||
* @brief Enable console color support for Windows.
|
||||
* @details This actually is enable virtual console feature for \c stdout and \c stderr.
|
||||
* @return True if success, otherwise false.
|
||||
* @remarks
|
||||
* This function only works on Windows and do nothing on other platforms such as Linux,
|
||||
* because we assume all terminals existing on other platform support color feature as default.
|
||||
*/
|
||||
bool EnableColorfulConsole();
|
||||
|
||||
/**
|
||||
* @brief Reads the next line of UTF8 characters from the standard input stream.
|
||||
* @return
|
||||
* The next line of UTF8 characters from the input stream.
|
||||
* Empty string if user just press Enter key or function failed.
|
||||
*/
|
||||
yycc_u8string ReadLine();
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object
|
||||
* to the standard output stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void Format(const yycc_char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object,
|
||||
* followed by the current line terminator,
|
||||
* to the standard output stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void FormatLine(const yycc_char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief Writes the specified string value to the standard output stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void Write(const yycc_char8_t* u8_strl);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the specified string value, followed by the current line terminator,
|
||||
* to the standard output stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void WriteLine(const yycc_char8_t* u8_strl);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object
|
||||
* to the standard error stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void ErrFormat(const yycc_char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object,
|
||||
* followed by the current line terminator,
|
||||
* to the standard error stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void ErrFormatLine(const yycc_char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief Writes the specified string value to the standard error stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void ErrWrite(const yycc_char8_t* u8_strl);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the specified string value, followed by the current line terminator,
|
||||
* to the standard error stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void ErrWriteLine(const yycc_char8_t* u8_strl);
|
||||
|
||||
}
|
@ -1,378 +0,0 @@
|
||||
#include "DialogHelper.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "StringHelper.hpp"
|
||||
|
||||
namespace YYCC::DialogHelper {
|
||||
|
||||
#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
|
@ -1,312 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#include "COMHelper.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <initializer_list>
|
||||
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include <shlobj_core.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
|
||||
/**
|
||||
* @brief The namespace providing Windows universal dialog features.
|
||||
* @details
|
||||
* This namespace only available on Windows platform.
|
||||
* See also \ref dialog_helper.
|
||||
*/
|
||||
namespace YYCC::DialogHelper {
|
||||
|
||||
/**
|
||||
* @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_DEL_CLS_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_DEL_CLS_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<yycc_u8string>;
|
||||
using FilterName = yycc_u8string;
|
||||
using FilterPair = std::pair<FilterName, FilterModes>;
|
||||
|
||||
std::vector<FilterPair> m_Filters;
|
||||
};
|
||||
|
||||
/**
|
||||
* @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_DEL_CLS_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;
|
||||
COMHelper::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_DEL_CLS_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;
|
||||
yycc_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, yycc_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<yycc_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, yycc_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, yycc_u8string& ret);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,115 +0,0 @@
|
||||
#include "WinFctHelper.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "COMHelper.hpp"
|
||||
|
||||
namespace YYCC::WinFctHelper {
|
||||
|
||||
HMODULE GetCurrentModule() {
|
||||
// Reference: https://stackoverflow.com/questions/557081/how-do-i-get-the-hmodule-for-the-currently-executing-code
|
||||
HMODULE hModule = NULL;
|
||||
::GetModuleHandleExW(
|
||||
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, // get address and do not inc ref counter.
|
||||
(LPCWSTR)GetCurrentModule,
|
||||
&hModule);
|
||||
|
||||
return hModule;
|
||||
}
|
||||
|
||||
bool GetTempDirectory(yycc_u8string& ret) {
|
||||
// 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, set to empty
|
||||
return false;
|
||||
}
|
||||
|
||||
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
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// resize result
|
||||
wpath.resize(expected_size);
|
||||
// convert to utf8 and return
|
||||
return YYCC::EncodingHelper::WcharToUTF8(wpath, ret);
|
||||
}
|
||||
|
||||
bool GetModuleFileName(HINSTANCE hModule, yycc_u8string& ret) {
|
||||
// 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 false;
|
||||
}
|
||||
|
||||
// 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, break while
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// resize result
|
||||
wpath.resize(copied_size);
|
||||
// convert to utf8 and return
|
||||
return YYCC::EncodingHelper::WcharToUTF8(wpath, ret);
|
||||
}
|
||||
|
||||
bool GetLocalAppData(yycc_u8string& ret) {
|
||||
// check whether com initialized
|
||||
if (!COMHelper::IsInitialized()) return false;
|
||||
|
||||
// fetch path
|
||||
LPWSTR _known_path;
|
||||
HRESULT hr = SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_CREATE, NULL, &_known_path);
|
||||
if (FAILED(hr)) return false;
|
||||
COMHelper::SmartLPWSTR known_path(_known_path);
|
||||
|
||||
// convert to utf8
|
||||
return YYCC::EncodingHelper::WcharToUTF8(known_path.get(), ret);
|
||||
}
|
||||
|
||||
bool IsValidCodePage(UINT code_page) {
|
||||
CPINFOEXW cpinfo;
|
||||
return ::GetCPInfoExW(code_page, 0, &cpinfo);
|
||||
}
|
||||
|
||||
BOOL CopyFile(const yycc_u8string_view& lpExistingFileName, const yycc_u8string_view& lpNewFileName, BOOL bFailIfExists) {
|
||||
std::wstring wExistingFileName, wNewFileName;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(lpExistingFileName, wExistingFileName)) return FALSE;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(lpNewFileName, wNewFileName)) return FALSE;
|
||||
return ::CopyFileW(wExistingFileName.c_str(), wNewFileName.c_str(), bFailIfExists);
|
||||
}
|
||||
|
||||
BOOL MoveFile(const yycc_u8string_view& lpExistingFileName, const yycc_u8string_view& lpNewFileName) {
|
||||
std::wstring wExistingFileName, wNewFileName;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(lpExistingFileName, wExistingFileName)) return FALSE;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(lpNewFileName, wNewFileName)) return FALSE;
|
||||
return ::MoveFileW(wExistingFileName.c_str(), wNewFileName.c_str());
|
||||
}
|
||||
|
||||
BOOL DeleteFile(const yycc_u8string_view& lpFileName) {
|
||||
std::wstring wFileName;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(lpFileName, wFileName)) return FALSE;
|
||||
return ::DeleteFileW(wFileName.c_str());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,106 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
|
||||
/**
|
||||
* @brief The helper providing assistance of Win32 functions.
|
||||
* @details
|
||||
* This helper is Windows specific.
|
||||
* If current environment is not Windows, the whole namespace will be unavailable.
|
||||
* See also \ref win_fct_helper
|
||||
*/
|
||||
namespace YYCC::WinFctHelper {
|
||||
|
||||
/**
|
||||
* @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, NULL if failed.
|
||||
*/
|
||||
HMODULE GetCurrentModule();
|
||||
|
||||
/**
|
||||
* @brief Get path to Windows temporary folder.
|
||||
* @details Windows temporary folder usually is the target of \%TEMP\%.
|
||||
* @param[out] ret The variable receiving UTF8 encoded path to Windows temp folder.
|
||||
* @return True if success, otherwise false.
|
||||
*/
|
||||
bool GetTempDirectory(yycc_u8string& ret);
|
||||
|
||||
/**
|
||||
* @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 True if success, otherwise false.
|
||||
*/
|
||||
bool GetModuleFileName(HINSTANCE hModule, yycc_u8string& ret);
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
bool GetLocalAppData(yycc_u8string& ret);
|
||||
|
||||
/**
|
||||
* @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 IsValidCodePage(UINT code_page);
|
||||
|
||||
/**
|
||||
* @brief Copies an existing file to a new file.
|
||||
* @param lpExistingFileName The name of an existing file.
|
||||
* @param lpNewFileName The name of the new file.
|
||||
* @param 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
|
||||
* If the function succeeds, the return value is nonzero.
|
||||
* If the function fails, the return value is zero. To get extended error information, call \c GetLastError.
|
||||
* @remarks Same as Windows \c CopyFile: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-copyfilew
|
||||
*/
|
||||
BOOL CopyFile(const yycc_u8string_view& lpExistingFileName, const yycc_u8string_view& lpNewFileName, BOOL bFailIfExists);
|
||||
|
||||
/**
|
||||
* @brief Moves an existing file or a directory, including its children.
|
||||
* @param lpExistingFileName The current name of the file or directory on the local computer.
|
||||
* @param 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
|
||||
* If the function succeeds, the return value is nonzero.
|
||||
* If the function fails, the return value is zero. To get extended error information, call \c GetLastError.
|
||||
* @remarks Same as Windows \c MoveFile: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefilew
|
||||
*/
|
||||
BOOL MoveFile(const yycc_u8string_view& lpExistingFileName, const yycc_u8string_view& lpNewFileName);
|
||||
|
||||
/**
|
||||
* @brief Deletes an existing file.
|
||||
* @param lpFileName The name of the file to be deleted.
|
||||
* @return
|
||||
* If the function succeeds, the return value is nonzero.
|
||||
* If the function fails, the return value is zero. To get extended error information, call \c GetLastError.
|
||||
* @remarks Same as Windows \c DeleteFile: https://learn.microsoft.com/e-us/windows/win32/api/winbase/nf-winbase-deletefile
|
||||
*/
|
||||
BOOL DeleteFile(const yycc_u8string_view& lpFileName);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
247
src/yycc/carton/csconsole.cpp
Normal file
247
src/yycc/carton/csconsole.cpp
Normal file
@ -0,0 +1,247 @@
|
||||
#include "csconsole.hpp"
|
||||
#include "../macro/os_detector.hpp"
|
||||
|
||||
#include "../string/op.hpp"
|
||||
#include "../string/reinterpret.hpp"
|
||||
#include "../encoding/windows.hpp"
|
||||
#include <iostream>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
#include "../windows/import_guard_head.hpp"
|
||||
#include <Windows.h>
|
||||
#include "../windows/import_guard_tail.hpp"
|
||||
#endif
|
||||
|
||||
#define OP ::yycc::string::op
|
||||
#define REINTERPRET ::yycc::string::reinterpret
|
||||
#define ENC ::yycc::encoding::windows
|
||||
|
||||
namespace yycc::carton::csconsole {
|
||||
|
||||
#pragma region Windows Console Specific Functions
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
template<bool BIsConsole>
|
||||
static std::u8string win_console_read(HANDLE hStdIn) {
|
||||
using TChar = std::conditional_t<BIsConsole, wchar_t, char>;
|
||||
|
||||
// Prepare an internal buffer because the read data may not be fully used.
|
||||
// For example, we may read x\ny in a single calling but after processing \n, this function will return
|
||||
// so y will temporarily stored in this internal buffer for next using.
|
||||
// Thus this function is not thread safe.
|
||||
static std::basic_string<TChar> internal_buffer;
|
||||
// create return value buffer
|
||||
std::basic_string<TChar> return_buffer;
|
||||
|
||||
// Prepare some variables
|
||||
DWORD dwReadNumberOfChars;
|
||||
TChar szReadChars[64];
|
||||
size_t eol_pos;
|
||||
|
||||
// try fetching EOL
|
||||
while (true) {
|
||||
// if internal buffer is empty,
|
||||
// try fetching it.
|
||||
if (internal_buffer.empty()) {
|
||||
// console and non-console use different method to read.
|
||||
if constexpr (BIsConsole) {
|
||||
// console handle, use ReadConsoleW.
|
||||
// read from console, the read data is wchar based
|
||||
if (!ReadConsoleW(hStdIn, szReadChars, sizeof(szReadChars) / sizeof(TChar), &dwReadNumberOfChars, NULL)) break;
|
||||
} else {
|
||||
// anything else, use ReadFile instead.
|
||||
// the read data is utf8 based
|
||||
if (!ReadFile(hStdIn, szReadChars, sizeof(szReadChars), &dwReadNumberOfChars, NULL)) break;
|
||||
}
|
||||
|
||||
// send to internal buffer
|
||||
if (dwReadNumberOfChars == 0) break;
|
||||
internal_buffer.append(szReadChars, dwReadNumberOfChars);
|
||||
}
|
||||
|
||||
// try finding EOL in internal buffer
|
||||
if constexpr (std::is_same_v<TChar, char>) eol_pos = internal_buffer.find_first_of('\n');
|
||||
else eol_pos = internal_buffer.find_first_of(L'\n');
|
||||
// check finding result
|
||||
if (eol_pos == std::wstring::npos) {
|
||||
// the whole string do not include EOL, fully appended to return value
|
||||
return_buffer += internal_buffer;
|
||||
internal_buffer.clear();
|
||||
// need more data, continue while
|
||||
} else {
|
||||
// split result
|
||||
// push into result and remain some in internal buffer.
|
||||
return_buffer.append(internal_buffer, 0u, eol_pos);
|
||||
internal_buffer.erase(0u, eol_pos + 1u); // +1 because EOL take one place.
|
||||
// break while mean success finding
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// post-process for return value
|
||||
std::u8string real_return_buffer;
|
||||
if constexpr (BIsConsole) {
|
||||
// console mode need convert wchar to utf8
|
||||
auto rv = ENC::to_utf8(return_buffer);
|
||||
if (rv.has_value()) real_return_buffer = std::move(rv.value());
|
||||
} else {
|
||||
// non-console just copt the result
|
||||
real_return_buffer = REINTERPRET::as_utf8(return_buffer);
|
||||
}
|
||||
// every mode need delete \r words
|
||||
OP::replace(real_return_buffer, u8"\r", u8"");
|
||||
// return value
|
||||
return real_return_buffer;
|
||||
}
|
||||
|
||||
static void win_console_write(const std::u8string_view& strl, bool to_stderr) {
|
||||
// Prepare some Win32 variables
|
||||
// fetch stdout handle first
|
||||
HANDLE hStdOut = GetStdHandle(to_stderr ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE);
|
||||
DWORD dwConsoleMode;
|
||||
DWORD dwWrittenNumberOfChars;
|
||||
|
||||
// if stdout was redirected, this handle may point to a file handle or anything else,
|
||||
// WriteConsoleW can not write data into such scenario, so we need check whether this handle is console handle
|
||||
if (GetConsoleMode(hStdOut, &dwConsoleMode)) {
|
||||
// console handle, use WriteConsoleW.
|
||||
// convert utf8 string to wide char first
|
||||
auto rv = ENC::to_wchar(strl);
|
||||
if (!rv.has_value()) return;
|
||||
std::wstring wstrl(std::move(rv.value()));
|
||||
size_t wstrl_size = wstrl.size();
|
||||
// write string with size check
|
||||
if (wstrl_size <= std::numeric_limits<DWORD>::max()) {
|
||||
WriteConsoleW(hStdOut, wstrl.c_str(), static_cast<DWORD>(wstrl_size), &dwWrittenNumberOfChars, NULL);
|
||||
}
|
||||
} else {
|
||||
// anything else, use WriteFile instead.
|
||||
// WriteFile do not need extra convertion, because it is direct writing.
|
||||
// check whether string length is overflow
|
||||
size_t strl_size = strl.size() * sizeof(std::u8string_view::value_type);
|
||||
// write string with size check
|
||||
if (strl_size <= std::numeric_limits<DWORD>::max()) {
|
||||
WriteFile(hStdOut, strl.data(), static_cast<DWORD>(strl_size), &dwWrittenNumberOfChars, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Read Functions
|
||||
|
||||
std::u8string read_line() {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
// get stdin mode
|
||||
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
|
||||
// use different method to get according to whether stdin is redirected
|
||||
DWORD dwConsoleMode;
|
||||
if (GetConsoleMode(hStdIn, &dwConsoleMode)) {
|
||||
return win_console_read<true>(hStdIn);
|
||||
} else {
|
||||
return win_console_read<false>(hStdIn);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// in linux, directly use C++ function to fetch.
|
||||
std::string cmd;
|
||||
if (std::getline(std::cin, cmd).fail()) cmd.clear();
|
||||
return REINTERPRET::as_utf8(cmd);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Write Functions
|
||||
|
||||
template<bool BNeedFmt, bool BIsErr, bool BHasEOL>
|
||||
static void raw_write(const char8_t* u8_fmt, va_list argptr) {
|
||||
// Buiild string need to be written first
|
||||
// If no format string or plain string for writing, return.
|
||||
if (u8_fmt == nullptr) return;
|
||||
// Build or simply copy string
|
||||
std::u8string strl;
|
||||
if constexpr (BNeedFmt) {
|
||||
// treat as format string
|
||||
va_list argcpy;
|
||||
va_copy(argcpy, argptr);
|
||||
auto rv = OP::vprintf(u8_fmt, argcpy);
|
||||
va_end(argcpy);
|
||||
// check format result
|
||||
if (!rv.has_value()) return;
|
||||
else strl = std::move(rv.value());
|
||||
} else {
|
||||
// treat as plain string
|
||||
strl = u8_fmt;
|
||||
}
|
||||
// Checkout whether add EOL
|
||||
if constexpr (BHasEOL) {
|
||||
strl += u8'\n';
|
||||
}
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
// call Windows specific writer
|
||||
win_console_write(strl, BIsErr);
|
||||
#else
|
||||
// in linux, directly use C function to write.
|
||||
std::fputs(REINTERPRET::as_ordinary(strl.c_str()), BIsErr ? stderr : stdout);
|
||||
#endif
|
||||
}
|
||||
|
||||
void format(const char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
raw_write<true, false, false>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void format_line(const char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
raw_write<true, false, true>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void write(const char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
raw_write<false, false, false>(u8_strl, empty);
|
||||
}
|
||||
|
||||
void write_line(const char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
raw_write<false, false, true>(u8_strl, empty);
|
||||
}
|
||||
|
||||
void eformat(const char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
raw_write<true, true, false>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void eformat_line(const char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
raw_write<true, true, true>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void ewrite(const char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
raw_write<false, true, false>(u8_strl, empty);
|
||||
}
|
||||
|
||||
void ewrite_line(const char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
raw_write<false, true, true>(u8_strl, empty);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
}
|
111
src/yycc/carton/csconsole.hpp
Normal file
111
src/yycc/carton/csconsole.hpp
Normal file
@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* @brief The helper providing universal C\# style console function and other console related stuff
|
||||
* @details
|
||||
* The origin of this namespace is coming from the requirement of UTF8 console in Windows.
|
||||
* There are 3 ways to make Windows console enable UTF8 mode.
|
||||
*
|
||||
* First one is calling \c SetConsoleCP and \c SetConsoleOutputCP.
|
||||
* The side effect of this is \c std::cin and \c std::cout is broken,
|
||||
* however there is a patch for this issue.
|
||||
*
|
||||
* Second one is calling \c _set_mode with \c _O_U8TEXT or \c _O_U16TEXT to enable Unicode mode for Windows console.
|
||||
* This also have side effect which is stronger than first one.
|
||||
* All "puts" family functions (ASCII-based output functions) will throw assertion exception.
|
||||
* You only can use "putws" family functions (wide-char-based output functions).
|
||||
* However these functions can not be used without calling \c _set_mode in Windows design.
|
||||
*
|
||||
* There still is another method, using \c WriteConsoleW directly visiting console.
|
||||
* This function family can output correct string without calling any extra functions!
|
||||
* This method is what we adopted and finally become this namespace.
|
||||
*
|
||||
* Reference:
|
||||
* \li https://stackoverflow.com/questions/45575863/how-to-print-utf-8-strings-to-stdcout-on-windows
|
||||
* \li https://stackoverflow.com/questions/69830460/reading-utf-8-input
|
||||
*
|
||||
* For how to utilize this functions provided by this namespace, please view \ref console_helper.
|
||||
*
|
||||
* @warning
|
||||
* All functions provided by this namespace are too aggressive.
|
||||
* Once you use it, you should not use any other input output functions.
|
||||
*
|
||||
* @deprecated
|
||||
* This namespace provided functions are too aggressive and can not cover all use scenario.
|
||||
* So I start to give this up when migrating this namespace during developing YYCC 2.x version.
|
||||
* I just do a simple type fix and rename when migrating this namespace to make it "just works".
|
||||
* There is no suggestion for using this namespace. And there is no update for this namespace.
|
||||
* Programmer should treat Windows UTF8 issue on their own.
|
||||
*/
|
||||
namespace yycc::carton::csconsole {
|
||||
|
||||
/**
|
||||
* @brief Reads the next line of UTF8 characters from the standard input stream.
|
||||
* @return
|
||||
* The next line of UTF8 characters from the input stream.
|
||||
* Empty string if user just press Enter key or function failed.
|
||||
*/
|
||||
std::u8string read_line();
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object
|
||||
* to the standard output stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void format(const char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object,
|
||||
* followed by the current line terminator,
|
||||
* to the standard output stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void format_line(const char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief Writes the specified string value to the standard output stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void write(const char8_t* u8_strl);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the specified string value, followed by the current line terminator,
|
||||
* to the standard output stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void write_line(const char8_t* u8_strl);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object
|
||||
* to the standard error stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void eformat(const char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object,
|
||||
* followed by the current line terminator,
|
||||
* to the standard error stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void eformat_line(const char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief Writes the specified string value to the standard error stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void ewrite(const char8_t* u8_strl);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the specified string value, followed by the current line terminator,
|
||||
* to the standard error stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void ewrite_line(const char8_t* u8_strl);
|
||||
|
||||
}
|
@ -152,8 +152,13 @@ namespace yycc::encoding::windows {
|
||||
// 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 (true) {
|
||||
bool not_tail = ptr < end;
|
||||
const char* new_ptr = not_tail ? ptr : &NULL_TERMINAL;
|
||||
size_t new_size = not_tail ? end - ptr : sizeof(NULL_TERMINAL);
|
||||
size_t rc = std::mbrtoc16(&c16, new_ptr, new_size, &state);
|
||||
if (!rc) break;
|
||||
|
||||
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
|
||||
|
@ -7,8 +7,8 @@
|
||||
|
||||
/// @brief Explicitly remove move (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DELETE_MOVE(CLSNAME) \
|
||||
CLSNAME(CLSNAME&&) = delete; \
|
||||
CLSNAME& operator=(CLSNAME&&) = delete;
|
||||
CLSNAME(CLSNAME&&) noexcept = delete; \
|
||||
CLSNAME& operator=(CLSNAME&&) noexcept = delete;
|
||||
|
||||
/// @brief Explicitly remove (copy and move) (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DELETE_COPY_MOVE(CLSNAME) \
|
||||
@ -22,10 +22,41 @@
|
||||
|
||||
/// @brief Explicitly set default move (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEFAULT_MOVE(CLSNAME) \
|
||||
CLSNAME(CLSNAME&&) = default; \
|
||||
CLSNAME& operator=(CLSNAME&&) = default;
|
||||
CLSNAME(CLSNAME&&) noexcept = default; \
|
||||
CLSNAME& operator=(CLSNAME&&) noexcept = default;
|
||||
|
||||
/// @brief Explicitly set default (copy and move) (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEFAULT_COPY_MOVE(CLSNAME) \
|
||||
YYCC_DEFAULT_COPY(CLSNAME) \
|
||||
YYCC_DEFAULT_MOVE(CLSNAME)
|
||||
|
||||
/// @brief Make declaration of copy (\c constructor and \c operator\=) for given class to avoid typo.
|
||||
#define YYCC_DECL_COPY(CLSNAME) \
|
||||
CLSNAME(const CLSNAME&); \
|
||||
CLSNAME& operator=(const CLSNAME&);
|
||||
|
||||
/// @brief Make declaration of move (\c constructor and \c operator\=) for given class to avoid typo.
|
||||
#define YYCC_DECL_MOVE(CLSNAME) \
|
||||
CLSNAME(CLSNAME&&) noexcept; \
|
||||
CLSNAME& operator=(CLSNAME&&) noexcept;
|
||||
|
||||
/// @brief Make declaration of copy and move (\c constructor and \c operator\=) for given class to avoid typo.
|
||||
#define YYCC_DECL_COPY_MOVE(CLSNAME) \
|
||||
YYCC_DECL_COPY(CLSNAME) \
|
||||
YYCC_DECL_MOVE(CLSNAME)
|
||||
|
||||
/// @brief Make implementation signature of copy \c constrctor for given class and right operand name to avoid typo.
|
||||
#define YYCC_IMPL_COPY_CTOR(CLSNAME, RHS) \
|
||||
CLSNAME::CLSNAME(const CLSNAME& RHS)
|
||||
|
||||
/// @brief Make implementation signature of copy \c operator\= for given class and right operand name to avoid typo.
|
||||
#define YYCC_IMPL_COPY_OPER(CLSNAME, RHS) \
|
||||
CLSNAME& CLSNAME::operator=(const CLSNAME& RHS)
|
||||
|
||||
/// @brief Make implementation signature of move \c constrctor for given class and right operand name to avoid typo.
|
||||
#define YYCC_IMPL_MOVE_CTOR(CLSNAME, RHS) \
|
||||
CLSNAME::CLSNAME(CLSNAME&& RHS) noexcept
|
||||
|
||||
/// @brief Make implementation signature of move \c operator\= for given class and right operand name to avoid typo.
|
||||
#define YYCC_IMPL_MOVE_OPER(CLSNAME, RHS) \
|
||||
CLSNAME& CLSNAME::operator=(CLSNAME&& RHS) noexcept
|
||||
|
23
src/yycc/string/stream.cpp
Normal file
23
src/yycc/string/stream.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
#include "stream.hpp"
|
||||
#include "reinterpret.hpp"
|
||||
|
||||
#define REINTERPRET ::yycc::string::reinterpret
|
||||
|
||||
namespace yycc::string::stream {
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const std::u8string_view& u8str) {
|
||||
os << REINTERPRET::as_ordinary_view(u8str);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const char8_t* u8str) {
|
||||
os << REINTERPRET::as_ordinary(u8str);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, char8_t u8chr) {
|
||||
os << static_cast<char>(u8chr);
|
||||
return os;
|
||||
}
|
||||
|
||||
}
|
17
src/yycc/string/stream.hpp
Normal file
17
src/yycc/string/stream.hpp
Normal file
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#include <ostream>
|
||||
#include <string_view>
|
||||
|
||||
/**
|
||||
* @brief This namespace add UTF8 support for \c std::ostream.
|
||||
* @details
|
||||
* The operator overloads written in this namespace will give \c std::ostream ability to write UTF8 string and its char.
|
||||
* For using this feature, please directly use <TT>using namespace yycc::string:stream;</TT> to import this namespace.
|
||||
*/
|
||||
namespace yycc::string::stream {
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const std::u8string_view& u8str);
|
||||
std::ostream& operator<<(std::ostream& os, const char8_t* u8str);
|
||||
std::ostream& operator<<(std::ostream& os, char8_t u8chr);
|
||||
|
||||
}
|
38
src/yycc/windows/console.cpp
Normal file
38
src/yycc/windows/console.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
#include "console.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "import_guard_head.hpp"
|
||||
#include <Windows.h>
|
||||
#include <io.h>
|
||||
#include "import_guard_tail.hpp"
|
||||
|
||||
namespace yycc::windows::console {
|
||||
|
||||
static ExecResult<void> colorful_fs(FILE* fs) {
|
||||
if (!_isatty(_fileno(fs))) {
|
||||
return std::unexpected(ExecError::NotTty);
|
||||
}
|
||||
|
||||
HANDLE h_output;
|
||||
DWORD dw_mode;
|
||||
|
||||
h_output = (HANDLE) _get_osfhandle(_fileno(fs));
|
||||
if (!GetConsoleMode(h_output, &dw_mode)) {
|
||||
return std::unexpected(ExecError::GetMode);
|
||||
}
|
||||
if (!SetConsoleMode(h_output, dw_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT)) {
|
||||
return std::unexpected(ExecError::SetMode);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ExecResult<void> colorful_console() {
|
||||
return colorful_fs(stdout).and_then([]() { return colorful_fs(stderr); });
|
||||
}
|
||||
|
||||
} // namespace yycc::windows::console
|
||||
|
||||
#endif
|
33
src/yycc/windows/console.hpp
Normal file
33
src/yycc/windows/console.hpp
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include "../macro/os_detector.hpp"
|
||||
#include "../macro/stl_detector.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
|
||||
#include <expected>
|
||||
|
||||
/**
|
||||
* @brief The namespace provide patches for Windows console.
|
||||
*/
|
||||
namespace yycc::windows::console {
|
||||
|
||||
/// @brief Error occurs in this module.
|
||||
enum class ExecError {
|
||||
NotTty, ///< Given stream is not TTY.
|
||||
GetMode, ///< Can not get stream mode.
|
||||
SetMode, ///< Can not set stream mode.
|
||||
};
|
||||
|
||||
/// @brief Result type used in this module.
|
||||
template<typename T>
|
||||
using ExecResult = std::expected<T, ExecError>;
|
||||
|
||||
/**
|
||||
* @brief Enable console color support for Windows.
|
||||
* @details This actually is enable virtual console feature for \c stdout and \c stderr.
|
||||
* @return Nothing or error occurs.
|
||||
*/
|
||||
ExecResult<void> colorful_console();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,344 +1,616 @@
|
||||
#include "dialog.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
|
||||
#include "../string/op.hpp"
|
||||
#include "../encoding/windows.hpp"
|
||||
#include "../num/safe_cast.hpp"
|
||||
#include "../num/safe_op.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
#define ENC ::yycc::encoding::windows
|
||||
#define OP ::yycc::string::op
|
||||
#define SAFECAST ::yycc::num::safe_cast
|
||||
#define SAFEOP ::yycc::num::safe_op
|
||||
#define WINCOM ::yycc::windows::com
|
||||
|
||||
namespace yycc::windows::dialog {
|
||||
|
||||
#pragma region FileFilters
|
||||
#pragma region WinFileFilters
|
||||
|
||||
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);
|
||||
WinFileFilters::WinFileFilters() : m_WinFilters(), m_WinDataStruct() {}
|
||||
|
||||
// assign filter patterns
|
||||
FilterModes modes;
|
||||
for (const yycc_char8_t* pattern : il) {
|
||||
if (pattern != nullptr) modes.emplace_back(yycc_u8string(pattern));
|
||||
}
|
||||
WinFileFilters::~WinFileFilters() {}
|
||||
|
||||
// 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;
|
||||
YYCC_IMPL_COPY_CTOR(WinFileFilters, rhs) : m_WinFilters(rhs.m_WinFilters), m_WinDataStruct() {
|
||||
// Update data
|
||||
this->update();
|
||||
}
|
||||
|
||||
bool FileFilters::Generate(WinFileFilters& win_result) const {
|
||||
// clear Windows oriented data
|
||||
win_result.Clear();
|
||||
YYCC_IMPL_COPY_OPER(WinFileFilters, rhs) {
|
||||
// Copy data and mark desync
|
||||
this->m_WinFilters = rhs.m_WinFilters;
|
||||
this->m_WinDataStruct.clear();
|
||||
// Update data
|
||||
this->update();
|
||||
// Return self
|
||||
return *this;
|
||||
}
|
||||
|
||||
// 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;
|
||||
YYCC_IMPL_MOVE_CTOR(WinFileFilters, rhs) : m_WinFilters(std::move(rhs.m_WinFilters)), m_WinDataStruct() {
|
||||
// In theory, there is no update should perform,
|
||||
// however we do it because there is no guarantee that no memory allocation during this move.
|
||||
this->update();
|
||||
}
|
||||
|
||||
// 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;
|
||||
YYCC_IMPL_MOVE_OPER(WinFileFilters, rhs) {
|
||||
// Move data
|
||||
this->m_WinFilters = std::move(rhs.m_WinFilters);
|
||||
this->m_WinDataStruct.clear();
|
||||
// Same reason for updating
|
||||
this->update();
|
||||
// Return self
|
||||
return *this;
|
||||
}
|
||||
|
||||
// append new pair
|
||||
win_result.m_WinFilters.emplace_back(std::make_pair(name, modes));
|
||||
UINT WinFileFilters::get_filter_count() const {
|
||||
// We have check this length when building this class,
|
||||
// so we can safely and directly cast it in there.
|
||||
return static_cast<UINT>(m_WinFilters.size());
|
||||
}
|
||||
|
||||
const COMDLG_FILTERSPEC* WinFileFilters::get_filter_specs() const {
|
||||
return m_WinDataStruct.data();
|
||||
}
|
||||
|
||||
void WinFileFilters::update() {
|
||||
// Make sure they have same size
|
||||
auto n = m_WinFilters.size();
|
||||
m_WinDataStruct.resize(n);
|
||||
|
||||
// Assign data one by one
|
||||
for (auto i = 0; i < n; ++i) {
|
||||
// Fetch item
|
||||
const auto& filter = m_WinFilters[i];
|
||||
auto& data_struct = m_WinDataStruct[i];
|
||||
// Assign pointer
|
||||
data_struct.pszName = filter.first.c_str();
|
||||
data_struct.pszSpec = filter.second.c_str();
|
||||
}
|
||||
|
||||
// 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
|
||||
#pragma region FileFilters
|
||||
|
||||
bool FileDialog::Generate(WinFileDialog& win_result) const {
|
||||
// clear Windows oriented data
|
||||
win_result.Clear();
|
||||
FileFilters::FileFilters() : m_Filters() {}
|
||||
|
||||
FileFilters::~FileFilters() {}
|
||||
|
||||
void FileFilters::add_filter(const std::u8string_view& filter_name, std::initializer_list<std::u8string_view> il) {
|
||||
// assign filter name
|
||||
FilterName name(filter_name);
|
||||
// check filter name
|
||||
if (name.empty()) {
|
||||
throw std::invalid_argument("filter name is empty");
|
||||
}
|
||||
|
||||
// assign filter patterns
|
||||
FilterModes modes;
|
||||
for (const auto& item : il) {
|
||||
modes.emplace_back(item);
|
||||
}
|
||||
// check filter patterns
|
||||
if (modes.empty()) {
|
||||
throw std::invalid_argument("filter pattern list is empty");
|
||||
}
|
||||
|
||||
// add into pairs and return
|
||||
m_Filters.emplace_back(std::make_pair(std::move(name), std::move(modes)));
|
||||
}
|
||||
|
||||
size_t FileFilters::get_count() const {
|
||||
return m_Filters.size();
|
||||
}
|
||||
|
||||
void FileFilters::clear() {
|
||||
m_Filters.clear();
|
||||
}
|
||||
|
||||
DialogResult<WinFileFilters> FileFilters::to_windows() const {
|
||||
// prepare return value
|
||||
WinFileFilters rv;
|
||||
|
||||
// check filter size
|
||||
// if it overflow the maximum value, return.
|
||||
size_t count = m_Filters.size();
|
||||
if (!SAFECAST::try_to<UINT>(count).has_value()) {
|
||||
return std::unexpected(DialogError::TooManyFilters);
|
||||
}
|
||||
|
||||
// build new Windows oriented string vector
|
||||
for (const auto& item : m_Filters) {
|
||||
// convert name to wchar
|
||||
auto win_name = ENC::to_wchar(item.first);
|
||||
if (!win_name.has_value()) {
|
||||
return std::unexpected(DialogError::BadEncoding);
|
||||
}
|
||||
|
||||
// join pattern string and convert to wchar
|
||||
const auto& modes = item.second;
|
||||
auto joined_modes = OP::join(modes.begin(), modes.end(), u8";");
|
||||
auto win_modes = ENC::to_wchar(joined_modes);
|
||||
if (!win_modes.has_value()) {
|
||||
return std::unexpected(DialogError::BadEncoding);
|
||||
}
|
||||
|
||||
// append this pair
|
||||
rv.m_WinFilters.emplace_back(std::make_pair(win_name.value(), win_modes.value()));
|
||||
}
|
||||
|
||||
// update data struct
|
||||
rv.update();
|
||||
|
||||
// okey, return value
|
||||
// please note that we do not check whether this list is empty in there.
|
||||
// this was done in generic_file_dialog() function.
|
||||
return rv;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region WinFileDialog
|
||||
|
||||
/**
|
||||
* @brief Assistant function for making copy of IShellItem*.
|
||||
* @param[in] obj The object for making copy.
|
||||
* @return Copied COM object.
|
||||
*/
|
||||
static WINCOM::SmartIShellItem duplicate_shell_item(const WINCOM::SmartIShellItem& obj) {
|
||||
// COM object is actually like a std::shared_ptr.
|
||||
// So we simply copy its raw pointer and increase it reference counter if it is not nullptr.
|
||||
WINCOM::SmartIShellItem rv(obj.get());
|
||||
if (rv) rv->AddRef();
|
||||
// Okey, return copied object.
|
||||
return rv;
|
||||
}
|
||||
|
||||
WinFileDialog::WinFileDialog() :
|
||||
m_WinOwner(NULL), m_WinFileTypes(), m_WinDefaultFileTypeIndex(0u), m_WinTitle(std::nullopt), m_WinInitFileName(std::nullopt),
|
||||
m_WinInitDirectory(nullptr) {}
|
||||
|
||||
WinFileDialog::~WinFileDialog() {}
|
||||
|
||||
YYCC_IMPL_COPY_CTOR(WinFileDialog, rhs) :
|
||||
m_WinOwner(rhs.m_WinOwner), m_WinFileTypes(rhs.m_WinFileTypes), m_WinDefaultFileTypeIndex(rhs.m_WinDefaultFileTypeIndex),
|
||||
m_WinTitle(rhs.m_WinTitle), m_WinInitFileName(rhs.m_WinInitFileName),
|
||||
m_WinInitDirectory(duplicate_shell_item(rhs.m_WinInitDirectory)) {}
|
||||
|
||||
YYCC_IMPL_COPY_OPER(WinFileDialog, rhs) {
|
||||
this->m_WinOwner = rhs.m_WinOwner;
|
||||
this->m_WinFileTypes = rhs.m_WinFileTypes;
|
||||
this->m_WinDefaultFileTypeIndex = rhs.m_WinDefaultFileTypeIndex;
|
||||
this->m_WinTitle = rhs.m_WinTitle;
|
||||
this->m_WinInitFileName = rhs.m_WinInitFileName;
|
||||
this->m_WinInitDirectory = duplicate_shell_item(rhs.m_WinInitDirectory);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool WinFileDialog::has_owner() const {
|
||||
return m_WinOwner != NULL;
|
||||
}
|
||||
|
||||
HWND WinFileDialog::get_owner() const {
|
||||
if (!has_owner()) throw std::logic_error("fetch not set owner");
|
||||
return m_WinOwner;
|
||||
}
|
||||
|
||||
bool WinFileDialog::has_title() const {
|
||||
return m_WinTitle.has_value();
|
||||
}
|
||||
|
||||
const wchar_t* WinFileDialog::get_title() const {
|
||||
if (!has_title()) throw std::logic_error("fetch not set title");
|
||||
return m_WinTitle.value().c_str();
|
||||
}
|
||||
|
||||
bool WinFileDialog::has_init_file_name() const {
|
||||
return m_WinInitFileName.has_value();
|
||||
}
|
||||
|
||||
const wchar_t* WinFileDialog::get_init_file_name() const {
|
||||
if (!has_init_file_name()) throw std::logic_error("fetch not set init file name");
|
||||
return m_WinInitFileName.value().c_str();
|
||||
}
|
||||
|
||||
bool WinFileDialog::has_init_directory() const {
|
||||
return m_WinInitDirectory.get() != nullptr;
|
||||
}
|
||||
|
||||
IShellItem* WinFileDialog::get_init_directory() const {
|
||||
if (!has_init_file_name()) throw std::logic_error("fetch not set init directory");
|
||||
return m_WinInitDirectory.get();
|
||||
}
|
||||
|
||||
const WinFileFilters& WinFileDialog::get_file_types() const {
|
||||
return m_WinFileTypes;
|
||||
}
|
||||
|
||||
UINT WinFileDialog::get_default_file_type_index() const {
|
||||
// This index has been checked so we return it directly.
|
||||
return m_WinDefaultFileTypeIndex;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region FileDialog
|
||||
|
||||
FileDialog::FileDialog() :
|
||||
m_Owner(NULL), m_FileTypes(), m_DefaultFileTypeIndex(0u), m_Title(std::nullopt), m_InitFileName(std::nullopt),
|
||||
m_InitDirectory(std::nullopt) {}
|
||||
|
||||
FileDialog::~FileDialog() {}
|
||||
|
||||
void FileDialog::set_owner(HWND owner) {
|
||||
m_Owner = owner;
|
||||
}
|
||||
|
||||
void FileDialog::set_title(const std::u8string_view& title) {
|
||||
m_Title = title;
|
||||
}
|
||||
|
||||
void FileDialog::unset_title() {
|
||||
m_Title = std::nullopt;
|
||||
}
|
||||
|
||||
void FileDialog::set_init_file_name(const std::u8string_view& init_filename) {
|
||||
m_InitFileName = init_filename;
|
||||
}
|
||||
|
||||
void FileDialog::unset_init_file_name() {
|
||||
m_InitFileName = std::nullopt;
|
||||
}
|
||||
|
||||
void FileDialog::set_init_directory(const std::u8string_view& init_dir) {
|
||||
m_InitDirectory = init_dir;
|
||||
}
|
||||
|
||||
void FileDialog::unset_init_directory() {
|
||||
m_InitDirectory = std::nullopt;
|
||||
}
|
||||
|
||||
FileFilters& FileDialog::configre_file_types() {
|
||||
return m_FileTypes;
|
||||
}
|
||||
|
||||
void FileDialog::set_default_file_type_index(size_t idx) {
|
||||
m_DefaultFileTypeIndex = idx;
|
||||
}
|
||||
|
||||
void FileDialog::clear() {
|
||||
m_Owner = NULL;
|
||||
m_Title = std::nullopt;
|
||||
m_InitFileName = std::nullopt;
|
||||
m_InitDirectory = std::nullopt;
|
||||
m_FileTypes.clear();
|
||||
m_DefaultFileTypeIndex = 0u;
|
||||
}
|
||||
|
||||
DialogResult<WinFileDialog> FileDialog::to_windows() const {
|
||||
// prepare return value
|
||||
WinFileDialog rv;
|
||||
|
||||
// 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);
|
||||
rv.m_WinOwner = m_Owner;
|
||||
|
||||
// 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;
|
||||
}
|
||||
if (m_Title.has_value()) {
|
||||
auto win_title = ENC::to_wchar(m_Title.value());
|
||||
if (!win_title.has_value()) return std::unexpected(DialogError::BadEncoding);
|
||||
else rv.m_WinTitle = win_title.value();
|
||||
} else rv.m_WinTitle = std::nullopt;
|
||||
if (m_InitFileName.has_value()) {
|
||||
auto win_init_file_name = ENC::to_wchar(m_InitFileName.value());
|
||||
if (!win_init_file_name.has_value()) return std::unexpected(DialogError::BadEncoding);
|
||||
else rv.m_WinInitFileName = win_init_file_name.value();
|
||||
} else rv.m_WinInitFileName = std::nullopt;
|
||||
|
||||
// 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;
|
||||
if (m_InitDirectory.has_value()) {
|
||||
// convert to wchar path
|
||||
auto w_init_dir = ENC::to_wchar(m_InitDirectory.value());
|
||||
if (!w_init_dir.has_value()) {
|
||||
return std::unexpected(DialogError::BadEncoding);
|
||||
}
|
||||
|
||||
// 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;
|
||||
HRESULT hr = SHCreateItemFromParsingName(w_init_dir.value().c_str(), NULL, IID_PPV_ARGS(&init_directory));
|
||||
if (FAILED(hr)) return std::unexpected(DialogError::NoSuchDir);
|
||||
|
||||
// assign IShellItem*
|
||||
win_result.m_WinInitDirectory.reset(init_directory);
|
||||
rv.m_WinInitDirectory.reset(init_directory);
|
||||
} else rv.m_WinInitDirectory.reset();
|
||||
|
||||
// build file filters
|
||||
auto win_file_types = m_FileTypes.to_windows();
|
||||
if (!win_file_types.has_value()) return std::unexpected(win_file_types.error());
|
||||
else rv.m_WinFileTypes = std::move(win_file_types.value());
|
||||
|
||||
// check and assign default file type index
|
||||
// check whether it can be safely casted into UINT.
|
||||
auto win_def_index_base0 = SAFECAST::try_to<UINT>(m_DefaultFileTypeIndex);
|
||||
if (!win_def_index_base0.has_value()) {
|
||||
return std::unexpected(DialogError::IndexOverflow);
|
||||
}
|
||||
// check whether it can safely make addition with 1.
|
||||
auto win_def_index = SAFEOP::checked_add<UINT>(win_def_index_base0.value(), 1);
|
||||
if (!win_def_index.has_value()) {
|
||||
return std::unexpected(DialogError::IndexOverflow);
|
||||
}
|
||||
// okey, assign it.
|
||||
// please note we do not check the relation between this variable and file filters.
|
||||
// this was done in generic_file_dialog() function.
|
||||
rv.m_WinDefaultFileTypeIndex = win_def_index.value();
|
||||
|
||||
// everything is okey
|
||||
return true;
|
||||
return rv;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Windows Dialog Code
|
||||
#pragma region Exposed Functions
|
||||
|
||||
enum class CommonFileDialogType { OpenFile, OpenMultipleFiles, SaveFile, OpenFolder };
|
||||
enum class GenericFileDialogType { OpenFile, OpenFiles, 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) {
|
||||
* @brief Extract display name from given IShellItem*.
|
||||
* @param[in] item The pointer to IShellItem for extracting.
|
||||
* @return Extract display name, or error occurs.
|
||||
* @remarks This is an assist function of generic_file_dialog().
|
||||
*/
|
||||
static DialogResult<std::u8string> extract_display_name(IShellItem* item) {
|
||||
// fetch display name from IShellItem*
|
||||
LPWSTR _name;
|
||||
HRESULT hr = item->GetDisplayName(SIGDN_FILESYSPATH, &_name);
|
||||
if (FAILED(hr)) return false;
|
||||
COMHelper::SmartLPWSTR display_name(_name);
|
||||
LPWSTR display_name_ptr;
|
||||
HRESULT hr = item->GetDisplayName(SIGDN_FILESYSPATH, &display_name_ptr);
|
||||
if (FAILED(hr)) return std::unexpected(DialogError::BadCOMCall);
|
||||
WINCOM::SmartLPWSTR display_name(display_name_ptr);
|
||||
|
||||
// convert result
|
||||
if (!YYCC::EncodingHelper::WcharToUTF8(display_name.get(), ret)) return false;
|
||||
|
||||
// finished
|
||||
return true;
|
||||
// convert result and return
|
||||
return ENC::to_utf8(display_name.get()).transform_error([](auto err) { return DialogError::BadEncoding; });
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
* @brief Generic file dialog.
|
||||
* @details This function is the real underlying function of all dialog functions.
|
||||
* @param[in] params User specified parameter controlling the behavior of this file dialog, including title, file types and etc.
|
||||
* @return
|
||||
* The full path to user selected files or folders.
|
||||
* For multiple selection, the count of items >= 1. For others, the count of item must be 1.
|
||||
* Or nothing (click "Cancel"), or error occurs.
|
||||
*/
|
||||
template<GenericFileDialogType EDialogType>
|
||||
static DialogResult<DialogOutcome<std::vector<std::u8string>>> generic_file_dialog(const FileDialog& params) {
|
||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog
|
||||
// prepare result variable
|
||||
HRESULT hr;
|
||||
|
||||
// create a const bad com call unexpected result
|
||||
// because it is widely used in this function.
|
||||
constexpr auto BAD_COM_CALL = std::unexpected(DialogError::BadCOMCall);
|
||||
|
||||
// check whether COM environment has been initialized
|
||||
if (!COMHelper::IsInitialized()) return false;
|
||||
if (!WINCOM::is_initialized()) return BAD_COM_CALL;
|
||||
|
||||
// create file dialog instance
|
||||
// fetch dialog CLSID first
|
||||
CLSID dialog_clsid;
|
||||
switch (EDialogType) {
|
||||
case CommonFileDialogType::OpenFile:
|
||||
case CommonFileDialogType::OpenMultipleFiles:
|
||||
case CommonFileDialogType::OpenFolder:
|
||||
case GenericFileDialogType::OpenFile:
|
||||
case GenericFileDialogType::OpenFiles:
|
||||
case GenericFileDialogType::OpenFolder:
|
||||
dialog_clsid = CLSID_FileOpenDialog;
|
||||
break;
|
||||
case CommonFileDialogType::SaveFile:
|
||||
case GenericFileDialogType::SaveFile:
|
||||
dialog_clsid = CLSID_FileSaveDialog;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
throw std::invalid_argument("unknown dialog type");
|
||||
}
|
||||
// create raw dialog pointer
|
||||
IFileDialog* _pfd = nullptr;
|
||||
hr = CoCreateInstance(dialog_clsid, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&_pfd));
|
||||
if (FAILED(hr)) return false;
|
||||
IFileDialog* raw_pfd = nullptr;
|
||||
hr = CoCreateInstance(dialog_clsid, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&raw_pfd));
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
// create memory-safe dialog pointer
|
||||
COMHelper::SmartIFileDialog pfd(_pfd);
|
||||
WINCOM::SmartIFileDialog pfd(raw_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;
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
// 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:
|
||||
case GenericFileDialogType::OpenFile:
|
||||
dwFlags |= FOS_FORCEFILESYSTEM;
|
||||
dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR;
|
||||
break;
|
||||
case CommonFileDialogType::OpenMultipleFiles:
|
||||
case GenericFileDialogType::OpenFiles:
|
||||
dwFlags |= FOS_FORCEFILESYSTEM;
|
||||
dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR;
|
||||
dwFlags |= FOS_ALLOWMULTISELECT;
|
||||
break;
|
||||
case CommonFileDialogType::SaveFile:
|
||||
case GenericFileDialogType::SaveFile:
|
||||
dwFlags |= FOS_FORCEFILESYSTEM;
|
||||
dwFlags |= FOS_OVERWRITEPROMPT | FOS_NOREADONLYRETURN | FOS_PATHMUSTEXIST | FOS_NOCHANGEDIR;
|
||||
break;
|
||||
case CommonFileDialogType::OpenFolder:
|
||||
case GenericFileDialogType::OpenFolder:
|
||||
dwFlags |= FOS_FORCEFILESYSTEM;
|
||||
dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR;
|
||||
dwFlags |= FOS_PICKFOLDERS;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
throw std::invalid_argument("unknown dialog type");
|
||||
}
|
||||
// set folder dialog options
|
||||
hr = pfd->SetOptions(dwFlags);
|
||||
if (FAILED(hr)) return false;
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
|
||||
// build Windows used file dialog parameters
|
||||
WinFileDialog win_params;
|
||||
if (!params.Generate(win_params)) return false;
|
||||
auto pending_win_params = params.to_windows();
|
||||
if (!pending_win_params.has_value()) return std::unexpected(pending_win_params.error());
|
||||
WinFileDialog win_params(std::move(pending_win_params.value()));
|
||||
|
||||
// setup title and init file name
|
||||
if (win_params.HasTitle()) {
|
||||
hr = pfd->SetTitle(win_params.GetTitle());
|
||||
if (FAILED(hr)) return false;
|
||||
if (win_params.has_title()) {
|
||||
hr = pfd->SetTitle(win_params.get_title());
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
}
|
||||
if (win_params.HasInitFileName()) {
|
||||
hr = pfd->SetFileName(win_params.GetInitFileName());
|
||||
if (FAILED(hr)) return false;
|
||||
if (win_params.has_init_file_name()) {
|
||||
hr = pfd->SetFileName(win_params.get_init_file_name());
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
}
|
||||
|
||||
// setup init directory
|
||||
if (win_params.HasInitDirectory()) {
|
||||
hr = pfd->SetFolder(win_params.GetInitDirectory());
|
||||
if (win_params.has_init_directory()) {
|
||||
hr = pfd->SetFolder(win_params.get_init_directory());
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
}
|
||||
|
||||
// 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;
|
||||
if constexpr (EDialogType != GenericFileDialogType::OpenFolder) {
|
||||
// fetch file filters
|
||||
const auto& file_filters = win_params.get_file_types();
|
||||
|
||||
// first, check whether file filters is empty
|
||||
if (file_filters.get_filter_count() == 0) {
|
||||
return std::unexpected(DialogError::EmptyFilters);
|
||||
}
|
||||
// then validate index
|
||||
if (win_params.get_default_file_type_index() > file_filters.get_filter_count()) {
|
||||
return std::unexpected(DialogError::IndexOutOfRange);
|
||||
}
|
||||
|
||||
// set file types list
|
||||
hr = pfd->SetFileTypes(file_filters.get_filter_count(), file_filters.get_filter_specs());
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
// set default file type index
|
||||
hr = pfd->SetFileTypeIndex(win_params.GetDefaultFileTypeIndex());
|
||||
if (FAILED(hr)) return false;
|
||||
hr = pfd->SetFileTypeIndex(win_params.get_default_file_type_index());
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
}
|
||||
|
||||
// show the dialog
|
||||
hr = pfd->Show(win_params.HasOwner() ? win_params.GetOwner() : nullptr);
|
||||
if (FAILED(hr)) return false;
|
||||
// show the dialog and return if user click "Cancel"
|
||||
hr = pfd->Show(win_params.has_owner() ? win_params.get_owner() : NULL);
|
||||
if (FAILED(hr)) return std::nullopt;
|
||||
|
||||
// prepare return value
|
||||
std::vector<std::u8string> rv;
|
||||
// obtain result when user click "OK" button.
|
||||
switch (EDialogType) {
|
||||
case CommonFileDialogType::OpenFile:
|
||||
case CommonFileDialogType::OpenFolder:
|
||||
case CommonFileDialogType::SaveFile: {
|
||||
case GenericFileDialogType::OpenFile:
|
||||
case GenericFileDialogType::OpenFolder:
|
||||
case GenericFileDialogType::SaveFile: {
|
||||
// obtain one file entry
|
||||
IShellItem* _item;
|
||||
hr = pfd->GetResult(&_item);
|
||||
if (FAILED(hr)) return false;
|
||||
COMHelper::SmartIShellItem result_item(_item);
|
||||
IShellItem* raw_item;
|
||||
hr = pfd->GetResult(&raw_item);
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
WINCOM::SmartIShellItem item(raw_item);
|
||||
|
||||
// extract display name
|
||||
yycc_u8string result_name;
|
||||
if (!ExtractDisplayName(result_item.get(), result_name)) return false;
|
||||
auto display_name = extract_display_name(item.get());
|
||||
if (!display_name.has_value()) return std::unexpected(display_name.error());
|
||||
|
||||
// append result
|
||||
ret.emplace_back(std::move(result_name));
|
||||
rv.emplace_back(std::move(display_name.value()));
|
||||
} break;
|
||||
case CommonFileDialogType::OpenMultipleFiles: {
|
||||
case GenericFileDialogType::OpenFiles: {
|
||||
// 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);
|
||||
IFileOpenDialog* raw_pfod = nullptr;
|
||||
hr = pfd->QueryInterface(IID_PPV_ARGS(&raw_pfod));
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
WINCOM::SmartIFileOpenDialog pfod(raw_pfod);
|
||||
|
||||
// obtain multiple file entires
|
||||
IShellItemArray* _items;
|
||||
hr = pfod->GetResults(&_items);
|
||||
if (FAILED(hr)) return false;
|
||||
COMHelper::SmartIShellItemArray result_items(_items);
|
||||
IShellItemArray* raw_items;
|
||||
hr = pfod->GetResults(&raw_items);
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
WINCOM::SmartIShellItemArray items(raw_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;
|
||||
DWORD items_count = 0u;
|
||||
hr = items->GetCount(&items_count);
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
// iterate array
|
||||
for (DWORD i = 0u; i < result_items_count; ++i) {
|
||||
for (DWORD i = 0u; i < 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);
|
||||
IShellItem* raw_item;
|
||||
hr = items->GetItemAt(i, &raw_item);
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
WINCOM::SmartIShellItem item(raw_item);
|
||||
|
||||
// extract display name
|
||||
yycc_u8string result_name;
|
||||
if (!ExtractDisplayName(result_item.get(), result_name)) return false;
|
||||
auto display_name = extract_display_name(item.get());
|
||||
if (!display_name.has_value()) return std::unexpected(display_name.error());
|
||||
|
||||
// append result
|
||||
ret.emplace_back(std::move(result_name));
|
||||
rv.emplace_back(std::move(display_name.value()));
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
return false;
|
||||
throw std::invalid_argument("unknown dialog type");
|
||||
}
|
||||
|
||||
// everything is okey
|
||||
return true;
|
||||
return rv;
|
||||
}
|
||||
|
||||
#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;
|
||||
/**
|
||||
* @brief Assistant function for extracting item from given value returned by generic file dialog.
|
||||
* @details This function will check whether inner vector only contain one item.
|
||||
* @param[in] rhs The return value to be extracted.
|
||||
* @return Extracted return value.
|
||||
*/
|
||||
static DialogResult<DialogOutcome<std::u8string>> transform_generic_rv(DialogResult<DialogOutcome<std::vector<std::u8string>>>&& rhs) {
|
||||
if (rhs.has_value()) {
|
||||
const auto& inner = rhs.value();
|
||||
if (inner.has_value()) {
|
||||
const auto& vec = inner.value();
|
||||
if (vec.size() != 1u) throw std::logic_error("return value doesn't contain exactly one item");
|
||||
else return vec.front();
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
} else {
|
||||
return std::unexpected(rhs.error());
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
DialogResult<DialogOutcome<std::u8string>> open_file(const FileDialog& params){
|
||||
return transform_generic_rv(generic_file_dialog<GenericFileDialogType::OpenFile>(params));
|
||||
}
|
||||
|
||||
DialogResult<DialogOutcome<std::vector<std::u8string>>> open_files(const FileDialog& params) {
|
||||
return generic_file_dialog<GenericFileDialogType::OpenFiles>(params);
|
||||
}
|
||||
|
||||
DialogResult<DialogOutcome<std::u8string>> save_file(const FileDialog& params) {
|
||||
return transform_generic_rv(generic_file_dialog<GenericFileDialogType::SaveFile>(params));
|
||||
}
|
||||
|
||||
DialogResult<DialogOutcome<std::u8string>> open_folder(const FileDialog& params) {
|
||||
return transform_generic_rv(generic_file_dialog<GenericFileDialogType::OpenFolder>(params));
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
@ -6,14 +6,19 @@
|
||||
#include "../macro/class_copy_move.hpp"
|
||||
#include "com.hpp"
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <initializer_list>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
|
||||
#include "import_guard_head.hpp"
|
||||
#include <Windows.h>
|
||||
#include <shlobj_core.h>
|
||||
#include "import_guard_tail.hpp"
|
||||
|
||||
#define NS_YYCC_WINDOWS_COM ::yycc::windows::com
|
||||
|
||||
/**
|
||||
* @brief The namespace providing Windows universal dialog features.
|
||||
* @details
|
||||
@ -22,283 +27,291 @@
|
||||
*/
|
||||
namespace yycc::windows::dialog {
|
||||
|
||||
/// @brief The error occurs in this module.
|
||||
enum class DialogError {
|
||||
BadEncoding, ///< Error occurs when perform encoding convertion.
|
||||
TooManyFilters, ///< The size of file filters list is too large for Windows.
|
||||
IndexOverflow, ///< Default filter index is too large for Windows.
|
||||
EmptyFilters, ///< File filters is empty when picking file.
|
||||
IndexOutOfRange, ///< Default filter index is out of range of filters list.
|
||||
NoSuchDir, ///< Given initial directory path is invalid.
|
||||
BadCOMCall, ///< Some COM function calls failed.
|
||||
};
|
||||
|
||||
/// @brief The result type used in this module.
|
||||
template<typename T>
|
||||
using DialogResult = std::expected<T, DialogError>;
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
* @brief The class representing the file types region in file dialog.
|
||||
* @details
|
||||
* This class is private and 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;
|
||||
public:
|
||||
WinFileFilters();
|
||||
~WinFileFilters();
|
||||
YYCC_DECL_COPY_MOVE(WinFileFilters)
|
||||
|
||||
/// @brief Clear all current file filters
|
||||
void Clear() {
|
||||
m_WinDataStruct.reset();
|
||||
m_WinFilters.clear();
|
||||
}
|
||||
public:
|
||||
/**
|
||||
* @brief Get the count of available file filters
|
||||
* @return Count of file filters.
|
||||
*/
|
||||
UINT get_filter_count() const;
|
||||
/**
|
||||
* @brief Get pointer to Windows used file filters declarations
|
||||
* @return Pointer for Windows use.
|
||||
*/
|
||||
const COMDLG_FILTERSPEC* get_filter_specs() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Update COMDLG_FILTERSPEC according to file filter list.
|
||||
* @remarks Programmer \b MUST call this function after you modify m_WinFilters.
|
||||
*/
|
||||
void update();
|
||||
|
||||
private:
|
||||
std::vector<WinFilterPair> m_WinFilters;
|
||||
std::vector<COMDLG_FILTERSPEC> m_WinDataStruct;
|
||||
};
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
* @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>;
|
||||
|
||||
public:
|
||||
FileFilters();
|
||||
~FileFilters();
|
||||
YYCC_DEFAULT_COPY_MOVE(FileFilters)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @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 a string representing a single filter pattern.
|
||||
* This list at least should have one pattern.
|
||||
* @return True if added success, otherwise false.
|
||||
* @warning This function will not validate the content of these filter patterns, so please write them carefully.
|
||||
* @exception std::invalid_argument Given filtern name is blank, or filter patterns is empty.
|
||||
*/
|
||||
void add_filter(const std::u8string_view& filter_name, std::initializer_list<std::u8string_view> il);
|
||||
/**
|
||||
* @brief Get the count of added file filters.
|
||||
* @return The count of added file filters.
|
||||
*/
|
||||
size_t get_count() const;
|
||||
/**
|
||||
* @brief Clear filter pairs for following re-use.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief Build Windows used file filters struct.
|
||||
* @return Built Windows used struct, or error occurs.
|
||||
*/
|
||||
DialogResult<WinFileFilters> to_windows() const;
|
||||
|
||||
private:
|
||||
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.
|
||||
*/
|
||||
* @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)
|
||||
WinFileDialog();
|
||||
~WinFileDialog();
|
||||
YYCC_DECL_COPY(WinFileDialog)
|
||||
YYCC_DEFAULT_MOVE(WinFileDialog)
|
||||
|
||||
/// @brief Get whether this dialog has owner.
|
||||
bool HasOwner() const { return m_WinOwner != NULL; }
|
||||
bool has_owner() const;
|
||||
/// @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; }
|
||||
|
||||
HWND get_owner() const;
|
||||
/// @brief Get whether dialog has custom title.
|
||||
bool HasTitle() const { return m_HasTitle; }
|
||||
bool has_title() const;
|
||||
/// @brief Get custom title of dialog.
|
||||
const wchar_t* GetTitle() const { return m_WinTitle.c_str(); }
|
||||
const wchar_t* get_title() const;
|
||||
/// @brief Get whether dialog has custom initial file name.
|
||||
bool HasInitFileName() const { return m_HasInitFileName; }
|
||||
bool has_init_file_name() const;
|
||||
/// @brief Get custom initial file name of dialog
|
||||
const wchar_t* GetInitFileName() const { return m_WinInitFileName.c_str(); }
|
||||
|
||||
const wchar_t* get_init_file_name() const;
|
||||
/// @brief Get whether dialog has custom initial directory.
|
||||
bool HasInitDirectory() const { return m_WinInitDirectory.get() != nullptr; }
|
||||
bool has_init_directory() const;
|
||||
/// @brief Get custom initial directory of dialog.
|
||||
IShellItem* GetInitDirectory() const { return m_WinInitDirectory.get(); }
|
||||
IShellItem* get_init_directory() const;
|
||||
/// @brief Get the struct holding Windows used file filters data.
|
||||
const WinFileFilters& get_file_types() const;
|
||||
/// @brief Get the index of default selected file filter.
|
||||
UINT get_default_file_type_index() const;
|
||||
|
||||
protected:
|
||||
private:
|
||||
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.
|
||||
*/
|
||||
* @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();
|
||||
}
|
||||
std::optional<std::wstring> m_WinTitle, m_WinInitFileName;
|
||||
NS_YYCC_WINDOWS_COM::SmartIShellItem m_WinInitDirectory;
|
||||
};
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
* @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)
|
||||
FileDialog();
|
||||
~FileDialog();
|
||||
YYCC_DEFAULT_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 the owner of dialog.
|
||||
* @param[in] owner The \c HWND pointing to the owner of dialog, or NULL to remove owner.
|
||||
*/
|
||||
void set_owner(HWND 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 Set custom title of dialog
|
||||
* @param[in] title The string pointer to custom title.
|
||||
*/
|
||||
void set_title(const std::u8string_view& 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 Remove custom title of dialog (keep system default)
|
||||
*/
|
||||
void unset_title();
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
void set_init_file_name(const std::u8string_view& init_filename);
|
||||
/**
|
||||
* @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 Remove custom initial file name of dialog (keep system default)
|
||||
*/
|
||||
void unset_init_file_name();
|
||||
/**
|
||||
* @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 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.
|
||||
*/
|
||||
void set_init_directory(const std::u8string_view& init_dir);
|
||||
/**
|
||||
* @brief Remove custom initial directory of dialog (keep system default)
|
||||
*/
|
||||
void unset_init_directory();
|
||||
/**
|
||||
* @brief Fetch the struct describing file filters for future configuration.
|
||||
* @return The reference to the struct describing file filters.
|
||||
*/
|
||||
FileFilters& configre_file_types();
|
||||
/**
|
||||
* @brief Set the index of default selected file filter.
|
||||
* @param[in] idx
|
||||
* The index to default file filter.
|
||||
* This must be a valid index in file filters.
|
||||
*/
|
||||
void set_default_file_type_index(size_t idx);
|
||||
/**
|
||||
* @brief Clear file dialog parameters for following re-use.
|
||||
*/
|
||||
void 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 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;
|
||||
* @private
|
||||
* @brief Build Windows used dialog info struct.
|
||||
* @return Built Windows used struct, or error occurs.
|
||||
*/
|
||||
DialogResult<WinFileDialog> to_windows() const;
|
||||
|
||||
protected:
|
||||
HWND m_Owner;
|
||||
bool m_HasTitle, m_HasInitFileName, m_HasInitDirectory;
|
||||
std::u8string m_Title, m_InitFileName, m_InitDirectory;
|
||||
std::optional<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.
|
||||
*/
|
||||
* @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 The result after user close the dialog.
|
||||
* @details
|
||||
* It is an alias to \c std::optional.
|
||||
* If it do not have value, it means that user click "Cancel" button.
|
||||
* otherwise it contain a value that user selected in dialog.
|
||||
*/
|
||||
template<typename T>
|
||||
using DialogOutcome = std::optional<T>;
|
||||
|
||||
/**
|
||||
* @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 open.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @return Full path to user selected file, or nothing (click "Cancel"), or error occurs.
|
||||
*/
|
||||
DialogResult<DialogOutcome<std::u8string>> open_file(const FileDialog& params);
|
||||
/**
|
||||
* @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 multiple file to open.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @return The list of full path of user selected files, or nothing (click "Cancel"), or error occurs.
|
||||
*/
|
||||
DialogResult<DialogOutcome<std::vector<std::u8string>>> open_files(const FileDialog& params);
|
||||
/**
|
||||
* @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);
|
||||
* @brief Open the dialog which order user select single file to save.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @return Full path to user selected file, or nothing (click "Cancel"), or error occurs.
|
||||
*/
|
||||
DialogResult<DialogOutcome<std::u8string>> save_file(const FileDialog& params);
|
||||
/**
|
||||
* @brief Open the dialog which order user select single directory to open.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @return Full path to user selected directory, or nothing (click "Cancel"), or error occurs.
|
||||
*/
|
||||
DialogResult<DialogOutcome<std::u8string>> open_folder(const FileDialog& params);
|
||||
|
||||
}
|
||||
|
||||
#undef NS_YYCC_WINDOWS_COM
|
||||
|
||||
#endif
|
||||
|
@ -19,8 +19,9 @@ PRIVATE
|
||||
yycc/patch/ptr_pad.cpp
|
||||
yycc/patch/fopen.cpp
|
||||
yycc/rust/env.cpp
|
||||
yycc/string/op.cpp
|
||||
yycc/string/reinterpret.cpp
|
||||
yycc/string/op.cpp
|
||||
yycc/string/stream.cpp
|
||||
yycc/num/parse.cpp
|
||||
yycc/num/stringify.cpp
|
||||
yycc/num/op.cpp
|
||||
@ -30,8 +31,9 @@ PRIVATE
|
||||
yycc/encoding/windows.cpp
|
||||
yycc/encoding/iconv.cpp
|
||||
yycc/windows/com.cpp
|
||||
#yycc/windows/dialog.cpp
|
||||
yycc/windows/dialog.cpp
|
||||
yycc/windows/winfct.cpp
|
||||
yycc/windows/console.cpp
|
||||
|
||||
yycc/carton/pycodec.cpp
|
||||
yycc/carton/termcolor.cpp
|
||||
|
@ -7,94 +7,6 @@ namespace Console = YYCC::ConsoleHelper;
|
||||
|
||||
namespace YYCCTestbench {
|
||||
|
||||
#pragma region Unicode Test Data
|
||||
|
||||
// UNICODE Test Strings
|
||||
// Ref: https://stackoverflow.com/questions/478201/how-to-test-an-application-for-correct-encoding-e-g-utf-8
|
||||
#define TEST_UNICODE_STR_JAPAN "\u30E6\u30FC\u30B6\u30FC\u5225\u30B5\u30A4\u30C8"
|
||||
#define TEST_UNICODE_STR_CHINA "\u7B80\u4F53\u4E2D\u6587"
|
||||
#define TEST_UNICODE_STR_KOREA "\uD06C\uB85C\uC2A4 \uD50C\uB7AB\uD3FC\uC73C\uB85C"
|
||||
#define TEST_UNICODE_STR_ISRAEL "\u05DE\u05D3\u05D5\u05E8\u05D9\u05DD \u05DE\u05D1\u05D5\u05E7\u05E9\u05D9\u05DD"
|
||||
#define TEST_UNICODE_STR_EGYPT "\u0623\u0641\u0636\u0644 \u0627\u0644\u0628\u062D\u0648\u062B"
|
||||
#define TEST_UNICODE_STR_GREECE "\u03A3\u1F72 \u03B3\u03BD\u03C9\u03C1\u03AF\u03B6\u03C9 \u1F00\u03C0\u1F78"
|
||||
#define TEST_UNICODE_STR_RUSSIA "\u0414\u0435\u0441\u044F\u0442\u0443\u044E \u041C\u0435\u0436\u0434\u0443\u043D\u0430\u0440\u043E\u0434\u043D\u0443\u044E"
|
||||
#define TEST_UNICODE_STR_THAILAND "\u0E41\u0E1C\u0E48\u0E19\u0E14\u0E34\u0E19\u0E2E\u0E31\u0E48\u0E19\u0E40\u0E2A\u0E37\u0E48\u0E2D\u0E21\u0E42\u0E17\u0E23\u0E21\u0E41\u0E2A\u0E19\u0E2A\u0E31\u0E07\u0E40\u0E27\u0E0A"
|
||||
#define TEST_UNICODE_STR_FRANCE "fran\u00E7ais langue \u00E9trang\u00E8re"
|
||||
#define TEST_UNICODE_STR_SPAIN "ma\u00F1ana ol\u00E9"
|
||||
#define TEST_UNICODE_STR_MATHMATICS "\u222E E\u22C5da = Q, n \u2192 \u221E, \u2211 f(i) = \u220F g(i)"
|
||||
#define TEST_UNICODE_STR_EMOJI "\U0001F363 \u2716 \U0001F37A" // sushi x beer mug
|
||||
|
||||
#define CONCAT(prefix, strl) prefix ## strl
|
||||
#define CPP_U8_LITERAL(strl) YYCC_U8(strl)
|
||||
#define CPP_U16_LITERAL(strl) CONCAT(u, strl)
|
||||
#define CPP_U32_LITERAL(strl) CONCAT(U, strl)
|
||||
#define CPP_WSTR_LITERAL(strl) CONCAT(L, strl)
|
||||
|
||||
static std::vector<YYCC::yycc_u8string> c_UTF8TestStrTable {
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_JAPAN),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_CHINA),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_KOREA),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_ISRAEL),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_EGYPT),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_GREECE),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_RUSSIA),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_THAILAND),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_FRANCE),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_SPAIN),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_MATHMATICS),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_EMOJI),
|
||||
};
|
||||
static std::vector<std::wstring> c_WStrTestStrTable {
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_JAPAN),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_CHINA),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_KOREA),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_ISRAEL),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_EGYPT),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_GREECE),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_RUSSIA),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_THAILAND),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_FRANCE),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_SPAIN),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_MATHMATICS),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_EMOJI),
|
||||
};
|
||||
static std::vector<std::u16string> c_UTF16TestStrTable {
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_JAPAN),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_CHINA),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_KOREA),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_ISRAEL),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_EGYPT),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_GREECE),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_RUSSIA),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_THAILAND),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_FRANCE),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_SPAIN),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_MATHMATICS),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_EMOJI),
|
||||
};
|
||||
static std::vector<std::u32string> c_UTF32TestStrTable {
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_JAPAN),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_CHINA),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_KOREA),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_ISRAEL),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_EGYPT),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_GREECE),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_RUSSIA),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_THAILAND),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_FRANCE),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_SPAIN),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_MATHMATICS),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_EMOJI),
|
||||
};
|
||||
|
||||
#undef CPP_WSTR_LITERAL
|
||||
#undef CPP_U32_LITERAL
|
||||
#undef CPP_U16_LITERAL
|
||||
#undef CPP_U8_LITERAL
|
||||
#undef CONCAT
|
||||
|
||||
#pragma endregion
|
||||
|
||||
static void Assert(bool condition, const YYCC::yycc_char8_t* description) {
|
||||
if (condition) {
|
||||
Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_GREEN("OK: %s")), description);
|
||||
@ -105,24 +17,6 @@ namespace YYCCTestbench {
|
||||
}
|
||||
|
||||
static void ConsoleTestbench() {
|
||||
// Color Test
|
||||
Console::EnableColorfulConsole();
|
||||
Console::WriteLine(YYCC_U8("Color Test:"));
|
||||
|
||||
#define TEST_MACRO(col) Console::WriteLine(YYCC_U8("\t" YYCC_COLOR_ ## col ("\u2588\u2588") YYCC_COLOR_LIGHT_ ## col("\u2588\u2588") " " #col " / LIGHT " #col ));
|
||||
// U+2588 is full block
|
||||
|
||||
TEST_MACRO(BLACK);
|
||||
TEST_MACRO(RED);
|
||||
TEST_MACRO(GREEN);
|
||||
TEST_MACRO(YELLOW);
|
||||
TEST_MACRO(BLUE);
|
||||
TEST_MACRO(MAGENTA);
|
||||
TEST_MACRO(CYAN);
|
||||
TEST_MACRO(WHITE);
|
||||
|
||||
#undef TEST_MACRO
|
||||
|
||||
// UTF8 Output Test
|
||||
Console::WriteLine(YYCC_U8("UTF8 Output Test:"));
|
||||
for (const auto& strl : c_UTF8TestStrTable) {
|
||||
@ -142,40 +36,6 @@ namespace YYCCTestbench {
|
||||
|
||||
}
|
||||
|
||||
static void DialogTestbench() {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
YYCC::yycc_u8string ret;
|
||||
std::vector<YYCC::yycc_u8string> rets;
|
||||
|
||||
YYCC::DialogHelper::FileDialog params;
|
||||
auto& filters = params.ConfigreFileTypes();
|
||||
filters.Add(YYCC_U8("Microsoft Word (*.docx; *.doc)"), { YYCC_U8("*.docx"), YYCC_U8("*.doc") });
|
||||
filters.Add(YYCC_U8("Microsoft Excel (*.xlsx; *.xls)"), { YYCC_U8("*.xlsx"), YYCC_U8("*.xls") });
|
||||
filters.Add(YYCC_U8("Microsoft PowerPoint (*.pptx; *.ppt)"), { YYCC_U8("*.pptx"), YYCC_U8("*.ppt") });
|
||||
filters.Add(YYCC_U8("Text File (*.txt)"), { YYCC_U8("*.txt") });
|
||||
filters.Add(YYCC_U8("All Files (*.*)"), { YYCC_U8("*.*") });
|
||||
params.SetDefaultFileTypeIndex(0u);
|
||||
if (YYCC::DialogHelper::OpenFileDialog(params, ret)) {
|
||||
Console::FormatLine(YYCC_U8("Open File: %s"), ret.c_str());
|
||||
}
|
||||
if (YYCC::DialogHelper::OpenMultipleFileDialog(params, rets)) {
|
||||
Console::WriteLine(YYCC_U8("Open Multiple Files:"));
|
||||
for (const auto& item : rets) {
|
||||
Console::FormatLine(YYCC_U8("\t%s"), item.c_str());
|
||||
}
|
||||
}
|
||||
if (YYCC::DialogHelper::SaveFileDialog(params, ret)) {
|
||||
Console::FormatLine(YYCC_U8("Save File: %s"), ret.c_str());
|
||||
}
|
||||
params.Clear();
|
||||
if (YYCC::DialogHelper::OpenFolderDialog(params, ret)) {
|
||||
Console::FormatLine(YYCC_U8("Open Folder: %s"), ret.c_str());
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
static void ExceptionTestbench() {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
@ -210,72 +70,6 @@ namespace YYCCTestbench {
|
||||
#endif
|
||||
}
|
||||
|
||||
static void WinFctTestbench() {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
HMODULE test_current_module;
|
||||
Assert((test_current_module = YYCC::WinFctHelper::GetCurrentModule()) != nullptr, YYCC_U8("YYCC::WinFctHelper::GetCurrentModule"));
|
||||
Console::FormatLine(YYCC_U8("Current Module HANDLE: 0x%" PRI_XPTR_LEFT_PADDING PRIXPTR), test_current_module);
|
||||
|
||||
YYCC::yycc_u8string test_temp;
|
||||
Assert(YYCC::WinFctHelper::GetTempDirectory(test_temp), YYCC_U8("YYCC::WinFctHelper::GetTempDirectory"));
|
||||
Console::FormatLine(YYCC_U8("Temp Directory: %s"), test_temp.c_str());
|
||||
|
||||
YYCC::yycc_u8string test_module_name;
|
||||
Assert(YYCC::WinFctHelper::GetModuleFileName(YYCC::WinFctHelper::GetCurrentModule(), test_module_name), YYCC_U8("YYCC::WinFctHelper::GetModuleFileName"));
|
||||
Console::FormatLine(YYCC_U8("Current Module File Name: %s"), test_module_name.c_str());
|
||||
|
||||
YYCC::yycc_u8string test_localappdata_path;
|
||||
Assert(YYCC::WinFctHelper::GetLocalAppData(test_localappdata_path), YYCC_U8("YYCC::WinFctHelper::GetLocalAppData"));
|
||||
Console::FormatLine(YYCC_U8("Local AppData: %s"), test_localappdata_path.c_str());
|
||||
|
||||
Assert(YYCC::WinFctHelper::IsValidCodePage(static_cast<UINT>(1252)), YYCC_U8("YYCC::WinFctHelper::IsValidCodePage"));
|
||||
Assert(!YYCC::WinFctHelper::IsValidCodePage(static_cast<UINT>(114514)), YYCC_U8("YYCC::WinFctHelper::IsValidCodePage"));
|
||||
|
||||
// MARK: There is no testbench for MoveFile, CopyFile DeleteFile.
|
||||
// Because they can operate file system files.
|
||||
// And may cause test environment entering unstable status.
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
static void StdPatchTestbench() {
|
||||
|
||||
// Std Path
|
||||
|
||||
std::filesystem::path test_path;
|
||||
for (const auto& strl : c_UTF8TestStrTable) {
|
||||
test_path /= YYCC::StdPatch::ToStdPath(strl);
|
||||
}
|
||||
YYCC::yycc_u8string test_slashed_path(YYCC::StdPatch::ToUTF8Path(test_path));
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
std::wstring wdelimiter(1u, std::filesystem::path::preferred_separator);
|
||||
YYCC::yycc_u8string delimiter(YYCC::EncodingHelper::WcharToUTF8(wdelimiter));
|
||||
#else
|
||||
YYCC::yycc_u8string delimiter(1u, std::filesystem::path::preferred_separator);
|
||||
#endif
|
||||
YYCC::yycc_u8string test_joined_path(YYCC::StringHelper::Join(c_UTF8TestStrTable.begin(), c_UTF8TestStrTable.end(), delimiter));
|
||||
|
||||
Assert(test_slashed_path == test_joined_path, YYCC_U8("YYCC::StdPatch::ToStdPath, YYCC::StdPatch::ToUTF8Path"));
|
||||
|
||||
// StartsWith, EndsWith
|
||||
YYCC::yycc_u8string test_starts_ends_with(YYCC_U8("aaabbbccc"));
|
||||
Assert(YYCC::StdPatch::StartsWith(test_starts_ends_with, YYCC_U8("aaa")), YYCC_U8("YYCC::StdPatch::StartsWith"));
|
||||
Assert(!YYCC::StdPatch::StartsWith(test_starts_ends_with, YYCC_U8("ccc")), YYCC_U8("YYCC::StdPatch::StartsWith"));
|
||||
Assert(!YYCC::StdPatch::EndsWith(test_starts_ends_with, YYCC_U8("aaa")), YYCC_U8("YYCC::StdPatch::EndsWith"));
|
||||
Assert(YYCC::StdPatch::EndsWith(test_starts_ends_with, YYCC_U8("ccc")), YYCC_U8("YYCC::StdPatch::EndsWith"));
|
||||
|
||||
// Contains
|
||||
std::set<int> test_set { 1, 2, 3, 4, 6, 7 };
|
||||
Assert(YYCC::StdPatch::Contains(test_set, static_cast<int>(1)), YYCC_U8("YYCC::StdPatch::Contains"));
|
||||
Assert(!YYCC::StdPatch::Contains(test_set, static_cast<int>(5)), YYCC_U8("YYCC::StdPatch::Contains"));
|
||||
std::map<int, float> test_map { { 1, 1.0f }, { 4, 4.0f } };
|
||||
Assert(YYCC::StdPatch::Contains(test_map, static_cast<int>(1)), YYCC_U8("YYCC::StdPatch::Contains"));
|
||||
Assert(!YYCC::StdPatch::Contains(test_map, static_cast<int>(5)), YYCC_U8("YYCC::StdPatch::Contains"));
|
||||
|
||||
}
|
||||
|
||||
enum class TestEnum : int8_t {
|
||||
Test1, Test2, Test3
|
||||
};
|
||||
|
@ -157,7 +157,7 @@ namespace yyccshared::literals {
|
||||
|
||||
#pragma region OtherLiterals Data
|
||||
|
||||
std::vector<OtherLiteral> OTHERLIT_OTHERSTR_VEC{{"\xC4\xE3\xBA\xC3\xD6\xD0\xB9\xFA", UINT32_C(936), "GBK", u8"gbk"}};
|
||||
static std::vector<OtherLiteral> OTHERLIT_OTHERSTR_VEC{{"\xC4\xE3\xBA\xC3\xD6\xD0\xB9\xFA", UINT32_C(936), "GBK", u8"gbk"}};
|
||||
|
||||
#define OTHER_STR_GBK "\u4f60\u597d\u4e2d\u56fd"
|
||||
|
||||
|
@ -1,13 +1,61 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/carton/tabulate.hpp>
|
||||
#include <yycc/string/reinterpret.hpp>
|
||||
#include <sstream>
|
||||
|
||||
#define TABULATE ::yycc::carton::tabulate
|
||||
#define REINTERPRET ::yycc::string::reinterpret
|
||||
|
||||
namespace yycctest::carton::tabulate {
|
||||
|
||||
TEST(CartonTabulate, Main) {
|
||||
|
||||
class CartonTabulate : public ::testing::Test {
|
||||
protected:
|
||||
CartonTabulate() : table(3u), ss() {
|
||||
// setup basic data
|
||||
table.set_prefix(u8"# ");
|
||||
table.set_header({u8"中文1", u8"中文2", u8"中文3"});
|
||||
table.set_bar(u8"===");
|
||||
table.add_row({u8"a", u8"b", u8"c"});
|
||||
}
|
||||
~CartonTabulate() override = default;
|
||||
|
||||
void expected_print(const std::u8string_view& exp) {
|
||||
ss.str("");
|
||||
table.print(ss);
|
||||
EXPECT_EQ(REINTERPRET::as_utf8_view(ss.view()), exp);
|
||||
}
|
||||
|
||||
TABULATE::Tabulate table;
|
||||
std::stringstream ss;
|
||||
};
|
||||
|
||||
TEST_F(CartonTabulate, Full) {
|
||||
table.show_header(true);
|
||||
table.show_bar(true);
|
||||
expected_print(u8"# 中文1 中文2 中文3 \n"
|
||||
u8"# === === === \n"
|
||||
u8"# a b c \n");
|
||||
}
|
||||
|
||||
}
|
||||
TEST_F(CartonTabulate, NoHeader) {
|
||||
table.show_header(false);
|
||||
table.show_bar(true);
|
||||
expected_print(u8"# === === === \n"
|
||||
u8"# a b c \n");
|
||||
}
|
||||
|
||||
TEST_F(CartonTabulate, NoBar) {
|
||||
table.show_header(true);
|
||||
table.show_bar(false);
|
||||
expected_print(u8"# 中文1 中文2 中文3 \n"
|
||||
u8"# a b c \n");
|
||||
}
|
||||
|
||||
TEST_F(CartonTabulate, OnlyData) {
|
||||
table.show_header(false);
|
||||
table.show_bar(false);
|
||||
expected_print(u8"# a b c \n");
|
||||
}
|
||||
|
||||
} // namespace yycctest::carton::tabulate
|
||||
|
31
testbench/yycc/string/stream.cpp
Normal file
31
testbench/yycc/string/stream.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/string/stream.hpp>
|
||||
#include <yycc/string/reinterpret.hpp>
|
||||
#include <sstream>
|
||||
|
||||
#define REINTERPRET ::yycc::string::reinterpret
|
||||
using namespace std::literals::string_view_literals;
|
||||
using namespace ::yycc::string::stream;
|
||||
|
||||
namespace yycctest::string::stream {
|
||||
|
||||
TEST(StringStream, StringView) {
|
||||
std::stringstream ss;
|
||||
ss << u8"hello"sv;
|
||||
EXPECT_EQ(REINTERPRET::as_utf8_view(ss.view()), u8"hello"sv);
|
||||
}
|
||||
|
||||
TEST(StringStream, CStrPtr) {
|
||||
std::stringstream ss;
|
||||
ss << u8"hello";
|
||||
EXPECT_EQ(REINTERPRET::as_utf8_view(ss.view()), u8"hello");
|
||||
}
|
||||
|
||||
TEST(StringStream, Character) {
|
||||
std::stringstream ss;
|
||||
ss << u8'y';
|
||||
EXPECT_EQ(REINTERPRET::as_utf8_view(ss.view()), u8"y");
|
||||
}
|
||||
|
||||
}
|
17
testbench/yycc/windows/console.cpp
Normal file
17
testbench/yycc/windows/console.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/windows/console.hpp>
|
||||
|
||||
#define CONSOLE ::yycc::windows::console
|
||||
|
||||
namespace yycctest::windows::console {
|
||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
|
||||
TEST(WindowsConsole, ColorfulConsole) {
|
||||
// Set colorful console should always be success.
|
||||
auto rv = CONSOLE::colorful_console();
|
||||
EXPECT_TRUE(rv.has_value());
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
@ -7,10 +7,49 @@
|
||||
namespace yycctest::windows::dialog {
|
||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
|
||||
TEST(WindowsDialog, Normal) {
|
||||
// TODO:
|
||||
// I temporaryly disable all dialog open functions in this function after testing them.
|
||||
// Because they need human to operate them to finish the test.
|
||||
// Once I find a better way to do automatic test (maybe send message to these dialogs to close them?)
|
||||
// I will add them back.
|
||||
|
||||
// TEST(WindowsDialog, Normal) {
|
||||
|
||||
// }
|
||||
// Prepare parameters
|
||||
DIALOG::FileDialog params;
|
||||
auto& filters = params.configre_file_types();
|
||||
filters.add_filter(u8"Microsoft Word (*.docx; *.doc)", {u8"*.docx", u8"*.doc"});
|
||||
filters.add_filter(u8"Microsoft Excel (*.xlsx; *.xls)", {u8"*.xlsx", u8"*.xls"});
|
||||
filters.add_filter(u8"Microsoft PowerPoint (*.pptx; *.ppt)", {u8"*.pptx", u8"*.ppt"});
|
||||
filters.add_filter(u8"Text File (*.txt)", {u8"*.txt"});
|
||||
filters.add_filter(u8"All Files (*.*)", {u8"*.*"});
|
||||
params.set_default_file_type_index(1u);
|
||||
|
||||
//// Open file
|
||||
//{
|
||||
// auto rv = DIALOG::open_file(params);
|
||||
// EXPECT_TRUE(rv.has_value());
|
||||
//}
|
||||
//// Open files
|
||||
//{
|
||||
// auto rv = DIALOG::open_files(params);
|
||||
// EXPECT_TRUE(rv.has_value());
|
||||
//}
|
||||
//// Save file
|
||||
//{
|
||||
// auto rv = DIALOG::save_file(params);
|
||||
// EXPECT_TRUE(rv.has_value());
|
||||
//}
|
||||
|
||||
// Clear file filters for following operations
|
||||
params.clear();
|
||||
params.set_default_file_type_index(0u);
|
||||
|
||||
//// Open folder
|
||||
//{
|
||||
// auto rv = DIALOG::open_folder(params);
|
||||
// EXPECT_TRUE(rv.has_value());
|
||||
//}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
@ -26,9 +26,10 @@ namespace yycctest::windows::winfct {
|
||||
}
|
||||
|
||||
TEST(WindowsWinFct, IsValidCodePage) {
|
||||
// Test valid code page
|
||||
EXPECT_TRUE(WINFCT::is_valid_code_page(437));
|
||||
EXPECT_TRUE(WINFCT::is_valid_code_page(65001));
|
||||
|
||||
// This code page must be invalid
|
||||
EXPECT_FALSE(WINFCT::is_valid_code_page(6161));
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user