Compare commits

2 Commits

Author SHA1 Message Date
7234b31ee0 temp 2025-08-20 21:59:51 +08:00
15aade052f refactor: expand ugly statement from while statement header to body. 2025-08-20 19:32:44 +08:00
6 changed files with 459 additions and 200 deletions

View File

@ -300,6 +300,9 @@ StatementMacros:
- YYCC_DEFAULT_COPY - YYCC_DEFAULT_COPY
- YYCC_DEFAULT_MOVE - YYCC_DEFAULT_MOVE
- YYCC_DEFAULT_COPY_MOVE - YYCC_DEFAULT_COPY_MOVE
- YYCC_DECL_COPY
- YYCC_DECL_MOVE
- YYCC_DECL_COPY_MOVE
TableGenBreakInsideDAGArg: DontBreak TableGenBreakInsideDAGArg: DontBreak
TabWidth: 4 TabWidth: 4
UseTab: Never UseTab: Never

View File

@ -17,7 +17,7 @@ PRIVATE
yycc/rust/panic.cpp yycc/rust/panic.cpp
yycc/rust/env.cpp yycc/rust/env.cpp
yycc/windows/com.cpp yycc/windows/com.cpp
#yycc/windows/dialog.cpp yycc/windows/dialog.cpp
yycc/windows/winfct.cpp yycc/windows/winfct.cpp
yycc/encoding/stl.cpp yycc/encoding/stl.cpp
yycc/encoding/windows.cpp yycc/encoding/windows.cpp
@ -61,7 +61,7 @@ FILES
yycc/windows/import_guard_head.hpp yycc/windows/import_guard_head.hpp
yycc/windows/import_guard_tail.hpp yycc/windows/import_guard_tail.hpp
yycc/windows/com.hpp yycc/windows/com.hpp
#yycc/windows/dialog.hpp yycc/windows/dialog.hpp
yycc/windows/winfct.hpp yycc/windows/winfct.hpp
yycc/constraint.hpp yycc/constraint.hpp
yycc/constraint/builder.hpp yycc/constraint/builder.hpp

View File

@ -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, // 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. // 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'; static const char NULL_TERMINAL = '\0';
while ( while (true) {
size_t rc = std::mbrtoc16(&c16, (ptr < end ? ptr : &NULL_TERMINAL), (ptr < end ? end - ptr : sizeof(NULL_TERMINAL)), &state)) { 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); 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) -2) return std::unexpected(ConvError::IncompleteUtf8);
else if (rc == (size_t) -3) dst.push_back(c16); // from earlier surrogate pair else if (rc == (size_t) -3) dst.push_back(c16); // from earlier surrogate pair

View File

@ -7,8 +7,8 @@
/// @brief Explicitly remove move (\c constructor and \c operator\=) for given class. /// @brief Explicitly remove move (\c constructor and \c operator\=) for given class.
#define YYCC_DELETE_MOVE(CLSNAME) \ #define YYCC_DELETE_MOVE(CLSNAME) \
CLSNAME(CLSNAME&&) = delete; \ CLSNAME(CLSNAME&&) noexcept = delete; \
CLSNAME& operator=(CLSNAME&&) = delete; CLSNAME& operator=(CLSNAME&&) noexcept = delete;
/// @brief Explicitly remove (copy and move) (\c constructor and \c operator\=) for given class. /// @brief Explicitly remove (copy and move) (\c constructor and \c operator\=) for given class.
#define YYCC_DELETE_COPY_MOVE(CLSNAME) \ #define YYCC_DELETE_COPY_MOVE(CLSNAME) \
@ -22,10 +22,41 @@
/// @brief Explicitly set default move (\c constructor and \c operator\=) for given class. /// @brief Explicitly set default move (\c constructor and \c operator\=) for given class.
#define YYCC_DEFAULT_MOVE(CLSNAME) \ #define YYCC_DEFAULT_MOVE(CLSNAME) \
CLSNAME(CLSNAME&&) = default; \ CLSNAME(CLSNAME&&) noexcept = default; \
CLSNAME& operator=(CLSNAME&&) = default; CLSNAME& operator=(CLSNAME&&) noexcept = default;
/// @brief Explicitly set default (copy and move) (\c constructor and \c operator\=) for given class. /// @brief Explicitly set default (copy and move) (\c constructor and \c operator\=) for given class.
#define YYCC_DEFAULT_COPY_MOVE(CLSNAME) \ #define YYCC_DEFAULT_COPY_MOVE(CLSNAME) \
YYCC_DEFAULT_COPY(CLSNAME) \ YYCC_DEFAULT_COPY(CLSNAME) \
YYCC_DEFAULT_MOVE(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

View File

@ -1,76 +1,299 @@
#include "dialog.hpp" #include "dialog.hpp"
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL) #if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
#include "../string/op.hpp"
#include "../encoding/windows.hpp"
#include "../num/safe_cast.hpp"
#include <stdexcept>
#define ENC ::yycc::encoding::windows
#define OP ::yycc::string::op
#define SAFECAST ::yycc::num::safe_cast
#define WINCOM ::yycc::windows::com
namespace yycc::windows::dialog { 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) { WinFileFilters::WinFileFilters() : m_WinFilters(), m_WinDataStruct() {}
// assign filter name
if (filter_name == nullptr) return false;
FilterName name(filter_name);
// assign filter patterns WinFileFilters::~WinFileFilters() {}
FilterModes modes;
for (const yycc_char8_t* pattern : il) {
if (pattern != nullptr) modes.emplace_back(yycc_u8string(pattern));
}
// check filter patterns YYCC_IMPL_COPY_CTOR(WinFileFilters, rhs) : m_WinFilters(rhs.m_WinFilters), m_WinDataStruct() {
if (modes.empty()) return false; // Update data
this->update();
// 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 { YYCC_IMPL_COPY_OPER(WinFileFilters, rhs) {
// clear Windows oriented data // Copy data and mark desync
win_result.Clear(); 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 YYCC_IMPL_MOVE_CTOR(WinFileFilters, rhs) : m_WinFilters(std::move(rhs.m_WinFilters)), m_WinDataStruct() {
for (const auto& it : m_Filters) { // In theory, there is no update should perform,
// convert name to wchar // however we do it because there is no guarantee that no memory allocation during this move.
WinFileFilters::WinFilterName name; this->update();
if (!YYCC::EncodingHelper::UTF8ToWchar(it.first, name)) return false; }
// convert pattern and join them YYCC_IMPL_MOVE_OPER(WinFileFilters, rhs) {
const auto& filter_modes = it.second; // Move data
yycc_u8string joined_modes(YYCC::StringHelper::Join(filter_modes.begin(), filter_modes.end(), YYCC_U8(";"))); this->m_WinFilters = std::move(rhs.m_WinFilters);
WinFileFilters::WinFilterModes modes; this->m_WinDataStruct.clear();
if (!YYCC::EncodingHelper::UTF8ToWchar(joined_modes, modes)) return false; // Same reason for updating
this->update();
// Return self
return *this;
}
// append new pair UINT WinFileFilters::get_filter_count() const {
win_result.m_WinFilters.emplace_back(std::make_pair(name, modes)); // 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[n];
auto& data_struct = m_WinDataStruct[n];
// 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 endregion
#pragma region File Dialog #pragma region FileFilters
bool FileDialog::Generate(WinFileDialog& win_result) const { FileFilters::FileFilters() : m_Filters() {}
// clear Windows oriented data
win_result.Clear(); 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)));
}
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::TooLargeSize);
}
// 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
return rv;
}
#pragma endregion
#pragma region WinFileDialog
/**
* @brief Assistant function for making copy of COM object.
* @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 // set owner
win_result.m_WinOwner = m_Owner; rv.m_WinOwner = m_Owner;
// build file filters // build file filters
if (!m_FileTypes.Generate(win_result.m_WinFileTypes)) return false; if (!m_FileTypes.Generate(win_result.m_WinFileTypes)) return false;
@ -110,12 +333,12 @@ namespace yycc::windows::dialog {
} }
// everything is okey // everything is okey
return true; return rv;
} }
#pragma endregion #pragma endregion
#pragma region Windows Dialog Code #pragma region Exposed Functions
enum class CommonFileDialogType { OpenFile, OpenMultipleFiles, SaveFile, OpenFolder }; enum class CommonFileDialogType { OpenFile, OpenMultipleFiles, SaveFile, OpenFolder };
@ -131,7 +354,7 @@ namespace yycc::windows::dialog {
LPWSTR _name; LPWSTR _name;
HRESULT hr = item->GetDisplayName(SIGDN_FILESYSPATH, &_name); HRESULT hr = item->GetDisplayName(SIGDN_FILESYSPATH, &_name);
if (FAILED(hr)) return false; if (FAILED(hr)) return false;
COMHelper::SmartLPWSTR display_name(_name); WINCOM::SmartLPWSTR display_name(_name);
// convert result // convert result
if (!YYCC::EncodingHelper::WcharToUTF8(display_name.get(), ret)) return false; if (!YYCC::EncodingHelper::WcharToUTF8(display_name.get(), ret)) return false;
@ -156,7 +379,7 @@ namespace yycc::windows::dialog {
HRESULT hr; HRESULT hr;
// check whether COM environment has been initialized // check whether COM environment has been initialized
if (!COMHelper::IsInitialized()) return false; if (!WINCOM::is_initialized()) return false;
// create file dialog instance // create file dialog instance
// fetch dialog CLSID first // fetch dialog CLSID first
@ -178,7 +401,7 @@ namespace yycc::windows::dialog {
hr = CoCreateInstance(dialog_clsid, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&_pfd)); hr = CoCreateInstance(dialog_clsid, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&_pfd));
if (FAILED(hr)) return false; if (FAILED(hr)) return false;
// create memory-safe dialog pointer // create memory-safe dialog pointer
COMHelper::SmartIFileDialog pfd(_pfd); WINCOM::SmartIFileDialog pfd(_pfd);
// set options for dialog // set options for dialog
// before setting, always get the options first in order. // before setting, always get the options first in order.
@ -261,7 +484,7 @@ namespace yycc::windows::dialog {
IShellItem* _item; IShellItem* _item;
hr = pfd->GetResult(&_item); hr = pfd->GetResult(&_item);
if (FAILED(hr)) return false; if (FAILED(hr)) return false;
COMHelper::SmartIShellItem result_item(_item); WINCOM::SmartIShellItem result_item(_item);
// extract display name // extract display name
yycc_u8string result_name; yycc_u8string result_name;
@ -276,13 +499,13 @@ namespace yycc::windows::dialog {
IFileOpenDialog* _pfod = nullptr; IFileOpenDialog* _pfod = nullptr;
hr = pfd->QueryInterface(IID_PPV_ARGS(&_pfod)); hr = pfd->QueryInterface(IID_PPV_ARGS(&_pfod));
if (FAILED(hr)) return false; if (FAILED(hr)) return false;
COMHelper::SmartIFileOpenDialog pfod(_pfod); WINCOM::SmartIFileOpenDialog pfod(_pfod);
// obtain multiple file entires // obtain multiple file entires
IShellItemArray* _items; IShellItemArray* _items;
hr = pfod->GetResults(&_items); hr = pfod->GetResults(&_items);
if (FAILED(hr)) return false; if (FAILED(hr)) return false;
COMHelper::SmartIShellItemArray result_items(_items); WINCOM::SmartIShellItemArray result_items(_items);
// analyze file entries // analyze file entries
// get array count first // get array count first
@ -296,7 +519,7 @@ namespace yycc::windows::dialog {
; ;
hr = result_items->GetItemAt(i, &_item); hr = result_items->GetItemAt(i, &_item);
if (FAILED(hr)) return false; if (FAILED(hr)) return false;
COMHelper::SmartIShellItem result_item(_item); WINCOM::SmartIShellItem result_item(_item);
// extract display name // extract display name
yycc_u8string result_name; yycc_u8string result_name;

View File

@ -6,14 +6,19 @@
#include "../macro/class_copy_move.hpp" #include "../macro/class_copy_move.hpp"
#include "com.hpp" #include "com.hpp"
#include <string> #include <string>
#include <string_view>
#include <vector> #include <vector>
#include <initializer_list> #include <initializer_list>
#include <expected>
#include <optional>
#include "import_guard_head.hpp" #include "import_guard_head.hpp"
#include <Windows.h> #include <Windows.h>
#include <shlobj_core.h> #include <shlobj_core.h>
#include "import_guard_tail.hpp" #include "import_guard_tail.hpp"
#define NS_YYCC_WINDOWS_COM ::yycc::windows::com
/** /**
* @brief The namespace providing Windows universal dialog features. * @brief The namespace providing Windows universal dialog features.
* @details * @details
@ -22,39 +27,58 @@
*/ */
namespace yycc::windows::dialog { namespace yycc::windows::dialog {
/// @brief The error occurs in this module.
enum class DialogError {
BadEncoding, ///< Error occurs when perform encoding convertion.
TooLargeSize, ///< The size of file filters list is too large for Windows.
};
/// @brief The result type used in this module.
template<typename T>
using DialogResult = std::expected<T, DialogError>;
/** /**
* @private * @private
* @brief The class representing the file types region in file dialog. * @brief The class representing the file types region in file dialog.
* @details * @details
* This class is served for Windows used. * This class is private and served for Windows used.
* Programmer should \b not create this class manually. * Programmer should \b not create this class manually.
*/ */
class WinFileFilters { class WinFileFilters {
friend class FileFilters; friend class FileFilters;
friend class WinFileDialog;
public: 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 WinFilterModes = std::wstring;
using WinFilterName = std::wstring; using WinFilterName = std::wstring;
using WinFilterPair = std::pair<WinFilterName, WinFilterModes>; using WinFilterPair = std::pair<WinFilterName, WinFilterModes>;
std::vector<WinFilterPair> m_WinFilters; public:
std::unique_ptr<COMDLG_FILTERSPEC[]> m_WinDataStruct; WinFileFilters();
~WinFileFilters();
YYCC_DECL_COPY_MOVE(WinFileFilters)
/// @brief Clear all current file filters public:
void Clear() { /**
m_WinDataStruct.reset(); * @brief Get the count of available file filters
m_WinFilters.clear(); * @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;
}; };
/** /**
@ -62,52 +86,46 @@ namespace yycc::windows::dialog {
* @details * @details
* This class is served for programmer using. * This class is served for programmer using.
* But you don't need create it on your own. * But you don't need create it on your own.
* You can simply fetch it by FileDialog::ConfigreFileTypes , * You can simply fetch it by FileDialog::ConfigreFileTypes(),
* because this class is a part of FileDialog. * because this class is a part of FileDialog.
*/ */
class FileFilters { class FileFilters {
public: public:
FileFilters() : m_Filters() {} using FilterModes = std::vector<std::u8string>;
YYCC_DELETE_COPY_MOVE(FileFilters) 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. * @brief Add a filter pair in file types list.
* @param[in] filter_name The friendly name of the filter. * @param[in] filter_name The friendly name of the filter.
* @param[in] il * @param[in] il
* A C++ initialize list containing acceptable file filter pattern. * A C++ initialize list containing acceptable file filter pattern.
* Every entries must be `const yycc_char8_t*` representing a single filter pattern. * Every entries must be a string representing a single filter pattern.
* The list at least should have one valid pattern. * This list at least should have one pattern.
* This function will not validate these filter patterns, so please write them carefully.
* @return True if added success, otherwise false. * @return True if added success, otherwise false.
* @remarks * @warning This function will not validate the content of these filter patterns, so please write them carefully.
* This function allow you register multiple filter patterns for single friendly name. * @exception std::invalid_argument Given filtern name is blank, or filter patterns is empty.
* 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); void add_filter(const std::u8string_view& filter_name, std::initializer_list<std::u8string_view> il);
/** /**
* @brief Get the count of added filter pairs. * @brief Clear filter pairs for following re-use.
* @return The count of already added filter pairs. */
*/ void clear();
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. * @private
* @param[out] win_result The class receiving the generated filter data struct. * @brief Build Windows used file filters struct.
* @return True if generation success, otherwise false. * @return Built Windows used struct, or error occurs.
* @remarks */
* Programmer should not call this function, DialogResult<WinFileFilters> to_windows() const;
* 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>;
private:
std::vector<FilterPair> m_Filters; std::vector<FilterPair> m_Filters;
}; };
@ -122,36 +140,33 @@ namespace yycc::windows::dialog {
friend class FileDialog; friend class FileDialog;
public: public:
WinFileDialog() : WinFileDialog();
m_WinOwner(NULL), m_WinFileTypes(), m_WinDefaultFileTypeIndex(0u), m_HasTitle(false), m_HasInitFileName(false), m_WinTitle(), ~WinFileDialog();
m_WinInitFileName(), m_WinInitDirectory(nullptr) {} YYCC_DECL_COPY(WinFileDialog)
YYCC_DELETE_COPY_MOVE(WinFileDialog) YYCC_DEFAULT_MOVE(WinFileDialog)
/// @brief Get whether this dialog has owner. /// @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. /// @brief Get the \c HWND of dialog owner.
HWND GetOwner() const { return m_WinOwner; } HWND get_owner() const;
/// @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. /// @brief Get whether dialog has custom title.
bool HasTitle() const { return m_HasTitle; } bool has_title() const;
/// @brief Get custom title of dialog. /// @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. /// @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 /// @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. /// @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. /// @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; HWND m_WinOwner;
WinFileFilters m_WinFileTypes; WinFileFilters m_WinFileTypes;
/** /**
@ -162,20 +177,8 @@ namespace yycc::windows::dialog {
* Because the same field located in FileDialog is 0-based index. * Because the same field located in FileDialog is 0-based index.
*/ */
UINT m_WinDefaultFileTypeIndex; UINT m_WinDefaultFileTypeIndex;
bool m_HasTitle, m_HasInitFileName; std::optional<std::wstring> m_WinTitle, m_WinInitFileName;
std::wstring m_WinTitle, m_WinInitFileName; NS_YYCC_WINDOWS_COM::SmartIShellItem m_WinInitDirectory;
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();
}
}; };
/** /**
@ -186,79 +189,71 @@ namespace yycc::windows::dialog {
*/ */
class FileDialog { class FileDialog {
public: public:
FileDialog() : FileDialog();
m_Owner(NULL), m_FileTypes(), m_DefaultFileTypeIndex(0u), m_Title(), m_InitFileName(), m_InitDirectory(), m_HasTitle(false), ~FileDialog();
m_HasInitFileName(false), m_HasInitDirectory(false) {} YYCC_DEFAULT_COPY_MOVE(FileDialog)
YYCC_DELETE_COPY_MOVE(FileDialog)
/** /**
* @brief Set the owner of dialog. * @brief Set the owner of dialog.
* @param[in] owner The \c HWND pointing to the owner of dialog, or NULL to remove owner. * @param[in] owner The \c HWND pointing to the owner of dialog, or NULL to remove owner.
*/ */
void SetOwner(HWND owner) { m_Owner = owner; } void set_owner(HWND owner);
/** /**
* @brief Set custom title of dialog * @brief Set custom title of dialog
* @param[in] title The string pointer to custom title, or nullptr to remove it. * @param[in] title The string pointer to custom title.
*/ */
void SetTitle(const yycc_char8_t* title) { void set_title(const std::u8string_view& title);
if (m_HasTitle = title != nullptr) m_Title = title; /**
} * @brief Remove custom title of dialog (keep system default)
*/
void unset_title();
/**
* @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 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.
*/
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. * @brief Fetch the struct describing file filters for future configuration.
* @return The reference to the struct describing file filters. * @return The reference to the struct describing file filters.
*/ */
FileFilters& ConfigreFileTypes() { return m_FileTypes; } FileFilters& configre_file_types();
/** /**
* @brief Set the index of default selected file filter. * @brief Set the index of default selected file filter.
* @param[in] idx * @param[in] idx
* The index to default one. * The index to default file filter.
* This must be a valid index in file filters. * This must be a valid index in file filters.
*/ */
void SetDefaultFileTypeIndex(size_t idx) { m_DefaultFileTypeIndex = idx; } void set_default_file_type_index(size_t idx);
/** /**
* @brief Set the initial file name of dialog * @brief Clear file dialog parameters for following re-use.
* @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 clear();
*/
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. * @private
* @param[out] win_result The class receiving the generated filter data struct. * @brief Build Windows used dialog info struct.
* @return True if generation is success, otherwise false. * @return Built Windows used struct, or error occurs.
* @remarks
* Programmer should not call this function.
* This function is used as YYCC internal code.
*/ */
bool Generate(WinFileDialog& win_result) const; DialogResult<WinFileDialog> to_windows() const;
protected: protected:
HWND m_Owner; HWND m_Owner;
bool m_HasTitle, m_HasInitFileName, m_HasInitDirectory; std::optional<std::u8string> m_Title, m_InitFileName, m_InitDirectory;
std::u8string m_Title, m_InitFileName, m_InitDirectory;
FileFilters m_FileTypes; FileFilters m_FileTypes;
/** /**
* @brief The default selected file type in dialog * @brief The default selected file type in dialog
@ -301,4 +296,6 @@ namespace yycc::windows::dialog {
} }
#undef NS_YYCC_WINDOWS_COM
#endif #endif