#pragma once #include "../macro/os_detector.hpp" #include "../macro/stl_detector.hpp" #if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL) #include "../macro/class_copy_move.hpp" #include "com.hpp" #include #include #include #include #include #include #include "import_guard_head.hpp" #include #include #include "import_guard_tail.hpp" #define NS_YYCC_WINDOWS_COM ::yycc::windows::com /** * @brief The namespace providing Windows universal dialog features. * @details * This namespace only available on Windows platform. * See also \ref dialog_helper. */ namespace yycc::windows::dialog { /// @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 using DialogResult = std::expected; /** * @private * @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; public: using WinFilterModes = std::wstring; using WinFilterName = std::wstring; using WinFilterPair = std::pair; public: WinFileFilters(); ~WinFileFilters(); YYCC_DECL_COPY_MOVE(WinFileFilters) 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 m_WinFilters; std::vector 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. */ class FileFilters { public: using FilterModes = std::vector; using FilterName = std::u8string; using FilterPair = std::pair; 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 il); /** * @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 to_windows() const; private: std::vector m_Filters; }; /** * @private * @brief The class representing the file dialog. * @details * This class is served for Windows used. * Programmer should \b not create this class manually. */ class WinFileDialog { friend class FileDialog; public: WinFileDialog(); ~WinFileDialog(); YYCC_DECL_COPY(WinFileDialog) YYCC_DEFAULT_MOVE(WinFileDialog) /// @brief Get whether this dialog has owner. bool has_owner() const; /// @brief Get the \c HWND of dialog owner. HWND get_owner() const; /// @brief Get whether dialog has custom title. bool has_title() const; /// @brief Get custom title of dialog. const wchar_t* get_title() const; /// @brief Get whether dialog has custom initial file name. bool has_init_file_name() const; /// @brief Get custom initial file name of dialog const wchar_t* get_init_file_name() const; /// @brief Get whether dialog has custom initial directory. bool has_init_directory() const; /// @brief Get custom initial directory of dialog. 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; 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. */ UINT m_WinDefaultFileTypeIndex; std::optional 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. */ class FileDialog { public: 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 set_owner(HWND owner); /** * @brief Set custom title of dialog * @param[in] title The string pointer to custom title. */ void set_title(const std::u8string_view& 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. * @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(); /** * @private * @brief Build Windows used dialog info struct. * @return Built Windows used struct, or error occurs. */ DialogResult to_windows() const; protected: HWND m_Owner; std::optional m_Title, m_InitFileName, m_InitDirectory; FileFilters m_FileTypes; /** * @brief The default selected file type in dialog * @remarks * The index Windows used is 1-based index. * But for universal experience, we order this is 0-based index. * And do convertion when generating Windows used struct. */ size_t m_DefaultFileTypeIndex; }; /** * @brief Open the dialog which order user select single file to open. * @param[in] params The configuration of dialog. * @param[out] ret Full path to user selected file. * @return False if user calcel the operation or something went wrong, otherwise true. */ bool OpenFileDialog(const FileDialog& params, std::u8string& ret); /** * @brief Open the dialog which order user select multiple file to open. * @param[in] params The configuration of dialog. * @param[out] ret The list of full path of user selected files. * @return False if user calcel the operation or something went wrong, otherwise true. */ bool OpenMultipleFileDialog(const FileDialog& params, std::vector& ret); /** * @brief Open the dialog which order user select single file to save. * @param[in] params The configuration of dialog. * @param[out] ret Full path to user selected file. * @return False if user calcel the operation or something went wrong, otherwise true. */ bool SaveFileDialog(const FileDialog& params, std::u8string& ret); /** * @brief Open the dialog which order user select single directory to open. * @param[in] params The configuration of dialog. * @param[out] ret Full path to user selected directory. * @return False if user calcel the operation or something went wrong, otherwise true. */ bool OpenFolderDialog(const FileDialog& params, std::u8string& ret); } #undef NS_YYCC_WINDOWS_COM #endif