diff --git a/.travis.yml b/.travis.yml index 170ef287..ba037042 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,7 +56,7 @@ matrix: - llvm-toolchain-precise before_install: - - git clone --depth=1 --branch=develop https://github.com/qPCR4vir/nana-demo.git ../nana-demo + - git clone --depth=1 --branch=hotfix-1.4 https://github.com/qPCR4vir/nana-demo.git ../nana-demo - export PATH="$HOME/bin:$PATH" - mkdir ~/bin - wget --no-check-certificate --no-clobber -O /tmp/tools/cmake https://cmake.org/files/v3.4/cmake-3.4.0-rc3-Linux-x86_64.sh || true diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c9c9c97..8f0dc030 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,12 +68,26 @@ if(WIN32) add_definitions(-DWIN32) #Global MSVC definitions. You may prefer the hand-tuned sln and projects from the nana repository. if(MSVC) - option(WIN32_USE_MP "Set to ON to build nana with the /MP option (Visual Studio 2005 and above)." ON) - # ?? - if(WIN32_USE_MP) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") - endif(WIN32_USE_MP) - endif(MSVC) + option(MSVC_USE_MP "Set to ON to build nana with the /MP option (Visual Studio 2005 and above)." ON) + option(MSVC_USE_STATIC_RUNTIME "Set to ON to build nana with the /MT(d) option." ON) + + # Change the MSVC Compiler flags + if(MSVC_USE_MP) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + endif(MSVC_USE_MP) + + if(MSVC_USE_STATIC_RUNTIME) + foreach(flag + CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE + CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO + CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) + if(${flag} MATCHES "/MD") + string(REGEX REPLACE "/MD" "/MT" ${flag} "${${flag}}") + endif() + endforeach() + endif(MSVC_USE_STATIC_RUNTIME) + endif(MSVC) if(MINGW) if(NANA_CMAKE_ENABLE_MINGW_STD_THREADS_WITH_MEGANZ) @@ -251,8 +265,8 @@ foreach(subdir ${NANA_SOURCE_SUBDIRS}) # message("Subir: ${subdir}") # message("Files: ${sources}") endforeach(subdir ${NANA_SOURCE_SUBDIRS}) -include_directories(${NANA_INCLUDE_DIR}) add_library(${PROJECT_NAME} ${sources} ) +target_include_directories(${PROJECT_NAME} PUBLIC ${NANA_INCLUDE_DIR}) target_link_libraries(${PROJECT_NAME} ${NANA_LINKS}) # Headers: use INCLUDE_DIRECTORIES @@ -289,6 +303,4 @@ message ( "NANA_CMAKE_BOOST_FILESYSTEM_FORCE = " ${NANA_CMAKE_BOOST_FILE message ( "NANA_CMAKE_BOOST_FILESYSTEM_INCLUDE_ROOT = " ${NANA_CMAKE_BOOST_FILESYSTEM_INCLUDE_ROOT}) message ( "NANA_CMAKE_BOOST_FILESYSTEM_LIB = " ${NANA_CMAKE_BOOST_FILESYSTEM_LIB}) message ( "NANA_CMAKE_AUTOMATIC_GUI_TESTING = " ${NANA_CMAKE_AUTOMATIC_GUI_TESTING}) -message ( "NANA_CMAKE_ADD_DEF_AUTOMATIC_GUI_TESTING = " ${NANA_CMAKE_ADD_DEF_AUTOMATIC_GUI_TESTING}) - - +message ( "NANA_CMAKE_ADD_DEF_AUTOMATIC_GUI_TESTING = " ${NANA_CMAKE_ADD_DEF_AUTOMATIC_GUI_TESTING}) \ No newline at end of file diff --git a/LICENSE_1_0.txt b/LICENSE similarity index 100% rename from LICENSE_1_0.txt rename to LICENSE diff --git a/build/codeblocks/nana.cbp b/build/codeblocks/nana.cbp index 18e276ca..c598a95d 100644 --- a/build/codeblocks/nana.cbp +++ b/build/codeblocks/nana.cbp @@ -49,7 +49,7 @@ - + @@ -96,6 +96,7 @@ + @@ -113,6 +114,7 @@ + diff --git a/build/vc2013/nana.vcxproj b/build/vc2013/nana.vcxproj index c52001e4..8501f83f 100644 --- a/build/vc2013/nana.vcxproj +++ b/build/vc2013/nana.vcxproj @@ -187,6 +187,7 @@ + @@ -232,6 +233,7 @@ + @@ -249,6 +251,7 @@ + diff --git a/build/vc2013/nana.vcxproj.filters b/build/vc2013/nana.vcxproj.filters index dd9f87a2..6fe722eb 100644 --- a/build/vc2013/nana.vcxproj.filters +++ b/build/vc2013/nana.vcxproj.filters @@ -324,6 +324,15 @@ Source Files\nana\gui\detail + + Source Files\nana + + + Source Files\nana\gui\widgets\skeletons + + + Source Files\nana\detail + diff --git a/build/vc2015/nana.vcxproj b/build/vc2015/nana.vcxproj index e9c0aa02..9dd9c3bc 100644 --- a/build/vc2015/nana.vcxproj +++ b/build/vc2015/nana.vcxproj @@ -181,6 +181,7 @@ + @@ -226,6 +227,7 @@ + @@ -243,6 +245,7 @@ + diff --git a/build/vc2015/nana.vcxproj.filters b/build/vc2015/nana.vcxproj.filters index 4fcfbb6b..32f7afd6 100644 --- a/build/vc2015/nana.vcxproj.filters +++ b/build/vc2015/nana.vcxproj.filters @@ -282,5 +282,14 @@ Source Files\gui\detail + + Source Files + + + Source Files\gui\widgets\skeletons + + + Source Files\detail + \ No newline at end of file diff --git a/build/vc2017/nana.sln b/build/vc2017/nana.sln new file mode 100644 index 00000000..d01415ee --- /dev/null +++ b/build/vc2017/nana.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26228.4 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nana", "nana.vcxproj", "{42D0520F-EFA5-4831-84FE-2B9085301C5D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {42D0520F-EFA5-4831-84FE-2B9085301C5D}.Debug|x64.ActiveCfg = Debug|x64 + {42D0520F-EFA5-4831-84FE-2B9085301C5D}.Debug|x64.Build.0 = Debug|x64 + {42D0520F-EFA5-4831-84FE-2B9085301C5D}.Debug|x86.ActiveCfg = Debug|Win32 + {42D0520F-EFA5-4831-84FE-2B9085301C5D}.Debug|x86.Build.0 = Debug|Win32 + {42D0520F-EFA5-4831-84FE-2B9085301C5D}.Release|x64.ActiveCfg = Release|x64 + {42D0520F-EFA5-4831-84FE-2B9085301C5D}.Release|x64.Build.0 = Release|x64 + {42D0520F-EFA5-4831-84FE-2B9085301C5D}.Release|x86.ActiveCfg = Release|Win32 + {42D0520F-EFA5-4831-84FE-2B9085301C5D}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/build/vc2017/nana.vcxproj b/build/vc2017/nana.vcxproj new file mode 100644 index 00000000..6ee38270 --- /dev/null +++ b/build/vc2017/nana.vcxproj @@ -0,0 +1,209 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {42D0520F-EFA5-4831-84FE-2B9085301C5D} + Win32Proj + nana + 10.0.14393.0 + + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + false + v141 + true + Unicode + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + + + ..\..\include;$(IncludePath) + ../bin/ + ..\..\..\temp\$(ProjectName)\$(PlatformToolset)_$(Configuration)_$(PlatformShortName)\ + $(ProjectName)_$(PlatformToolset)_$(Configuration)_$(PlatformShortName) + + + ..\..\include;$(IncludePath) + ../bin/ + ..\..\..\temp\$(ProjectName)\$(PlatformToolset)_$(Configuration)_$(PlatformShortName)\ + $(ProjectName)_$(PlatformToolset)_$(Configuration)_$(PlatformShortName) + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + + + Windows + + + + + + + Level3 + Disabled + _DEBUG;_LIB;%(PreprocessorDefinitions) + + + Windows + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + + + Windows + true + true + + + + + Level3 + + + MaxSpeed + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + + + Windows + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/vc2017/nana.vcxproj.filters b/build/vc2017/nana.vcxproj.filters new file mode 100644 index 00000000..13f8c102 --- /dev/null +++ b/build/vc2017/nana.vcxproj.filters @@ -0,0 +1,244 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {81850bad-7436-405a-beb5-357c5e34f039} + + + {44582b36-4575-4663-ac02-e80417f95d05} + + + {b7e3cdb7-99ac-473d-86c8-53dddce70480} + + + {02fa693c-edc1-4e04-bf1d-ec3c2a89182a} + + + {cffe7506-b96c-42aa-a747-41b5115d9580} + + + {b6b2c032-c6a4-4884-8c14-eca4aa69ef0c} + + + {58f2e0f8-4d63-40db-807d-d7adf71c4ebe} + + + {f288a25d-3ce8-4c2e-a86f-9aeda44bc557} + + + {90b2da01-605d-489b-b6c5-2af8d3c2d8a6} + + + {430feed0-e1d9-45cb-8d59-e1a48a04d19f} + + + {dcf62634-a658-453b-a58d-f1a96a12a8b8} + + + {c1cdf46a-519f-422a-947f-39e173045414} + + + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件\audio\detail + + + 源文件\audio\detail + + + 源文件\audio\detail + + + 源文件\audio + + + 源文件\detail + + + 源文件\filesystem + + + 源文件\gui\detail + + + 源文件\gui\detail + + + 源文件\gui\detail + + + 源文件\gui\detail + + + 源文件\gui\detail + + + 源文件\gui\detail + + + 源文件\gui\detail + + + 源文件\gui\detail + + + 源文件\gui\detail + + + 源文件\gui\detail + + + 源文件\gui\widgets\skeletons + + + 源文件\gui\widgets\skeletons + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\gui\widgets + + + 源文件\paint\detail + + + 源文件\paint\detail + + + 源文件\paint + + + 源文件\paint + + + 源文件\paint + + + 源文件\paint + + + 源文件\paint + + + 源文件\system + + + 源文件\system + + + 源文件\system + + + 源文件\system + + + 源文件\threads + + + 源文件\detail + + + \ No newline at end of file diff --git a/include/nana/basic_types.hpp b/include/nana/basic_types.hpp index 6ddb6912..af9faa26 100644 --- a/include/nana/basic_types.hpp +++ b/include/nana/basic_types.hpp @@ -1,7 +1,7 @@ /* * Basic Types definition * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -84,7 +84,7 @@ namespace nana enum class mouse_action { - begin, normal = begin, hovered, pressed, end + begin, normal = begin, normal_captured, hovered, pressed, end }; enum class element_state @@ -97,12 +97,6 @@ namespace nana disabled }; - typedef unsigned scalar_t; - typedef unsigned char uint8_t; - typedef unsigned long uint32_t; - typedef unsigned uint_t; - typedef long long long_long_t; - union pixel_argb_t { struct element_tag @@ -304,8 +298,7 @@ namespace nana color(color_rgb); color(color_argb); color(color_rgba); - color(unsigned red, unsigned green, unsigned blue); - color(unsigned red, unsigned green, unsigned blue, double alpha); + color(unsigned red, unsigned green, unsigned blue, double alpha = 1.0); /// Initializes the color with a CSS-like rgb string. explicit color(std::string css_rgb); @@ -319,10 +312,7 @@ namespace nana /// @param lightness in range of [0, 1] color& from_hsl(double hue, double saturation, double lightness); ///< immutable alpha channel - color blend(const color& bgcolor, bool ignore_bgcolor_alpha) const; - - /// Blends two colors with the specified alpha, and the alpha values that come with these two colors are both ignored. - color blend(const color& bgcolor, double alpha) const; + color blend(const color& blending_color, double alpha) const; /// Determines whether the color is completely transparent. bool invisible() const; @@ -457,8 +447,8 @@ namespace nana rectangle& pare_off(int pixels); /// is a known issue on libstdc++, it works on libc++) * - STD_THREAD_NOT_SUPPORTED (GCC < 4.8.1) - * - STD_put_time_NOT_SUPPORTED (GCC < 5) * - STD_NUMERIC_CONVERSIONS_NOT_SUPPORTED (MinGW with GCC < 4.8.1) * - STD_NUMERIC_CONVERSIONS_NOT_SUPPORTED (MinGW with GCC < 4.8.1) * - STD_TO_STRING_NOT_SUPPORTED (MinGW with GCC < 4.8) * - STD_FILESYSTEM_NOT_SUPPORTED (GCC < 5.3) .... * - CXX_NO_INLINE_NAMESPACE (Visual C++ < 2015) - * - STD_MAKE_UNIQUE_NOT_SUPPORTED (GCC < 4.9) + * - _enable_std_make_unique (GCC < 4.9) + * - _enable_std_put_time (GCC < 5) + * - _enable_std_clamp (Visual C++ < 2017) */ #ifndef NANA_CXX_DEFINES_INCLUDED @@ -57,7 +58,7 @@ # define CXX_NO_INLINE_NAMESPACE //no support of C++11 inline namespace until Visual C++ 2015 # define noexcept //no support of noexcept until Visual C++ 2015 -# define constexpr const //no support of constexpr until Visual C++ 2015 ? const ?? +# define constexpr //no support of constexpr until Visual C++ 2015 ? const ?? # else # undef STD_FILESYSTEM_NOT_SUPPORTED # endif @@ -102,15 +103,21 @@ #define _CRT_SECURE_NO_DEPRECATE #pragma warning(disable : 4996) - #if (_MSC_VER == 1900) +# if (_MSC_VER >= 1900) // google: break any code that tries to use codecvt or codecvt. // google: It appears the C++ libs haven't been compiled with native char16_t/char32_t support. // google: Those definitions are for codecvt::id, codecvt::id and codecvt::id respectively. // However, the codecvt::id and codecvt::id definitions aren't there, and indeed, if you look at locale0.cpp in the CRT source code you'll see they're not defined at all. // google: That's a known issue, tracked by an active bug (DevDiv#1060849). We were able to update the STL's headers in response to char16_t/char32_t, but we still need to update the separately compiled sources. - #define STD_CODECVT_NOT_SUPPORTED - #endif // _MSC_VER == 1900 +# define STD_CODECVT_NOT_SUPPORTED +# endif // _MSC_VER == 1900 +# if (_MSC_VER < 1910) //VS2017 RTM +# define _enable_std_clamp +# endif + +#elif defined(NANA_MINGW) +# define STD_THREAD_NOT_SUPPORTED #elif defined(__clang__) //Clang #include //Introduces some implement-specific flags of ISO C++ Library @@ -119,13 +126,15 @@ #define STD_CODECVT_NOT_SUPPORTED #if !defined(__cpp_lib_make_unique) || (__cpp_lib_make_unique != 201304) - #ifndef STD_MAKE_UNIQUE_NOT_SUPPORTED - #define STD_MAKE_UNIQUE_NOT_SUPPORTED + #ifndef _enable_std_make_unique + #define _enable_std_make_unique #endif #endif #endif +# define _enable_std_clamp + #elif defined(__GNUC__) //GCC #include //Introduces some implement-specific flags of ISO C++ Library @@ -145,9 +154,9 @@ #endif - #if ((__GNUC__ < 5) ) - # define STD_put_time_NOT_SUPPORTED - #endif +# if ((__GNUC__ < 5) ) +# define _enable_std_put_time +# endif #if ((__GNUC__ > 5) || ((__GNUC__ == 5) && (__GNUC_MINOR__ >= 3 ) ) ) # undef STD_FILESYSTEM_NOT_SUPPORTED @@ -160,7 +169,7 @@ #endif #if (__GNUC_MINOR__ < 9) - #define STD_MAKE_UNIQUE_NOT_SUPPORTED + #define _enable_std_make_unique #endif #if defined(NANA_MINGW) @@ -181,6 +190,8 @@ #endif #endif #endif + +# define _enable_std_clamp #endif diff --git a/include/nana/deploy.hpp b/include/nana/deploy.hpp index 82025904..686bb4ce 100644 --- a/include/nana/deploy.hpp +++ b/include/nana/deploy.hpp @@ -1,7 +1,7 @@ /* * The Deploy Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -17,98 +17,11 @@ #include #include +#include - -#include #include -//Implement workarounds for GCC/MinGW which version is below 4.8.2 -#if defined(STD_NUMERIC_CONVERSIONS_NOT_SUPPORTED) -namespace std -{ - //Workaround for no implemenation of std::stoi in MinGW. - int stoi(const std::string&, std::size_t * pos = nullptr, int base = 10); - int stoi(const std::wstring&, std::size_t* pos = nullptr, int base = 10); - - //Workaround for no implemenation of std::stof in MinGW. - float stof(const std::string&, std::size_t * pos = nullptr); - float stof(const std::wstring&, std::size_t* pos = nullptr); - - //Workaround for no implemenation of std::stod in MinGW. - double stod(const std::string&, std::size_t * pos = nullptr); - double stod(const std::wstring&, std::size_t* pos = nullptr); - - //Workaround for no implemenation of std::stold in MinGW. - long double stold(const std::string&, std::size_t * pos = nullptr); - long double stold(const std::wstring&, std::size_t* pos = nullptr); - - //Workaround for no implemenation of std::stol in MinGW. - long stol(const std::string&, std::size_t* pos = nullptr, int base = 10); - long stol(const std::wstring&, std::size_t* pos = nullptr, int base = 10); - - //Workaround for no implemenation of std::stoll in MinGW. - long long stoll(const std::string&, std::size_t* pos = nullptr, int base = 10); - long long stoll(const std::wstring&, std::size_t* pos = nullptr, int base = 10); - - //Workaround for no implemenation of std::stoul in MinGW. - unsigned long stoul(const std::string&, std::size_t* pos = nullptr, int base = 10); - unsigned long stoul(const std::wstring&, std::size_t* pos = nullptr, int base = 10); - - //Workaround for no implemenation of std::stoull in MinGW. - unsigned long long stoull(const std::string&, std::size_t* pos = nullptr, int base = 10); - unsigned long long stoull(const std::wstring&, std::size_t* pos = nullptr, int base = 10); -} -#endif //STD_NUMERIC_CONVERSIONS_NOT_SUPPORTED - -#ifdef STD_TO_STRING_NOT_SUPPORTED -namespace std -{ - //Workaround for no implemenation of std::to_string/std::to_wstring in MinGW. - std::string to_string(long double); - std::string to_string(double); - std::string to_string(unsigned); - std::string to_string(int); - std::string to_string(long); - std::string to_string(unsigned long); - std::string to_string(long long); - std::string to_string(unsigned long long); - std::string to_string(float); -} -#endif - -#ifdef STD_TO_WSTRING_NOT_SUPPORTED -namespace std -{ - std::wstring to_wstring(long double); - std::wstring to_wstring(double); - std::wstring to_wstring(unsigned); - std::wstring to_wstring(int); - std::wstring to_wstring(long); - std::wstring to_wstring(unsigned long); - std::wstring to_wstring(long long); - std::wstring to_wstring(unsigned long long); - std::wstring to_wstring(float); -} -#endif - -#ifdef STD_put_time_NOT_SUPPORTED -#include -namespace std -{ - //Workaround for no implemenation of std::put_time in gcc < 5. - /* std unspecified return type */ - //template< class CharT, class RTSTR >// let fail for CharT != char / wchar_t - //RTSTR put_time(const std::tm* tmb, const CharT* fmt); - - //template< > - std::string put_time/**/(const std::tm* tmb, const char* fmt); - - //Defined in header - // std::size_t strftime(char* str, std::size_t count, const char* format, const std::tm* time); - //template<> - //std::wstring put_time(const std::tm* tmb, const wchar_t* fmt); -} -#endif // STD_put_time_NOT_SUPPORTED +#include namespace nana { @@ -192,45 +105,5 @@ namespace nana #define NANA_RGB(a) (((DWORD)(a) & 0xFF)<<16) | ((DWORD)(a) & 0xFF00) | (((DWORD)(a) & 0xFF0000) >> 16 ) - -#ifdef STD_MAKE_UNIQUE_NOT_SUPPORTED -// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3656.htm - -#include -#include -#include -#include - -namespace std { - template struct _Unique_if { - typedef unique_ptr _Single_object; - }; - - template struct _Unique_if { - typedef unique_ptr _Unknown_bound; - }; - - template struct _Unique_if { - typedef void _Known_bound; - }; - - template - typename _Unique_if::_Single_object - make_unique(Args&&... args) { - return unique_ptr(new T(std::forward(args)...)); - } - - template - typename _Unique_if::_Unknown_bound - make_unique(size_t n) { - typedef typename remove_extent::type U; - return unique_ptr(new U[n]()); - } - - template - typename _Unique_if::_Known_bound - make_unique(Args&&...) = delete; -} -#endif //STD_make_unique_NOT_SUPPORTED #include #endif //NANA_DEPLOY_HPP diff --git a/include/nana/filesystem/filesystem_ext.hpp b/include/nana/filesystem/filesystem_ext.hpp index 39fc4a07..2c28acad 100644 --- a/include/nana/filesystem/filesystem_ext.hpp +++ b/include/nana/filesystem/filesystem_ext.hpp @@ -1,6 +1,6 @@ /** * Nana C++ Library(http://www.nanapro.org) -* Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) +* Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -23,13 +23,13 @@ namespace filesystem_ext { #if defined(NANA_WINDOWS) - constexpr auto def_root = "C:"; - constexpr auto def_rootstr = "C:\\"; - constexpr auto def_rootname = "Local Drive(C:)"; + constexpr auto const def_root = "C:"; + constexpr auto const def_rootstr = "C:\\"; + constexpr auto const def_rootname = "Local Drive(C:)"; #elif defined(NANA_LINUX) - constexpr auto def_root = "/"; - constexpr auto def_rootstr = "/"; - constexpr auto def_rootname = "Root/"; + constexpr auto const def_root = "/"; + constexpr auto const def_rootstr = "/"; + constexpr auto const def_rootname = "Root/"; #endif std::experimental::filesystem::path path_user(); ///< extention ? diff --git a/include/nana/gui/detail/basic_window.hpp b/include/nana/gui/detail/basic_window.hpp index 038acdbb..2230a422 100644 --- a/include/nana/gui/detail/basic_window.hpp +++ b/include/nana/gui/detail/basic_window.hpp @@ -1,7 +1,7 @@ /** * A Basic Window Widget Definition * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -17,6 +17,7 @@ #include "drawer.hpp" #include "events_holder.hpp" #include "widget_geometrics.hpp" +#include "widget_content_measurer_interface.hpp" #include "widget_notifier_interface.hpp" #include #include @@ -85,7 +86,7 @@ namespace detail enum class update_state { - none, lazy, refresh + none, lazy, refreshed, request_refresh }; struct edge_nimbus_action @@ -191,6 +192,7 @@ namespace detail //The following pointers refer to the widget's object. std::shared_ptr events_ptr; widget_geometrics* scheme{ nullptr }; + ::nana::dev::widget_content_measurer_interface* content_measurer{ nullptr }; }annex; struct @@ -220,9 +222,6 @@ namespace detail basic_window* focus{nullptr}; basic_window* menubar{nullptr}; bool ime_enabled{false}; -#if defined(NANA_WINDOWS) - cursor running_cursor{ nana::cursor::arrow }; -#endif cursor state_cursor{nana::cursor::arrow}; basic_window* state_cursor_window{ nullptr }; diff --git a/include/nana/gui/detail/bedrock.hpp b/include/nana/gui/detail/bedrock.hpp index cda91c98..5462857d 100644 --- a/include/nana/gui/detail/bedrock.hpp +++ b/include/nana/gui/detail/bedrock.hpp @@ -1,7 +1,7 @@ /** * A Bedrock Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -70,6 +70,9 @@ namespace detail element_store& get_element_store() const; void map_through_widgets(core_window_t*, native_drawable_type); + + //Closes the windows which are associated with the specified thread. If the given thread_id is 0, it closes all windows + void close_thread_window(unsigned thread_id); public: void event_expose(core_window_t *, bool exposed); void event_move(core_window_t*, int x, int y); @@ -92,7 +95,6 @@ namespace detail private: void _m_emit_core(event_code, core_window_t*, bool draw_only, const event_arg&); void _m_event_filter(event_code, core_window_t*, thread_context*); - void _m_except_handler(); private: static bedrock bedrock_object; diff --git a/include/nana/gui/detail/drawer.hpp b/include/nana/gui/detail/drawer.hpp index 472f822e..5a202438 100644 --- a/include/nana/gui/detail/drawer.hpp +++ b/include/nana/gui/detail/drawer.hpp @@ -1,7 +1,7 @@ /* * A Drawer Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -126,8 +126,7 @@ namespace nana void* draw(std::function &&, bool diehard); void erase(void* diehard); private: - void _m_effect_bground(bool before); - bool _m_lazy_decleared() const; + void _m_effect_bground_subsequent(); method_state& _m_mth_state(int pos); template @@ -140,8 +139,6 @@ namespace nana if (realizer && (method_state::not_overrided != mth_state)) { - _m_effect_bground(true); - if (method_state::pending == mth_state) { (realizer->*mfptr)(graphics, arg); @@ -154,8 +151,7 @@ namespace nana else (realizer->*mfptr)(graphics, arg); - if (_m_lazy_decleared()) - _m_effect_bground(false); + _m_effect_bground_subsequent(); } } public: diff --git a/include/nana/gui/detail/effects_renderer.hpp b/include/nana/gui/detail/effects_renderer.hpp index 155fcb78..3727933a 100644 --- a/include/nana/gui/detail/effects_renderer.hpp +++ b/include/nana/gui/detail/effects_renderer.hpp @@ -86,7 +86,7 @@ namespace nana{ } //Avoiding duplicated rendering. If the window is declared to lazy refresh, it should be rendered. - if ((forced && (action.window == wd)) || (focused == action.window) || !action.rendered || (action.window->other.upd_state == core_window_t::update_state::refresh)) + if ((forced && (action.window == wd)) || (focused == action.window) || !action.rendered || (action.window->other.upd_state == core_window_t::update_state::refreshed)) { rd_set.emplace_back(r, action.window); action.rendered = true; diff --git a/include/nana/gui/detail/handle_manager.hpp b/include/nana/gui/detail/handle_manager.hpp deleted file mode 100644 index 6319e319..00000000 --- a/include/nana/gui/detail/handle_manager.hpp +++ /dev/null @@ -1,295 +0,0 @@ -/* - * Handle Manager Implementation - * Copyright(C) 2003-2013 Jinhao(cnjinhao@hotmail.com) - * - * Distributed under the Boost Software License, Version 1.0. - * (See accompanying file LICENSE_1_0.txt or copy at - * http://www.boost.org/LICENSE_1_0.txt) - * - * @file: nana/gui/detail/handle_manager.hpp - * - * @description: - * this manages all the window handles - */ - -#ifndef NANA_GUI_DETAIL_HANDLE_MANAGER_HPP -#define NANA_GUI_DETAIL_HANDLE_MANAGER_HPP - -#include -#include -#if defined(STD_THREAD_NOT_SUPPORTED) - #include -#else - #include -#endif - -#include -#include -#include - - -namespace nana -{ - namespace detail - { - template - class cache - : noncopyable - { - public: - typedef Key key_type; - typedef Value value_type; - typedef std::pair pair_type; - typedef std::size_t size_type; - - cache() - :addr_(reinterpret_cast(::operator new(sizeof(pair_type) * CacheSize))) - { - for(std::size_t i = 0; i < CacheSize; ++i) - { - bitmap_[i] = 0; - seq_[i] = nana::npos; - } - } - - ~cache() - { - for(std::size_t i = 0; i < CacheSize; ++i) - { - if(bitmap_[i]) - addr_[i].~pair_type(); - } - - ::operator delete(addr_); - } - - bool insert(key_type k, value_type v) - { - size_type pos = _m_find_key(k); - if(pos != nana::npos) - { - addr_[pos].second = v; - } - else - { - //No key exists - pos = _m_find_pos(); - - if(pos == nana::npos) - { //No room, and remove the last pair - pos = seq_[CacheSize - 1]; - (addr_ + pos)->~pair_type(); - } - - if(seq_[0] != nana::npos) - {//Need to move - for(int i = CacheSize - 1; i > 0; --i) - seq_[i] = seq_[i - 1]; - } - - seq_[0] = pos; - - new (addr_ + pos) pair_type(k, v); - bitmap_[pos] = 1; - } - return v; - } - - value_type * get(key_type k) - { - size_type pos = _m_find_key(k); - if(pos != nana::npos) - return &(addr_[pos].second); - return 0; - } - private: - size_type _m_find_key(key_type k) const - { - for(std::size_t i = 0; i < CacheSize; ++i) - { - if(bitmap_[i] && (addr_[i].first == k)) - return i; - } - return nana::npos; - } - - size_type _m_find_pos() const - { - for(std::size_t i = 0; i < CacheSize; ++i) - { - if(bitmap_[i] == 0) - return i; - } - return nana::npos; - } - private: - char bitmap_[CacheSize]; - size_type seq_[CacheSize]; - pair_type * addr_; - }; - - //handle_manager - //@brief - // handle_manager maintains handles of a type. removing a handle dose not destroy it, - // it will be inserted to a trash queue for deleting at a safe time. - // For efficiency, this class is not a thread-safe. - template - class handle_manager - : nana::noncopyable - { - public: - typedef HandleType handle_type; - typedef Condition cond_type; - typedef Deleter deleter_type; - typedef std::map handle_map_t; - typedef std::pair holder_pair; - - ~handle_manager() - { - delete_trash(0); - } - - void insert(handle_type handle, unsigned tid) - { - std::lock_guard lock(mutex_); - - holder_[handle] = tid; - - is_queue::value, std::vector >::insert(handle, queue_); - cacher_.insert(handle, true); - } - - void operator()(const handle_type handle) - { - remove(handle); - } - - void remove(const handle_type handle) - { - std::lock_guard lock(mutex_); - - auto i = static_cast(holder_).find(handle); - if(holder_.cend() != i) - { - is_queue::value, std::vector >::erase(handle, queue_); - cacher_.insert(handle, false); - trash_.push_back(*i); - holder_.erase(i); - } - } - - void delete_trash(unsigned tid) - { - std::lock_guard lock(mutex_); - - if(trash_.size()) - { - deleter_type del_functor; - if(tid == 0) - { - for(auto & m : trash_) - del_functor(m.first); - trash_.clear(); - } - else - { - for(auto i = trash_.begin(), end = trash_.end(); i != end;) - { - if(tid == i->second) - { - del_functor(i->first); - i = trash_.erase(i); - end = trash_.end(); - } - else - ++i; - } - } - } - } - - handle_type last() const - { - std::lock_guard lock(mutex_); - if(queue_.size()) - return queue_.back(); - return handle_type(); - } - - std::size_t size() const - { - return holder_.size(); - } - - handle_type get(unsigned index) const - { - std::lock_guard lock(mutex_); - if(index < queue_.size()) - return queue_[index]; - return handle_type(); - } - - bool available(const handle_type handle) const - { - if (nullptr == handle) - return false; - - std::lock_guard lock(mutex_); - bool * v = cacher_.get(handle); - if(v) return *v; - return cacher_.insert(handle, (holder_.count(handle) != 0)); - } - - void all(std::vector & v) const - { - std::lock_guard lock(mutex_); - std::copy(queue_.cbegin(), queue_.cend(), std::back_inserter(v)); - } - private: - - template - struct is_queue - { - public: - static void insert(handle_type handle, Container& queue) - { - if(cond_type::is_queue(handle)) - queue.push_back(handle); - } - - static void erase(handle_type handle, Container& queue) - { - if(cond_type::is_queue(handle)) - { - for (auto i = queue.begin(); i != queue.end(); ++i) - { - if (handle == *i) - { - queue.erase(i); - break; - } - } - } - } - }; - - template - struct is_queue - { - public: - static void insert(handle_type handle, Container& queue){} - static void erase(handle_type handle, Container& queue){} - }; - - private: - mutable std::recursive_mutex mutex_; - mutable cache cacher_; - handle_map_t holder_; - std::vector queue_; - std::vector trash_; - };//end class handle_manager - }//end namespace detail -}// end namespace nana -#include - -#endif diff --git a/include/nana/gui/detail/widget_content_measurer_interface.hpp b/include/nana/gui/detail/widget_content_measurer_interface.hpp new file mode 100644 index 00000000..4f175493 --- /dev/null +++ b/include/nana/gui/detail/widget_content_measurer_interface.hpp @@ -0,0 +1,48 @@ +/* +* Widget Content Measurer Interface +* Nana C++ Library(http://www.nanapro.org) +* Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) +* +* Distributed under the Boost Software License, Version 1.0. +* (See accompanying file LICENSE_1_0.txt or copy at +* http://www.boost.org/LICENSE_1_0.txt) +* +* @file: nana/gui/detail/widget_content_measurer_interface.hpp +*/ + +#ifndef NANA_WIDGET_CONTENT_MEASURER_INTERFACE_HEADER_INCLUDED +#define NANA_WIDGET_CONTENT_MEASURER_INTERFACE_HEADER_INCLUDED + +#include +#include +#include + +namespace nana +{ + namespace dev + { + /// An interface for measuring content of the widget + class widget_content_measurer_interface + { + public: + using graph_reference = paint::graphics&; + virtual ~widget_content_measurer_interface() = default; + + /// Measures content + /** + * @param graph The graphics for the operation. + * @param limit_pixels The number of pixels of the limited edge. If this parameter is zero, it is ignored. + * @param limit_width True if limits the width, false if limits the height. + * @return the size of content. + */ + virtual optional measure(graph_reference graph, unsigned limit_pixels, bool limit_width) const = 0; + + /// Returns the extension to the size of widget from content extent + /** + * @return the width and height of extension to the widget size. + */ + virtual size extension() const = 0; + }; + } +} +#endif diff --git a/include/nana/gui/detail/window_layout.hpp b/include/nana/gui/detail/window_layout.hpp index 34987291..da099cf5 100644 --- a/include/nana/gui/detail/window_layout.hpp +++ b/include/nana/gui/detail/window_layout.hpp @@ -1,6 +1,6 @@ /* * Window Layout Implementation - * Copyright(C) 2003-2013 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -42,10 +42,16 @@ namespace detail core_window_t * window; rectangle r; }; - public: - static void paint(core_window_t*, bool is_redraw, bool is_child_refreshed); - static bool maproot(core_window_t*, bool have_refreshed, bool is_child_refreshed); + enum class paint_operation { + none, + have_refreshed, + try_refresh + }; + public: + static void paint(core_window_t*, paint_operation, bool request_refresh_children); + + static bool maproot(core_window_t*, bool have_refreshed, bool request_refresh_children); static void paste_children_to_graphics(core_window_t*, nana::paint::graphics& graph); @@ -68,13 +74,12 @@ namespace detail //_m_paste_children //@brief:paste children window to the root graphics directly. just paste the visual rectangle - static void _m_paste_children(core_window_t*, bool is_child_refreshed, bool have_refreshed, const nana::rectangle& parent_rect, nana::paint::graphics& graph, const nana::point& graph_rpos); + static void _m_paste_children(core_window_t*, bool have_refreshed, bool request_refresh_children, const nana::rectangle& parent_rect, nana::paint::graphics& graph, const nana::point& graph_rpos); static void _m_paint_glass_window(core_window_t*, bool is_redraw, bool is_child_refreshed, bool called_by_notify, bool notify_other); - //_m_notify_glasses - //@brief: Notify the glass windows that are overlapped with the specified vis_rect - static void _m_notify_glasses(core_window_t* const sigwd, const nana::rectangle& r_visual); + //Notify the windows which have brground to update their background buffer. + static void _m_notify_glasses(core_window_t* const sigwd); private: struct data_section { diff --git a/include/nana/gui/detail/window_manager.hpp b/include/nana/gui/detail/window_manager.hpp index 1548b79f..8fa5cd53 100644 --- a/include/nana/gui/detail/window_manager.hpp +++ b/include/nana/gui/detail/window_manager.hpp @@ -1,7 +1,7 @@ /** * Window Manager Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -20,7 +20,6 @@ #include -#include "window_layout.hpp" #include "event_code.hpp" #include "inner_fwd.hpp" #include @@ -31,6 +30,7 @@ namespace nana namespace paint { class image; + class graphics; } } @@ -68,12 +68,10 @@ namespace detail using mutex_type = revertible_mutex; using core_window_t = basic_window; - using window_layer = window_layout; window_manager(); ~window_manager(); - static bool is_queue(core_window_t*); std::size_t number_of_core_window() const; mutex_type & internal_lock() const; void all_handles(std::vector&) const; @@ -103,12 +101,11 @@ namespace detail // Deletes a window whose category type is a root type or a frame type. void destroy_handle(core_window_t*); - void default_icon(const paint::image& _small_icon, const paint::image& big_icon); void icon(core_window_t*, const paint::image& small_icon, const paint::image& big_icon); bool show(core_window_t* wd, bool visible); - core_window_t* find_window(native_window_type root, int x, int y); + core_window_t* find_window(native_window_type root, const point& pos); //move the wnd and its all children window, x and y is a relatively coordinate for wnd's parent window bool move(core_window_t*, int x, int y, bool passive); @@ -124,7 +121,7 @@ namespace detail bool update(core_window_t*, bool redraw, bool force, const rectangle* update_area = nullptr); void refresh_tree(core_window_t*); - bool do_lazy_refresh(core_window_t*, bool force_copy_to_screen, bool refresh_tree = false); + void do_lazy_refresh(core_window_t*, bool force_copy_to_screen, bool refresh_tree = false); bool get_graphics(core_window_t*, nana::paint::graphics&); bool get_visual_rectangle(core_window_t*, nana::rectangle&); diff --git a/include/nana/gui/filebox.hpp b/include/nana/gui/filebox.hpp index 561dc5b7..1800eb9f 100644 --- a/include/nana/gui/filebox.hpp +++ b/include/nana/gui/filebox.hpp @@ -1,7 +1,7 @@ /** * Filebox * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at diff --git a/include/nana/gui/place.hpp b/include/nana/gui/place.hpp index dbc12111..6ead606d 100644 --- a/include/nana/gui/place.hpp +++ b/include/nana/gui/place.hpp @@ -22,6 +22,11 @@ namespace nana { + namespace paint + { + class graphics; + } + class widget; namespace detail { @@ -111,8 +116,11 @@ namespace nana */ void bind(window handle); window window_handle() const; + + void splitter_renderer(std::function fn); - void div(const char* s); ///< Divides the attached widget into fields. + void div(const char* s); ///< Divides the attached widget into fields. + const std::string& div() const noexcept; ///< Returns div-text that depends on fields status. void modify(const char* field_name, const char* div_text); ///< Modifies a specified field. field_reference field(const char* name);///< Returns a field with the specified name. diff --git a/include/nana/gui/programming_interface.hpp b/include/nana/gui/programming_interface.hpp index 06ff5716..0a1d55d8 100644 --- a/include/nana/gui/programming_interface.hpp +++ b/include/nana/gui/programming_interface.hpp @@ -1,7 +1,7 @@ /* * Nana GUI Programming Interface Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -16,6 +16,7 @@ #include "effects.hpp" #include "detail/general_events.hpp" #include "detail/color_schemes.hpp" +#include "detail/widget_content_measurer_interface.hpp" #include #include @@ -53,6 +54,8 @@ namespace API effects::edge_nimbus effects_edge_nimbus(window); void effects_bground(window, const effects::bground_factory_interface&, double fade_rate); + void effects_bground(std::initializer_list wdgs, const effects::bground_factory_interface&, double fade_rate); + bground_mode effects_bground_mode(window); void effects_bground_remove(window); @@ -63,7 +66,7 @@ namespace API void affinity_execute(window window_handle, const std::function&); bool set_events(window, const std::shared_ptr&); - + template std::unique_ptr make_scheme() { @@ -73,6 +76,9 @@ namespace API void set_scheme(window, widget_geometrics*); widget_geometrics* get_scheme(window); + /// Sets a content measurer + void set_measurer(window, ::nana::dev::widget_content_measurer_interface*); + void attach_drawer(widget&, drawer_trigger&); ::nana::detail::native_string_type window_caption(window) throw(); void window_caption(window, ::nana::detail::native_string_type); @@ -92,6 +98,9 @@ namespace API void enable_space_click(window, bool enable); + bool copy_transparent_background(window, paint::graphics&); + bool copy_transparent_background(window, const rectangle& src_r, paint::graphics&, const point& dst_pt); + /// Refreshs a widget surface /* * This function will copy the drawer surface into system window after the event process finished. @@ -157,7 +166,7 @@ namespace API }//end namespace detail void exit(); ///< close all windows in current thread - void exit_all(); ///< close all windows + void exit_all(); ///< close all windows /// @brief Searchs whether the text contains a '&' and removes the character for transforming. /// If the text contains more than one '&' charachers, the others are ignored. e.g @@ -177,7 +186,7 @@ namespace API template void enum_widgets(window wd, bool recursive, EnumFunction && fn) { - static_assert(std::is_convertible::value, "enum_widgets: The specified Widget is not a widget type."); + static_assert(std::is_convertible::type*, ::nana::widget*>::value, "enum_widgets: The specified Widget is not a widget type."); detail::enum_widgets_function enum_fn(static_cast(fn)); enum_fn.enum_widgets(wd, recursive); @@ -185,12 +194,14 @@ namespace API void window_icon_default(const paint::image& small_icon, const paint::image& big_icon = {}); void window_icon(window, const paint::image& small_icon, const paint::image& big_icon = {}); - + bool empty_window(window); ///< Determines whether a window is existing. bool is_window(window); ///< Determines whether a window is existing, equal to !empty_window. bool is_destroying(window); ///< Determines whether a window is destroying void enable_dropfiles(window, bool); + bool is_transparent_background(window); + /// \brief Retrieves the native window of a Nana.GUI window. /// /// The native window type is platform-dependent. Under Microsoft Windows, a conversion can be employed between @@ -288,7 +299,7 @@ namespace API * @param window_handle A handle to the window to be refreshed. */ void refresh_window(window window_handle); - void refresh_window_tree(window); ///< Refreshs the specified window and all its children windows, then display it immediately + void refresh_window_tree(window); ///< Refreshes the specified window and all its children windows, then display it immediately void update_window(window); ///< Copies the off-screen buffer to the screen for immediate display. void window_caption(window, const std::string& title_utf8); @@ -300,7 +311,7 @@ namespace API void activate_window(window); - /// Determines whether the specified window will get the keyboard focus when its root window gets native system focus. + /// Determines whether the specified window will get the keyboard focus when its root window gets native system focus. bool is_focus_ready(window); /// Returns the current keyboard focus window. @@ -318,7 +329,7 @@ namespace API * @param ignore_children Indicates whether to redirect the mouse input to its children if the mouse pointer is over its children. */ void set_capture(window window_handle, bool ignore_children); - + /// Disable a window to grab the mouse input. /** * @param window handle A handle to a window to release grab of mouse input. @@ -399,6 +410,17 @@ namespace API bool ignore_mouse_focus(window); ///< Determines whether the mouse focus is enabled void at_safe_place(window, std::function); + + /// Returns a widget content extent size + /** + * @param wd A handle to a window that returns its content extent size. + * @param limited_px Specifies the max pixels of width or height. If this parameter is zero, this parameter will be ignored. + * @param limit_width Indicates whether the it limits the width or height. If this parameter is *true*, the width is limited. + * If the parameter is *false*, the height is limited. This parameter is ignored if limited_px = 0. + * @return if optional has a value, the first size indicates the content extent, the second size indicates the size of + * widget by the content extent. + */ + optional> content_extent(window wd, unsigned limited_px, bool limit_width); }//end namespace API }//end namespace nana diff --git a/include/nana/gui/tooltip.hpp b/include/nana/gui/tooltip.hpp index ca1f9d1d..e724d19b 100644 --- a/include/nana/gui/tooltip.hpp +++ b/include/nana/gui/tooltip.hpp @@ -62,7 +62,7 @@ namespace nana _m_hold_factory(new factory); } - tooltip(){} + tooltip() = default; tooltip(window w, const ::std::string &tip){set(w,tip);} diff --git a/include/nana/gui/widgets/button.hpp b/include/nana/gui/widgets/button.hpp index e1813917..9be71fd6 100644 --- a/include/nana/gui/widgets/button.hpp +++ b/include/nana/gui/widgets/button.hpp @@ -1,7 +1,7 @@ /** * A Button Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -25,6 +25,7 @@ namespace nana{ /// Draw the button class trigger: public drawer_trigger { + class measurer; public: trigger(); ~trigger(); @@ -58,6 +59,8 @@ namespace nana{ element::cite_bground cite_{"button"}; + std::unique_ptr measurer_; + struct attr_tag { element_state e_state; diff --git a/include/nana/gui/widgets/categorize.hpp b/include/nana/gui/widgets/categorize.hpp index 1e64fa14..dcbdde12 100644 --- a/include/nana/gui/widgets/categorize.hpp +++ b/include/nana/gui/widgets/categorize.hpp @@ -1,7 +1,7 @@ /** * A Categorize Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at diff --git a/include/nana/gui/widgets/combox.hpp b/include/nana/gui/widgets/combox.hpp index 7e478557..fc8e4522 100644 --- a/include/nana/gui/widgets/combox.hpp +++ b/include/nana/gui/widgets/combox.hpp @@ -1,7 +1,7 @@ /** * A Combox Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -68,7 +68,7 @@ namespace nana void key_press(graph_reference, const arg_keyboard&) override; void key_char(graph_reference, const arg_keyboard&) override; private: - drawer_impl * drawer_; + drawer_impl * const drawer_; }; class item_proxy diff --git a/include/nana/gui/widgets/detail/tree_cont.hpp b/include/nana/gui/widgets/detail/tree_cont.hpp index c9684ba6..2490ab95 100644 --- a/include/nana/gui/widgets/detail/tree_cont.hpp +++ b/include/nana/gui/widgets/detail/tree_cont.hpp @@ -1,6 +1,6 @@ /* * A Tree Container class implementation - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -58,6 +58,31 @@ namespace detail t = t_next; } } + + bool is_ancestor_of(const tree_node* child) const + { + while (child) + { + if (child->owner == this) + return true; + + child = child->owner; + } + return false; + } + + tree_node * front() const + { + if (this->owner && (this != this->owner->child)) + { + auto i = this->owner->child; + while (i->next != this) + i = i->next; + + return i; + } + return nullptr; + } }; template @@ -76,12 +101,17 @@ namespace detail ~tree_cont() { - clear(); + clear(&root_); } - void clear() + void clear(node_type* node) { - remove(root_.child); + while (node->child) + { + //If there is a sibling of child, the root_.child + //will be assigned with the sibling. + remove(node->child); + } } bool verify(const node_type* node) const diff --git a/include/nana/gui/widgets/label.hpp b/include/nana/gui/widgets/label.hpp index f8c97eb9..ee26383b 100644 --- a/include/nana/gui/widgets/label.hpp +++ b/include/nana/gui/widgets/label.hpp @@ -1,7 +1,7 @@ /** * A Label Control Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -31,11 +31,11 @@ namespace nana class trigger: public drawer_trigger { public: - struct impl_t; + struct implement; trigger(); ~trigger(); - impl_t * impl() const; + implement * impl() const; private: void attached(widget_reference, graph_reference) override; void refresh(graph_reference) override; @@ -43,7 +43,7 @@ namespace nana void mouse_leave(graph_reference, const arg_mouse&) override; void click(graph_reference, const arg_click&) override; private: - impl_t * impl_; + implement * impl_; }; }//end namespace label diff --git a/include/nana/gui/widgets/listbox.hpp b/include/nana/gui/widgets/listbox.hpp index 63ee49ca..410e5118 100644 --- a/include/nana/gui/widgets/listbox.hpp +++ b/include/nana/gui/widgets/listbox.hpp @@ -1,7 +1,7 @@ /** * A List Box Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -14,6 +14,7 @@ * leobackes(pr#86,pr#97) * Benjamin Navarro(pr#81) * besh81(pr#130) + * dankan1890(pr#158) */ #ifndef NANA_GUI_WIDGETS_LISTBOX_HPP @@ -38,6 +39,9 @@ namespace nana { namespace listbox { + using size_type = std::size_t; + using native_string_type = ::nana::detail::native_string_type; + /// An interface of column operations class column_interface { @@ -61,6 +65,22 @@ namespace nana */ virtual void width(unsigned minimum, unsigned maximum) = 0; + /// Returns the position of the column + /** + * @param disp_order Indicates whether the display position or absolute position to be returned + * @return the position of the column. + */ + virtual size_type position(bool disp_order) const noexcept = 0; + + /// Returns the caption of column + virtual std::string text() const noexcept = 0; + + /// Sets the caption of column + /** + * @param text_utf8 A UTF-8 string for the caption. + */ + virtual void text(std::string text_utf8) = 0; + /// Sets alignment of column text /** * @param align Alignment @@ -92,7 +112,7 @@ namespace nana struct intern { public: - virtual ~intern() = default; + virtual ~intern() noexcept = default; }; template @@ -101,7 +121,7 @@ namespace nana { const T * ptr; - real_pointer(const T* p) + real_pointer(const T* p) noexcept : ptr(p) {} }; @@ -118,13 +138,13 @@ namespace nana { } - ~const_virtual_pointer() + ~const_virtual_pointer() noexcept { delete intern_; } template - const typename std::remove_const::type *get() const + const typename std::remove_const::type *get() const noexcept { using value_type = typename std::remove_const::type; auto target = dynamic_cast*>(intern_); @@ -141,8 +161,8 @@ namespace nana ::nana::color bgcolor; ::nana::color fgcolor; - format() = default; - format(const ::nana::color& bgcolor, const ::nana::color& fgcolor); + format() noexcept = default; + format(const ::nana::color& bgcolor, const ::nana::color& fgcolor) noexcept; }; using format_ptr = ::std::unique_ptr; @@ -152,13 +172,12 @@ namespace nana cell() = default; cell(const cell&); - cell(cell&&); - cell(::std::string); + cell(cell&&) noexcept; + cell(::std::string) noexcept; cell(::std::string, const format&); - cell(::std::string, const ::nana::color& bgcolor, const ::nana::color& fgcolor); cell& operator=(const cell&); - cell& operator=(cell&&); + cell& operator=(cell&&) noexcept; }; class container_interface @@ -167,7 +186,7 @@ namespace nana public: virtual ~container_interface() = default; - virtual void clear() = 0; + virtual void clear() noexcept = 0; virtual void erase(std::size_t pos) = 0; virtual std::size_t size() const = 0; @@ -219,7 +238,7 @@ namespace nana translator_({ vtrans, ctrans }) {} private: - void clear() override + void clear() noexcept override { container_.clear(); } @@ -308,7 +327,7 @@ namespace nana } private: - void clear() override + void clear() noexcept override { container_.clear(); } @@ -462,8 +481,8 @@ namespace nana virtual void lock() = 0; virtual void unlock() = 0; - virtual container_interface* container() = 0; - virtual const container_interface* container() const = 0; + virtual container_interface* container() noexcept = 0; + virtual const container_interface* container() const noexcept = 0; }; class model_guard @@ -568,12 +587,12 @@ namespace nana mutex_.unlock(); } - container_interface* container() override + container_interface* container() noexcept override { return &container_; } - const container_interface* container() const override + const container_interface* container() const noexcept override { return &container_; } @@ -610,12 +629,12 @@ namespace nana mutex_.unlock(); } - container_interface* container() override + container_interface* container() noexcept override { return container_ptr_.get(); } - const container_interface* container() const override + const container_interface* container() const noexcept override { return container_ptr_.get(); } @@ -624,53 +643,52 @@ namespace nana std::unique_ptr container_ptr_; }; - using size_type = std::size_t; - using native_string_type = ::nana::detail::native_string_type; /// usefull for both absolute and display (sorted) positions struct index_pair { + constexpr static const size_type npos = ::nana::npos; + size_type cat; //The pos of category size_type item; //the pos of item in a category. - explicit index_pair(size_type cat_pos = 0, size_type item_pos = 0) - : cat(cat_pos), - item(item_pos) + explicit index_pair(size_type cat_pos = 0, size_type item_pos = 0) noexcept + : cat(cat_pos), item(item_pos) {} - bool empty() const + bool empty() const noexcept { return (npos == cat); } - void set_both(size_type n) + void set_both(size_type n) noexcept { cat = item = n; } - bool is_category() const + bool is_category() const noexcept { return (npos != cat && npos == item); } - bool is_item() const - { - return (npos != cat && npos != item); - } - - bool operator==(const index_pair& r) const + bool operator==(const index_pair& r) const noexcept { return (r.cat == cat && r.item == item); } - bool operator!=(const index_pair& r) const + bool operator!=(const index_pair& r) const noexcept { return !this->operator==(r); } - bool operator>(const index_pair& r) const + bool operator<(const index_pair& r) const noexcept { - return (cat > r.cat) || (cat == r.cat && item > r.item); + return (cat < r.cat) || ((cat == r.cat) && (r.item != npos) && ((item == npos) || (item < r.item))); + } + + bool operator>(const index_pair& r) const noexcept + { + return (cat > r.cat) || ((cat == r.cat) && (item != npos) && ((r.item == npos) || (item > r.item))); } }; @@ -693,7 +711,7 @@ namespace nana class oresolver { public: - oresolver(essence*); + oresolver(essence*) noexcept; oresolver& operator<<(bool); oresolver& operator<<(short); oresolver& operator<<(unsigned short); @@ -715,9 +733,9 @@ namespace nana oresolver& operator<<(cell); oresolver& operator<<(std::nullptr_t); - std::vector && move_cells(); + std::vector && move_cells() noexcept; - ::nana::listbox& listbox(); + ::nana::listbox& listbox() noexcept; private: essence* const ess_; std::vector cells_; @@ -726,7 +744,7 @@ namespace nana class iresolver { public: - iresolver(std::vector); + iresolver(std::vector) noexcept; iresolver& operator>>(bool&); iresolver& operator>>(short&); @@ -744,7 +762,7 @@ namespace nana iresolver& operator>>(std::string& text_utf8); iresolver& operator>>(std::wstring&); iresolver& operator>>(cell&); - iresolver& operator>>(std::nullptr_t); + iresolver& operator>>(std::nullptr_t) noexcept; private: std::vector cells_; std::size_t pos_{0}; @@ -761,9 +779,7 @@ namespace nana public: trigger(); ~trigger(); - essence& ess() const; - private: - void _m_draw_border(); + essence& ess() const noexcept; private: void attached(widget_reference, graph_reference) override; void detached() override; @@ -773,7 +789,6 @@ namespace nana void mouse_leave(graph_reference, const arg_mouse&) override; void mouse_down(graph_reference, const arg_mouse&) override; void mouse_up(graph_reference, const arg_mouse&) override; - void mouse_wheel(graph_reference, const arg_wheel&) override; void dbl_click(graph_reference, const arg_mouse&) override; void resized(graph_reference, const arg_resized&) override; void key_press(graph_reference, const arg_keyboard&) override; @@ -790,8 +805,7 @@ namespace nana : public std::iterator { public: - item_proxy(essence*); - item_proxy(essence*, const index_pair&); + item_proxy(essence*, const index_pair& = index_pair{npos, npos}); /// the main porpose of this it to make obvious that item_proxy operate with absolute positions, and dont get moved during sort() static item_proxy from_display(essence *, const index_pair &relative) ; @@ -800,12 +814,31 @@ namespace nana /// posible use: last_selected_display = last_selected.to_display().item; use with caution, it get invalidated after a sort() index_pair to_display() const; - bool empty() const; + /// Determines whether the item is displayed on the screen + bool displayed() const; - item_proxy & check(bool ck); + bool empty() const noexcept; + + /// Checks/unchecks the item + /** + * @param chk Indicates whether to check or uncheck the item + * @param scroll_view Indicates whether to scroll the view to the item. It is ignored if the item is displayed. + * @return the reference of *this. + */ + item_proxy & check(bool chk, bool scroll_view = false); + + /// Determines whether the item is checked bool checked() const; - item_proxy & select(bool); + /// Selects/unselects the item + /** + * @param sel Indicates whether to select or unselect the item + * @param scroll_view Indicates whether to scroll the view to the item. It is ignored if the item is displayed. + * @return the reference of *this. + */ + item_proxy & select(bool sel, bool scroll_view = false); + + /// Determines whether he item is selected bool selected() const; item_proxy & bgcolor(const nana::color&); @@ -814,14 +847,22 @@ namespace nana item_proxy& fgcolor(const nana::color&); nana::color fgcolor() const; - index_pair pos() const; + index_pair pos() const noexcept; - size_type columns() const; + size_type columns() const noexcept; - item_proxy& text(size_type col, cell); - item_proxy& text(size_type col, std::string); - item_proxy& text(size_type col, const std::wstring&); - std::string text(size_type col) const; + /// Converts a position of column between display position and absolute position + /** + * @param col The display position or absolute position. + * @param disp_order Indicates whether the col is a display position or absolute position. If this parameter is true, the col is display position + * @return absolute position if disp_order is false, display position otherwise. + */ + size_type column_cast(size_type col, bool disp_order) const; + + item_proxy& text(size_type abs_col, cell); + item_proxy& text(size_type abs_col, std::string); + item_proxy& text(size_type abs_col, const std::wstring&); + std::string text(size_type abs_col) const; void icon(const nana::paint::image&); @@ -922,7 +963,7 @@ namespace nana bool operator!=(const item_proxy&) const; //Undocumented method - essence * _m_ess() const; + essence * _m_ess() const noexcept; private: std::vector _m_cells() const; nana::any * _m_value(bool alloc_if_empty); @@ -942,9 +983,9 @@ namespace nana template using value_translator = typename container_translator::value_translator; template using cell_translator = typename container_translator::cell_translator; - cat_proxy() = default; - cat_proxy(essence*, size_type pos); - cat_proxy(essence*, category_t*); + cat_proxy() noexcept = default; + cat_proxy(essence*, size_type pos) noexcept; + cat_proxy(essence*, category_t*) noexcept; /// Append an item at abs end of the category, set_value determines whether assign T object to the value of item. template @@ -1022,12 +1063,14 @@ namespace nana item_proxy at(size_type pos_abs) const; item_proxy back() const; - /// Returns the absolute index of a item by its display pos, the index of the item isn't changed after sorting. - /// convert from display order to absolute (find the real item in that display pos) but without check from current active sorting, in fact using just the last sorting !!! - size_type index_by_display_order(size_type disp_order) const; - - /// find display order for the real item but without check from current active sorting, in fact using just the last sorting !!! - size_type display_order(size_type pos) const; + /// Converts the index between absolute position and display position + /** + * @param from The index to be converted + * @param from_display_order If this parameter is true, the method converts a display position to an absolute position. + * If the parameter is false, the method converts an absolute position to a display position. + * @return a display position or an absolute position that are depending on from_display_order. + */ + size_type index_cast(size_type from, bool from_display_order) const; /// this cat position size_type position() const; @@ -1066,8 +1109,8 @@ namespace nana private: void _m_append(std::vector && cells); void _m_try_append_model(const const_virtual_pointer&); - void _m_cat_by_pos(); - void _m_update(); + void _m_cat_by_pos() noexcept; + void _m_update() noexcept; void _m_reset_model(model_interface*); private: essence* ess_{nullptr}; @@ -1130,9 +1173,13 @@ namespace nana : public widget_geometrics { color_proxy header_bgcolor{static_cast(0xf1f2f4)}; + color_proxy header_fgcolor{ colors::black }; color_proxy header_grabbed{ static_cast(0x8BD6F6)}; color_proxy header_floated{ static_cast(0xBABBBC)}; - color_proxy item_selected{ static_cast(0xD5EFFC) }; + color_proxy item_selected{ static_cast(0xCCE8FF) }; + color_proxy item_highlighted{ static_cast(0xE5F3FF) }; + + color_proxy selection_box{ static_cast(0x3399FF) }; ///< Color of selection box border. /// The max column width which is generated by fit_content is allowed. It is ignored when it is 0, or a max value is passed to fit_content. unsigned max_fit_content{ 0 }; @@ -1142,9 +1189,7 @@ namespace nana unsigned suspension_width { 8 }; ///< def= . the trigger will set this to the width if ("...") unsigned text_margin { 5 }; ///< def= 5. Additional or extended with added (before) to the text width to determine the cell width. cell_w = text_w + ext_w +1 unsigned header_height { 25 }; ///< def=25 . header height header_size - unsigned text_height { 14 }; ///< the trigger will set this to the height of the text font unsigned item_height_ex { 6 }; ///< Set !=0 !!!! def=6. item_height = text_height + item_height_ex - unsigned item_height { 24 }; ///< def=24 . the trigger will set this TO item_height = text_height + item_height_ex unsigned header_splitter_area_before{ 2 }; ///< def=2. But 4 is better... IMO unsigned header_splitter_area_after { 3 }; ///< def=3. But 4 is better... @@ -1324,7 +1369,7 @@ the nana::detail::basic_window member pointer scheme bool assoc_ordered(bool); - void auto_draw(bool); ///< Set state: Redraw automatically after an operation + void auto_draw(bool) noexcept; ///< Set state: Redraw automatically after an operation template void avoid_drawing(Function fn) @@ -1353,26 +1398,28 @@ the nana::detail::basic_window member pointer scheme /// Access a column at specified position /** * @param pos Position of column + * @param disp_order Indicates whether the pos is display position or absolute position. * @return Reference to the requested column * @except std::out_of_range if !(pos < columns()) */ - column_interface & column_at(size_type pos); + column_interface & column_at(size_type pos, bool disp_order = false); /// Access a column at specified position /** * @param pos Position of column + * @param disp_order Indicates whether the pos is display position or absolute position. * @return Constant reference to the requested column * @except std::out_of_range if !(pos < columns()) */ - const column_interface & column_at(size_type pos) const; + const column_interface & column_at(size_type pos, bool disp_order = false) const; /// Returns the number of columns size_type column_size() const; - cat_proxy append(std::string); ///< Appends a new category to the end - cat_proxy append(std::wstring); ///< Appends a new category to the end - void append(std::initializer_list); ///< Appends categories to the end - void append(std::initializer_list); ///< Appends categories to the end + cat_proxy append(std::string category); ///< Appends a new category to the end + cat_proxy append(std::wstring category); ///< Appends a new category to the end + void append(std::initializer_list categories); ///< Appends categories to the end + void append(std::initializer_list categories); ///< Appends categories to the end cat_proxy insert(cat_proxy, ::std::string); cat_proxy insert(cat_proxy, ::std::wstring); @@ -1392,18 +1439,19 @@ the nana::detail::basic_window member pointer scheme void insert_item(const index_pair& abs_pos, const ::std::wstring& text); /// Returns an index of item which contains the specified point. - index_pair cast(const point & pos) const; + index_pair cast(const point & screen_pos) const; - /// Returns the column which contains the specified point. - size_type column_from_pos(const point & pos); + /// Returns the absolute position of column which contains the specified point. + size_type column_from_pos(const point & pos) const; void checkable(bool); index_pairs checked() const; ///, bool create_if_not_exists); - void _m_erase_key(nana::detail::key_interface*); + void _m_erase_key(nana::detail::key_interface*) noexcept; }; }//end namespace nana diff --git a/include/nana/gui/widgets/menu.hpp b/include/nana/gui/widgets/menu.hpp index 02372079..693310a2 100644 --- a/include/nana/gui/widgets/menu.hpp +++ b/include/nana/gui/widgets/menu.hpp @@ -1,22 +1,20 @@ /** * A Menu implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2009-2014 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2009-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at * http://www.boost.org/LICENSE_1_0.txt) * * @file: nana/gui/widgets/menu.hpp + * */ #ifndef NANA_GUI_WIDGETS_MENU_HPP #define NANA_GUI_WIDGETS_MENU_HPP #include "widget.hpp" -#include -#include #include - #include namespace nana @@ -50,6 +48,8 @@ namespace nana item_proxy& checked(bool); bool checked() const; + std::string text() const; + std::size_t index() const; private: std::size_t index_; @@ -71,25 +71,12 @@ namespace nana menu_type *sub_menu{nullptr}; std::string text; - event_fn_t functor; + event_fn_t event_handler; checks style{checks::none}; paint::image image; mutable wchar_t hotkey{0}; }; - struct menu_type - { - typedef std::vector item_container; - typedef item_container::iterator iterator; - typedef item_container::const_iterator const_iterator; - - std::vector owner; - std::vector items; - unsigned max_pixels; - unsigned item_pixels; - nana::point gaps; - }; - class renderer_interface { public: @@ -138,12 +125,24 @@ namespace nana ~menu(); /// Appends an item to the menu. - item_proxy append(const std::string& text, const event_fn_t& callback= event_fn_t()); + item_proxy append(std::string text_utf8, const event_fn_t& handler = {}); void append_splitter(); + + /// Inserts new item at specified position + /** + * It will invalidate the existing item proxies from the specified position. + * @param pos The position where new item to be inserted + * @param text_utf8 The title of item + * @param handler The event handler for the item. + * @return the item proxy to the new inserted item. + */ + item_proxy insert(std::size_t pos, std::string text_utf8, const event_fn_t& handler = {}); + void clear(); ///< Erases all of the items. /// Closes the menu. It does not destroy the menu; just close the window for the menu. void close(); void image(std::size_t pos, const paint::image& icon); + void text(std::size_t pos, std::string text_utf8); void check_style(std::size_t pos, checks); void checked(std::size_t pos, bool); bool checked(std::size_t pos) const; @@ -156,7 +155,7 @@ namespace nana void popup(window owner, int x, int y); ///< Popup the menu at the owner window. void popup_await(window owner, int x, int y); void answerer(std::size_t index, const event_fn_t&); ///< Modify answerer of the specified item. - void destroy_answer(const std::function&); ///< Sets an answerer for the callback while the menu window is closing. + void destroy_answer(std::function); ///< Sets an answerer for the callback while the menu window is closing. void gaps(const nana::point&); ///< Sets the gap between a menu and its sub menus.(\See Note4) void goto_next(bool forward); ///< Moves the focus to the next or previous item. bool goto_submen();///< Popup the submenu of the current item if it has a sub menu. Returns true if succeeds. @@ -175,7 +174,7 @@ namespace nana const pat::cloneable& renderer() const; private: - void _m_popup(window, int x, int y, bool called_by_menubar); + void _m_popup(window, const point& position, bool called_by_menubar); private: implement * impl_; }; diff --git a/include/nana/gui/widgets/menubar.hpp b/include/nana/gui/widgets/menubar.hpp index 3c799936..b636a125 100644 --- a/include/nana/gui/widgets/menubar.hpp +++ b/include/nana/gui/widgets/menubar.hpp @@ -24,6 +24,16 @@ namespace nana { using native_string_type = ::nana::detail::native_string_type; + struct scheme + : public widget_geometrics + { + color_proxy text_fgcolor{ colors::black }; + color_proxy body_highlight{ static_cast(0xc0ddfc) }; + color_proxy body_selected{ colors::white }; + color_proxy border_selected{ colors::dark_border }; + color_proxy border_highlight{ colors::highlight }; + }; + class item_renderer { public: @@ -33,13 +43,16 @@ namespace nana }; using graph_reference = paint::graphics&; + using scheme = ::nana::drawerbase::menubar::scheme; item_renderer(window, graph_reference); virtual void background(const point&, const ::nana::size&, state); virtual void caption(const point&, const native_string_type&); + scheme *scheme_ptr() const { return scheme_ptr_; }; private: window handle_; graph_reference graph_; + scheme *scheme_ptr_; }; class trigger @@ -72,7 +85,7 @@ namespace nana bool _m_track_mouse(const ::nana::point&); private: widget *widget_; - nana::paint::graphics *graph_; + paint::graphics *graph_; itembase* items_; @@ -104,7 +117,7 @@ namespace nana /// /// The widget sets as shortkey the character behind the first of & in the text, for the item. e.g. "File(&F)" or "&File". class menubar - : public widget_object + : public widget_object { public: menubar() = default; ///< The default constructor delay creation. diff --git a/include/nana/gui/widgets/panel.hpp b/include/nana/gui/widgets/panel.hpp index 648880ec..b159fa35 100644 --- a/include/nana/gui/widgets/panel.hpp +++ b/include/nana/gui/widgets/panel.hpp @@ -1,7 +1,7 @@ /** * A Panel Implementation * Nana C++ Library(http://www.nanaro.org) - * Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -57,7 +57,7 @@ namespace nana bool transparent() const { - return (bground_mode::basic == API::effects_bground_mode(*this)); + return API::is_transparent_background(*this); } void transparent(bool tr) diff --git a/include/nana/gui/widgets/picture.hpp b/include/nana/gui/widgets/picture.hpp index fc8e9c63..9050f8be 100644 --- a/include/nana/gui/widgets/picture.hpp +++ b/include/nana/gui/widgets/picture.hpp @@ -1,7 +1,7 @@ /** * A Picture Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at diff --git a/include/nana/gui/widgets/scroll.hpp b/include/nana/gui/widgets/scroll.hpp index dbbb6452..c4e39981 100644 --- a/include/nana/gui/widgets/scroll.hpp +++ b/include/nana/gui/widgets/scroll.hpp @@ -122,7 +122,9 @@ namespace nana void value(size_type s) { - if (s + metrics_.range > metrics_.peak) + if (metrics_.range > metrics_.peak) + s = 0; + else if (s + metrics_.range > metrics_.peak) s = metrics_.peak - metrics_.range; if (graph_ && (metrics_.value != s)) diff --git a/include/nana/gui/widgets/skeletons/text_editor.hpp b/include/nana/gui/widgets/skeletons/text_editor.hpp index b4f6318d..3fe3e157 100644 --- a/include/nana/gui/widgets/skeletons/text_editor.hpp +++ b/include/nana/gui/widgets/skeletons/text_editor.hpp @@ -1,7 +1,7 @@ /* * A text editor implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -19,7 +19,6 @@ #include "text_editor_part.hpp" #include -//#include #include #include @@ -56,6 +55,8 @@ namespace nana{ namespace widgets class keyword_parser; class helper_pencil; + struct text_section; + text_editor(const text_editor&) = delete; text_editor& operator=(const text_editor&) = delete; @@ -89,6 +90,8 @@ namespace nana{ namespace widgets void set_keyword(const ::std::wstring& kw, const std::string& name, bool case_sensitive, bool whole_word_matched); void erase_keyword(const ::std::wstring& kw); + colored_area_access_interface& colored_area(); + void set_accept(std::function); void set_accept(accepts); bool respond_char(const arg_keyboard& arg); @@ -99,14 +102,10 @@ namespace nana{ namespace widgets void indent(bool, std::function generator); void set_event(event_interface*); - /// Determine whether the text_editor is line wrapped. - bool line_wrapped() const; - - /// Set the text_editor whether it is line wrapped, it returns false if the state is not changed. - bool line_wrapped(bool); - bool load(const char*); + void text_align(::nana::align alignment); + /// Sets the text area. /// @return true if the area is changed with the new value. bool text_area(const nana::rectangle&); @@ -116,7 +115,12 @@ namespace nana{ namespace widgets bool tip_string(::std::string&&); - const attributes & attr() const; + /// Returns the reference of listbox attributes + const attributes & attr() const noexcept; + + /// Set the text_editor whether it is line wrapped, it returns false if the state is not changed. + bool line_wrapped(bool); + bool multi_lines(bool); /// Enables/disables the editability of text_editor @@ -134,9 +138,10 @@ namespace nana{ namespace widgets std::size_t undo_max_steps() const; renderers& customized_renderers(); + void clear_undo(); ///< same with undo_max_steps(0) unsigned line_height() const; - unsigned screen_lines() const; + unsigned screen_lines(bool completed_line = false) const; bool getline(std::size_t pos, ::std::wstring&) const; void text(std::wstring, bool end_caret); @@ -148,17 +153,18 @@ namespace nana{ namespace widgets * @param reset indicates whether to reset the text position by the pos. If this parameter is true, the text position is set by pos. If the parameter is false, it only moves the UI caret to the specified position. */ bool move_caret(const upoint& pos, bool reset = false); - void move_caret_end(); + void move_caret_end(bool update); void reset_caret_pixels() const; - void reset_caret(); + void reset_caret(bool stay_in_view = false); void show_caret(bool isshow); bool selected() const; + bool get_selected_points(nana::upoint&, nana::upoint&) const; + bool select(bool); - bool get_select_points(nana::upoint&, nana::upoint&) const; /// Sets the end position of a selected string. - void set_end_caret(); + void set_end_caret(bool stay_in_view); bool hit_text_area(const point&) const; bool hit_select_area(nana::upoint pos, bool ignore_when_select_all) const; @@ -192,7 +198,7 @@ namespace nana{ namespace widgets void move_ns(bool to_north); //Moves up and down void move_left(); void move_right(); - const upoint& mouse_caret(const point& screen_pos); + const upoint& mouse_caret(const point& screen_pos, bool stay_in_view); const upoint& caret() const; point caret_screen_pos() const; bool scroll(bool upwards, bool vertical); @@ -200,21 +206,29 @@ namespace nana{ namespace widgets bool focus_changed(const arg_focus&); bool mouse_enter(bool entering); bool mouse_move(bool left_button, const point& screen_pos); - bool mouse_pressed(const arg_mouse& arg); + void mouse_pressed(const arg_mouse& arg); skeletons::textbase& textbase(); const skeletons::textbase& textbase() const; + + bool try_refresh(); private: + nana::color _m_draw_colored_area(paint::graphics& graph, const std::pair& row, bool whole_line); + std::vector _m_render_text(const ::nana::color& text_color); void _m_pre_calc_lines(std::size_t line_off, std::size_t lines); + ::nana::point _m_caret_to_screen(::nana::upoint pos) const; + ::nana::upoint _m_screen_to_caret(::nana::point pos) const; + + bool _m_pos_from_secondary(std::size_t textline, const nana::upoint& secondary, unsigned & pos); + bool _m_pos_secondary(const nana::upoint& charpos, nana::upoint& secondary_pos) const; + bool _m_move_caret_ns(bool to_north); + void _m_update_line(std::size_t pos, std::size_t secondary_count_before); + bool _m_accepts(char_type) const; ::nana::color _m_bgcolor() const; - bool _m_scroll_text(bool vertical); - void _m_scrollbar(); - ::nana::rectangle _m_text_area() const; - - void _m_get_scrollbar_size(); + void _m_reset_content_size(bool calc_lines = false); void _m_reset(); ::nana::upoint _m_put(::std::wstring); ::nana::upoint _m_erase_select(); @@ -231,23 +245,25 @@ namespace nana{ namespace widgets bool _m_move_select(bool record_undo); int _m_text_top_base() const; + int _m_text_topline() const; + + /// Returns the logical position that text starts of a specified line in x-axis + int _m_text_x(const text_section&) const; void _m_draw_parse_string(const keyword_parser&, bool rtl, ::nana::point pos, const ::nana::color& fgcolor, const wchar_t*, std::size_t len) const; //_m_draw_string //@brief: Draw a line of string - void _m_draw_string(int top, const ::nana::color&, const nana::upoint& str_pos, const ::std::wstring&, bool if_mask) const; + void _m_draw_string(int top, const ::nana::color&, const nana::upoint& str_pos, const text_section&, bool if_mask) const; //_m_update_caret_line //@brief: redraw whole line specified by caret pos. //@return: true if caret overs the border bool _m_update_caret_line(std::size_t secondary_before); - void _m_offset_y(int y); - - unsigned _m_char_by_pixels(const unicode_bidi::entity&, unsigned pos); - + unsigned _m_char_by_pixels(const unicode_bidi::entity&, unsigned pos) const; unsigned _m_pixels_by_char(const ::std::wstring&, ::std::size_t pos) const; void _m_handle_move_key(const arg_keyboard& arg); + unsigned _m_width_px(bool exclude_vs) const; void _m_draw_border(); private: struct implementation; @@ -264,6 +280,8 @@ namespace nana{ namespace widgets { ::std::string tip_string; + ::nana::align alignment{ ::nana::align::left }; + bool line_wrapped{false}; bool multi_lines{true}; bool editable{true}; @@ -275,27 +293,24 @@ namespace nana{ namespace widgets { nana::rectangle area; - bool captured; - unsigned tab_space; - unsigned scroll_pixels; - unsigned vscroll; - unsigned hscroll; + bool captured{ false }; + unsigned tab_space{ 4 }; }text_area_; struct selection { - enum class mode{ no_selected, mouse_selected, method_selected, move_selected }; + enum class mode{ no_selected, mouse_selected, method_selected, move_selected, move_selected_take_effect }; + + bool ignore_press{ false }; + bool move_to_end{ false }; + mode mode_selection{ mode::no_selected }; + text_focus_behavior behavior{text_focus_behavior::none}; - text_focus_behavior behavior; - bool move_to_end; - mode mode_selection; - bool ignore_press; nana::upoint a, b; }select_; struct coordinate { - nana::point offset; //x stands for pixels, y for lines nana::upoint caret; //position of caret by text, it specifies the position of a new character nana::upoint shift_begin_caret; unsigned xpos{0}; //This data is used for move up/down diff --git a/include/nana/gui/widgets/skeletons/text_editor_part.hpp b/include/nana/gui/widgets/skeletons/text_editor_part.hpp index 0b5eb266..4e77a48f 100644 --- a/include/nana/gui/widgets/skeletons/text_editor_part.hpp +++ b/include/nana/gui/widgets/skeletons/text_editor_part.hpp @@ -26,7 +26,8 @@ namespace nana struct text_editor_scheme : public ::nana::widget_geometrics { - color_proxy selection{static_cast(0x3399FF)}; + color_proxy selection{ static_cast(0x3399FF) }; + color_proxy selection_unfocused{ static_cast(0xF0F0F0) }; color_proxy selection_text{colors::white}; parameters::mouse_wheel mouse_wheel; ///< The number of lines/characters to scroll when the vertical/horizontal mouse wheel is moved. @@ -39,6 +40,29 @@ namespace nana virtual void text_exposed(const std::vector&) = 0; }; + + struct colored_area_type + { + const ::std::size_t begin; ///< The begin line position + ::std::size_t count; ///< The number of lines + + ::nana::color bgcolor; + ::nana::color fgcolor; + }; + + class colored_area_access_interface + { + public: + using colored_area_type = skeletons::colored_area_type; + + virtual ~colored_area_access_interface(); + + virtual std::shared_ptr get(std::size_t line_pos) = 0; + virtual bool clear() = 0; + virtual bool remove(std::size_t line_pos) = 0; + virtual std::size_t size() const = 0; + virtual std::shared_ptr at(std::size_t index) = 0; + }; } } } diff --git a/include/nana/gui/widgets/skeletons/text_token_stream.hpp b/include/nana/gui/widgets/skeletons/text_token_stream.hpp index 3807ac16..ea61349e 100644 --- a/include/nana/gui/widgets/skeletons/text_token_stream.hpp +++ b/include/nana/gui/widgets/skeletons/text_token_stream.hpp @@ -435,7 +435,7 @@ namespace nana{ namespace widgets{ namespace skeletons }; ::std::string font; - std::size_t font_size; + double font_size; bool bold; bool bold_empty; //bold should be ignored if bold_empty is true aligns::t text_align; @@ -859,7 +859,7 @@ namespace nana{ namespace widgets{ namespace skeletons //Create a default fblock. fblock * fbp = new fblock; - fbp->font_size = 0xFFFFFFFF; + fbp->font_size = -1; fbp->bold = false; fbp->bold_empty = true; fbp->text_align = fblock::aligns::baseline; diff --git a/include/nana/gui/widgets/spinbox.hpp b/include/nana/gui/widgets/spinbox.hpp index e090426a..2ecabc78 100644 --- a/include/nana/gui/widgets/spinbox.hpp +++ b/include/nana/gui/widgets/spinbox.hpp @@ -1,7 +1,7 @@ /** * A Spin box widget * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -56,6 +56,7 @@ namespace nana private: //Overrides drawer_trigger void attached(widget_reference, graph_reference) override; + void detached() override; void refresh(graph_reference) override; void focus(graph_reference, const arg_focus&) override; @@ -95,8 +96,11 @@ namespace nana void range(double begin, double last, double step); /// Sets the string spin values. - void range(std::initializer_list values_utf8); - void range(std::initializer_list values); + void range(std::vector values_utf8); + + std::vector range_string() const; + std::pair range_int() const; + std::pair range_double() const; /// Gets the spined value ::std::string value() const; diff --git a/include/nana/gui/widgets/tabbar.hpp b/include/nana/gui/widgets/tabbar.hpp index 1ff4fccc..f22e6e5c 100644 --- a/include/nana/gui/widgets/tabbar.hpp +++ b/include/nana/gui/widgets/tabbar.hpp @@ -248,7 +248,7 @@ namespace nana API::refresh_window(this->handle()); } - pat::cloneable& renderer() const + const pat::cloneable& renderer() const { return this->get_drawer_trigger().ext_renderer(); } diff --git a/include/nana/gui/widgets/textbox.hpp b/include/nana/gui/widgets/textbox.hpp index 6a8af7ff..e37ed6fc 100644 --- a/include/nana/gui/widgets/textbox.hpp +++ b/include/nana/gui/widgets/textbox.hpp @@ -1,7 +1,7 @@ /** * A Textbox Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -100,9 +100,12 @@ namespace nana :public widget_object { public: - using text_focus_behavior = widgets::skeletons::text_focus_behavior; + using colored_area_type = widgets::skeletons::colored_area_type; + using colored_area_access_interface = widgets::skeletons::colored_area_access_interface; + using text_focus_behavior = widgets::skeletons::text_focus_behavior; using text_positions = std::vector; + /// The default constructor without creating the widget. textbox(); @@ -134,6 +137,8 @@ namespace nana void store(std::string file); void store(std::string file, nana::unicode encoding); + colored_area_access_interface* colored_area_access(); + /// Enables/disables the textbox to indent a line. Idents a new line when it is created by pressing enter. /// @param generator generates text for identing a line. If it is empty, textbox indents the line according to last line. textbox& indention(bool, std::function generator = {}); @@ -162,6 +167,9 @@ namespace nana /// Read the text from a specified line. It returns true for success. bool getline(std::size_t pos, std::string&) const; + /// Read the text from a specified line with a set offset. It returns true for success. + bool getline(std::size_t line_index,std::size_t offset,std::string& text) const; + /// Gets the caret position /// Returns true if the caret is in the area of display, false otherwise. bool caret_pos(point& pos, bool text_coordinate) const; @@ -196,6 +204,7 @@ namespace nana /// Returns true if some text is selected. bool selected() const; + bool get_selected_points(nana::upoint &a, nana::upoint &b) const; /// Selects/unselects all text. void select(bool); @@ -215,12 +224,17 @@ namespace nana textbox& from(int); textbox& from(double); + void clear_undo(); + void set_highlight(const std::string& name, const ::nana::color& fgcolor, const ::nana::color& bgcolor); void erase_highlight(const std::string& name); void set_keywords(const std::string& name, bool case_sensitive, bool whole_word_match, std::initializer_list kw_list); void set_keywords(const std::string& name, bool case_sensitive, bool whole_word_match, std::initializer_list kw_list_utf8); void erase_keyword(const std::string& kw); + /// Sets the text alignment + textbox& text_align(::nana::align alignment); + /// Returns the text position of each line that currently displays on screen. text_positions text_position() const; diff --git a/include/nana/gui/widgets/treebox.hpp b/include/nana/gui/widgets/treebox.hpp index ae5e03b1..a05f52f3 100644 --- a/include/nana/gui/widgets/treebox.hpp +++ b/include/nana/gui/widgets/treebox.hpp @@ -1,7 +1,7 @@ /** * A Tree Box Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -37,7 +37,7 @@ namespace nana { enum class component { - begin, expender = begin, crook, icon, text, bground, end + begin, expander = begin, crook, icon, text, bground, end }; struct node_image_tag @@ -73,8 +73,8 @@ namespace nana typedef compset_interface::comp_attribute_t comp_attribute_t; virtual ~renderer_interface() = default; - virtual void set_color(const nana::color& bgcolor, const nana::color& fgcolor) = 0; + virtual void begin_paint(::nana::widget&) = 0; virtual void bground(graph_reference, const compset_interface *) const = 0; virtual void expander(graph_reference, const compset_interface *) const = 0; virtual void crook(graph_reference, const compset_interface *) const = 0; @@ -110,45 +110,30 @@ namespace nana struct pseudo_node_type{}; - typedef widgets::detail::tree_cont tree_cont_type; - typedef tree_cont_type::node_type node_type; + using tree_cont_type = widgets::detail::tree_cont; + using node_type = tree_cont_type::node_type; trigger(); ~trigger(); implement * impl() const; - void auto_draw(bool); - void checkable(bool); - bool checkable() const; void check(node_type*, checkstate); - bool draw(); - - const tree_cont_type & tree() const; - tree_cont_type & tree(); void renderer(::nana::pat::cloneable&&); const ::nana::pat::cloneable& renderer() const; void placer(::nana::pat::cloneable&&); const ::nana::pat::cloneable& placer() const; - nana::any & value(node_type*) const; node_type* insert(node_type*, const std::string& key, std::string&&); node_type* insert(const std::string& path, std::string&&); - bool verify(const void*) const; - bool verify_kinship(node_type* parent, node_type* child) const; - - void remove(node_type*); node_type * selected() const; void selected(node_type*); - void set_expand(node_type*, bool); - void set_expand(const ::std::string& path, bool); node_image_tag& icon(const ::std::string&) const; void icon_erase(const ::std::string&); void node_icon(node_type*, const ::std::string& id); - unsigned node_width(const node_type*) const; bool rename(node_type*, const char* key, const char* name); @@ -206,6 +191,9 @@ namespace nana /// Set the check state, and it returns itself. item_proxy& check(bool); + /// Clears the child nodes + item_proxy& clear(); + /// Return true when the node is expanded \todo change to expanded ?? bool expanded() const; @@ -421,6 +409,9 @@ namespace nana bool checkable() const; ///< Determinte whether the checkboxs are enabled. + /// Clears the contents + void clear(); + /// \brief Creates an icon scheme with the specified name. /// /// The icon scheme includes 3 images for node states. diff --git a/include/nana/gui/widgets/widget.hpp b/include/nana/gui/widgets/widget.hpp index b46125b1..922fbe3b 100644 --- a/include/nana/gui/widgets/widget.hpp +++ b/include/nana/gui/widgets/widget.hpp @@ -1,7 +1,7 @@ /** * The fundamental widget class implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -64,7 +64,7 @@ namespace nana template void i18n(std::string msgid, Args&&... args) { - _m_caption(nana::internationalization().get(msgid, std::forward(args)...)); + _m_caption(::nana::to_nstring(::nana::internationalization().get(msgid, std::forward(args)...))); } void i18n(i18n_eval); @@ -247,7 +247,6 @@ namespace nana { widget_base::_m_notify_destroy(); events_ = std::make_shared(); - API::dev::set_events(handle_, events_); } private: DrawerTrigger trigger_; @@ -312,7 +311,6 @@ namespace nana { widget_base::_m_notify_destroy(); events_ = std::make_shared(); - API::dev::set_events(handle_, events_); } private: std::shared_ptr events_; @@ -451,7 +449,6 @@ namespace nana { widget_base::_m_notify_destroy(); events_ = std::make_shared(); - API::dev::set_events(handle_, events_); } private: DrawerTrigger trigger_; diff --git a/include/nana/gui/wvl.hpp b/include/nana/gui/wvl.hpp index 0500a6a0..ccee91d3 100644 --- a/include/nana/gui/wvl.hpp +++ b/include/nana/gui/wvl.hpp @@ -1,7 +1,7 @@ /** * Nana GUI Library Definition * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -59,6 +59,8 @@ namespace nana template using form_loader = detail::form_loader; +#ifdef NANA_AUTOMATIC_GUI_TESTING + /// @brief Take control of the GUI and optionaly automaticaly tests it. /// /// @detail It transfers to nana the program flow control, which begin pumping messages @@ -77,6 +79,9 @@ namespace nana /// in seconds void Wait(unsigned wait = 0); +#else + void exec(); +#endif }//end namespace nana diff --git a/include/nana/internationalization.hpp b/include/nana/internationalization.hpp index 7aec20a8..4b3e3918 100644 --- a/include/nana/internationalization.hpp +++ b/include/nana/internationalization.hpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace nana { @@ -23,6 +24,9 @@ namespace nana { friend class i18n_eval; public: + /// Sets a handler to handle a msgid which hasn't been translated. + static void set_missing(std::function handler); + void load(const std::string& file); void load_utf8(const std::string& file); @@ -32,9 +36,8 @@ namespace nana std::vector arg_strs; _m_fetch_args(arg_strs, std::forward(args)...); - ::std::string msgstr; - if (_m_get(msgid_utf8, msgstr)) - _m_replace_args(msgstr, &arg_strs); + auto msgstr = _m_get(std::move(msgid_utf8)); + _m_replace_args(msgstr, &arg_strs); return msgstr; } @@ -47,16 +50,24 @@ namespace nana return get(msgid_utf8, std::forward(args)...); } private: - bool _m_get(std::string& msgid, ::std::string& msgstr) const; + std::string _m_get(std::string&& msgid) const; void _m_replace_args(::std::string& str, std::vector<::std::string> * arg_strs) const; - void _m_fetch_args(std::vector&) const //Termination of _m_fetch_args - {} + void _m_fetch_args(std::vector&) const; //Termination of _m_fetch_args + + void _m_fetch_args(std::vector& v, const char* arg) const; + void _m_fetch_args(std::vector& v, const std::string& arg) const; + void _m_fetch_args(std::vector& v, std::string& arg) const; + void _m_fetch_args(std::vector& v, std::string&& arg) const; + void _m_fetch_args(std::vector& v, const wchar_t* arg) const; + void _m_fetch_args(std::vector& v, const std::wstring& arg) const; + void _m_fetch_args(std::vector& v, std::wstring& arg) const; + void _m_fetch_args(std::vector& v, std::wstring&& arg) const; template void _m_fetch_args(std::vector& v, Arg&& arg) const { - std::wstringstream ss; + std::stringstream ss; ss << arg; v.emplace_back(ss.str()); } @@ -89,6 +100,34 @@ namespace nana _m_fetch_args(v, std::forward(args)...); } + template + void _m_fetch_args(std::vector& v, const wchar_t* arg, Args&&... args) const + { + v.emplace_back(to_utf8(arg)); + _m_fetch_args(v, std::forward(args)...); + } + + template + void _m_fetch_args(std::vector& v, const std::wstring& arg, Args&&... args) const + { + v.emplace_back(to_utf8(arg)); + _m_fetch_args(v, std::forward(args)...); + } + + template + void _m_fetch_args(std::vector& v, std::wstring& arg, Args&&... args) const + { + v.emplace_back(to_utf8(arg)); + _m_fetch_args(v, std::forward(args)...); + } + + template + void _m_fetch_args(std::vector& v, std::wstring&& arg, Args&&... args) const + { + v.emplace_back(to_utf8(arg)); + _m_fetch_args(v, std::forward(args)...); + } + template void _m_fetch_args(std::vector& v, Arg&& arg, Args&&... args) const { diff --git a/include/nana/key_type.hpp b/include/nana/key_type.hpp index 9bca1e18..8d77edbd 100644 --- a/include/nana/key_type.hpp +++ b/include/nana/key_type.hpp @@ -1,7 +1,7 @@ /* * A Key Implementation * Nana C++ Library(http://www.nanapro.org) -* Copyright(C) 2003-2014 Jinhao(cnjinhao@hotmail.com) +* Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -21,12 +21,12 @@ namespace nana public: virtual ~key_interface(){} - virtual bool same_type(const key_interface*) const = 0; - virtual bool compare(const key_interface*) const = 0; ///< is this key less than right key? [call it less(rk), less_than(rk) or compare_less(rk)?: if (lk.less_than(rk )) ] + virtual bool same_type(const key_interface*) const noexcept = 0; + virtual bool compare(const key_interface*) const noexcept = 0; ///< is this key less than right key? [call it less(rk), less_than(rk) or compare_less(rk)?: if (lk.less_than(rk )) ] }; //end class key_interface //Use less compare for equal compare [call it equal_by_less()?] - inline bool pred_equal(const key_interface * left, const key_interface* right) + inline bool pred_equal(const key_interface * left, const key_interface* right) noexcept { return (left->same_type(right) && (left->compare(right) == false) && (right->compare(left) == false)); } @@ -104,12 +104,12 @@ namespace nana } public: //implement key_interface methods - bool same_type(const key_interface * p) const override + bool same_type(const key_interface * p) const noexcept override { return (nullptr != dynamic_cast(p)); } - bool compare(const key_interface* p) const override + bool compare(const key_interface* p) const noexcept override { auto rhs = dynamic_cast(p); return rhs && compare_(key_object_, rhs->key_object_); diff --git a/include/nana/optional.hpp b/include/nana/optional.hpp new file mode 100644 index 00000000..49b81d88 --- /dev/null +++ b/include/nana/optional.hpp @@ -0,0 +1,364 @@ +/** + * Optional + * Nana C++ Library(http://www.nanapro.org) + * Copyright(C) 2017 Jinhao(cnjinhao@hotmail.com) + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * @file nana/optional.hpp + * + * @brief An implementation of experimental library optional of C++ standard(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3793.html) + */ + +#ifndef NANA_STD_OPTIONAL_HEADER_INCLUDED +#define NANA_STD_OPTIONAL_HEADER_INCLUDED + +#include +#include +namespace nana +{ + namespace detail + { + template + class storage + { + public: + using value_type = T; + + storage() = default; + + template + storage(U&& value) + : initialized_{ true } + { + ::new (data_) value_type(std::forward(value)); + } + + template + storage(const U& value) + : initialized_{ true } + { + ::new (data_) value_type(value); + } + + storage(const storage& other) + : initialized_{ other.initialized_ } + { + if (other.initialized_) + ::new (data_) value_type(*other.ptr()); + } + + storage(storage&& other) + : initialized_{ other.initialized_ } + { + if (other.initialized_) + ::new (data_) value_type(std::move(*other.ptr())); + } + + template + storage(const storage& other) + : initialized_{ other.initialized_ } + { + if (other.initialized_) + ::new (data_) value_type(*other.ptr()); + } + + ~storage() + { + destroy(); + } + + bool initialized() const noexcept + { + return initialized_; + } + + void set_initialized() + { + initialized_ = true; + } + + void destroy() + { + if (initialized_) + { + ptr()->~T(); + initialized_ = false; + } + } + + template + void assign(U&& value) + { + if (initialized_) + *ptr() = std::forward(value); + else + { + ::new (data_) value_type(std::forward(value)); + initialized_ = true; + } + } + + void assign(const storage& other) + { + if (!other.initialized_) + { + destroy(); + return; + } + + if (initialized_) + *ptr() = *other.ptr(); + else + { + ::new (data_) value_type(*other.ptr()); + initialized_ = true; + } + } + + void assign(storage&& other) + { + if (!other.initialized_) + { + destroy(); + return; + } + + if (initialized_) + *ptr() = std::move(*other.ptr()); + else + { + ::new (data_) value_type(std::move(*other.ptr())); + initialized_ = true; + } + } + + const T* ptr() const + { + return reinterpret_cast(data_); + } + + T* ptr() + { + return reinterpret_cast(data_); + } + private: + bool initialized_{ false }; + char data_[sizeof(value_type)]; + }; + }//end namespace detail + + class bad_optional_access + : public std::logic_error + { + public: + bad_optional_access() + : std::logic_error("Attempted to access the value of an uninitialized optional object.") + {} + }; + + template + class optional + { + public: + using value_type = T; + + constexpr optional() = default; + constexpr optional(std::nullptr_t) {} + + optional(const optional& other) + : storage_(other.storage_) + {} + + optional(optional&& other) + : storage_(std::move(other.storage_)) + {} + + constexpr optional(const value_type& value) + : storage_(value) + {} + + constexpr optional(value_type&& value) + : storage_(std::move(value)) + {} + + optional& operator=(std::nullptr_t) + { + storage_.destroy(); + return *this; + } + + optional& operator=(const optional& other) + { + if (this != &other) + { + storage_.assign(other.storage_); + } + return *this; + } + + optional& operator=(optional&& other) + { + if (this != &other) + { + storage_.assign(std::move(other.storage_)); + } + return *this; + } + + template + optional& operator=(U&& value) + { + storage_.assign(std::forward(value)); + + return *this; + } + + //Value access + //constexpr + value_type* operator->() + { + return storage_.ptr(); + } + constexpr const value_type* operator->() const + { + return storage_.ptr(); + } + + //constexpr + value_type& operator*() + { + return *storage_.ptr(); + } + + constexpr const value_type& operator*() const + { + return *storage_.ptr(); + } + + /* + //constexpr + value_type&& operator*() && + { + return std::move(*storage_.ptr()); + } + + //constexpr + const value_type&& operator*() const && + { + return std::move(*storage_.ptr()); + } + */ + + //Condition + constexpr explicit operator bool() const + { + return storage_.initialized(); + } + + constexpr bool has_value() const + { + return storage_.initialized(); + } + + //constexpr + value_type& value() + { + if (!storage_.initialized()) + throw bad_optional_access{}; + + return *storage_.ptr(); + } + + constexpr const value_type& value() const + { + if (!storage_.initialized()) + throw bad_optional_access{}; + + return *storage_.ptr(); + } + /* + //constexpr + value_type&& value() + { + if (!storage_.initialized()) + throw bad_optional_access{}; + + return std::move(*storage_.ptr()); + } + + constexpr const value_type&& value() const + { + if (!storage_.initialized()) + throw bad_optional_access{}; + + return std::move(*storage_.ptr()); + } + */ + + template + constexpr T value_or(U&& default_value) const + { + return (has_value() ? **this : static_cast(std::forward(default_value))); + } + + template + //constexpr + T value_or(U&& default_value) + { + return (has_value() ? std::move(**this) : static_cast(std::forward(default_value))); + } + + //Modifiers + void swap(optional& other) + { + if (has_value() && other.has_value()) + { + std::swap(**this, *other); + return; + } + else if (has_value()) + { + other.emplace(std::move(***this)); + storage_.destroy(); + } + else if (other.has_value()) + { + this->emplace(std::move(*other)); + other.storage_.destroy(); + } + } + + void reset() + { + storage_.destroy(); + } + + template + void emplace(Args&&... args) + { + storage_.destroy(); + ::new (storage_.ptr()) T(std::forward(args)...); + + storage_.set_initialized(); + } + + template + void emplace(std::initializer_list il, Args&& ... args) + { + storage_.destroy(); + ::new (storage_.ptr()) T(il, std::forward(args)...); + + storage_.set_initialized(); + } + + + private: + detail::storage storage_; + + }; +} + +#endif diff --git a/include/nana/paint/detail/image_impl_interface.hpp b/include/nana/paint/detail/image_impl_interface.hpp index 3e75d18e..4a99abaf 100644 --- a/include/nana/paint/detail/image_impl_interface.hpp +++ b/include/nana/paint/detail/image_impl_interface.hpp @@ -14,7 +14,7 @@ namespace nana{ namespace paint{ { image_impl_interface& operator=(const image_impl_interface& rhs); public: - typedef nana::paint::graphics& graph_reference; + using graph_reference = nana::paint::graphics&; virtual ~image_impl_interface() = 0; //The destructor is defined in ../image.cpp virtual bool open(const std::experimental::filesystem::path& file) = 0; virtual bool open(const void* data, std::size_t bytes) = 0; // reads image from memory diff --git a/include/nana/paint/detail/image_processor.hpp b/include/nana/paint/detail/image_processor.hpp index 4051eb90..3832456f 100644 --- a/include/nana/paint/detail/image_processor.hpp +++ b/include/nana/paint/detail/image_processor.hpp @@ -426,7 +426,7 @@ namespace detail unsigned char * fade_table = nullptr; std::unique_ptr autoptr; - nana::pixel_argb_t rgb_imd; + nana::pixel_argb_t rgb_imd = {}; if(fade_rate != 0.0) { autoptr = detail::alloc_fade_table(1 - fade_rate); diff --git a/include/nana/paint/detail/native_paint_interface.hpp b/include/nana/paint/detail/native_paint_interface.hpp index 4a3dba44..844427a5 100644 --- a/include/nana/paint/detail/native_paint_interface.hpp +++ b/include/nana/paint/detail/native_paint_interface.hpp @@ -25,12 +25,12 @@ namespace detail void free_fade_table(const unsigned char*); //color = bgcolor * fade_rate + fgcolor * (1 - fade_rate); - //nana::pixel_color_t fade_color(nana::pixel_color_t bgcolor, nana::pixel_color_t fgcolor, double fade_rate); //deprecated nana::pixel_color_t fade_color(nana::pixel_color_t bgcolor, nana::pixel_color_t fgcolor, const unsigned char* const fade_table); nana::pixel_color_t fade_color_intermedia(pixel_color_t fgcolor, const unsigned char* fade_table); nana::pixel_color_t fade_color_by_intermedia(pixel_color_t bgcolor, nana::pixel_color_t fgcolor_intermedia, const unsigned char* const fade_table); - void blend(drawable_type dw, const nana::rectangle& r, pixel_color_t, double fade_rate); + //dw color = dw color * fade_rate + bdcolor * (1 - fade_rate) + void blend(drawable_type dw, const nana::rectangle& r, pixel_color_t bdcolor, double fade_rate); nana::size raw_text_extent_size(drawable_type, const wchar_t*, std::size_t len); nana::size text_extent_size(drawable_type, const wchar_t*, std::size_t len); diff --git a/include/nana/paint/detail/ptdefs.hpp b/include/nana/paint/detail/ptdefs.hpp new file mode 100644 index 00000000..ac5647e6 --- /dev/null +++ b/include/nana/paint/detail/ptdefs.hpp @@ -0,0 +1,29 @@ +#ifndef NANA_PAINT_PTDEFS_INCLUDED +#define NANA_PAINT_PTDEFS_INCLUDED + +namespace nana +{ + namespace detail + { + struct native_font_signature; + + + struct font_style + { + unsigned weight{ 400 }; //normal + bool italic{ false }; + bool underline{ false }; + bool strike_out{ false }; + + font_style() = default; + font_style(unsigned weight, bool italic = false, bool underline = false, bool strike_out = false); + }; + }//end namespace detail + + namespace paint + { + using native_font_type = ::nana::detail::native_font_signature*; + } +} + +#endif \ No newline at end of file diff --git a/include/nana/paint/graphics.hpp b/include/nana/paint/graphics.hpp index a2fb04bb..4eafffa2 100644 --- a/include/nana/paint/graphics.hpp +++ b/include/nana/paint/graphics.hpp @@ -1,7 +1,7 @@ /* * Paint Graphics Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -17,40 +17,42 @@ #include "../basic_types.hpp" #include "../gui/basis.hpp" +#include + +#include "detail/ptdefs.hpp" namespace nana { namespace paint { - namespace detail - { - struct native_font_signature; - }// end namespace detail - - typedef detail::native_font_signature* native_font_type; - class font { friend class graphics; public: + using path_type = ::std::experimental::filesystem::path; + + using font_style = ::nana::detail::font_style; + font(); font(drawable_type); font(const font&); - font(const ::std::string& name, unsigned size, bool bold = false, bool italic = false, bool underline = false, bool strike_out = false); + + font(const ::std::string& name, double size_pt, const font_style& fs = {}); + font(double size_pt, const path_type& truetype, const font_style& ft = {}); + ~font(); bool empty() const; - void make(const ::std::string& name, unsigned size, bool bold = false, bool italic = false, bool underline = false, bool strike_out = false); - void make_raw(const ::std::string& name, unsigned height, unsigned weight, bool italic, bool underline, bool strike_out); void set_default() const; ::std::string name() const; - unsigned size() const; + double size() const; bool bold() const; - unsigned height() const; unsigned weight() const; bool italic() const; native_font_type handle() const; void release(); + bool strikeout() const; + bool underline() const; font& operator=(const font&); bool operator==(const font&) const; @@ -116,7 +118,7 @@ namespace nana void bitblt(const ::nana::rectangle& r_dst, const graphics& src, const point& p_src);///< Transfers the color data corresponding to r_dst from the src graphics at point p_src to this graphics. void blend(const ::nana::rectangle& r, const ::nana::color&, double fade_rate); - void blend(const ::nana::rectangle& s_r, graphics& dst, const point& d_pos, double fade_rate) const;///< blends with the dst object. + void blend(const ::nana::rectangle& blend_r, const graphics& blend_graph, const point& blend_graph_point, double fade_rate);///< blends with the blend_graph. void blur(const ::nana::rectangle& r, std::size_t radius); ///< Blur process. diff --git a/include/nana/pat/abstract_factory.hpp b/include/nana/pat/abstract_factory.hpp index 27966fe2..9f23642e 100644 --- a/include/nana/pat/abstract_factory.hpp +++ b/include/nana/pat/abstract_factory.hpp @@ -84,7 +84,7 @@ namespace nana { std::unique_ptr create() override { - constexpr auto Size = std::tuple_size::value; + constexpr auto const Size = std::tuple_size::value; return std::unique_ptr{ _m_new(make_pack{}) }; } diff --git a/include/nana/stdc++.hpp b/include/nana/stdc++.hpp new file mode 100644 index 00000000..4d8bbef9 --- /dev/null +++ b/include/nana/stdc++.hpp @@ -0,0 +1,156 @@ +/** + * Standard Library for C++11/14/17 + * Nana C++ Library(http://www.nanapro.org) + * Copyright(C) 2017 Jinhao(cnjinhao@hotmail.com) + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * @file nana/stdc++.hpp + * + * @brief Implement the lack support of standard library. + */ + +#include "c++defines.hpp" + +//Implement workarounds for GCC/MinGW which version is below 4.8.2 +#if defined(STD_NUMERIC_CONVERSIONS_NOT_SUPPORTED) +namespace std +{ + //Workaround for no implemenation of std::stoi in MinGW. + int stoi(const std::string&, std::size_t * pos = nullptr, int base = 10); + int stoi(const std::wstring&, std::size_t* pos = nullptr, int base = 10); + + //Workaround for no implemenation of std::stof in MinGW. + float stof(const std::string&, std::size_t * pos = nullptr); + float stof(const std::wstring&, std::size_t* pos = nullptr); + + //Workaround for no implemenation of std::stod in MinGW. + double stod(const std::string&, std::size_t * pos = nullptr); + double stod(const std::wstring&, std::size_t* pos = nullptr); + + //Workaround for no implemenation of std::stold in MinGW. + long double stold(const std::string&, std::size_t * pos = nullptr); + long double stold(const std::wstring&, std::size_t* pos = nullptr); + + //Workaround for no implemenation of std::stol in MinGW. + long stol(const std::string&, std::size_t* pos = nullptr, int base = 10); + long stol(const std::wstring&, std::size_t* pos = nullptr, int base = 10); + + //Workaround for no implemenation of std::stoll in MinGW. + long long stoll(const std::string&, std::size_t* pos = nullptr, int base = 10); + long long stoll(const std::wstring&, std::size_t* pos = nullptr, int base = 10); + + //Workaround for no implemenation of std::stoul in MinGW. + unsigned long stoul(const std::string&, std::size_t* pos = nullptr, int base = 10); + unsigned long stoul(const std::wstring&, std::size_t* pos = nullptr, int base = 10); + + //Workaround for no implemenation of std::stoull in MinGW. + unsigned long long stoull(const std::string&, std::size_t* pos = nullptr, int base = 10); + unsigned long long stoull(const std::wstring&, std::size_t* pos = nullptr, int base = 10); +} +#endif //STD_NUMERIC_CONVERSIONS_NOT_SUPPORTED + +#ifdef STD_TO_STRING_NOT_SUPPORTED +namespace std +{ + //Workaround for no implemenation of std::to_string/std::to_wstring in MinGW. + std::string to_string(long double); + std::string to_string(double); + std::string to_string(unsigned); + std::string to_string(int); + std::string to_string(long); + std::string to_string(unsigned long); + std::string to_string(long long); + std::string to_string(unsigned long long); + std::string to_string(float); +} +#endif + +#ifdef STD_TO_WSTRING_NOT_SUPPORTED +namespace std +{ + std::wstring to_wstring(long double); + std::wstring to_wstring(double); + std::wstring to_wstring(unsigned); + std::wstring to_wstring(int); + std::wstring to_wstring(long); + std::wstring to_wstring(unsigned long); + std::wstring to_wstring(long long); + std::wstring to_wstring(unsigned long long); + std::wstring to_wstring(float); +} +#endif + +#ifdef _enable_std_make_unique + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3656.htm + +#include +#include +#include +#include + +namespace std { + template struct _Unique_if { + typedef unique_ptr _Single_object; + }; + + template struct _Unique_if { + typedef unique_ptr _Unknown_bound; + }; + + template struct _Unique_if { + typedef void _Known_bound; + }; + + template + typename _Unique_if::_Single_object + make_unique(Args&&... args) { + return unique_ptr(new T(std::forward(args)...)); + } + + template + typename _Unique_if::_Unknown_bound + make_unique(size_t n) { + typedef typename remove_extent::type U; + return unique_ptr(new U[n]()); + } + + template + typename _Unique_if::_Known_bound + make_unique(Args&&...) = delete; +} +#endif //_enable_std_make_unique + +#ifdef _enable_std_put_time +#include +#include +namespace std +{ + //Workaround for no implemenation of std::put_time in gcc < 5. + /* std unspecified return type */ + //template< class CharT, class RTSTR >// let fail for CharT != char / wchar_t + //RTSTR put_time(const std::tm* tmb, const CharT* fmt); + + //template< > + std::string put_time/**/(const std::tm* tmb, const char* fmt); + + //Defined in header + // std::size_t strftime(char* str, std::size_t count, const char* format, const std::tm* time); + //template<> + //std::wstring put_time(const std::tm* tmb, const wchar_t* fmt); +} +#endif // _enable_std_put_time + +#if defined(_enable_std_clamp) +namespace std +{ + // since C++17 + template + constexpr const T& clamp(const T& v, const T& lo, const T& hi) + { + return (v < lo ? lo : (hi < v ? hi : v)); + } +} +#endif \ No newline at end of file diff --git a/include/nana/system/dataexch.hpp b/include/nana/system/dataexch.hpp index 575a79c3..7ce6be72 100644 --- a/include/nana/system/dataexch.hpp +++ b/include/nana/system/dataexch.hpp @@ -1,6 +1,6 @@ /* * Data Exchanger Implementation - * Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -12,7 +12,7 @@ #ifndef NANA_SYSTEM_DATAEXCH_HPP #define NANA_SYSTEM_DATAEXCH_HPP -#include +#include namespace nana{ @@ -31,15 +31,17 @@ namespace system{ text, pixmap }; - void set(const std::string & text_utf8); - void set(const std::wstring& text); + void set(const std::string & text_utf8, native_window_type owner = nullptr); + void set(const std::wstring& text, native_window_type owner = nullptr); - bool set(const nana::paint::graphics& g); + bool set(const nana::paint::graphics& g, native_window_type owner = nullptr); void get(std::string& text_utf8); void get(std::wstring& text); + + std::wstring wget(); private: - bool _m_set(format, const void* buf, std::size_t size); + bool _m_set(format, const void* buf, std::size_t size, native_window_type); void* _m_get(format, size_t& size); }; diff --git a/include/nana/threads/pool.hpp b/include/nana/threads/pool.hpp index 16edac1f..38d9952e 100644 --- a/include/nana/threads/pool.hpp +++ b/include/nana/threads/pool.hpp @@ -54,11 +54,17 @@ namespace threads struct task_signal; class impl; + + pool(const pool&) = delete; + pool& operator=(const pool&) = delete; public: pool(); ///< Creates a group of threads. + pool(pool&&); pool(std::size_t thread_number); ///< Creates a number of threads specifed by thread_number. ~pool(); ///< waits for the all running tasks till they are finished and skips all the queued tasks. + pool& operator=(pool&&); + template void push(const Function& f) { diff --git a/include/nana/verbose_preprocessor.hpp b/include/nana/verbose_preprocessor.hpp index 07b306f8..b4804546 100644 --- a/include/nana/verbose_preprocessor.hpp +++ b/include/nana/verbose_preprocessor.hpp @@ -30,9 +30,9 @@ - #define STRING2(x) #x + #define STRING2(...) #__VA_ARGS__ #define STRING(x) STRING2(x) - #define SHOW_VALUE(x) " " #x " = " STRING2(x) + #define SHOW_VALUE(x) " " #x " = " STRING2(x) #pragma message ( "\n -----> Verbose preprocessor" ) #pragma message ( SHOW_VALUE(VERBOSE_PREPROCESSOR) ) @@ -80,7 +80,7 @@ #pragma message ( SHOW_VALUE(USE_github_com_meganz_mingw_std_threads) ) #pragma message ( SHOW_VALUE(NANA_ENABLE_MINGW_STD_THREADS_WITH_MEGANZ) ) #pragma message ( SHOW_VALUE(STD_THREAD_NOT_SUPPORTED) ) - #pragma message ( SHOW_VALUE(STD_put_time_NOT_SUPPORTED) ) + #pragma message ( SHOW_VALUE(_enable_std_put_time) ) #pragma message ( SHOW_VALUE(STD_MAKE_UNIQUE_NOT_SUPPORTED) ) #pragma message ( SHOW_VALUE(STD_FILESYSTEM_NOT_SUPPORTED) ) @@ -124,7 +124,7 @@ #pragma message ( SHOW_VALUE(USE_LIBJPEG_FROM_OS) ) #pragma message ( SHOW_VALUE(NANA_LIBJPEG) ) - + // #pragma message ( "\n =" STRING() ", \n =" STRING()" , \n =" STRING() ) #if defined(STOP_VERBOSE_PREPROCESSOR) diff --git a/source/basic_types.cpp b/source/basic_types.cpp index 6a40ee59..01bc5ef6 100644 --- a/source/basic_types.cpp +++ b/source/basic_types.cpp @@ -1,7 +1,7 @@ /* * Basic Types definition * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -53,11 +53,6 @@ namespace nana a_((static_cast(rgba) & 0xFF) / 255.0) {} - color::color(unsigned red, unsigned green, unsigned blue) - : r_(red), g_(green), b_(blue), a_(1.0) - { - } - color::color(unsigned red, unsigned green, unsigned blue, double alpha) : r_(red), g_(green), b_(blue), a_(alpha) { @@ -426,47 +421,12 @@ namespace nana return *this; } - color color::blend(const color& bgcolor, bool ignore_bgcolor_alpha) const - { - if (a_ < 1.0) - { - color result; - if (0.0 < a_) - { - if (ignore_bgcolor_alpha || (1.0 == bgcolor.b_)) - { - result.r_ = r_ * a_ + bgcolor.r_ * (1.0 - a_); - result.g_ = g_ * a_ + bgcolor.g_ * (1.0 - a_); - result.b_ = b_ * a_ + bgcolor.b_ * (1.0 - a_); - result.a_ = 1.0; - } - else - { - result.r_ = r_ * a_ + bgcolor.r_ * bgcolor.a_ * (1.0 - a_); - result.g_ = g_ * a_ + bgcolor.g_ * bgcolor.a_ * (1.0 - a_); - result.b_ = b_ * a_ + bgcolor.b_ * bgcolor.a_ * (1.0 - a_); - result.a_ = a_ + (bgcolor.a_ * (1.0 - a_)); - } - } - else - { - result.r_ = bgcolor.r_; - result.g_ = bgcolor.g_; - result.b_ = bgcolor.b_; - result.a_ = (ignore_bgcolor_alpha ? 1.0 : bgcolor.a_); - } - return result; - } - - return *this; - } - color color::blend(const color& bgcolor, double alpha) const { color result; - result.r_ = r_ * alpha + bgcolor.r_ * (1.0 - alpha); - result.g_ = g_ * alpha + bgcolor.g_ * (1.0 - alpha); - result.b_ = b_ * alpha + bgcolor.b_ * (1.0 - alpha); + result.r_ = r_ * (1.0 - alpha) + bgcolor.r_ * alpha; + result.g_ = g_ * (1.0 - alpha) + bgcolor.g_ * alpha; + result.b_ = b_ * (1.0 - alpha) + bgcolor.b_ * alpha; result.a_ = 1.0; return result; } @@ -649,12 +609,12 @@ namespace nana return *this; } - int rectangle::right() const + int rectangle::right() const noexcept { return x + static_cast(width); } - int rectangle::bottom() const + int rectangle::bottom() const noexcept { return y + static_cast(height); } diff --git a/source/deploy.cpp b/source/deploy.cpp index 7f715ef9..0c68a2ae 100644 --- a/source/deploy.cpp +++ b/source/deploy.cpp @@ -1,7 +1,7 @@ /* * The Deploy Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -21,463 +21,9 @@ #include #elif defined(NANA_LINUX) || defined(NANA_MACOS) #include - #include + #include "detail/platform_spec_selector.hpp" #endif -//Implement workarounds for GCC/MinGW which version is below 4.8.2 -#if defined(STD_NUMERIC_CONVERSIONS_NOT_SUPPORTED) -#include -namespace std -{ - int stoi(const std::string& str, std::size_t * pos, int base) - { - auto sptr = str.c_str(); - char *end; - errno = 0; - auto result = std::strtol(sptr, &end, base); - - if (sptr == end) - throw std::invalid_argument("invalid stoi argument"); - if (errno == ERANGE) - throw std::out_of_range("stoi argument out of range"); - - if (pos) - *pos = (std::size_t)(end - sptr); - return ((int)result); - } - - int stoi(const std::wstring& str, std::size_t* pos, int base) - { - auto sptr = str.data(); - wchar_t *end; - errno = 0; - auto result = std::wcstol(sptr, &end, base); - - if (sptr == end) - throw std::invalid_argument("invalid stoi argument"); - if (errno == ERANGE) - throw std::out_of_range("stoi argument out of range"); - - if (pos) - *pos = (std::size_t)(end - sptr); - return ((int)result); - } - using ::strtof; - using ::strtold; - using ::wcstold; - using ::strtoll; - using ::wcstoll; - using ::strtoull; - using ::wcstoull; - - float stof(const std::string& str, std::size_t * pos) - { - auto *ptr = str.data(); - errno = 0; - char *end; - auto result = std::strtof(ptr, &end); - - if (ptr == end) - throw std::invalid_argument("invalid stod argument"); - if (errno == ERANGE) - throw std::out_of_range("stof argument out of range"); - if (pos) - *pos = (std::size_t)(end - ptr); - return result; - } - - float stof(const std::wstring& str, std::size_t* pos) - { - auto *ptr = str.data(); - errno = 0; - wchar_t *end; - auto result = std::wcstof(ptr, &end); - - if (ptr == end) - throw std::invalid_argument("invalid stod argument"); - if (errno == ERANGE) - throw std::out_of_range("stof argument out of range"); - if (pos) - *pos = (std::size_t)(end - ptr); - return result; - } - - double stod(const std::string& str, std::size_t * pos) - { - auto *ptr = str.data(); - errno = 0; - char *end; - auto result = std::strtod(ptr, &end); - - if (ptr == end) - throw std::invalid_argument("invalid stod argument"); - if (errno == ERANGE) - throw std::out_of_range("stod argument out of range"); - if (pos) - *pos = (std::size_t)(end - ptr); - return result; - } - - double stod(const std::wstring& str, std::size_t* pos) - { - auto *ptr = str.data(); - errno = 0; - wchar_t *end; - auto result = std::wcstod(ptr, &end); - - if (ptr == end) - throw std::invalid_argument("invalid stod argument"); - if (errno == ERANGE) - throw std::out_of_range("stod argument out of range"); - if (pos) - *pos = (std::size_t)(end - ptr); - return result; - } - - long double stold(const std::string& str, std::size_t * pos) - { - auto *ptr = str.data(); - errno = 0; - char *end; - auto result = std::strtold(ptr, &end); - - if (ptr == end) - throw std::invalid_argument("invalid stod argument"); - if (errno == ERANGE) - throw std::out_of_range("stold argument out of range"); - if (pos) - *pos = (std::size_t)(end - ptr); - return result; - } - - long double stold(const std::wstring& str, std::size_t* pos) - { - auto *ptr = str.data(); - errno = 0; - wchar_t *end; - auto result = std::wcstold(ptr, &end); - - if (ptr == end) - throw std::invalid_argument("invalid stod argument"); - if (errno == ERANGE) - throw std::out_of_range("stold argument out of range"); - if (pos) - *pos = (std::size_t)(end - ptr); - return result; - } - - long stol(const std::string& str, std::size_t* pos, int base) - { - auto *ptr = str.data(); - errno = 0; - char *end; - auto result = std::strtol(ptr, &end, base); - - if (ptr == end) - throw std::invalid_argument("invalid stod argument"); - if (errno == ERANGE) - throw std::out_of_range("stol argument out of range"); - if (pos) - *pos = (std::size_t)(end - ptr); - return result; - } - - long stol(const std::wstring& str, std::size_t* pos, int base) - { - auto *ptr = str.data(); - errno = 0; - wchar_t *end; - auto result = std::wcstol(ptr, &end, base); - - if (ptr == end) - throw std::invalid_argument("invalid stod argument"); - if (errno == ERANGE) - throw std::out_of_range("stol argument out of range"); - if (pos) - *pos = (std::size_t)(end - ptr); - return result; - } - - //Workaround for no implemenation of std::stoll in MinGW. - long long stoll(const std::string& str, std::size_t* pos, int base) - { - auto *ptr = str.data(); - errno = 0; - char* end; - auto result = std::strtoll(ptr, &end, base); - - if (ptr == end) - throw std::invalid_argument("invalid stod argument"); - if (errno == ERANGE) - throw std::out_of_range("stoll argument out of range"); - if (pos) - *pos = (std::size_t)(end - ptr); - return result; - } - - long long stoll(const std::wstring& str, std::size_t* pos, int base) - { - auto *ptr = str.data(); - errno = 0; - wchar_t* end; - auto result = std::wcstoll(ptr, &end, base); - - if (ptr == end) - throw std::invalid_argument("invalid stod argument"); - if (errno == ERANGE) - throw std::out_of_range("stoll argument out of range"); - if (pos) - *pos = (std::size_t)(end - ptr); - return result; - } - - unsigned long long stoull(const std::string& str, std::size_t* pos, int base) - { - auto *ptr = str.data(); - errno = 0; - char* end; - auto result = std::strtoull(ptr, &end, base); - - if (ptr == end) - throw std::invalid_argument("invalid stod argument"); - if (errno == ERANGE) - throw std::out_of_range("stoull argument out of range"); - if (pos) - *pos = (std::size_t)(end - ptr); - return result; - } - - unsigned long long stoull(const std::wstring& str, std::size_t* pos, int base) - { - auto *ptr = str.data(); - errno = 0; - wchar_t* end; - auto result = std::wcstoull(ptr, &end, base); - - if (ptr == end) - throw std::invalid_argument("invalid stod argument"); - if (errno == ERANGE) - throw std::out_of_range("stoull argument out of range"); - if (pos) - *pos = (std::size_t)(end - ptr); - return result; - } - - //Workaround for no implemenation of std::stoul in MinGW. - unsigned long stoul(const std::string& str, std::size_t* pos, int base) - { - auto *ptr = str.data(); - errno = 0; - char* end; - auto result = std::strtoul(ptr, &end, base); - - if (ptr == end) - throw std::invalid_argument("invalid stod argument"); - if (errno == ERANGE) - throw std::out_of_range("stoul argument out of range"); - if (pos) - *pos = (std::size_t)(end - ptr); - return result; - } - - unsigned long stoul(const std::wstring& str, std::size_t* pos, int base) - { - auto *ptr = str.data(); - errno = 0; - wchar_t* end; - auto result = std::wcstoul(ptr, &end, base); - - if (ptr == end) - throw std::invalid_argument("invalid stod argument"); - if (errno == ERANGE) - throw std::out_of_range("stoul argument out of range"); - if (pos) - *pos = (std::size_t)(end - ptr); - return result; - } -}//end namespace std -#endif //STD_NUMERIC_CONVERSIONS_NOT_SUPPORTED - -#ifdef STD_TO_STRING_NOT_SUPPORTED -#include -namespace std -{ - std::string to_string(double v) - { - std::stringstream ss; - ss << v; - return ss.str(); - } - - std::string to_string(long double v) - { - std::stringstream ss; - ss << v; - return ss.str(); - } - - std::string to_string(unsigned v) - { - std::stringstream ss; - ss << v; - return ss.str(); - } - - std::string to_string(int v) - { - std::stringstream ss; - ss << v; - return ss.str(); - } - - std::string to_string(long v) - { - std::stringstream ss; - ss << v; - return ss.str(); - } - - std::string to_string(unsigned long v) - { - std::stringstream ss; - ss << v; - return ss.str(); - } - - std::string to_string(long long v) - { - std::stringstream ss; - ss << v; - return ss.str(); - } - - std::string to_string(unsigned long long v) - { - std::stringstream ss; - ss << v; - return ss.str(); - } - - std::string to_string(float v) - { - std::stringstream ss; - ss << v; - return ss.str(); - } -} -#endif // STD_TO_STRING_NOT_SUPPORTED - -#ifdef STD_TO_WSTRING_NOT_SUPPORTED -#include -namespace std -{ - std::wstring to_wstring(double v) - { - std::wstringstream ss; - ss << v; - return ss.str(); - } - - std::wstring to_wstring(long double v) - { - std::wstringstream ss; - ss << v; - return ss.str(); - } - - std::wstring to_wstring(unsigned v) - { - std::wstringstream ss; - ss << v; - return ss.str(); - } - - std::wstring to_wstring(int v) - { - std::wstringstream ss; - ss << v; - return ss.str(); - } - - std::wstring to_wstring(long v) - { - std::wstringstream ss; - ss << v; - return ss.str(); - } - - std::wstring to_wstring(unsigned long v) - { - std::wstringstream ss; - ss << v; - return ss.str(); - } - - std::wstring to_wstring(long long v) - { - std::wstringstream ss; - ss << v; - return ss.str(); - } - - std::wstring to_wstring(unsigned long long v) - { - std::wstringstream ss; - ss << v; - return ss.str(); - } - - std::wstring to_wstring(float v) - { - std::wstringstream ss; - ss << v; - return ss.str(); - } -} -#endif - -//#ifdef STD_put_time_NOT_SUPPORTED -#include -#include -namespace std -{ - //Workaround for no implemenation of std::put_time in gcc < 5. - /* std unspecified return type */ - //template< class CharT, class RTSTR >// let fail for CharT != char / wchar_t - //RTSTR put_time(const std::tm* tmb, const CharT* fmt); - - //template< > - std::string put_time/**/(const std::tm* tmb, const char* fmt) - { - std::size_t sz = 200; - std::string str(sz, '\0'); - sz = std::strftime(&str[0], str.size() - 1, fmt, tmb); - str.resize(sz); - return str; - } - //Defined in header - // std::size_t strftime(char* str, std::size_t count, const char* format, const std::tm* time); - //template<> - //std::wstring put_time(const std::tm* tmb, const wchar_t* fmt) - //{ - // unsigned sz = 200; - // std::wstring str(sz, L'\0'); - // sz = std::wcsftime(&str[0], str.size() - 1, fmt, tmb); - // str.resize(sz); - // return str; - //} - // http://en.cppreference.com/w/cpp/chrono/c/wcsftime - // Defined in header - // std::size_t wcsftime(wchar_t* str, std::size_t count, const wchar_t* format, const std::tm* time); - // Converts the date and time information from a given calendar time time to a null - terminated - // wide character string str according to format string format.Up to count bytes are written. - // Parameters - // str - pointer to the first element of the wchar_t array for output - // count - maximum number of wide characters to write - // format - pointer to a null - terminated wide character string specifying the format of conversion. - - } -//#endif // STD_put_time_NOT_SUPPORTED #include @@ -523,12 +69,12 @@ namespace nana # endif #endif - void utf8_Error::emit() - { - if (use_throw) - throw utf8_Error(*this); - std::cerr << what(); - } + void utf8_Error::emit() + { + if (use_throw) + throw utf8_Error(*this); + std::cerr << what(); + } //bool utf8_Error::use_throw{true}; bool utf8_Error::use_throw{ false }; @@ -536,25 +82,18 @@ namespace nana void throw_not_utf8(const std::string& text) { - if (!is_utf8(text.c_str(), text.length())) - return utf8_Error(std::string("\nThe text is not encoded in UTF8: ") + text).emit(); + throw_not_utf8(text.c_str(), text.size()); + } + + void throw_not_utf8(const char* text) + { + throw_not_utf8(text, std::strlen(text)); } void throw_not_utf8(const char* text, std::size_t len) { if (!is_utf8(text, len)) return utf8_Error(std::string("\nThe text is not encoded in UTF8: ") + std::string(text, len) ).emit(); - - //throw std::invalid_argument("The text is not encoded in UTF8"); - } - - void throw_not_utf8(const char* text) - { - if (!is_utf8(text, std::strlen(text))) - return utf8_Error(std::string("\nThe text is not encoded in UTF8: ") + text).emit(); - - //throw std::invalid_argument("The text is not encoded in UTF8"); - } std::string recode_to_utf8(std::string no_utf8) @@ -587,8 +126,6 @@ namespace nana return false; } - - const std::string& to_utf8(const std::string& str) { return str; diff --git a/include/nana/detail/win32/platform_spec.hpp b/source/detail/mswin/platform_spec.hpp similarity index 86% rename from include/nana/detail/win32/platform_spec.hpp rename to source/detail/mswin/platform_spec.hpp index c681d8a3..6de8c4e2 100644 --- a/include/nana/detail/win32/platform_spec.hpp +++ b/source/detail/mswin/platform_spec.hpp @@ -25,6 +25,8 @@ #include #include +#include "../platform_abstraction_types.hpp" + namespace nana { @@ -87,31 +89,16 @@ namespace detail }; }; - struct font_tag - { - native_string_type name; - unsigned height; - unsigned weight; - bool italic; - bool underline; - bool strikeout; - HFONT handle; - - struct deleter - { - void operator()(const font_tag*) const; - }; - }; - struct drawable_impl_type { - typedef std::shared_ptr font_ptr_t; + using font_type = ::std::shared_ptr; HDC context; HBITMAP pixmap; pixel_argb_t* pixbuf_ptr{nullptr}; std::size_t bytes_per_line{0}; - font_ptr_t font; + + font_type font; struct pen_spec { @@ -179,7 +166,6 @@ namespace detail platform_spec(platform_spec&&) = delete; platform_spec& operator=(platform_spec&&) = delete; public: - typedef drawable_impl_type::font_ptr_t font_ptr_t; typedef ::nana::event_code event_code; typedef ::nana::native_window_type native_window_type; @@ -200,12 +186,6 @@ namespace detail ~platform_spec(); - const font_ptr_t& default_native_font() const; - void default_native_font(const font_ptr_t&); - unsigned font_size_to_height(unsigned) const; - unsigned font_height_to_size(unsigned) const; - font_ptr_t make_native_font(const char* name, unsigned height, unsigned weight, bool italic, bool underline, bool strike_out); - static platform_spec& instance(); void keep_window_icon(native_window_type, const paint::image&sml_icon, const paint::image& big_icon); diff --git a/source/detail/platform_abstraction.cpp b/source/detail/platform_abstraction.cpp new file mode 100644 index 00000000..40d12d46 --- /dev/null +++ b/source/detail/platform_abstraction.cpp @@ -0,0 +1,408 @@ +#include "platform_abstraction.hpp" +#include +#include "../paint/truetype.hpp" + +#ifdef NANA_WINDOWS +# include +/////////////////////////////////////////////////////////////////////////////////////////////////////// + +/****************************************************************** +* * +* VersionHelpers.h -- This module defines helper functions to * +* promote version check with proper * +* comparisons. * +* * +* Copyright (c) Microsoft Corp. All rights reserved. * +* * +******************************************************************/ + +#include // for _In_, etc. + +#if !defined(__midl) && !defined(SORTPP_PASS) + +#if (NTDDI_VERSION >= NTDDI_WINXP) + +#ifdef __cplusplus + +#define VERSIONHELPERAPI inline bool + +#else // __cplusplus + +#define VERSIONHELPERAPI FORCEINLINE BOOL + +#endif // __cplusplus + +VERSIONHELPERAPI +IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor) +{ + OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, { 0 }, 0, 0 }; + DWORDLONG const dwlConditionMask = VerSetConditionMask( + VerSetConditionMask( + VerSetConditionMask( + 0, VER_MAJORVERSION, VER_GREATER_EQUAL), + VER_MINORVERSION, VER_GREATER_EQUAL), + VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + + osvi.dwMajorVersion = wMajorVersion; + osvi.dwMinorVersion = wMinorVersion; + osvi.wServicePackMajor = wServicePackMajor; + + return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE; +} + +VERSIONHELPERAPI +IsWindowsXPOrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 0); +} + +VERSIONHELPERAPI +IsWindowsXPSP1OrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 1); +} + +VERSIONHELPERAPI +IsWindowsXPSP2OrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 2); +} + +VERSIONHELPERAPI +IsWindowsXPSP3OrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 3); +} + +VERSIONHELPERAPI +IsWindowsVistaOrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 0); +} + +VERSIONHELPERAPI +IsWindowsVistaSP1OrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 1); +} + +VERSIONHELPERAPI +IsWindowsVistaSP2OrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 2); +} + +VERSIONHELPERAPI +IsWindows7OrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN7), LOBYTE(_WIN32_WINNT_WIN7), 0); +} + +VERSIONHELPERAPI +IsWindows7SP1OrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN7), LOBYTE(_WIN32_WINNT_WIN7), 1); +} + +#ifndef _WIN32_WINNT_WIN8 // (0x0602) +#define _WIN32_WINNT_WIN8 (0x0602) +#endif // _WIN32_WINNT_WIN8(0x0602) + +VERSIONHELPERAPI +IsWindows8OrGreater() +{ + + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), LOBYTE(_WIN32_WINNT_WIN8), 0); +} + +#ifndef _WIN32_WINNT_WINBLUE // (0x0602) +#define _WIN32_WINNT_WINBLUE (0x0602) +#endif // _WIN32_WINNT_WINBLUE (0x0602) + +VERSIONHELPERAPI +IsWindows8Point1OrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINBLUE), LOBYTE(_WIN32_WINNT_WINBLUE), 0); +} + +VERSIONHELPERAPI +IsWindowsServer() +{ + OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, { 0 }, 0, 0, 0, VER_NT_WORKSTATION }; + DWORDLONG const dwlConditionMask = VerSetConditionMask(0, VER_PRODUCT_TYPE, VER_EQUAL); + + return !VerifyVersionInfoW(&osvi, VER_PRODUCT_TYPE, dwlConditionMask); +} + +#endif // NTDDI_VERSION + +#endif // defined(__midl) +#else +# include "posix/platform_spec.hpp" +# include +# if defined(NANA_USE_XFT) +# include +# include +# include +# endif +#endif + +namespace nana +{ + + class internal_font + : public font_interface + { + public: + using path_type = std::experimental::filesystem::path; + + internal_font(const path_type& ttf, const std::string& font_family, double font_size, const font_style& fs, native_font_type native_font): + ttf_(ttf), + family_(font_family), + size_(font_size), + style_(fs), + native_handle_(native_font) + {} + + ~internal_font() + { +#ifdef NANA_WINDOWS + ::DeleteObject(reinterpret_cast(native_handle_)); +#elif defined(NANA_X11) + auto disp = ::nana::detail::platform_spec::instance().open_display(); +# ifdef NANA_USE_XFT + ::XftFontClose(disp, reinterpret_cast(native_handle_)); +# else + ::XFreeFontSet(disp, reinterpret_cast(native_handle_)); +# endif +#endif + if (!ttf_.empty()) + platform_abstraction::font_resource(false, ttf_); + } + public: + const std::string& family() const override + { + return family_; + } + + double size() const override + { + return size_; + } + + const font_style & style() const override + { + return style_; + } + + native_font_type native_handle() const override + { + return native_handle_; + } + private: + path_type const ttf_; + std::string const family_; + double const size_; + font_style const style_; + native_font_type const native_handle_; + }; + + struct platform_runtime + { + std::shared_ptr font; + +#ifdef NANA_X11 + std::map fontconfig_counts; +#endif + }; + + namespace + { + namespace data + { + static platform_runtime* storage; + } + } + + static platform_runtime& platform_storage() + { + if (nullptr == data::storage) + throw std::runtime_error("platform_abstraction is empty"); + + return *data::storage; + } + + void platform_abstraction::initialize() + { + if (nullptr == data::storage) + data::storage = new platform_runtime; + } + + void platform_abstraction::shutdown() + { + auto & r = platform_storage(); + + if (r.font.use_count() > 1) + throw std::runtime_error("platform_abstraction is disallowed to shutdown"); + + r.font.reset(); + + delete data::storage; + data::storage = nullptr; + } + + ::std::shared_ptr platform_abstraction::default_font(const ::std::shared_ptr& new_font) + { + auto & r = platform_storage(); + if (new_font) + { + auto f = r.font; + if (new_font != r.font) + r.font = new_font; + + return f; + } + + if (!r.font) + r.font = make_font({}, 0, {}); + + return r.font; + } + + static std::shared_ptr font_factory(::std::string font_family, double size_pt, const platform_abstraction::font::font_style& fs, internal_font::path_type ttf) + { + using native_font_type = platform_abstraction::font::native_font_type; +#ifdef NANA_WINDOWS + std::wstring wfont_family = to_nstring(font_family); + + //Make sure the length of font family less than LF_FACESIZE which is defined by Windows + if (wfont_family.length() + 1 > LF_FACESIZE) + wfont_family.clear(); + + //Translate pt to px + auto hDC = ::GetDC(nullptr); + auto font_height = -static_cast(size_pt * ::GetDeviceCaps(hDC, LOGPIXELSY) / 72); + ::ReleaseDC(nullptr, hDC); + + if (wfont_family.empty() || (0 == font_height)) + { + //Create default font object. + NONCLIENTMETRICS metrics = {}; + metrics.cbSize = sizeof metrics; +#if(WINVER >= 0x0600) +#if defined(NANA_MINGW) + OSVERSIONINFO osvi = {}; + osvi.dwOSVersionInfoSize = sizeof(osvi); + ::GetVersionEx(&osvi); + if (osvi.dwMajorVersion < 6) + metrics.cbSize -= sizeof(metrics.iPaddedBorderWidth); +#else + if (!IsWindowsVistaOrGreater()) + metrics.cbSize -= sizeof(metrics.iPaddedBorderWidth); +#endif +#endif + ::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof metrics, &metrics, 0); + + if (wfont_family.empty()) + { + wfont_family = metrics.lfMessageFont.lfFaceName; + font_family = to_utf8(wfont_family); + } + + if (0 == font_height) + font_height = metrics.lfMessageFont.lfHeight; + } + + + ::LOGFONT lf{}; + + std::wcscpy(lf.lfFaceName, wfont_family.c_str()); + lf.lfHeight = font_height; + lf.lfCharSet = DEFAULT_CHARSET; + lf.lfWeight = fs.weight; + lf.lfQuality = PROOF_QUALITY; + lf.lfPitchAndFamily = FIXED_PITCH; + lf.lfItalic = fs.italic; + lf.lfUnderline = fs.underline; + lf.lfStrikeOut = fs.strike_out; + + auto fd = ::CreateFontIndirect(&lf); +#elif defined(NANA_X11) + auto disp = ::nana::detail::platform_spec::instance().open_display(); +# ifdef NANA_USE_XFT + if(font_family.empty()) + font_family = '*'; + + std::string pat_str = font_family + '-' + std::to_string(size_pt ? size_pt : 10); + auto pat = ::XftNameParse(pat_str.c_str()); + XftResult res; + auto match_pat = ::XftFontMatch(disp, ::XDefaultScreen(disp), pat, &res); + + ::XftFont* fd = nullptr; + if (match_pat) + fd = ::XftFontOpenPattern(disp, match_pat); +# else + std::string pat_str; + if (font_family.empty()) + pat_str = "-misc-fixed-*"; + else + pat_str = "-misc-fixed-" + font_family; + + char ** missing_list; + int missing_count; + char * defstr; + XFontSet fd = ::XCreateFontSet(display_, const_cast(pat_str.c_str()), &missing_list, &missing_count, &defstr); +# endif +#endif + if (fd) + return std::make_shared(std::move(ttf), std::move(font_family), size_pt, fs, reinterpret_cast(fd)); + return{}; + } + + ::std::shared_ptr platform_abstraction::make_font(const std::string& font_family, double size_pt, const font::font_style& fs) + { + return font_factory(font_family, size_pt, fs, {}); + } + + ::std::shared_ptr platform_abstraction::make_font_from_ttf(const path_type& ttf, double size_pt, const font::font_style& fs) + { + ::nana::spec::truetype truetype{ ttf }; + if (truetype.font_family().empty()) + return nullptr; + + font_resource(true, ttf); + + return font_factory(truetype.font_family(), size_pt, fs, ttf); + } + + void platform_abstraction::font_resource(bool try_add, const path_type& ttf) + { +#ifdef NANA_WINDOWS + if (try_add) + ::AddFontResourceEx(ttf.wstring().c_str(), FR_PRIVATE, nullptr); + else + ::RemoveFontResourceEx(ttf.wstring().c_str(), FR_PRIVATE, nullptr); +#else + auto & fc = platform_storage().fontconfig_counts; + if(try_add) + { + if(1 == ++(fc[ttf.string()])) + { + ::FcConfigAppFontAddFile(nullptr, reinterpret_cast(ttf.string().c_str())); + } + } + else + { + auto i = fc.find(ttf.string()); + if(i != fc.end()) + { + if(0 == --(i->second)) + fc.erase(i); + + if(0 == fc.size()) + ::FcConfigAppFontClear(nullptr); + } + } +#endif + } +} \ No newline at end of file diff --git a/source/detail/platform_abstraction.hpp b/source/detail/platform_abstraction.hpp new file mode 100644 index 00000000..d22190c6 --- /dev/null +++ b/source/detail/platform_abstraction.hpp @@ -0,0 +1,43 @@ +/* + * Platform Abstraction + * Nana C++ Library(http://www.nanapro.org) + * Copyright(C) 2017 Jinhao(cnjinhao@hotmail.com) + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * @file: nana/detail/platform_spec.hpp + * + * The platform_abstraction provides some functions and types for the abstract + * system platform. + */ +#ifndef NANA_DETAIL_PLATFORM_ABSTRACTION_HEADER_INCLUDED +#define NANA_DETAIL_PLATFORM_ABSTRACTION_HEADER_INCLUDED + +#include "platform_abstraction_types.hpp" +#include +#include + +namespace nana +{ + + + class platform_abstraction + { + public: + using font = font_interface; + + using path_type = ::std::experimental::filesystem::path; + + static void initialize(); + /// Shutdown before destruction of platform_spec + static void shutdown(); + static ::std::shared_ptr default_font(const ::std::shared_ptr&); + static ::std::shared_ptr make_font(const ::std::string& font_family, double size_pt, const font::font_style& fs); + static ::std::shared_ptr make_font_from_ttf(const path_type& ttf, double size_pt, const font::font_style& fs); + static void font_resource(bool try_add, const path_type& ttf); + }; +} + +#endif \ No newline at end of file diff --git a/source/detail/platform_abstraction_types.hpp b/source/detail/platform_abstraction_types.hpp new file mode 100644 index 00000000..3321a57d --- /dev/null +++ b/source/detail/platform_abstraction_types.hpp @@ -0,0 +1,29 @@ +#ifndef NANA_DETAIL_PLATFORM_ABSTRACTION_TYPES_HEADER_INCLUDED +#define NANA_DETAIL_PLATFORM_ABSTRACTION_TYPES_HEADER_INCLUDED +#include +#include + +#include + +#ifdef NANA_X11 +# define NANA_USE_XFT +#endif + +namespace nana +{ + class font_interface + { + public: + using font_style = detail::font_style; + using native_font_type = paint::native_font_type; + + virtual ~font_interface() = default; + + virtual const std::string& family() const = 0; + virtual double size() const = 0; + virtual const font_style & style() const = 0; + virtual native_font_type native_handle() const = 0; + }; +} + +#endif \ No newline at end of file diff --git a/source/detail/platform_spec_posix.cpp b/source/detail/platform_spec_posix.cpp index c737cdf6..89ea7b8c 100644 --- a/source/detail/platform_spec_posix.cpp +++ b/source/detail/platform_spec_posix.cpp @@ -1,7 +1,7 @@ /* * Platform Specification Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Nana Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -14,7 +14,8 @@ * http://standards.freedesktop.org/clipboards-spec/clipboards-0.1.txt */ -#include +#include "platform_spec_selector.hpp" +#include "platform_abstraction.hpp" #if defined(NANA_POSIX) && defined(NANA_X11) #include @@ -33,7 +34,7 @@ #include #include -#include "x11/msg_dispatcher.hpp" +#include "posix/msg_dispatcher.hpp" namespace nana { @@ -451,24 +452,6 @@ namespace detail } } - class font_deleter - { - public: - void operator()(const font_tag* fp) const - { - if(fp && fp->handle) - { - platform_scope_guard psg; -#if defined(NANA_USE_XFT) - ::XftFontClose(nana::detail::platform_spec::instance().open_display(), fp->handle); -#else - ::XFreeFontSet(nana::detail::platform_spec::instance().open_display(), fp->handle); -#endif - } - delete fp; - } - };//end class font_deleter - platform_scope_guard::platform_scope_guard() { platform_spec::instance().lock_xlib(); @@ -561,9 +544,9 @@ namespace detail atombase_.xdnd_typelist = ::XInternAtom(display_, "XdndTypeList", False); atombase_.xdnd_finished = ::XInternAtom(display_, "XdndFinished", False); - //Create default font object. - def_font_ptr_ = make_native_font(nullptr, font_size_to_height(10), 400, false, false, false); msg_dispatcher_ = new msg_dispatcher(display_); + + platform_abstraction::initialize(); } platform_spec::~platform_spec() @@ -572,75 +555,11 @@ namespace detail //The font should be destroyed before closing display, //otherwise it crashs - def_font_ptr_.reset(); + platform_abstraction::shutdown(); close_display(); } - const platform_spec::font_ptr_t& platform_spec::default_native_font() const - { - return def_font_ptr_; - } - - void platform_spec::default_native_font(const font_ptr_t& fp) - { - def_font_ptr_ = fp; - } - - unsigned platform_spec::font_size_to_height(unsigned size) const - { - return size; - } - - unsigned platform_spec::font_height_to_size(unsigned height) const - { - return height; - } - - platform_spec::font_ptr_t platform_spec::make_native_font(const char* name, unsigned height, unsigned weight, bool italic, bool underline, bool strike_out) - { - font_ptr_t ref; -#if 1 //Xft - if(0 == name || *name == 0) - name = "*"; - - XftFont* handle = 0; - std::stringstream ss; - ss<(basestr.c_str()), &missing_list, &missing_count, &defstr); -#endif - if(handle) - { - font_tag * impl = new font_tag; - impl->name = name; - impl->height = height; - impl->weight = weight; - impl->italic = italic; - impl->underline = underline; - impl->strikeout = strike_out; - impl->handle = handle; - return font_ptr_t(impl, font_deleter()); - } - return font_ptr_t(); - } - Display* platform_spec::open_display() { return display_; @@ -1131,6 +1050,7 @@ namespace detail void platform_spec::write_selection(native_window_type owner, Atom type, const void * buf, size_t bufsize) { platform_scope_guard psg; + ::XSetSelectionOwner(display_, XA_PRIMARY, reinterpret_cast(owner), CurrentTime); ::XSetSelectionOwner(display_, atombase_.clipboard, reinterpret_cast(owner), CurrentTime); ::XFlush(display_); if(XA_STRING == type || atombase_.utf8_string == type) @@ -1162,8 +1082,16 @@ namespace detail // 2 = msg_dispatcher should ignore the msg, because the XEvent is processed by _m_msg_filter int platform_spec::_m_msg_filter(XEvent& evt, msg_packet_tag& msg) { + auto & bedrock = detail::bedrock::instance(); + platform_spec & self = instance(); - if(SelectionNotify == evt.type) + if(KeyPress == evt.type || KeyRelease == evt.type) + { + auto menu_wd = bedrock.get_menu(reinterpret_cast(evt.xkey.window), true); + if(menu_wd) + evt.xkey.window = reinterpret_cast(menu_wd); + } + else if(SelectionNotify == evt.type) { if(evt.xselection.property) { @@ -1196,7 +1124,6 @@ namespace detail } } - self.selection_.items.erase(self.selection_.items.begin()); std::lock_guardcond_mutex)> lock(im->cond_mutex); @@ -1372,9 +1299,8 @@ namespace detail { Window child; ::XTranslateCoordinates(self.display_, self.root_window(), evt.xclient.window, x, y, &self.xdnd_.pos.x, &self.xdnd_.pos.y, &child); - typedef detail::bedrock bedrock; - auto wd = bedrock::instance().wd_manager().find_window(reinterpret_cast(evt.xclient.window), self.xdnd_.pos.x, self.xdnd_.pos.y); + auto wd = bedrock.wd_manager().find_window(reinterpret_cast(evt.xclient.window), self.xdnd_.pos); if(wd && wd->flags.dropable) { accepted = true; diff --git a/include/nana/detail/platform_spec_selector.hpp b/source/detail/platform_spec_selector.hpp similarity index 84% rename from include/nana/detail/platform_spec_selector.hpp rename to source/detail/platform_spec_selector.hpp index fc4504f8..f37edf21 100644 --- a/include/nana/detail/platform_spec_selector.hpp +++ b/source/detail/platform_spec_selector.hpp @@ -15,7 +15,7 @@ #include #if defined(NANA_WINDOWS) -#include +#include "mswin/platform_spec.hpp" #elif defined(NANA_LINUX) || defined(NANA_MACOS) -#include +#include "posix/platform_spec.hpp" #endif \ No newline at end of file diff --git a/source/detail/platform_spec_windows.cpp b/source/detail/platform_spec_windows.cpp index df3e1195..3e4e044b 100644 --- a/source/detail/platform_spec_windows.cpp +++ b/source/detail/platform_spec_windows.cpp @@ -12,15 +12,18 @@ * @brief basis classes and data structures required by nana */ -#include +#include "platform_spec_selector.hpp" +#include "platform_abstraction.hpp" #if defined(NANA_WINDOWS) #include #include +#include #include + /////////////////////////////////////////////////////////////////////////////////////////////////////// /****************************************************************** @@ -310,15 +313,6 @@ namespace detail } } - //struct font_tag::deleter - void font_tag::deleter::operator()(const font_tag* tag) const - { - if(tag && tag->handle) - ::DeleteObject(tag->handle); - delete tag; - } - //end struct font_tag::deleter - //class platform_spec platform_spec::co_initializer::co_initializer() : ole32_(::LoadLibrary(L"OLE32.DLL")) @@ -354,104 +348,21 @@ namespace detail struct platform_spec::implementation { - font_ptr_t def_font_ptr; std::map iconbase; }; platform_spec::platform_spec() : impl_{ new implementation} { - //Create default font object. - NONCLIENTMETRICS metrics = {}; - metrics.cbSize = sizeof metrics; -#if(WINVER >= 0x0600) -#if defined(NANA_MINGW) - OSVERSIONINFO osvi = {}; - osvi.dwOSVersionInfoSize = sizeof(osvi); - ::GetVersionEx(&osvi); - if (osvi.dwMajorVersion < 6) - metrics.cbSize -= sizeof(metrics.iPaddedBorderWidth); -#else - if(!IsWindowsVistaOrGreater()) - metrics.cbSize -= sizeof(metrics.iPaddedBorderWidth); -#endif -#endif - ::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof metrics, &metrics, 0); - impl_->def_font_ptr = make_native_font(to_utf8(metrics.lfMessageFont.lfFaceName).c_str(), font_size_to_height(9), 400, false, false, false); + platform_abstraction::initialize(); } platform_spec::~platform_spec() { + platform_abstraction::shutdown(); delete impl_; } - const platform_spec::font_ptr_t& platform_spec::default_native_font() const - { - return impl_->def_font_ptr; - } - - void platform_spec::default_native_font(const font_ptr_t& fp) - { - impl_->def_font_ptr = fp; - } - - unsigned platform_spec::font_size_to_height(unsigned size) const - { - HDC hdc = ::GetDC(0); - size = ::MulDiv(int(size), ::GetDeviceCaps(hdc, LOGPIXELSY), 72); - ::ReleaseDC(0, hdc); - return size; - } - - unsigned platform_spec::font_height_to_size(unsigned height) const - { - HDC hdc = ::GetDC(0); - unsigned pixels = ::GetDeviceCaps(hdc, LOGPIXELSY); - ::ReleaseDC(0, hdc); - - height = static_cast(static_cast(72) * height / pixels); - return height; - } - - platform_spec::font_ptr_t platform_spec::make_native_font(const char* name, unsigned height, unsigned weight, bool italic, bool underline, bool strike_out) - { - ::LOGFONT logfont; - memset(&logfont, 0, sizeof logfont); - - if (name && *name) - std::wcscpy(logfont.lfFaceName, to_wstring(name).c_str()); - else - std::wcscpy(logfont.lfFaceName, impl_->def_font_ptr->name.c_str()); - - logfont.lfCharSet = DEFAULT_CHARSET; - HDC hdc = ::GetDC(0); - logfont.lfHeight = -static_cast(height); - ::ReleaseDC(0, hdc); - - logfont.lfWidth = 0; - logfont.lfWeight = weight; - logfont.lfQuality = PROOF_QUALITY; - logfont.lfPitchAndFamily = FIXED_PITCH; - logfont.lfItalic = italic; - logfont.lfUnderline = underline; - logfont.lfStrikeOut = strike_out; - HFONT result = ::CreateFontIndirect(&logfont); - - if(result) - { - font_tag * impl = new font_tag; - impl->name = logfont.lfFaceName; - impl->height = height; - impl->weight = weight; - impl->italic = italic; - impl->underline = underline; - impl->strikeout = strike_out; - impl->handle = result; - return std::shared_ptr(impl, font_tag::deleter()); - } - return nullptr; - } - platform_spec& platform_spec::instance() { static platform_spec object; diff --git a/source/detail/x11/msg_dispatcher.hpp b/source/detail/posix/msg_dispatcher.hpp similarity index 97% rename from source/detail/x11/msg_dispatcher.hpp rename to source/detail/posix/msg_dispatcher.hpp index 2f1688e0..632f604c 100644 --- a/source/detail/x11/msg_dispatcher.hpp +++ b/source/detail/posix/msg_dispatcher.hpp @@ -1,6 +1,6 @@ /* * Message Dispatcher Implementation - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -17,7 +17,7 @@ #ifndef NANA_DETAIL_MSG_DISPATCHER_HPP #define NANA_DETAIL_MSG_DISPATCHER_HPP -#include +#include "msg_packet.hpp" #include #include #include @@ -51,13 +51,22 @@ namespace detail typedef std::list msg_queue_type; msg_dispatcher(Display* disp) - : display_(disp), is_work_(false) + : display_(disp) { proc_.event_proc = 0; proc_.timer_proc = 0; proc_.filter_proc = 0; } + ~msg_dispatcher() + { + if(thrd_ && thrd_->joinable()) + { + is_work_ = false; + thrd_->join(); + } + } + void set(timer_proc_type timer_proc, event_proc_type event_proc, event_filter_type filter) { proc_.timer_proc = timer_proc; @@ -329,7 +338,7 @@ namespace detail private: Display * display_; - volatile bool is_work_; + volatile bool is_work_{ false }; std::unique_ptr thrd_; struct table_tag diff --git a/include/nana/detail/linux_X11/msg_packet.hpp b/source/detail/posix/msg_packet.hpp similarity index 100% rename from include/nana/detail/linux_X11/msg_packet.hpp rename to source/detail/posix/msg_packet.hpp diff --git a/include/nana/detail/linux_X11/platform_spec.hpp b/source/detail/posix/platform_spec.hpp similarity index 91% rename from include/nana/detail/linux_X11/platform_spec.hpp rename to source/detail/posix/platform_spec.hpp index d6d1a26f..f1f32448 100644 --- a/include/nana/detail/linux_X11/platform_spec.hpp +++ b/source/detail/posix/platform_spec.hpp @@ -36,8 +36,8 @@ #include #include #include "msg_packet.hpp" +#include "../platform_abstraction_types.hpp" -#define NANA_USE_XFT #if defined(NANA_USE_XFT) #include #include @@ -49,6 +49,7 @@ namespace nana namespace detail { class msg_dispatcher; + #if defined(NANA_USE_XFT) class conf { @@ -74,28 +75,14 @@ namespace detail }; #endif - struct font_tag - { - std::string name; - unsigned height; - unsigned weight; - bool italic; - bool underline; - bool strikeout; -#if defined(NANA_USE_XFT) - XftFont * handle; -#else - XFontSet handle; -#endif - }; - struct drawable_impl_type { - typedef std::shared_ptr font_ptr_t; + using font_type = ::std::shared_ptr; Pixmap pixmap; GC context; - font_ptr_t font; + + font_type font; nana::point line_begin_pos; @@ -105,6 +92,7 @@ namespace detail unsigned tab_pixels; unsigned whitespace_pixels; }string; + #if defined(NANA_USE_XFT) XftDraw * xftdraw{nullptr}; XftColor xft_fgcolor; @@ -199,7 +187,6 @@ namespace detail public: int error_code; public: - typedef drawable_impl_type::font_ptr_t font_ptr_t; typedef void (*timer_proc_type)(unsigned tid); typedef void (*event_proc_type)(Display*, msg_packet_tag&); typedef ::nana::event_code event_code; @@ -211,12 +198,6 @@ namespace detail platform_spec(); ~platform_spec(); - const font_ptr_t& default_native_font() const; - void default_native_font(const font_ptr_t&); - unsigned font_size_to_height(unsigned) const; - unsigned font_height_to_size(unsigned) const; - font_ptr_t make_native_font(const char* name, unsigned height, unsigned weight, bool italic, bool underline, bool strick_out); - Display* open_display(); void close_display(); @@ -277,7 +258,7 @@ namespace detail Display* display_; Colormap colormap_; atombase_tag atombase_; - font_ptr_t def_font_ptr_; + XKeyEvent key_state_; int (*def_X11_error_handler_)(Display*, XErrorEvent*); Window grab_; diff --git a/source/filesystem/filesystem.cpp b/source/filesystem/filesystem.cpp index 745a52a2..4613ca7a 100644 --- a/source/filesystem/filesystem.cpp +++ b/source/filesystem/filesystem.cpp @@ -845,7 +845,7 @@ namespace nana { namespace experimental { namespace filesystem GetFileSizeEx_fptr_t get_file_size_ex = reinterpret_cast(::GetProcAddress(::GetModuleHandleA("Kernel32.DLL"), "GetFileSizeEx")); if (get_file_size_ex) { - HANDLE handle = ::CreateFile(p.c_str(), GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + HANDLE handle = ::CreateFile(p.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (INVALID_HANDLE_VALUE != handle) { LARGE_INTEGER li; diff --git a/source/gui/detail/basic_window.cpp b/source/gui/detail/basic_window.cpp index 3f5849cc..67b24139 100644 --- a/source/gui/detail/basic_window.cpp +++ b/source/gui/detail/basic_window.cpp @@ -323,7 +323,7 @@ namespace nana { for (auto wd = this; wd; wd = wd->parent) { - if (basic_window::update_state::refresh == wd->other.upd_state) + if (basic_window::update_state::refreshed == wd->other.upd_state) return true; } return false; diff --git a/source/gui/detail/bedrock_pi.cpp b/source/gui/detail/bedrock_pi.cpp index c6617bd7..ca36a92f 100644 --- a/source/gui/detail/bedrock_pi.cpp +++ b/source/gui/detail/bedrock_pi.cpp @@ -1,7 +1,7 @@ /* * A Bedrock Platform-Independent Implementation * Nana C++ Library(http://www.nanapro.org) -* Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) +* Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -10,7 +10,7 @@ * @file: nana/gui/detail/bedrock_pi.cpp */ -#include +#include "../../detail/platform_spec_selector.hpp" #include #include #include @@ -119,6 +119,27 @@ namespace nana } } + void bedrock::close_thread_window(unsigned thread_id) + { + std::vector v; + wd_manager().all_handles(v); + + std::vector roots; + native_window_type root = nullptr; + for (auto wd : v) + { + if (((0 == thread_id) || (wd->thread_id == thread_id)) && (wd->root != root)) + { + root = wd->root; + if (roots.cend() == std::find(roots.cbegin(), roots.cend(), root)) + roots.emplace_back(root); + } + } + + for (auto i : roots) + native_interface::close_window(i); + } + void bedrock::event_expose(core_window_t * wd, bool exposed) { if (nullptr == wd) return; @@ -237,7 +258,7 @@ namespace nana return; native_interface::calc_window_point(native_handle, pos); - if (wd != wd_manager().find_window(native_handle, pos.x, pos.y)) + if (wd != wd_manager().find_window(native_handle, pos)) return; set_cursor(wd, wd->predef_cursor, thrd); @@ -585,29 +606,5 @@ namespace nana throw std::runtime_error("Invalid event code"); } } - - void bedrock::_m_except_handler() - { - std::vector v; - wd_manager().all_handles(v); - if (v.size()) - { - std::vector roots; - native_window_type root = nullptr; - unsigned tid = nana::system::this_thread_id(); - for (auto wd : v) - { - if ((wd->thread_id == tid) && (wd->root != root)) - { - root = wd->root; - if (roots.cend() == std::find(roots.cbegin(), roots.cend(), root)) - roots.emplace_back(root); - } - } - - for (auto i : roots) - native_interface::close_window(i); - } - } }//end namespace detail }//end namespace nana \ No newline at end of file diff --git a/source/gui/detail/bedrock_posix.cpp b/source/gui/detail/bedrock_posix.cpp index 26de1dbc..55c39b58 100644 --- a/source/gui/detail/bedrock_posix.cpp +++ b/source/gui/detail/bedrock_posix.cpp @@ -1,7 +1,7 @@ /* * A Bedrock Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -10,7 +10,7 @@ * @file: nana/gui/detail/linux_X11/bedrock.cpp */ -#include +#include "../../detail/platform_spec_selector.hpp" #if defined(NANA_POSIX) && defined(NANA_X11) #include #include @@ -237,7 +237,7 @@ namespace detail if(wd_manager().available(wd) == false) return false; - core_window_t * prev_wd; + core_window_t * prev_wd = nullptr; if(thrd) { prev_wd = thrd->event_window; @@ -376,7 +376,7 @@ namespace detail switch(msg.kind) { case nana::detail::msg_packet_tag::kind_mouse_drop: - msgwd = brock.wd_manager().find_window(native_window, msg.u.mouse_drop.x, msg.u.mouse_drop.y); + msgwd = brock.wd_manager().find_window(native_window, {msg.u.mouse_drop.x, msg.u.mouse_drop.y}); if(msgwd) { arg_dropfiles arg; @@ -511,7 +511,7 @@ namespace detail if(pressed_wd_space) break; - msgwnd = wd_manager.find_window(native_window, xevent.xcrossing.x, xevent.xcrossing.y); + msgwnd = wd_manager.find_window(native_window, {xevent.xcrossing.x, xevent.xcrossing.y}); if(msgwnd) { if (mouse_action::pressed != msgwnd->flags.action) @@ -567,9 +567,12 @@ namespace detail if(xevent.xbutton.button == Button4 || xevent.xbutton.button == Button5) break; - - msgwnd = wd_manager.find_window(native_window, xevent.xbutton.x, xevent.xbutton.y); - if(nullptr == msgwnd) break; + + msgwnd = wd_manager.find_window(native_window, {xevent.xbutton.x, xevent.xbutton.y}); + + pressed_wd = nullptr; + if(nullptr == msgwnd) + break; if ((msgwnd == msgwnd->root_widget->other.attribute.root->menubar) && brock.get_menu(msgwnd->root, true)) brock.erase_menu(true); @@ -578,7 +581,9 @@ namespace detail if(msgwnd->flags.enabled) { - bool dbl_click = (last_mouse_down_window == msgwnd) && (xevent.xbutton.time - last_mouse_down_time <= 400); + pressed_wd = msgwnd; + + const bool dbl_click = (last_mouse_down_window == msgwnd) && (xevent.xbutton.time - last_mouse_down_time <= 400); last_mouse_down_time = xevent.xbutton.time; last_mouse_down_window = msgwnd; @@ -597,28 +602,31 @@ namespace detail auto retain = msgwnd->annex.events_ptr; context.event_window = msgwnd; - pressed_wd = nullptr; - msgwnd->set_action(mouse_action::pressed); arg_mouse arg; assign_arg(arg, msgwnd, ButtonPress, xevent); arg.evt_code = dbl_click ? event_code::dbl_click : event_code::mouse_down; - if(brock.emit(arg.evt_code, msgwnd, arg, true, &context)) + if (brock.emit(arg.evt_code, msgwnd, arg, true, &context)) { - if (wd_manager.available(msgwnd)) + //If a root window is created during the mouse_down event, Nana.GUI will ignore the mouse_up event. + if (msgwnd->root != native_interface::get_focus_window()) { - pressed_wd = msgwnd; - //If a root window is created during the mouse_down event, Nana.GUI will ignore the mouse_up event. - if (msgwnd->root != native_interface::get_focus_window()) + auto pos = native_interface::cursor_position(); + auto rootwd = native_interface::find_window(pos.x, pos.y); + native_interface::calc_window_point(rootwd, pos); + if(msgwnd != wd_manager.find_window(rootwd, pos)) { //call the drawer mouse up event for restoring the surface graphics msgwnd->set_action(mouse_action::normal); + arg.evt_code = event_code::mouse_up; draw_invoker(&drawer::mouse_up, msgwnd, arg, &context); wd_manager.do_lazy_refresh(msgwnd, false); } } } + else + pressed_wd = nullptr; } break; case ButtonRelease: @@ -626,7 +634,7 @@ namespace detail if(pressed_wd_space) break; - msgwnd = wd_manager.find_window(native_window, xevent.xbutton.x, xevent.xbutton.y); + msgwnd = wd_manager.find_window(native_window, {xevent.xbutton.x, xevent.xbutton.y}); if(nullptr == msgwnd) break; @@ -746,7 +754,7 @@ namespace detail if(pressed_wd_space) break; - msgwnd = wd_manager.find_window(native_window, xevent.xmotion.x, xevent.xmotion.y); + msgwnd = wd_manager.find_window(native_window, {xevent.xmotion.x, xevent.xmotion.y}); if (wd_manager.available(hovered_wd) && (msgwnd != hovered_wd)) { brock.event_msleave(hovered_wd); @@ -768,7 +776,7 @@ namespace detail if(prev_captured_inside) { evt_code = event_code::mouse_leave; - msgwnd->set_action(mouse_action::normal); + msgwnd->set_action(mouse_action::normal_captured); } else { @@ -1146,7 +1154,7 @@ namespace detail } } - void bedrock::pump_event(window modal_window, bool /*is_modal*/) + void bedrock::pump_event(window condition_wd, bool is_modal) { thread_context * context = open_thread_context(); if(0 == context->window_count) @@ -1157,13 +1165,15 @@ namespace detail } ++(context->event_pump_ref_count); - wd_manager().internal_lock().revert(); + + auto & lock = wd_manager().internal_lock(); + lock.revert(); - native_window_type owner_native = 0; + native_window_type owner_native{}; core_window_t * owner = 0; - if(modal_window) + if(condition_wd && is_modal) { - native_window_type modal = reinterpret_cast(modal_window)->root; + native_window_type modal = reinterpret_cast(condition_wd)->root; owner_native = native_interface::get_owner_window(modal); if(owner_native) { @@ -1174,7 +1184,7 @@ namespace detail } } - nana::detail::platform_spec::instance().msg_dispatch(modal_window ? reinterpret_cast(modal_window)->root : 0); + nana::detail::platform_spec::instance().msg_dispatch(condition_wd ? reinterpret_cast(condition_wd)->root : 0); if(owner_native) { @@ -1182,11 +1192,20 @@ namespace detail owner->flags.enabled = true; native_interface::enable_window(owner_native, true); } - - wd_manager().internal_lock().forward(); + + //Before exit of pump_event, it should call the remove_trash_handle. + //Under Linux, if the windows are closed in other threads, all the widgets handles + //will be marked as deleted after exit of the event loop and in other threads. So the + //handle should be deleted from trash before exit the pump_event. + auto thread_id = ::nana::system::this_thread_id(); + wd_manager().call_safe_place(thread_id); + wd_manager().remove_trash_handle(thread_id); + + lock.forward(); + if(0 == --(context->event_pump_ref_count)) { - if(0 == modal_window || 0 == context->window_count) + if(0 == condition_wd || 0 == context->window_count) remove_thread_context(); } @@ -1203,17 +1222,7 @@ namespace detail { thread_context* thrd = get_thread_context(0); if(thrd && thrd->event_window) - { - //the state none should be tested, becuase in an event, there would be draw after an update, - //if the none is not tested, the draw after update will not be refreshed. - switch(thrd->event_window->other.upd_state) - { - case core_window_t::update_state::none: - case core_window_t::update_state::lazy: - thrd->event_window->other.upd_state = core_window_t::update_state::refresh; - default: break; - } - } + thrd->event_window->other.upd_state = core_window_t::update_state::refreshed; } //Dynamically set a cursor for a window @@ -1281,7 +1290,7 @@ namespace detail return; native_interface::calc_window_point(native_handle, pos); - auto rev_wd = wd_manager().find_window(native_handle, pos.x, pos.y); + auto rev_wd = wd_manager().find_window(native_handle, pos); if (rev_wd) set_cursor(rev_wd, rev_wd->predef_cursor, thrd); } diff --git a/source/gui/detail/bedrock_windows.cpp b/source/gui/detail/bedrock_windows.cpp index 300866f0..68d996b3 100644 --- a/source/gui/detail/bedrock_windows.cpp +++ b/source/gui/detail/bedrock_windows.cpp @@ -1,7 +1,7 @@ /** * A Bedrock Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -12,7 +12,7 @@ * @contributors: Ariel Vina-Rodriguez */ -#include +#include "../../detail/platform_spec_selector.hpp" #if defined(NANA_WINDOWS) #include #include @@ -345,7 +345,7 @@ namespace detail } } - void bedrock::pump_event(window modal_window, bool is_modal) + void bedrock::pump_event(window condition_wd, bool is_modal) { const unsigned tid = ::GetCurrentThreadId(); auto context = this->open_thread_context(tid); @@ -365,9 +365,9 @@ namespace detail try { MSG msg; - if(modal_window) + if (condition_wd) { - HWND native_handle = reinterpret_cast(reinterpret_cast(modal_window)->root); + HWND native_handle = reinterpret_cast(reinterpret_cast(condition_wd)->root); if (is_modal) { HWND owner = ::GetWindow(native_handle, GW_OWNER); @@ -437,35 +437,35 @@ namespace detail } catch(std::exception& e) { - (msgbox(modal_window, "An uncaptured std::exception during message pumping: ").icon(msgbox::icon_information) - <<"\n in form: "<< API::window_caption(modal_window) - <<"\n exception : "<< e.what() - ).show(); + (msgbox(condition_wd, "An uncaptured std::exception during message pumping: ").icon(msgbox::icon_information) + << "\n in form: " << API::window_caption(condition_wd) + <<"\n exception : "<< e.what() + ).show(); - internal_scope_guard lock; - _m_except_handler(); + internal_scope_guard lock; + this->close_thread_window(nana::system::this_thread_id()); - intr_locker.forward(); - if (0 == --(context->event_pump_ref_count)) - { - if ((nullptr == modal_window) || (0 == context->window_count)) - remove_thread_context(); - } - throw; + intr_locker.forward(); + if (0 == --(context->event_pump_ref_count)) + { + if ((nullptr == condition_wd) || (0 == context->window_count)) + remove_thread_context(); + } + throw; } catch(...) { - (msgbox(modal_window, "An exception during message pumping!").icon(msgbox::icon_information) + (msgbox(condition_wd, "An exception during message pumping!").icon(msgbox::icon_information) <<"An uncaptured non-std exception during message pumping!" - << "\n in form: " << API::window_caption(modal_window) + << "\n in form: " << API::window_caption(condition_wd) ).show(); internal_scope_guard lock; - _m_except_handler(); + this->close_thread_window(nana::system::this_thread_id()); intr_locker.forward(); if(0 == --(context->event_pump_ref_count)) { - if((nullptr == modal_window) || (0 == context->window_count)) + if ((nullptr == condition_wd) || (0 == context->window_count)) remove_thread_context(); } throw; @@ -474,7 +474,7 @@ namespace detail intr_locker.forward(); if(0 == --(context->event_pump_ref_count)) { - if((nullptr == modal_window) || (0 == context->window_count)) + if ((nullptr == condition_wd) || (0 == context->window_count)) remove_thread_context(); } }//end pump_event @@ -760,7 +760,7 @@ namespace detail if (bedrock::instance().wd_manager().available(wd) == false) return; - basic_window* prev_event_wd; + basic_window* prev_event_wd = nullptr; if (thrd) { prev_event_wd = thrd->event_window; @@ -784,7 +784,7 @@ namespace detail static auto& brock = bedrock::instance(); static restrict::TRACKMOUSEEVENT track = {sizeof track, 0x00000002}; - auto native_window = reinterpret_cast(root_window); + auto const native_window = reinterpret_cast(root_window); auto & wd_manager = brock.wd_manager(); auto* root_runtime = wd_manager.root_runtime(native_window); @@ -805,10 +805,10 @@ namespace detail internal_scope_guard lock; auto msgwnd = root_runtime->window; - switch(message) + switch (message) { case WM_IME_STARTCOMPOSITION: - if(msgwnd->other.attribute.root->ime_enabled) + if (msgwnd->other.attribute.root->ime_enabled) { auto native_font = msgwnd->drawer.graphics.typeface().handle(); LOGFONTW logfont; @@ -820,7 +820,7 @@ namespace detail POINT pos; ::GetCaretPos(&pos); - COMPOSITIONFORM cf = {CFS_POINT}; + COMPOSITIONFORM cf = { CFS_POINT }; cf.ptCurrentPos = pos; restrict::imm_set_composition_window(imc, &cf); restrict::imm_release_context(root_window, imc); @@ -828,36 +828,36 @@ namespace detail def_window_proc = true; break; case WM_GETMINMAXINFO: - { - bool take_over = false; - auto mmi = reinterpret_cast(lParam); + { + bool take_over = false; + auto mmi = reinterpret_cast(lParam); - if(!msgwnd->min_track_size.empty()) + if (!msgwnd->min_track_size.empty()) + { + mmi->ptMinTrackSize.x = static_cast(msgwnd->min_track_size.width + msgwnd->extra_width); + mmi->ptMinTrackSize.y = static_cast(msgwnd->min_track_size.height + msgwnd->extra_height); + take_over = true; + } + + if (false == msgwnd->flags.fullscreen) + { + if (msgwnd->max_track_size.width && msgwnd->max_track_size.height) { - mmi->ptMinTrackSize.x = static_cast(msgwnd->min_track_size.width + msgwnd->extra_width); - mmi->ptMinTrackSize.y = static_cast(msgwnd->min_track_size.height + msgwnd->extra_height); + mmi->ptMaxTrackSize.x = static_cast(msgwnd->max_track_size.width + msgwnd->extra_width); + mmi->ptMaxTrackSize.y = static_cast(msgwnd->max_track_size.height + msgwnd->extra_height); + if (mmi->ptMaxSize.x > mmi->ptMaxTrackSize.x) + mmi->ptMaxSize.x = mmi->ptMaxTrackSize.x; + if (mmi->ptMaxSize.y > mmi->ptMaxTrackSize.y) + mmi->ptMaxSize.y = mmi->ptMaxTrackSize.y; + take_over = true; } - - if(false == msgwnd->flags.fullscreen) - { - if(msgwnd->max_track_size.width && msgwnd->max_track_size.height) - { - mmi->ptMaxTrackSize.x = static_cast(msgwnd->max_track_size.width + msgwnd->extra_width); - mmi->ptMaxTrackSize.y = static_cast(msgwnd->max_track_size.height + msgwnd->extra_height); - if(mmi->ptMaxSize.x > mmi->ptMaxTrackSize.x) - mmi->ptMaxSize.x = mmi->ptMaxTrackSize.x; - if(mmi->ptMaxSize.y > mmi->ptMaxTrackSize.y) - mmi->ptMaxSize.y = mmi->ptMaxTrackSize.y; - - take_over = true; - } - } - - if (take_over) - return 0; } - break; + + if (take_over) + return 0; + } + break; case WM_SHOWWINDOW: if (msgwnd->visible == (FALSE == wParam)) brock.event_expose(msgwnd, !msgwnd->visible); @@ -882,7 +882,7 @@ namespace detail def_window_proc = true; break; case WM_MOUSEACTIVATE: - if(msgwnd->flags.take_active == false) + if (msgwnd->flags.take_active == false) return MA_NOACTIVATE; def_window_proc = true; @@ -893,8 +893,8 @@ namespace detail break; pressed_wd = nullptr; - msgwnd = wd_manager.find_window(native_window, pmdec.mouse.x, pmdec.mouse.y); - if(msgwnd && msgwnd->flags.enabled) + msgwnd = wd_manager.find_window(native_window, { pmdec.mouse.x, pmdec.mouse.y }); + if (msgwnd && msgwnd->flags.enabled) { if (msgwnd->flags.take_active && !msgwnd->flags.ignore_mouse_focus) { @@ -918,8 +918,14 @@ namespace detail if (pressed_wd_space) break; - msgwnd = wd_manager.find_window(native_window, pmdec.mouse.x, pmdec.mouse.y); - if ((nullptr == msgwnd) || (pressed_wd && (msgwnd != pressed_wd))) + msgwnd = wd_manager.find_window(native_window, { pmdec.mouse.x, pmdec.mouse.y }); + + //Don't take care about whether msgwnd is equal to the pressed_wd. + // + //pressed_wd will remains when opens a no-actived window in an mouse_down event(like combox popups the drop-list). + //After the no-actived window is closed, the window doesn't respond to the mouse click other than pressed_wd. + pressed_wd = nullptr; + if (nullptr == msgwnd) break; //if event on the menubar, just remove the menu if it is not associating with the menubar @@ -928,7 +934,7 @@ namespace detail else brock.close_menu_if_focus_other_window(msgwnd->root); - if(msgwnd->flags.enabled) + if (msgwnd->flags.enabled) { pressed_wd = msgwnd; @@ -956,7 +962,7 @@ namespace detail auto pos = native_interface::cursor_position(); auto rootwd = native_interface::find_window(pos.x, pos.y); native_interface::calc_window_point(rootwd, pos); - if(msgwnd != wd_manager.find_window(rootwd, pos.x, pos.y)) + if (msgwnd != wd_manager.find_window(rootwd, pos)) { //call the drawer mouse up event for restoring the surface graphics msgwnd->set_action(mouse_action::normal); @@ -971,7 +977,7 @@ namespace detail pressed_wd = nullptr; } break; - //mouse_click, mouse_up + //mouse_click, mouse_up case WM_LBUTTONUP: case WM_MBUTTONUP: case WM_RBUTTONUP: @@ -979,12 +985,12 @@ namespace detail if (pressed_wd_space) break; - msgwnd = wd_manager.find_window(native_window, pmdec.mouse.x, pmdec.mouse.y); - if(nullptr == msgwnd) + msgwnd = wd_manager.find_window(native_window, { pmdec.mouse.x, pmdec.mouse.y }); + if (nullptr == msgwnd) break; msgwnd->set_action(mouse_action::normal); - if(msgwnd->flags.enabled) + if (msgwnd->flags.enabled) { auto retain = msgwnd->annex.events_ptr; @@ -1008,7 +1014,7 @@ namespace detail } //Do mouse_up, this handle may be closed by click handler. - if(wd_manager.available(msgwnd) && msgwnd->flags.enabled) + if (wd_manager.available(msgwnd) && msgwnd->flags.enabled) { arg.evt_code = event_code::mouse_up; draw_invoker(&drawer::mouse_up, msgwnd, arg, &context); @@ -1034,7 +1040,7 @@ namespace detail if (pressed_wd_space) break; - msgwnd = wd_manager.find_window(native_window, pmdec.mouse.x, pmdec.mouse.y); + msgwnd = wd_manager.find_window(native_window, {pmdec.mouse.x, pmdec.mouse.y}); if (wd_manager.available(hovered_wd) && (msgwnd != hovered_wd)) { brock.event_msleave(hovered_wd); @@ -1057,7 +1063,7 @@ namespace detail if(prev_captured_inside) { evt_code = event_code::mouse_leave; - msgwnd->set_action(mouse_action::normal); + msgwnd->set_action(mouse_action::normal_captured); } else { @@ -1115,7 +1121,7 @@ namespace detail if (pointer_wd == root_window) { ::ScreenToClient(pointer_wd, &scr_pos); - auto scrolled_wd = wd_manager.find_window(reinterpret_cast(pointer_wd), scr_pos.x, scr_pos.y); + auto scrolled_wd = wd_manager.find_window(reinterpret_cast(pointer_wd), { scr_pos.x, scr_pos.y }); def_window_proc = true; auto evt_wd = scrolled_wd; @@ -1156,10 +1162,12 @@ namespace detail case WM_DROPFILES: { HDROP drop = reinterpret_cast(wParam); - POINT pos; - ::DragQueryPoint(drop, &pos); + POINT mswin_pos; + ::DragQueryPoint(drop, &mswin_pos); - msgwnd = wd_manager.find_window(native_window, pos.x, pos.y); + const point pos{ mswin_pos.x, mswin_pos.y }; + + msgwnd = wd_manager.find_window(native_window, pos); if(msgwnd) { arg_dropfiles dropfiles; @@ -1187,8 +1195,7 @@ namespace detail if(msgwnd) { - dropfiles.pos.x = pos.x; - dropfiles.pos.y = pos.y; + dropfiles.pos = pos; wd_manager.calc_window_point(msgwnd, dropfiles.pos); dropfiles.window_handle = reinterpret_cast(msgwnd); @@ -1624,7 +1631,7 @@ namespace detail if (wd_manager().available(wd) == false) return false; - basic_window* prev_event_wd; + basic_window* prev_event_wd = nullptr; if (thrd) { prev_event_wd = thrd->event_window; @@ -1691,17 +1698,7 @@ namespace detail { auto* thrd = get_thread_context(0); if (thrd && thrd->event_window) - { - //the state none should be tested, becuase in an event, there would be draw after an update, - //if the none is not tested, the draw after update will not be refreshed. - switch (thrd->event_window->other.upd_state) - { - case core_window_t::update_state::none: - case core_window_t::update_state::lazy: - thrd->event_window->other.upd_state = core_window_t::update_state::refresh; - default: break; - } - } + thrd->event_window->other.upd_state = core_window_t::update_state::refreshed; } //Dynamically set a cursor for a window @@ -1724,9 +1721,16 @@ namespace detail thrd->cursor.handle = ::LoadCursor(nullptr, translate(cur)); } - if (wd->root_widget->other.attribute.root->running_cursor != cur) + auto this_cur = reinterpret_cast( +#ifdef _WIN64 + ::GetClassLongPtr(reinterpret_cast(wd->root), GCLP_HCURSOR) +#else + ::GetClassLong(reinterpret_cast(wd->root), GCL_HCURSOR) +#endif + ); + + if(this_cur != thrd->cursor.handle) { - wd->root_widget->other.attribute.root->running_cursor = cur; #ifdef _WIN64 ::SetClassLongPtr(reinterpret_cast(wd->root), GCLP_HCURSOR, reinterpret_cast(thrd->cursor.handle)); @@ -1735,6 +1739,7 @@ namespace detail static_cast(reinterpret_cast(thrd->cursor.handle))); #endif } + if (cursor::arrow == thrd->cursor.predef_cursor) { thrd->cursor.window = nullptr; @@ -1779,7 +1784,7 @@ namespace detail } native_interface::calc_window_point(native_handle, pos); - auto rev_wd = wd_manager().find_window(native_handle, pos.x, pos.y); + auto rev_wd = wd_manager().find_window(native_handle, pos); if (rev_wd) { set_cursor(rev_wd, rev_wd->predef_cursor, thrd); @@ -1808,10 +1813,7 @@ namespace detail undefine_state_cursor(wd, thrd); if(wd == thrd->cursor.window) - { set_cursor(wd, cursor::arrow, thrd); - wd->root_widget->other.attribute.root->running_cursor = cursor::arrow; - } break; default: break; diff --git a/source/gui/detail/drawer.cpp b/source/gui/detail/drawer.cpp index 0ab725aa..c6619076 100644 --- a/source/gui/detail/drawer.cpp +++ b/source/gui/detail/drawer.cpp @@ -1,7 +1,7 @@ /* * A Drawer Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -18,7 +18,7 @@ #include "dynamic_drawing_object.hpp" #if defined(NANA_X11) - #include + #include "../../detail/posix/platform_spec.hpp" #endif namespace nana @@ -297,9 +297,8 @@ namespace nana if (data_impl_->realizer && !data_impl_->refreshing) { data_impl_->refreshing = true; - _m_effect_bground(true); data_impl_->realizer->refresh(graphics); - _m_effect_bground(false); + _m_effect_bground_subsequent(); graphics.flush(); data_impl_->refreshing = false; } @@ -318,6 +317,7 @@ namespace nana data_impl_->realizer = &realizer; realizer._m_reset_overrided(); realizer.attached(wd, graphics); + realizer.typeface_changed(graphics); } drawer_trigger* drawer::detached() @@ -371,27 +371,22 @@ namespace nana } } - void drawer::_m_effect_bground(bool before) + void drawer::_m_effect_bground_subsequent() { + auto & effect = data_impl_->window_handle->effect; + for (auto * dw : data_impl_->draws) dw->draw(graphics); - auto & effect = data_impl_->window_handle->effect; + if (effect.bground) { - if (before) + if (effect.bground_fade_rate >= 0.01) { - if (effect.bground_fade_rate < 0.01) - data_impl_->window_handle->other.glass_buffer.paste(graphics, 0, 0); + graphics.blend(::nana::rectangle{ data_impl_->window_handle->other.glass_buffer.size() }, data_impl_->window_handle->other.glass_buffer, {}, 1 - effect.bground_fade_rate); } - else if (effect.bground_fade_rate >= 0.01) - data_impl_->window_handle->other.glass_buffer.blend(::nana::rectangle{ data_impl_->window_handle->other.glass_buffer.size() }, graphics, nana::point(), effect.bground_fade_rate); } - } - - bool drawer::_m_lazy_decleared() const - { - return (basic_window::update_state::refresh == data_impl_->window_handle->other.upd_state); + } drawer::method_state& drawer::_m_mth_state(int pos) diff --git a/source/gui/detail/events_operation.cpp b/source/gui/detail/events_operation.cpp index bcafd688..40fe338e 100644 --- a/source/gui/detail/events_operation.cpp +++ b/source/gui/detail/events_operation.cpp @@ -80,19 +80,22 @@ namespace nana internal_scope_guard lock; if (dockers_) { - for (auto i = dockers_->begin(), end = dockers_->end(); i != end; ++i) { if (reinterpret_cast(evt) == *i) { //Checks whether this event is working now. - if (emitting_count_ > 1) + if (emitting_count_) { static_cast(*i)->flag_deleted = true; deleted_flags_ = true; } else + { + bedrock::instance().evt_operation().cancel(evt); dockers_->erase(i); + delete reinterpret_cast(evt); + } break; } } @@ -131,7 +134,11 @@ namespace nana for (auto i = evt_->dockers_->begin(); i != evt_->dockers_->end();) { if (static_cast(*i)->flag_deleted) + { + bedrock::instance().evt_operation().cancel(reinterpret_cast(*i)); + delete (*i); i = evt_->dockers_->erase(i); + } else ++i; } diff --git a/source/gui/detail/native_window_interface.cpp b/source/gui/detail/native_window_interface.cpp index 0657dd14..821296c1 100644 --- a/source/gui/detail/native_window_interface.cpp +++ b/source/gui/detail/native_window_interface.cpp @@ -1,7 +1,7 @@ /* * Platform Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -10,7 +10,7 @@ * @file: nana/gui/detail/native_window_interface.cpp */ -#include +#include "../../detail/platform_spec_selector.hpp" #include #include #include @@ -23,7 +23,6 @@ #include #endif #include - #include "../../paint/detail/image_ico.hpp" #elif defined(NANA_X11) #include #endif @@ -197,6 +196,13 @@ namespace nana{ return rectangle{ primary_monitor_size() }; } +#ifdef NANA_X11 + //The XMoveWindow and XMoveResizeWindow don't take effect if the specified window is + //unmapped, and the members x and y of XSetSizeHints is obsoluted. So the position that + //set to a unmapped windows should be kept and use the position when the window is mapped. + std::map exposed_positions; //locked by platform_scope_guard +#endif + //platform-dependent native_interface::window_result native_interface::create_window(native_window_type owner, bool nested, const rectangle& r, const appearance& app) { @@ -289,6 +295,7 @@ namespace nana{ { win_attr.save_under = True; attr_mask |= CWSaveUnder; + parent = restrict::spec.root_window(); calc_screen_point(owner, pos); } @@ -303,7 +310,12 @@ namespace nana{ { //make owner if it is a popup window if((!nested) && owner) + { restrict::spec.make_owner(owner, reinterpret_cast(handle)); + exposed_positions[handle] = pos; + } + + XChangeWindowAttributes(disp, handle, attr_mask, &win_attr); XTextProperty name; char text[] = "Nana Window"; @@ -655,6 +667,14 @@ namespace nana{ if(show) { ::XMapWindow(disp, reinterpret_cast(wd)); + + auto i = exposed_positions.find(reinterpret_cast(wd)); + if(i != exposed_positions.end()) + { + ::XMoveWindow(disp, reinterpret_cast(wd), i->second.x, i->second.y); + exposed_positions.erase(i); + } + Window grab = restrict::spec.grab(0); if(grab == reinterpret_cast(wd)) capture_window(wd, true); @@ -882,13 +902,7 @@ namespace nana{ XWindowAttributes attr; ::XGetWindowAttributes(disp, reinterpret_cast(wd), &attr); if(attr.map_state == IsUnmapped) - { - XSizeHints hints; - hints.flags = USPosition; - hints.x = x; - hints.y = y; - ::XSetWMNormalHints(disp, reinterpret_cast(wd), &hints); - } + exposed_positions[reinterpret_cast(wd)] = ::nana::point{x, y}; ::XMoveWindow(disp, reinterpret_cast(wd), x, y); #endif @@ -959,11 +973,11 @@ namespace nana{ ::XGetWindowAttributes(disp, reinterpret_cast(wd), &attr); if(attr.map_state == IsUnmapped) { - hints.flags |= (USPosition | USSize); - hints.x = x; - hints.y = y; + hints.flags |= USSize; hints.width = r.width; hints.height = r.height; + + exposed_positions[reinterpret_cast(wd)] = point{x, y}; } if(hints.flags) diff --git a/source/gui/detail/window_layout.cpp b/source/gui/detail/window_layout.cpp index ec69baa3..2596f67e 100644 --- a/source/gui/detail/window_layout.cpp +++ b/source/gui/detail/window_layout.cpp @@ -22,26 +22,26 @@ namespace nana namespace detail { //class window_layout - void window_layout::paint(core_window_t* wd, bool is_redraw, bool is_child_refreshed) + void window_layout::paint(core_window_t* wd, paint_operation operation, bool req_refresh_children) { - if (is_redraw && wd->flags.refreshing) + if (wd->flags.refreshing && (paint_operation::try_refresh == operation)) return; if (nullptr == wd->effect.bground) { - if (is_redraw && (!wd->drawer.graphics.empty())) + if ((paint_operation::try_refresh == operation) && (!wd->drawer.graphics.empty())) { wd->flags.refreshing = true; wd->drawer.refresh(); wd->flags.refreshing = false; } - maproot(wd, is_redraw, is_child_refreshed); + maproot(wd, (paint_operation::none != operation), req_refresh_children); } else - _m_paint_glass_window(wd, is_redraw, is_child_refreshed, false, true); + _m_paint_glass_window(wd, (paint_operation::try_refresh == operation), req_refresh_children, false, true); } - bool window_layout::maproot(core_window_t* wd, bool have_refreshed, bool is_child_refreshed) + bool window_layout::maproot(core_window_t* wd, bool have_refreshed, bool req_refresh_children) { auto check_opaque = wd->seek_non_lite_widget_ancestor(); if (check_opaque && check_opaque->flags.refreshing) @@ -56,7 +56,7 @@ namespace nana if (category::flags::lite_widget != wd->other.category) graph.bitblt(vr, wd->drawer.graphics, nana::point(vr.x - wd->pos_root.x, vr.y - wd->pos_root.y)); - _m_paste_children(wd, is_child_refreshed, have_refreshed, vr, graph, nana::point()); + _m_paste_children(wd, have_refreshed, req_refresh_children, vr, graph, nana::point()); if (wd->parent) { @@ -81,11 +81,11 @@ namespace nana graph.bitblt(el.r, (el.window->drawer.graphics), p_src); } - _m_paste_children(el.window, is_child_refreshed, false, el.r, graph, nana::point{}); + _m_paste_children(el.window, false, req_refresh_children, el.r, graph, nana::point{}); } } } - _m_notify_glasses(wd, vr); + _m_notify_glasses(wd); return true; } return false; @@ -116,7 +116,8 @@ namespace nana for (auto* parent = wd->parent; parent; parent = parent->parent) { - overlap(rectangle{ parent->pos_root, parent->dimension }, visual, visual); + if (!overlap(rectangle{ parent->pos_root, parent->dimension }, visual, visual)) + return false; } return true; @@ -255,8 +256,8 @@ namespace nana if (category::flags::lite_widget != child->other.category) glass_buffer.bitblt(nana::rectangle{ ovlp.x - wd->pos_owner.x, ovlp.y - wd->pos_owner.y, ovlp.width, ovlp.height }, child->drawer.graphics, {ovlp.position() - child->pos_owner}); - ovlp.x += wd->pos_root.x; - ovlp.y += wd->pos_root.y; + ovlp.x += wd->parent->pos_root.x; + ovlp.y += wd->parent->pos_root.y; _m_paste_children(child, false, false, ovlp, glass_buffer, rpos); } } @@ -267,7 +268,7 @@ namespace nana //_m_paste_children //@brief:paste children window to the root graphics directly. just paste the visual rectangle - void window_layout::_m_paste_children(core_window_t* wd, bool is_child_refreshed, bool have_refreshed, const nana::rectangle& parent_rect, nana::paint::graphics& graph, const nana::point& graph_rpos) + void window_layout::_m_paste_children(core_window_t* wd, bool have_refreshed, bool req_refresh_children, const nana::rectangle& parent_rect, nana::paint::graphics& graph, const nana::point& graph_rpos) { nana::rectangle rect; for (auto child : wd->children) @@ -278,7 +279,7 @@ namespace nana if (category::flags::root == child->other.category) { - paint(child, is_child_refreshed, is_child_refreshed); + paint(child, (req_refresh_children ? paint_operation::try_refresh : paint_operation::none), req_refresh_children); continue; } @@ -289,7 +290,7 @@ namespace nana bool have_child_refreshed = false; if (category::flags::lite_widget != child->other.category) { - if (is_child_refreshed && (false == child->flags.refreshing)) + if (req_refresh_children && (false == child->flags.refreshing)) { have_child_refreshed = true; child->flags.refreshing = true; @@ -300,13 +301,13 @@ namespace nana graph.bitblt(nana::rectangle(rect.x - graph_rpos.x, rect.y - graph_rpos.y, rect.width, rect.height), child->drawer.graphics, nana::point(rect.x - child->pos_root.x, rect.y - child->pos_root.y)); } - _m_paste_children(child, is_child_refreshed, have_child_refreshed, rect, graph, graph_rpos); + _m_paste_children(child, req_refresh_children, have_child_refreshed, rect, graph, graph_rpos); } } else { - //If have_refreshed, the glass should be notified. - _m_paint_glass_window(child, false, is_child_refreshed, have_refreshed, false); + //Update the glass window's background if the parent have_refreshed. + _m_paint_glass_window(child, have_refreshed, req_refresh_children, true, false); } } } @@ -353,16 +354,14 @@ namespace nana } if (notify_other) - _m_notify_glasses(wd, vr); + _m_notify_glasses(wd); } } - //_m_notify_glasses - //@brief: Notify the glass windows that are overlapped with the specified vis_rect - void window_layout::_m_notify_glasses(core_window_t* const sigwd, const nana::rectangle& /*r_visual*/) + /// Notify the glass windows that are overlapped with the specified visual rectangle. + /// If a child window of sigwd is a glass window, it doesn't to be notified. + void window_layout::_m_notify_glasses(core_window_t* const sigwd) { - typedef category::flags cat_flags; - nana::rectangle r_of_sigwd(sigwd->pos_root, sigwd->dimension); for (auto wd : data_sect.effects_bground_windows) { @@ -377,6 +376,8 @@ namespace nana } else if (sigwd != wd->parent) { + using cat_flags = category::flags; + if (wd->parent && (cat_flags::lite_widget == wd->parent->other.category)) { //Test if sigwd is an ancestor of the glass window, and there are lite widgets diff --git a/source/gui/detail/window_manager.cpp b/source/gui/detail/window_manager.cpp index 63e30e48..7a8980e8 100644 --- a/source/gui/detail/window_manager.cpp +++ b/source/gui/detail/window_manager.cpp @@ -1,7 +1,7 @@ /* * Window Manager Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -14,14 +14,17 @@ #include #include #include -#include #include +#include +#include "window_register.hpp" #include #include #include #include + #include #include +#include #if defined(STD_THREAD_NOT_SUPPORTED) #include @@ -34,6 +37,8 @@ namespace nana namespace detail { + using window_layer = window_layout; + //class shortkey_container struct shortkey_rep { @@ -266,7 +271,8 @@ namespace detail struct window_manager::wdm_private_impl { root_register misc_register; - handle_manager wd_register; + window_register wd_register; + paint::image default_icon_big; paint::image default_icon_small; @@ -274,26 +280,34 @@ namespace detail }; //end struct wdm_private_impl - //class revertible_mutex + //class revertible_mutex struct thread_refcount { - unsigned tid; - std::size_t ref; + unsigned tid; //Thread ID + std::vector callstack_refs; + + thread_refcount(unsigned thread_id, unsigned refs) + : tid(thread_id) + { + callstack_refs.push_back(refs); + } }; struct window_manager::revertible_mutex::implementation { std::recursive_mutex mutex; - thread_refcount thread; - std::vector invoke_stack; + unsigned thread_id; //Thread ID + unsigned refs; //Ref count + + std::vector records; }; window_manager::revertible_mutex::revertible_mutex() : impl_(new implementation) { - impl_->thread.tid = 0; - impl_->thread.ref = 0; + impl_->thread_id = 0; + impl_->refs = 0; } window_manager::revertible_mutex::~revertible_mutex() @@ -305,20 +319,20 @@ namespace detail { impl_->mutex.lock(); - if(0 == impl_->thread.tid) - impl_->thread.tid = nana::system::this_thread_id(); + if (0 == impl_->thread_id) + impl_->thread_id = nana::system::this_thread_id(); - ++(impl_->thread.ref); + ++(impl_->refs); } bool window_manager::revertible_mutex::try_lock() { - if(impl_->mutex.try_lock()) + if (impl_->mutex.try_lock()) { - if (0 == impl_->thread.tid) - impl_->thread.tid = nana::system::this_thread_id(); + if (0 == impl_->thread_id) + impl_->thread_id = nana::system::this_thread_id(); - ++(impl_->thread.ref); + ++(impl_->refs); return true; } return false; @@ -326,47 +340,80 @@ namespace detail void window_manager::revertible_mutex::unlock() { - if(impl_->thread.tid == nana::system::this_thread_id()) - if(0 == --(impl_->thread.ref)) - impl_->thread.tid = 0; + if (impl_->thread_id == nana::system::this_thread_id()) + if (0 == --(impl_->refs)) + impl_->thread_id = 0; impl_->mutex.unlock(); } void window_manager::revertible_mutex::revert() { - if(impl_->thread.ref && (impl_->thread.tid == nana::system::this_thread_id())) + if (impl_->thread_id == nana::system::this_thread_id()) { - std::size_t cnt = impl_->thread.ref; + auto const current_refs = impl_->refs; - impl_->invoke_stack.push_back(impl_->thread); - impl_->thread.tid = 0; - impl_->thread.ref = 0; + //Check if there is a record + for (auto & r : impl_->records) + { + if (r.tid == impl_->thread_id) + { + r.callstack_refs.push_back(current_refs); + impl_->thread_id = 0; //Indicates a record is existing + break; + } + } - for (std::size_t i = 0; i < cnt; ++i) + if (impl_->thread_id) + { + //Creates a new record + impl_->records.emplace_back(impl_->thread_id, current_refs); + impl_->thread_id = 0; + } + + impl_->refs = 0; + + for (std::size_t i = 0; i < current_refs; ++i) impl_->mutex.unlock(); } + else + throw std::runtime_error("The revert is not allowed"); } void window_manager::revertible_mutex::forward() { impl_->mutex.lock(); - if(impl_->invoke_stack.size()) + + if (impl_->records.size()) { - auto thr = impl_->invoke_stack.back(); - if(thr.tid == nana::system::this_thread_id()) + auto const this_tid = nana::system::this_thread_id(); + + for (auto i = impl_->records.begin(); i != impl_->records.end(); ++i) { - impl_->invoke_stack.pop_back(); - for (std::size_t i = 0; i < thr.ref; ++i) + if (this_tid != i->tid) + continue; + + auto const refs = i->callstack_refs.back(); + + for (std::size_t u = 1; u < refs; ++u) impl_->mutex.lock(); - impl_->thread = thr; + + impl_->thread_id = this_tid; + impl_->refs = refs; + + if (i->callstack_refs.size() > 1) + i->callstack_refs.pop_back(); + else + impl_->records.erase(i); + return; } - else - throw std::runtime_error("Nana.GUI: The forward is not matched."); + + throw std::runtime_error("The forward is not matched. Please report this issue"); } + impl_->mutex.unlock(); } - //end class revertible_mutex + //end class revertible_mutex //Utilities in this unit. namespace utl @@ -402,13 +449,11 @@ namespace detail delete impl_; } - bool window_manager::is_queue(core_window_t* wd) - { - return (wd && (category::flags::root == wd->other.category)); - } - std::size_t window_manager::number_of_core_window() const { + //Thread-Safe Required! + std::lock_guard lock(mutex_); + return impl_->wd_register.size(); } @@ -419,7 +464,9 @@ namespace detail void window_manager::all_handles(std::vector &v) const { - impl_->wd_register.all(v); + //Thread-Safe Required! + std::lock_guard lock(mutex_); + v = impl_->wd_register.queue(); } void window_manager::event_filter(core_window_t* wd, bool is_make, event_code evtid) @@ -436,11 +483,15 @@ namespace detail bool window_manager::available(core_window_t* wd) { + //Thread-Safe Required! + std::lock_guard lock(mutex_); return impl_->wd_register.available(wd); } bool window_manager::available(core_window_t * a, core_window_t* b) { + //Thread-Safe Required! + std::lock_guard lock(mutex_); return (impl_->wd_register.available(a) && impl_->wd_register.available(b)); } @@ -492,7 +543,7 @@ namespace detail auto* value = impl_->misc_register.insert(result.native_handle, root_misc(wd, result.width, result.height)); wd->bind_native_window(result.native_handle, result.width, result.height, result.extra_width, result.extra_height, value->root_graph); - impl_->wd_register.insert(wd, wd->thread_id); + impl_->wd_register.insert(wd); #ifndef WIDGET_FRAME_DEPRECATED if (owner && (category::flags::frame == owner->other.category)) @@ -573,7 +624,8 @@ namespace detail wd = new core_window_t(parent, std::move(wdg_notifier), r, (category::lite_widget_tag**)nullptr); else wd = new core_window_t(parent, std::move(wdg_notifier), r, (category::widget_tag**)nullptr); - impl_->wd_register.insert(wd, wd->thread_id); + + impl_->wd_register.insert(wd); return wd; } @@ -660,21 +712,47 @@ namespace detail } } - void window_manager::default_icon(const nana::paint::image& _small, const nana::paint::image& big) - { - impl_->default_icon_big = big; - impl_->default_icon_small = _small; - } - void window_manager::icon(core_window_t* wd, const paint::image& small_icon, const paint::image& big_icon) { if(!big_icon.empty() || !small_icon.empty()) { - std::lock_guard lock(mutex_); - if (impl_->wd_register.available(wd)) + if (nullptr == wd) { - if(category::flags::root == wd->other.category) - native_interface::window_icon(wd->root, small_icon, big_icon); + impl_->default_icon_big = big_icon; + impl_->default_icon_small = small_icon; + } + else + { + std::lock_guard lock(mutex_); + if (impl_->wd_register.available(wd)) + { + if (category::flags::root == wd->other.category) + native_interface::window_icon(wd->root, small_icon, big_icon); + } + } + } + } + + void sync_child_root_display(window_manager::core_window_t* wd) + { + for (auto & child : wd->children) + { + if (category::flags::root != child->other.category) + { + sync_child_root_display(child); + continue; + } + + auto const vs_parents = child->visible_parents(); + + if (vs_parents != child->visible) + { + native_interface::show_window(child->root, vs_parents, false); + } + else + { + if (child->visible != native_interface::is_window_visible(child->root)) + native_interface::show_window(child->root, child->visible, false); } } } @@ -713,13 +791,20 @@ namespace detail if(category::flags::root != wd->other.category) bedrock::instance().event_expose(wd, visible); - if(nv) + if (nv) + { + if (visible && !wd->visible_parents()) + return true; + native_interface::show_window(nv, visible, wd->flags.take_active); + } + + sync_child_root_display(wd); } return true; } - window_manager::core_window_t* window_manager::find_window(native_window_type root, int x, int y) + window_manager::core_window_t* window_manager::find_window(native_window_type root, const point& pos) { if (nullptr == root) return nullptr; @@ -729,7 +814,6 @@ namespace detail //Thread-Safe Required! std::lock_guard lock(mutex_); auto rrt = root_runtime(root); - point pos{ x, y }; if (rrt && _m_effective(rrt->window, pos)) return _m_find(rrt->window, pos); } @@ -759,6 +843,10 @@ namespace detail arg.window_handle = reinterpret_cast(wd); arg.x = x; arg.y = y; + + if (wd->effect.bground) + wd->other.upd_state = basic_window::update_state::request_refresh; + brock.emit(event_code::move, wd, arg, true, brock.get_thread_context()); return true; } @@ -797,12 +885,14 @@ namespace detail //Move child widgets if(r.x != wd->pos_owner.x || r.y != wd->pos_owner.y) { - point delta{ r.x - wd->pos_owner.x, r.y - wd->pos_owner.y }; - wd->pos_owner.x = r.x; - wd->pos_owner.y = r.y; + auto delta = r.position() - wd->pos_owner; + wd->pos_owner = r.position(); _m_move_core(wd, delta); moved = true; + if ((!size_changed) && wd->effect.bground) + wd->other.upd_state = basic_window::update_state::request_refresh; + arg_move arg; arg.window_handle = reinterpret_cast(wd); arg.x = r.x; @@ -989,26 +1079,28 @@ namespace detail if (wd->displayed()) { + using paint_operation = window_layer::paint_operation; + if(forced || (false == wd->belong_to_lazy())) { if (!wd->flags.refreshing) { - window_layer::paint(wd, redraw, false); + window_layer::paint(wd, (redraw ? paint_operation::try_refresh : paint_operation::none), false); this->map(wd, forced, update_area); return true; } else if (forced) { - window_layer::paint(wd, false, false); + window_layer::paint(wd, paint_operation::none, false); this->map(wd, true, update_area); return true; } } else if (redraw) - window_layer::paint(wd, true, false); + window_layer::paint(wd, paint_operation::try_refresh, false); if (wd->other.upd_state == core_window_t::update_state::lazy) - wd->other.upd_state = core_window_t::update_state::refresh; + wd->other.upd_state = core_window_t::update_state::refreshed; } return true; } @@ -1020,43 +1112,43 @@ namespace detail //It's not worthy to redraw if visible is false if (impl_->wd_register.available(wd) && wd->displayed()) - window_layer::paint(wd, true, true); + window_layer::paint(wd, window_layer::paint_operation::try_refresh, true); } //do_lazy_refresh //@brief: defined a behavior of flush the screen - //@return: it returns true if the wnd is available - bool window_manager::do_lazy_refresh(core_window_t* wd, bool force_copy_to_screen, bool refresh_tree) + void window_manager::do_lazy_refresh(core_window_t* wd, bool force_copy_to_screen, bool refresh_tree) { //Thread-Safe Required! std::lock_guard lock(mutex_); if (false == impl_->wd_register.available(wd)) - return false; + return; //It's not worthy to redraw if visible is false if(wd->visible && (!wd->is_draw_through())) { + using paint_operation = window_layer::paint_operation; if (wd->visible_parents()) { - if ((wd->other.upd_state == core_window_t::update_state::refresh) || force_copy_to_screen) + if ((wd->other.upd_state == core_window_t::update_state::refreshed) || (wd->other.upd_state == core_window_t::update_state::request_refresh) || force_copy_to_screen) { - window_layer::paint(wd, false, refresh_tree); + window_layer::paint(wd, (wd->other.upd_state == core_window_t::update_state::request_refresh ? paint_operation::try_refresh : paint_operation::have_refreshed), refresh_tree); this->map(wd, force_copy_to_screen); } else if (effects::edge_nimbus::none != wd->effect.edge_nimbus) { //The window is still mapped because of edge nimbus effect. //Avoid duplicate copy if action state is not changed and the window is not focused. - if ((wd->flags.action != wd->flags.action_before) || (bedrock::instance().focus() == wd)) + if (wd->flags.action != wd->flags.action_before) this->map(wd, true); } } else - window_layer::paint(wd, true, refresh_tree); //only refreshing if it has an invisible parent + window_layer::paint(wd, paint_operation::try_refresh, refresh_tree); //only refreshing if it has an invisible parent } wd->other.upd_state = core_window_t::update_state::none; - return true; + return; } //get_graphics @@ -1305,13 +1397,13 @@ namespace detail { auto & tabs = wd->root_widget->other.attribute.root->tabstop; + auto end = tabs.cend(); if (forward) { if (detail::tab_type::none == wd->flags.tab) return (tabs.front()); else if (detail::tab_type::tabstop & wd->flags.tab) { - auto end = tabs.cend(); auto i = std::find(tabs.cbegin(), end, wd); if (i != end) { @@ -1325,8 +1417,8 @@ namespace detail } else if (tabs.size() > 1) //at least 2 elments in tabs are required when moving backward. { - auto i = std::find(tabs.cbegin(), tabs.cend(), wd); - if (i != tabs.cend()) + auto i = std::find(tabs.cbegin(), end, wd); + if (i != end) return (tabs.cbegin() == i ? tabs.back() : *(i - 1)); } return nullptr; @@ -1371,6 +1463,8 @@ namespace detail void window_manager::remove_trash_handle(unsigned tid) { + //Thread-Safe Required! + std::lock_guard lock(mutex_); impl_->wd_register.delete_trash(tid); } @@ -1635,7 +1729,7 @@ namespace detail if (established) { - utl::erase(pa_children, wd); + utl::erase(wd->parent->children, wd); if (for_new->children.empty()) wd->index = 0; else @@ -1724,32 +1818,18 @@ namespace detail brock.emit(event_code::destroy, wd, arg, true, brock.get_thread_context()); //Delete the children widgets. - for (auto i = wd->children.rbegin(), end = wd->children.rend(); i != end;) + while (!wd->children.empty()) { - auto child = *i; - + auto child = wd->children.back(); if (category::flags::root == child->other.category) { - //closing a child root window erases itself from wd->children, - //to make sure the iterator is valid, it must be reloaded. - - auto offset = std::distance(wd->children.rbegin(), i); - - //!!! - //a potential issue is that if the calling thread is not same with child's thread, - //the child root window may not be erased from wd->children now. + //Only the nested_form meets the condition native_interface::close_window(child->root); - - i = wd->children.rbegin(); - std::advance(i, offset); - end = wd->children.rend(); continue; } _m_destroy(child); - ++i; + wd->children.pop_back(); } - wd->children.clear(); - _m_disengage(wd, nullptr); window_layer::enable_effects_bground(wd, false); @@ -1812,15 +1892,20 @@ namespace detail if(!wd->visible) return nullptr; - for(auto i = wd->children.rbegin(); i != wd->children.rend(); ++i) + if (!wd->children.empty()) { - core_window_t* child = *i; - if((child->other.category != category::flags::root) && _m_effective(child, pos)) + auto index = wd->children.size(); + + do { - child = _m_find(child, pos); - if(child) - return child; - } + auto child = wd->children[--index]; + if ((child->other.category != category::flags::root) && _m_effective(child, pos)) + { + child = _m_find(child, pos); + if (child) + return child; + } + } while (0 != index); } return wd; } diff --git a/source/gui/detail/window_register.hpp b/source/gui/detail/window_register.hpp new file mode 100644 index 00000000..65ac0fa8 --- /dev/null +++ b/source/gui/detail/window_register.hpp @@ -0,0 +1,202 @@ +#ifndef NANA_WINDOW_REGISTER_HEADER_INCLUDED +#define NANA_WINDOW_REGISTER_HEADER_INCLUDED + +#include +#include +#include +#include //std::find + +namespace nana +{ + namespace detail + { + template + class cache + : noncopyable + { + public: + typedef Key key_type; + typedef Value value_type; + typedef std::pair pair_type; + typedef std::size_t size_type; + + cache() + :addr_(reinterpret_cast(::operator new(sizeof(pair_type) * CacheSize))) + { + for (std::size_t i = 0; i < CacheSize; ++i) + { + bitmap_[i] = 0; + seq_[i] = nana::npos; + } + } + + ~cache() + { + for (std::size_t i = 0; i < CacheSize; ++i) + { + if (bitmap_[i]) + addr_[i].~pair_type(); + } + + ::operator delete(addr_); + } + + bool insert(key_type k, value_type v) + { + size_type pos = _m_find_key(k); + if (pos != nana::npos) + { + addr_[pos].second = v; + } + else + { + //No key exists + pos = _m_find_pos(); + + if (pos == nana::npos) + { //No room, and remove the last pair + pos = seq_[CacheSize - 1]; + (addr_ + pos)->~pair_type(); + } + + if (seq_[0] != nana::npos) + {//Need to move + for (int i = CacheSize - 1; i > 0; --i) + seq_[i] = seq_[i - 1]; + } + + seq_[0] = pos; + + new (addr_ + pos) pair_type(k, v); + bitmap_[pos] = 1; + } + return v; + } + + value_type * get(key_type k) + { + size_type pos = _m_find_key(k); + if (pos != nana::npos) + return &(addr_[pos].second); + return 0; + } + private: + size_type _m_find_key(key_type k) const + { + for (std::size_t i = 0; i < CacheSize; ++i) + { + if (bitmap_[i] && (addr_[i].first == k)) + return i; + } + return nana::npos; + } + + size_type _m_find_pos() const + { + for (std::size_t i = 0; i < CacheSize; ++i) + { + if (bitmap_[i] == 0) + return i; + } + return nana::npos; + } + private: + char bitmap_[CacheSize]; + size_type seq_[CacheSize]; + pair_type * addr_; + }; + + class window_register + { + public: + using window_handle_type = basic_window*; + + void insert(window_handle_type wd) + { + if (wd) + { + base_.insert(wd); + wdcache_.insert(wd, true); + + if (category::flags::root == wd->other.category) + queue_.push_back(wd); + } + } + + void operator()(window_handle_type wd) + { + remove(wd); + } + + void remove(window_handle_type wd) + { + if (base_.erase(wd)) + { + wdcache_.insert(wd, false); + trash_.push_back(wd); + + if (category::flags::root == wd->other.category) + { + auto i = std::find(queue_.begin(), queue_.end(), wd); + if (i != queue_.end()) + queue_.erase(i); + } + } + } + + void delete_trash(unsigned thread_id) + { + if (0 == thread_id) + { + for (auto wd : trash_) + delete wd; + + trash_.clear(); + } + else + { + for (auto i = trash_.begin(); i != trash_.end();) + { + if (thread_id == (*i)->thread_id) + { + delete (*i); + i = trash_.erase(i); + } + else + ++i; + } + } + } + + const std::vector& queue() const + { + return queue_; + } + + /// Returns the number of registered windows + std::size_t size() const + { + return base_.size(); + } + + bool available(window_handle_type wd) const + { + if (nullptr == wd) + return false; + + auto exists = wdcache_.get(wd); + if (exists) + return *exists; + + return wdcache_.insert(wd, (base_.count(wd) != 0)); + } + private: + mutable cache wdcache_; + std::set base_; + std::vector trash_; + std::vector queue_; + }; + } +} + +#endif diff --git a/source/gui/element.cpp b/source/gui/element.cpp index 8d31a447..a67a7341 100644 --- a/source/gui/element.cpp +++ b/source/gui/element.cpp @@ -1,7 +1,7 @@ /* * Elements of GUI Gadgets * Nana C++ Library(http://www.nanapro.org) -* Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com) +* Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -148,22 +148,22 @@ namespace nana } else { - ::nana::color highlighted(0x5e, 0xb6, 0xf7); + ::nana::color highlighted(static_cast(0x5eb6f7)); auto bld_bgcolor = bgcolor; auto bld_fgcolor = fgcolor; switch(es) { case element_state::hovered: case element_state::focus_hovered: - bld_bgcolor = bgcolor.blend(highlighted, 0.8); - bld_fgcolor = fgcolor.blend(highlighted, 0.8); + bld_bgcolor = bgcolor.blend(highlighted, 0.2); + bld_fgcolor = fgcolor.blend(highlighted, 0.2); break; case element_state::pressed: - bld_bgcolor = bgcolor.blend(highlighted, 0.4); - bld_fgcolor = fgcolor.blend(highlighted, 0.4); + bld_bgcolor = bgcolor.blend(highlighted, 0.6); + bld_fgcolor = fgcolor.blend(highlighted, 0.6); break; case element_state::disabled: - bld_bgcolor = bld_fgcolor = nana::color(0xb2, 0xb7, 0xbc); + bld_bgcolor = bld_fgcolor = static_cast(0xb2b7bc); break; default: //Leave things as they are @@ -493,10 +493,10 @@ namespace nana { case element_state::hovered: case element_state::focus_hovered: - bgcolor = arg_bgcolor.blend(colors::white, 0.8); + bgcolor = arg_bgcolor.blend(colors::white, 0.2); break; case element_state::pressed: - bgcolor = arg_bgcolor.blend(colors::black, 0.8); + bgcolor = arg_bgcolor.blend(colors::black, 0.2); break; case element_state::disabled: bgcolor = colors::dark_gray; @@ -505,13 +505,13 @@ namespace nana } auto part_px = (r.height - 3) * 5 / 13; - graph.rectangle(r, false, bgcolor.blend(colors::black, 0.6)); + graph.rectangle(r, false, bgcolor.blend(colors::black, 0.4)); ::nana::point left_top{ r.x + 1, r.y + 1 }, right_top{r.right() - 2, r.y + 1}; ::nana::point left_mid{ r.x + 1, r.y + 1 + static_cast(part_px) }, right_mid{ right_top.x, left_mid.y }; ::nana::point left_bottom{ r.x + 1, r.bottom() - 2 }, right_bottom{ r.right() - 2, r.bottom() - 2 }; - graph.palette(false, bgcolor.blend(colors::white, 0.9)); + graph.palette(false, bgcolor.blend(colors::white, 0.1)); graph.line(left_top, left_mid); graph.line(right_top, right_mid); @@ -520,12 +520,12 @@ namespace nana left_mid.y++; right_mid.y++; - graph.palette(false, bgcolor.blend(colors::black, 0.8)); + graph.palette(false, bgcolor.blend(colors::black, 0.2)); graph.line(left_mid, left_bottom); graph.line(right_mid, right_bottom); ::nana::rectangle part_r{ r.x + 2, r.y + 2, r.width - 4, part_px }; - graph.rectangle(part_r, true, bgcolor.blend(colors::white, 0.8)); + graph.rectangle(part_r, true, bgcolor.blend(colors::white, 0.2)); part_r.y += static_cast(part_r.height); part_r.height = (r.height - 3 - part_r.height); @@ -545,7 +545,7 @@ namespace nana { case element_state::hovered: case element_state::pressed: - clr = clr.blend(colors::black, 0.8); + clr = clr.blend(colors::black, 0.2); break; case element_state::disabled: clr = colors::dark_gray; @@ -954,7 +954,7 @@ namespace nana ps[11].x = r.x + gap; ps[11].y = r.y + gap; - graph.palette(false, fgcolor.blend(colors::black, true)); + graph.palette(false, fgcolor.blend(colors::black, 1.0 - fgcolor.a())); for (int i = 0; i < 11; ++i) graph.line(ps[i], ps[i + 1]); diff --git a/source/gui/filebox.cpp b/source/gui/filebox.cpp index ae57b0f7..ae90915d 100644 --- a/source/gui/filebox.cpp +++ b/source/gui/filebox.cpp @@ -1,7 +1,7 @@ /* * Filebox * Nana C++ Library(http://www.nanapro.org) -* Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) +* Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -44,13 +44,13 @@ namespace nana std::string name; ::tm modified_time; bool directory; - nana::long_long_t bytes; + long long bytes; friend listbox::iresolver& operator>>(listbox::iresolver& ires, item_fs& m) { std::wstring type; ires>>m.name>>type>>type; - m.directory = (type == L"Directory"); + m.directory = (to_utf8(type) == internationalization()("NANA_FILEBOX_DIRECTORY")); return ires; } @@ -65,6 +65,8 @@ namespace nana _m_add(tm, item.modified_time.tm_min)<<':'; _m_add(tm, item.modified_time.tm_sec); + internationalization i18n; + ores<&) @@ -157,7 +161,7 @@ namespace nana filter_.create(*this); filter_.multi_lines(false); - filter_.tip_string("Filter"); + filter_.tip_string(i18n("NANA_FILEBOX_FILTER")); filter_.events().key_release.connect_unignorable([this](const arg_keyboard&) { @@ -165,23 +169,23 @@ namespace nana }); btn_folder_.create(*this); - btn_folder_.caption("&New Folder"); + btn_folder_.i18n(i18n_eval("NANA_FILEBOX_NEW_FOLDER_SHORTKEY")); btn_folder_.events().click.connect_unignorable([this](const arg_click&) { form fm(this->handle(), API::make_center(*this, 300, 35)); - fm.caption("Name the new folder"); + fm.i18n(i18n_eval("NANA_FILEBOX_NEW_FOLDER_CAPTION")); textbox folder(fm, nana::rectangle(5, 5, 160, 25)); folder.multi_lines(false); button btn(fm, nana::rectangle(170, 5, 60, 25)); - btn.caption("Create"); + btn.i18n(i18n_eval("NANA_BUTTON_CREATE")); btn.events().click.connect_unignorable(folder_creator(*this, fm, folder)); button btn_cancel(fm, nana::rectangle(235, 5, 60, 25)); - btn_cancel.caption("Cancel"); + btn_cancel.i18n(i18n_eval("NANA_BUTTON_CANCEL")); btn_cancel.events().click.connect_unignorable([&fm](const arg_click&) { @@ -193,10 +197,10 @@ namespace nana tree_.create(*this); ls_file_.create(*this); - ls_file_.append_header("Name", 190); - ls_file_.append_header("Modified", 145); - ls_file_.append_header("Type", 80); - ls_file_.append_header("Size", 70); + ls_file_.append_header(i18n("NANA_FILEBOX_HEADER_NAME"), 190); + ls_file_.append_header(i18n("NANA_FILEBOX_HEADER_MODIFIED"), 145); + ls_file_.append_header(i18n("NANA_FILEBOX_HEADER_TYPE"), 80); + ls_file_.append_header(i18n("NANA_FILEBOX_HEADER_SIZE"), 70); auto fn_sel_file = [this](const arg_mouse& arg){ _m_sel_file(arg); @@ -274,7 +278,7 @@ namespace nana }); lb_file_.create(*this); - lb_file_.caption("File:"); + lb_file_.i18n(i18n_eval("NANA_FILEBOX_FILE_COLON")); tb_file_.create(*this); tb_file_.multi_lines(false); @@ -290,14 +294,15 @@ namespace nana cb_types_.events().selected.connect_unignorable([this](const arg_combox&){ _m_list_fs(); }); btn_ok_.create(*this); - btn_ok_.caption("&OK"); + btn_ok_.i18n(i18n_eval("NANA_BUTTON_OK_SHORTKEY")); + btn_ok_.events().click.connect_unignorable([this](const arg_click&) { _m_ok(); }); btn_cancel_.create(*this); - btn_cancel_.caption("&Cancel"); + btn_cancel_.i18n(i18n_eval("NANA_BUTTON_CANCEL_SHORTKEY")); btn_cancel_.events().click.connect_unignorable([this](const arg_click&) { @@ -309,7 +314,7 @@ namespace nana _m_init_tree(); if(0 == title.size()) - caption(io_read ? "Open" : "Save As"); + this->i18n(i18n_eval(io_read ? "NANA_FILEBOX_OPEN" : "NANA_FILEBOX_SAVE_AS")); else caption(title); } @@ -640,11 +645,12 @@ namespace nana { auto path = tx_path_.caption(); - msgbox mb(fm_, "Create Folder"); + internationalization i18n; + msgbox mb(fm_, i18n("NANA_FILEBOX_NEW_FOLDER")); mb.icon(msgbox::icon_warning); if(0 == path.size() || path[0] == '.' || path[0] == '/') { - mb<(API::root(impl_->owner)); ofn.lpstrFile = &(wfile[0]); @@ -1044,10 +1058,13 @@ namespace nana if (!impl_->open_or_save) ofn.Flags = OFN_OVERWRITEPROMPT; //Overwrite prompt if it is save mode ofn.Flags |= OFN_NOCHANGEDIR; - - if(FALSE == (impl_->open_or_save ? ::GetOpenFileName(&ofn) : ::GetSaveFileName(&ofn))) - return false; - + + { + internal_revert_guard revert; + if (FALSE == (impl_->open_or_save ? ::GetOpenFileName(&ofn) : ::GetSaveFileName(&ofn))) + return false; + } + wfile.resize(std::wcslen(wfile.data())); impl_->file = to_utf8(wfile); #elif defined(NANA_POSIX) diff --git a/source/gui/msgbox.cpp b/source/gui/msgbox.cpp index 4bafd225..6560fe93 100644 --- a/source/gui/msgbox.cpp +++ b/source/gui/msgbox.cpp @@ -61,14 +61,14 @@ namespace nana { _m_click(arg); }); - yes_.i18n(i18n_eval("OK")); + yes_.i18n(i18n_eval("NANA_BUTTON_OK")); width_pixel += 77; if(msgbox::yes_no == btn || msgbox::yes_no_cancel == btn) { - yes_.i18n(i18n_eval("Yes")); + yes_.i18n(i18n_eval("NANA_BUTTON_YES")); no_.create(*this); - no_.i18n(i18n_eval("No")); + no_.i18n(i18n_eval("NANA_BUTTON_NO")); no_.events().click.connect_unignorable([this](const arg_click& arg) { _m_click(arg); @@ -79,7 +79,7 @@ namespace nana if(msgbox::yes_no_cancel == btn) { cancel_.create(*this); - cancel_.i18n(i18n_eval("Cancel")); + cancel_.i18n(i18n_eval("NANA_BUTTON_CANCEL")); cancel_.events().click.connect_unignorable([this](const arg_click& arg) { _m_click(arg); @@ -493,7 +493,7 @@ namespace nana auto desc_extent = desc_.measure(470); btn_ok_.create(*this); - btn_ok_.i18n(i18n_eval("OK")); + btn_ok_.i18n(i18n_eval("NANA_BUTTON_OK")); btn_ok_.events().click.connect_unignorable([this]{ if (verifier_ && !verifier_(handle())) @@ -504,7 +504,7 @@ namespace nana }); btn_cancel_.create(*this); - btn_cancel_.i18n(i18n_eval("Cancel")); + btn_cancel_.i18n(i18n_eval("NANA_BUTTON_CANCEL")); btn_cancel_.events().click.connect_unignorable([this]{ close(); }); @@ -618,7 +618,7 @@ namespace nana place_.field_display(img_fields[i], false); } - size({desc_extent.width, height }); + move(API::make_center(this->owner(), desc_extent.width, height)); caption(title); } @@ -1141,7 +1141,7 @@ namespace nana impl->path_edit.multi_lines(false); impl->browse.create(impl->dock); - impl->browse.i18n(i18n_eval("Browse")); + impl->browse.i18n(i18n_eval("NANA_BUTTON_BROWSE")); impl->browse.events().click.connect_unignorable([wd, impl](const arg_click&) { impl->fbox.owner(wd); diff --git a/source/gui/notifier.cpp b/source/gui/notifier.cpp index e46541c9..545b3a47 100644 --- a/source/gui/notifier.cpp +++ b/source/gui/notifier.cpp @@ -26,7 +26,7 @@ #include #endif -#include +#include "../detail/platform_spec_selector.hpp" #if defined(NANA_LINUX) || defined(NANA_MACOS) #include @@ -201,7 +201,7 @@ namespace nana #if defined(NANA_WINDOWS) void notifications_window_proc(HWND wd, WPARAM wparam, LPARAM lparam) { - arg_notifier arg; + arg_notifier arg = {}; switch (lparam) { case WM_LBUTTONDBLCLK: @@ -321,6 +321,7 @@ namespace nana void notifier::icon(const std::string& icon_file) { +#if defined(NANA_WINDOWS) paint::image image_ico{ icon_file }; auto icon_handle = paint::image_accessor::icon(image_ico); if (icon_handle) @@ -330,14 +331,21 @@ namespace nana impl_->set_icon(image_ico); impl_->icon = image_ico; } +#else + static_cast(icon_file); //eliminate unused parameter warning +#endif } void notifier::insert_icon(const std::string& icon_file) { +#if defined(NANA_WINDOWS) paint::image image_ico{ icon_file }; auto icon_handle = paint::image_accessor::icon(image_ico); if (icon_handle) impl_->icons.emplace_back(static_cast(image_ico)); +#else + static_cast(icon_file); //eliminate unused parameter warning +#endif } void notifier::period(unsigned ms) diff --git a/source/gui/place.cpp b/source/gui/place.cpp index 1af71f0c..ff446802 100644 --- a/source/gui/place.cpp +++ b/source/gui/place.cpp @@ -1,20 +1,21 @@ /* * An Implementation of Place for Layout * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at * http://www.boost.org/LICENSE_1_0.txt) * * @file: nana/gui/place.cpp - * @contributors: Ariel Vina-Rodriguez + * @contributors: Ariel Vina-Rodriguez + * dankan1890(PR#156) */ #include #include #include -#include +#include #include #include #include @@ -29,6 +30,7 @@ #include //numeric_limits #include //std::abs #include //std::memset +#include //std::isalpha/std::isalnum #include "place_parts.hpp" @@ -55,52 +57,47 @@ namespace nana enum class token { div_start, div_end, splitter, - identifier, dock, vert, grid, number, array, reparray, - weight, gap, margin, arrange, variable, repeated, min_px, max_px, left, right, top, bottom, + identifier, dock, fit, hfit, vfit, vert, grid, number, array, reparray, + weight, gap, margin, arrange, variable, repeated, min_px, max_px, left, right, top, bottom, undisplayed, invisible, collapse, parameters, equal, eof, error }; - tokenizer(const char* p) + tokenizer(const char* p) noexcept : divstr_(p), sp_(p) {} - const std::string& idstr() const + const std::string& idstr() const noexcept { return idstr_; } - number_t number() const + const number_t& number() const { return number_; } - std::vector& array() + std::vector& array() noexcept { return array_; } - repeated_array&& reparray() + repeated_array&& reparray() noexcept { return std::move(reparray_); } - std::vector& parameters() + std::vector& parameters() noexcept { return parameters_; } - std::size_t pos() const + std::size_t pos() const noexcept { return (sp_ - divstr_); } - std::string pos_str() const - { - return std::to_string(pos()); - } - token read() { sp_ = _m_eat_whitespace(sp_); @@ -229,11 +226,11 @@ namespace nana break; } - if ('_' == *sp_ || isalpha(*sp_)) + if ('_' == *sp_ || std::isalpha(*sp_)) { const char * idstart = sp_++; - while ('_' == *sp_ || isalpha(*sp_) || isalnum(*sp_)) + while ('_' == *sp_ || std::isalpha(*sp_) || std::isalnum(*sp_)) ++sp_; idstr_.assign(idstart, sp_); @@ -251,15 +248,24 @@ namespace nana } else if ("dock" == idstr_) return token::dock; + else if ("fit" == idstr_) + return token::fit; else if ("vertical" == idstr_ || "vert" == idstr_) return token::vert; else if ("variable" == idstr_ || "repeated" == idstr_) return ('v' == idstr_[0] ? token::variable : token::repeated); - else if ("arrange" == idstr_ || "gap" == idstr_) + else if ("arrange" == idstr_ || "hfit" == idstr_ || "vfit" == idstr_ || "gap" == idstr_) { auto ch = idstr_[0]; _m_attr_reparray(); - return ('a' == ch ? token::arrange : token::gap); + switch (ch) + { + case 'a': return token::arrange; + case 'h': return token::hfit; + case 'v': return token::vfit; + case 'g': return token::gap; + default: break; + } } else if ("grid" == idstr_ || "margin" == idstr_) { @@ -275,7 +281,7 @@ namespace nana _m_throw_error("a parameter list is required after 'collapse'"); return token::collapse; } - else if ("left" == idstr_ || "right" == idstr_ || "top" == idstr_ || "bottom" == idstr_) + else if ("left" == idstr_ || "right" == idstr_ || "top" == idstr_ || "bottom" == idstr_ || "undisplayed" == idstr_ || "invisible" == idstr_) { switch (idstr_.front()) { @@ -283,27 +289,21 @@ namespace nana case 'r': return token::right; case 't': return token::top; case 'b': return token::bottom; + case 'u': return token::undisplayed; + case 'i': return token::invisible; } } + return token::identifier; } std::string err = "an invalid character '"; err += *sp_; err += "'"; - _m_throw_error(err); return token::error; //Useless, just for syntax correction. } private: - void _m_throw_error(char err_char) - { - std::string str = "place: invalid character '"; - str += err_char; - str += '\''; - _m_throw_error(str); - } - void _m_throw_error(const std::string& err) { throw std::runtime_error("nana::place: " + err + " at " + std::to_string(static_cast(sp_ - divstr_))); @@ -353,14 +353,14 @@ namespace nana } } - static const char* _m_eat_whitespace(const char* sp) + static const char* _m_eat_whitespace(const char* sp) noexcept { - while (*sp && !isgraph(*sp)) + while (*sp && !std::isgraph(*sp)) ++sp; return sp; } - std::size_t _m_number(const char* sp, bool negative) + std::size_t _m_number(const char* sp, bool negative) noexcept { const char* allstart = sp; sp = _m_eat_whitespace(sp); @@ -405,13 +405,22 @@ namespace nana if (gotcha) { sp = _m_eat_whitespace(sp); - if ('%' == *sp) + if ('%' != *sp) + return sp - allstart; + + switch (number_.kind_of()) { - if (number_t::kind::integer == number_.kind_of()) - number_.assign_percent(number_.integer()); - return sp - allstart + 1; + case number_t::kind::integer: + number_.assign_percent(number_.integer()); + break; + case number_t::kind::real: + number_.assign_percent(number_.real()); + break; + default: + break; } - return sp - allstart; + + return sp - allstart + 1; } number_.reset(); return 0; @@ -427,6 +436,131 @@ namespace nana }; //end class tokenizer } + static bool is_vert_dir(::nana::direction dir) + { + return (dir == ::nana::direction::north || dir == ::nana::direction::south); + } + + static int horz_point(bool vert, const point& pos) + { + return (vert ? pos.y : pos.x); + } + + + static bool is_idchar(int ch) noexcept + { + return ('_' == ch || std::isalnum(ch)); + } + + static std::size_t find_idstr(const std::string& text, const char* idstr, std::size_t off = 0) + { + const auto len = std::strlen(idstr); + + size_t pos; + while ((pos = text.find(idstr, off)) != text.npos) + { + if (!is_idchar(text[pos + len])) + { + if (pos == 0 || !is_idchar(text[pos - 1])) + return pos; + } + + off = pos + len; // occurrence not found, advancing the offset and try again + } + return text.npos; + } + + //Find the text bound of a field. The parameter start_pos is one of bound characters of the field whose bound will be returned + static std::pair get_field_bound(const std::string& div, std::size_t start_pos) + { + int depth = 0; + if ('<' == div[start_pos]) + { + auto off = start_pos + 1; + while (off < div.length()) + { + auto pos = div.find_first_of("<>", off); + if (div.npos == pos) + break; + + if ('<' == div[pos]) + { + ++depth; + off = pos + 1; + continue; + } + + if (0 == depth) + return{ start_pos, pos + 1 }; + + --depth; + off = pos + 1; + } + } + else if (('>' == div[start_pos]) && (start_pos > 0)) + { + auto off = start_pos - 1; + while (true) + { + auto pos = div.find_last_of("<>", off); + if (div.npos == pos) + break; + + if ('>' == div[pos]) + { + ++depth; + if (0 == pos) + break; + + off = pos - 1; + } + + if (0 == depth) + return{ pos, start_pos + 1 }; + + if (0 == pos) + break; + + off = pos - 1; + } + } + + return{}; + } + + static std::pair get_field_bound(const std::string& div, const char* idstr, int depth) + { + auto start_pos = find_idstr(div, idstr); + + if (depth < 0 || start_pos >= div.length()) + return{}; + + while (depth >= 0) + { + auto pos = div.find_last_of("<>", start_pos); + if (div.npos == pos) + return{}; + + if (div[pos] == '>') + { + ++depth; + start_pos = pos - 1; + continue; + } + + if (0 == depth) + { + start_pos = pos; + break; + } + + --depth; + start_pos = pos - 1; + } + + return get_field_bound(div, start_pos + 1); + } + //struct implement struct place::implement { @@ -442,11 +576,16 @@ namespace nana window window_handle{nullptr}; event_handle event_size_handle{nullptr}; + + std::string div_text; std::unique_ptr root_division; std::map fields; std::map docks; std::map dock_factoris; + std::function split_renderer; + std::set splitters; + //A temporary pointer used to refer to a specified div object which //will be deleted in modification process. std::unique_ptr tmp_replaced; @@ -457,13 +596,13 @@ namespace nana void collocate(); - static division * search_div_name(division* start, const std::string&); + static division * search_div_name(division* start, const std::string&) noexcept; std::unique_ptr scan_div(place_parts::tokenizer&); void check_unique(const division*) const; //connect the field/dock with div object void connect(division* start); - void disconnect(); + void disconnect() noexcept; }; //end struct implement class place::implement::field_gather @@ -475,16 +614,16 @@ namespace nana window handle; event_handle evt_destroy; - element_t(window h, event_handle event_destroy) + element_t(window h, event_handle event_destroy) noexcept :handle(h), evt_destroy(event_destroy) {} }; - field_gather(place * p) + field_gather(place * p) noexcept : place_ptr_(p) {} - ~field_gather() + ~field_gather() noexcept { for (auto & e : elements) API::umake_event(e.evt_destroy); @@ -502,7 +641,7 @@ namespace nana API::show_window(e.handle, vsb); } - static event_handle erase_element(std::vector& elements, window handle) + static event_handle erase_element(std::vector& elements, window handle) noexcept { for (auto i = elements.begin(); i != elements.end(); ++i) { @@ -516,18 +655,31 @@ namespace nana return nullptr; } private: - //Listen to destroy of a window - //It will delete the element and recollocate when the window destroyed. - event_handle _m_make_destroy(window wd) + void _m_insert_widget(window wd, bool to_fasten) { - return API::events(wd).destroy.connect([this, wd](const arg_destroy&) + if (API::empty_window(wd)) + throw std::invalid_argument("Place: An invalid window handle."); + + if (API::get_parent_window(wd) != place_ptr_->window_handle()) + throw std::invalid_argument("Place: the window is not a child of place bind window"); + + //Listen to destroy of a window + //It will delete the element and recollocate when the window destroyed. + auto evt = API::events(wd).destroy.connect([this, to_fasten](const arg_destroy& arg) { - if (erase_element(elements, wd)) + if (!to_fasten) { - if (!API::is_destroying(API::get_parent_window(wd))) - place_ptr_->collocate(); + if (erase_element(elements, arg.window_handle)) + { + if (!API::is_destroying(API::get_parent_window(arg.window_handle))) + place_ptr_->collocate(); + } } + else + erase_element(fastened, arg.window_handle); }); + + (to_fasten ? &fastened : &elements)->emplace_back(wd, evt); } field_interface& operator<<(const char* label_text) override @@ -542,33 +694,13 @@ namespace nana field_interface& operator<<(window wd) override { - if (API::empty_window(wd)) - throw std::invalid_argument("Place: An invalid window handle."); - - if (API::get_parent_window(wd) != place_ptr_->window_handle()) - throw std::invalid_argument("Place: the window is not a child of place bind window"); - - auto evt = _m_make_destroy(wd); - elements.emplace_back(wd, evt); + _m_insert_widget(wd, false); return *this; } field_interface& fasten(window wd) override { - if (API::empty_window(wd)) - throw std::invalid_argument("Place: An invalid window handle."); - - if (API::get_parent_window(wd) != place_ptr_->window_handle()) - throw std::invalid_argument("Place: the window is not a child of place bind window"); - - //Listen to destroy of a window. The deleting a fastened window - //does not change the layout. - auto evt = API::events(wd).destroy.connect([this](const arg_destroy& arg) - { - erase_element(fastened, arg.window_handle); - }); - - fastened.emplace_back(wd, evt); + _m_insert_widget(wd, true); return *this; } @@ -588,7 +720,6 @@ namespace nana class place::implement::field_dock { - public: div_dockpane * attached{ nullptr }; //attached div object std::unique_ptr dockarea; //the dockable widget @@ -596,13 +727,20 @@ namespace nana };//end class field_dock + enum class fit_policy + { + none, //Doesn't fit the content + both, //Fits both width and height of content + horz, //Fits width of content with a specified height + vert //Fits height of content with a specified width + }; class place::implement::division { public: enum class kind{ arrange, vertical_arrange, grid, splitter, dock, dockpane}; - division(kind k, std::string&& n) + division(kind k, std::string&& n) noexcept : kind_of_division(k), name(std::move(n)) {} @@ -614,6 +752,167 @@ namespace nana field->attached = nullptr; } + static unsigned calc_number(const place_parts::number_t& number, unsigned area_px, double adjustable_px, double& precise_px) + { + switch (number.kind_of()) + { + case number_t::kind::integer: + return static_cast(number.integer()); + case number_t::kind::real: + return static_cast(number.real()); + case number_t::kind::percent: + adjustable_px = area_px * number.real(); + case number_t::kind::none: + { + auto fpx = adjustable_px + precise_px; + auto px = static_cast(fpx); + precise_px = fpx - px; + return px; + } + break; + } + return 0; //Useless + } + + std::pair calc_weight_floor() + { + std::pair floor; + run_.fit_extents.clear(); + + run_.weight_floor = floor; + + if (this->display) + { + double ratio = 0; + + for (auto & child : children) + { + auto child_floor = child->calc_weight_floor(); + + if(child->is_percent()) + { + ratio += child->weight.real(); + } + else + { + floor.first += child_floor.first; + floor.second += child_floor.second; + } + } + + auto const vert_fields = (kind::vertical_arrange == this->kind_of_division); + auto const vert_div = (this->div_owner && (kind::vertical_arrange == this->div_owner->kind_of_division)); + double& fv = (vert_div ? floor.second : floor.first); + + if((ratio > 0.001) && (fv > 0)) + fv /= ratio; + + int set_weight = 0; + + if ((fit_policy::none != this->fit) && this->field) + { + std::size_t fit_count = 0; + + unsigned max_value = 0; + auto const fit_horz = (fit_policy::vert == this->fit); + + std::size_t pos = 0; + + for (auto & elm : this->field->elements) + { + ++pos; + + unsigned edge_px = 0; + if (fit_policy::both != this->fit) + { + auto fit_val = this->fit_parameters.at(pos - 1); + if (fit_val.empty()) + continue; + + edge_px = fit_val.integer(); + } + + auto extent = API::content_extent(elm.handle, edge_px, fit_horz); + if (extent) + { + run_.fit_extents[elm.handle] = extent->second; + ++fit_count; + if (vert_fields) + floor.second += extent->second.height; + else + floor.first += extent->second.width; + + max_value = (std::max)(max_value, (vert_fields ? extent->second.width : extent->second.height)); + + if (0 == set_weight) + set_weight = 1; + } + else + set_weight = -1; + } + + if (max_value) + { + if (vert_fields) + floor.first = max_value; + else + floor.second = max_value; + } + + //reverse deduction with gap + if (fit_count > 1) + { + double percent = 0; + for (std::size_t i = 0; i < fit_count - 1; ++i) + { + auto gap_value = gap.at(i); + if (gap_value.kind_of() == number_t::kind::percent) + { + percent += gap_value.real(); + } + else + { + double precise_px = 0; + fv += calc_number(gap_value, 100, 0, precise_px); + } + } + + fv *= (1 + percent); + } + + //reverse deduction with margin + double margin_per = _m_extend_with_margin(0, floor.second); + margin_per += _m_extend_with_margin(2, floor.second); + + if (margin_per < 1) + floor.second /= (1 - margin_per); + + margin_per = _m_extend_with_margin(1, floor.first); + margin_per += _m_extend_with_margin(3, floor.first); + if (margin_per < 1) + floor.first /= (1 - margin_per); + } + + if (!this->weight.empty()) + { + if (!this->is_percent()) + { + //Cancel to set weight + if (fv <= this->weight.real()) + set_weight = -1; + } + } + + if (1 == set_weight) + this->weight.assign(static_cast(fv)); + + run_.weight_floor = floor; + + } + + return floor; + } + void set_visible(bool vsb) { if (field) @@ -674,26 +973,39 @@ namespace nana } } - bool is_back(const division* div) const + bool is_back(const division* div) const noexcept { - for (auto i = children.crbegin(); i != children.crend(); ++i) + const division * last = nullptr; + for (auto & p : children) { - if (!(i->get()->display)) + if (!(p->display)) continue; - return (div == i->get()); + last = p.get(); } - return false; + + return (div == last); } - static double limit_px(const division* div, double px, unsigned area_px) + static double limit_px(const division* div, double px, unsigned area_px) noexcept { + auto const vert = (div->div_owner && (div->div_owner->kind_of_division == kind::vertical_arrange)); + + auto weight_floor = (vert? div->run_.weight_floor.second : div->run_.weight_floor.first); + if (!div->min_px.empty()) { auto v = div->min_px.get_value(static_cast(area_px)); + + if ((weight_floor > 0) && (v < weight_floor)) + v = weight_floor; + if (px < v) return v; } + else if ((weight_floor > 0) && (px < weight_floor)) + return weight_floor; + if (!div->max_px.empty()) { auto v = div->max_px.get_value(static_cast(area_px)); @@ -718,7 +1030,7 @@ namespace nana return margin.area(field_area); } - division * previous() const + division * previous() const noexcept { if (div_owner) { @@ -727,10 +1039,22 @@ namespace nana return child.get(); } return nullptr; - } public: - void _m_visible_for_child(division * div, bool vsb) + double _m_extend_with_margin(std::size_t edge, double & extended) + { + auto margin_edge = margin.get_edge(edge); + if (!margin_edge.empty()) + { + if (margin_edge.kind_of() != number_t::kind::percent) + extended += margin_edge.real(); + else + return margin_edge.real(); + } + return 0; + } + + static void _m_visible_for_child(division * div, bool vsb) noexcept { for (auto & child : div->children) { @@ -747,6 +1071,8 @@ namespace nana kind kind_of_division; bool display{ true }; bool visible{ true }; + fit_policy fit{ fit_policy::none }; + repeated_array fit_parameters; //it is ignored when fit is not fit_policy::horz or fit_policy::vert ::nana::direction dir{::nana::direction::west}; std::string name; std::vector> children; @@ -760,13 +1086,19 @@ namespace nana field_gather * field{ nullptr }; division * div_next{ nullptr }; division * div_owner{ nullptr }; + + struct run_data + { + std::pair weight_floor; + std::map fit_extents; + }run_; };//end class division class place::implement::div_arrange : public division { public: - div_arrange(bool vert, std::string&& name, place_parts::repeated_array&& arr) + div_arrange(bool vert, std::string&& name, place_parts::repeated_array&& arr) noexcept : division((vert ? kind::vertical_arrange : kind::arrange), std::move(name)), arrange_(std::move(arr)) {} @@ -806,12 +1138,15 @@ namespace nana child_px = adjustable_px; child_px = limit_px(child, child_px, area_px); + auto npx = static_cast(child_px); precise_px = child_px - npx; child_px = npx; } else + { child_px = static_cast(child->weight.integer()); + } //Use 'endpos' to calc width is to avoid deviation int endpos = static_cast(position + child_px); @@ -841,13 +1176,39 @@ namespace nana for (auto & el : field->elements) { element_r.x_ref() = static_cast(position); - auto px = _m_calc_number(arrange_.at(index), area_px, adjustable_px, precise_px); - element_r.w_ref() = px; - API::move_window(el.handle, element_r.result()); + bool moved = false; + unsigned px = 0; + + auto move_r = element_r.result(); + if (fit_policy::none != this->fit) + { + auto i = run_.fit_extents.find(el.handle); + if (run_.fit_extents.end() != i) + { + move_r.dimension(i->second); + + if (vert) + move_r.x += place_parts::differ(area_margined.width, move_r.width) / 2; + else + move_r.y += place_parts::differ(area_margined.height, move_r.height) / 2; + + px = (vert ? move_r.height : move_r.width); + moved = true; + } + } + + if (!moved) + { + px = calc_number(arrange_.at(index), area_px, adjustable_px, precise_px); + element_r.w_ref() = px; + move_r = element_r.result(); + } + + API::move_window(el.handle, move_r); if (index + 1 < field->elements.size()) - position += (px + _m_calc_number(gap.at(index), area_px, 0, precise_px)); + position += (px + calc_number(gap.at(index), area_px, 0, precise_px)); ++index; } @@ -857,28 +1218,6 @@ namespace nana } } private: - static unsigned _m_calc_number(const place_parts::number_t& number, unsigned area_px, double adjustable_px, double& precise_px) - { - switch (number.kind_of()) - { - case number_t::kind::integer: - return static_cast(number.integer()); - case number_t::kind::real: - return static_cast(number.real()); - case number_t::kind::percent: - adjustable_px = area_px * number.real(); - case number_t::kind::none: - { - auto fpx = adjustable_px + precise_px; - auto px = static_cast(fpx); - precise_px = fpx - px; - return px; - } - break; - } - return 0; //Useless - } - static std::pair _m_calc_fa(const place_parts::number_t& number, unsigned area_px, double& precise_px) { std::pair result; @@ -906,17 +1245,31 @@ namespace nana } //Returns the fixed pixels and the number of adjustable items. - std::pair _m_fixed_and_adjustable(kind match_kind, unsigned area_px) const + std::pair _m_fixed_and_adjustable(kind match_kind, unsigned area_px) const noexcept { std::pair result; if (field && (kind_of_division == match_kind)) { + auto const vert = (kind_of_division == kind::vertical_arrange); + //Calculate fixed and adjustable of elements double precise_px = 0; auto count = field->elements.size(); for (decltype(count) i = 0; i < count; ++i) { auto fa = _m_calc_fa(arrange_.at(i), area_px, precise_px); + + //The fit-content element is like a fixed element + if (fit_policy::none != this->fit) + { + auto fi = this->run_.fit_extents.find(field->elements[i].handle); + if (this->run_.fit_extents.cend() != fi) + { + fa.first = (vert ? fi->second.height : fi->second.width); + fa.second = 0; //This isn't an adjustable element + } + } + result.first += fa.first; result.second += fa.second; @@ -944,6 +1297,49 @@ namespace nana return result; } + struct revised_division + { + division * div; + double min_px; + double max_px; + }; + + static double _m_find_lowest_revised_division(const std::vector& revises, double level_px) noexcept + { + double v = (std::numeric_limits::max)(); + + for(auto & rev : revises) + { + if (rev.min_px >= 0 && rev.min_px < v && rev.min_px > level_px) + v = rev.min_px; + else if (rev.max_px >= 0 && rev.max_px < v) + v = rev.max_px; + } + return v; + } + + static std::size_t _m_remove_revised(std::vector& revises, double value, std::size_t& full_count) noexcept + { + full_count = 0; + std::size_t reached_mins = 0; + auto i = revises.begin(); + while (i != revises.end()) + { + if (i->max_px == value) + { + ++full_count; + i = revises.erase(i); + } + else + { + if (i->min_px == value) + ++reached_mins; + ++i; + } + } + return reached_mins; + } + double _m_revise_adjustable(std::pair& fa, unsigned area_px) { if (fa.first >= area_px || 0 == fa.second) @@ -951,28 +1347,28 @@ namespace nana double var_px = area_px - fa.first; - struct revise_t - { - division * div; - double min_px; - double max_px; - }; - std::size_t min_count = 0; double sum_min_px = 0; - std::vector revises; + std::vector revises; + for (auto& child : children) { - if ((!child->weight.empty()) || !child->display) + if ((!child->weight.empty()) || (!child->display)) continue; double min_px = std::numeric_limits::lowest(), max_px = std::numeric_limits::lowest(); if (!child->min_px.empty()) - { min_px = child->min_px.get_value(static_cast(area_px)); + + auto weight_floor = (this->kind_of_division == kind::arrange ? child->run_.weight_floor.first : child->run_.weight_floor.second); + if ((weight_floor > 0) && (min_px < weight_floor)) + min_px = weight_floor; + + if(!child->min_px.empty() || (weight_floor > 0)) + { sum_min_px += min_px; - ++min_count; + ++min_count; } if (!child->max_px.empty()) @@ -980,7 +1376,9 @@ namespace nana if (min_px >= 0 && max_px >= 0 && min_px > max_px) { - if (child->min_px.kind_of() == number_t::kind::percent) + if(weight_floor > 0) + max_px = min_px; + else if (child->min_px.kind_of() == number_t::kind::percent) min_px = std::numeric_limits::lowest(); else if (child->max_px.kind_of() == number_t::kind::percent) max_px = std::numeric_limits::lowest(); @@ -993,43 +1391,6 @@ namespace nana if (revises.empty()) return var_px / fa.second; - auto find_lowest = [&revises](double level_px) - { - double v = (std::numeric_limits::max)(); - - for (auto i = revises.begin(); i != revises.end(); ++i) - { - auto & rev = *i; - if (rev.min_px >= 0 && rev.min_px < v && rev.min_px > level_px) - v = rev.min_px; - else if (rev.max_px >= 0 && rev.max_px < v) - v = rev.max_px; - } - return v; - }; - - auto remove_full = [&revises](double value, std::size_t& full_count) - { - full_count = 0; - std::size_t reached_mins = 0; - auto i = revises.begin(); - while(i != revises.end()) - { - if (i->max_px == value) - { - ++full_count; - i = revises.erase(i); - } - else - { - if (i->min_px == value) - ++reached_mins; - ++i; - } - } - return reached_mins; - }; - double block_px = 0; double level_px = 0; auto rest_px = var_px - sum_min_px; @@ -1037,7 +1398,8 @@ namespace nana while ((rest_px > 0) && blocks) { - auto lowest = find_lowest(level_px); + auto lowest = _m_find_lowest_revised_division(revises, level_px); + double fill_px = 0; //blocks may be equal to min_count. E.g, all child divisions have min/max attribute. if (blocks > min_count) @@ -1055,7 +1417,7 @@ namespace nana rest_px -= (lowest-level_px) * (blocks - min_count); std::size_t full_count; - min_count -= remove_full(lowest, full_count); + min_count -= _m_remove_revised(revises, lowest, full_count); blocks -= full_count; level_px = lowest; } @@ -1070,7 +1432,7 @@ namespace nana : public division { public: - div_grid(std::string&& name, place_parts::repeated_array&& arrange, std::vector&& collapses) + div_grid(std::string&& name, place_parts::repeated_array&& arrange, std::vector&& collapses) noexcept : division(kind::grid, std::move(name)), arrange_(std::move(arrange)), collapses_(std::move(collapses)) @@ -1078,7 +1440,7 @@ namespace nana dimension.first = dimension.second = 0; } - void revise_collapses() + void revise_collapses() noexcept { if (collapses_.empty()) return; @@ -1120,163 +1482,134 @@ namespace nana if (!field || !(visible && display)) return; - auto area = margin_area(); + auto const area = margin_area(); + auto const gap_size = static_cast(gap.at(0).get_value(area.width)); //gap_size is 0 if gap isn't specified - unsigned gap_size = 0; - auto gap_number = gap.at(0); - if (!gap_number.empty()) - gap_size = static_cast(gap_number.get_value(area.width)); + auto i = field->elements.cbegin(); + auto const end = field->elements.cend(); - //When the amount pixels of gaps is out of the area bound. - if ((gap_size * dimension.first >= area.width) || (gap_size * dimension.second >= area.height)) + //The total gaps must not beyond the bound of the area. + if ((gap_size * dimension.first < area.width) && (gap_size * dimension.second < area.height)) { - for (auto & el : field->elements) - API::window_size(el.handle, size{ 0, 0 }); - return; - } - - if (dimension.first <= 1 && dimension.second <= 1) - { - auto n_of_wd = field->elements.size(); - std::size_t edge; - switch (n_of_wd) + if (dimension.first <= 1 && dimension.second <= 1) { - case 0: - case 1: - edge = 1; break; - case 2: case 3: case 4: - edge = 2; break; - default: - edge = static_cast(std::sqrt(n_of_wd)); - if ((edge * edge) < n_of_wd) ++edge; - } - - bool exit_for = false; - double y = area.y; - double block_w = area.width / double(edge); - double block_h = area.height / double((n_of_wd / edge) + (n_of_wd % edge ? 1 : 0)); - unsigned uns_block_w = static_cast(block_w); - unsigned uns_block_h = static_cast(block_h); - unsigned height = (uns_block_h > gap_size ? uns_block_h - gap_size : uns_block_h); - - auto i = field->elements.cbegin(), end = field->elements.cend(); - std::size_t arr_pos = 0; - for (std::size_t u = 0; u < edge; ++u) - { - double x = area.x; - for (std::size_t v = 0; v < edge; ++v) + auto n_of_wd = field->elements.size(); + std::size_t edge; + switch (n_of_wd) { - if (i == end) - { - exit_for = true; - break; - } - - unsigned value = 0; - auto arr = arrange_.at(arr_pos++); - - if (arr.empty()) - value = static_cast(block_w); - else - value = static_cast(arr.get_value(static_cast(area.width))); - - unsigned width = (value > uns_block_w ? uns_block_w : value); - if (width > gap_size) width -= gap_size; - API::move_window(i->handle, rectangle{ static_cast(x), static_cast(y), width, height }); - x += block_w; - ++i; - } - if (exit_for) break; - y += block_h; - } - } - else - { - double block_w = int(area.width - gap_size * (dimension.first - 1)) / double(dimension.first); - double block_h = int(area.height - gap_size * (dimension.second - 1)) / double(dimension.second); - - std::unique_ptr table_ptr(new char[dimension.first * dimension.second]); - - char *table = table_ptr.get(); - std::memset(table, 0, dimension.first * dimension.second); - - std::size_t lbp = 0; - bool exit_for = false; - - auto i = field->elements.cbegin(), end = field->elements.cend(); - - double precise_h = 0; - for (std::size_t c = 0; c < dimension.second; ++c) - { - unsigned block_height_px = static_cast(block_h + precise_h); - precise_h = (block_h + precise_h) - block_height_px; - - double precise_w = 0; - for (std::size_t l = 0; l < dimension.first; ++l) - { - if (table[l + lbp]) - { - precise_w += block_w; - auto px = static_cast(precise_w); - precise_w -= px; - continue; - } - - if (i == end) - { - exit_for = true; - break; - } - - std::pair room{ 1, 1 }; - - _m_find_collapse(static_cast(l), static_cast(c), room); - - int pos_x = area.x + static_cast(l * (block_w + gap_size)); - int pos_y = area.y + static_cast(c * (block_h + gap_size)); - - unsigned result_h; - if (room.first <= 1 && room.second <= 1) - { - precise_w += block_w; - result_h = block_height_px; - table[l + lbp] = 1; - } - else - { - precise_w += block_w * room.first + (room.first - 1) * gap_size; - result_h = static_cast(block_h * room.second + precise_h + (room.second - 1) * gap_size); - - for (unsigned y = 0; y < room.second; ++y) - for (unsigned x = 0; x < room.first; ++x) - table[l + x + lbp + y * dimension.first] = 1; - } - - unsigned result_w = static_cast(precise_w); - precise_w -= result_w; - - API::move_window(i->handle, rectangle{ pos_x, pos_y, result_w, result_h }); - ++i; + case 0: + case 1: + edge = 1; break; + case 2: case 3: case 4: + edge = 2; break; + default: + edge = static_cast(std::sqrt(n_of_wd)); + if ((edge * edge) < n_of_wd) ++edge; } - if (exit_for) + double y = area.y; + const double block_w = area.width / double(edge); + const double block_h = area.height / double((n_of_wd / edge) + (n_of_wd % edge ? 1 : 0)); + const unsigned uns_block_w = static_cast(block_w); + const unsigned uns_block_h = static_cast(block_h); + const unsigned height = (uns_block_h > gap_size ? uns_block_h - gap_size : uns_block_h); + + std::size_t arr_pos = 0; + for (std::size_t u = 0; (u < edge && i != end); ++u) { - size empty_sz; - for (; i != end; ++i) - API::window_size(i->handle, empty_sz); - break; + double x = area.x; + for (std::size_t v = 0; (v < edge && i != end); ++v, ++i) + { + unsigned value = 0; + auto arr = arrange_.at(arr_pos++); + + if (arr.empty()) + value = static_cast(block_w); + else + value = static_cast(arr.get_value(static_cast(area.width))); + + unsigned width = (value > uns_block_w ? uns_block_w : value); + if (width > gap_size) width -= gap_size; + API::move_window(i->handle, rectangle{ static_cast(x), static_cast(y), width, height }); + x += block_w; + } + y += block_h; + } + } + else + { + const double block_w = int(area.width - gap_size * (dimension.first - 1)) / double(dimension.first); + const double block_h = int(area.height - gap_size * (dimension.second - 1)) / double(dimension.second); + + std::unique_ptr table_ptr{ new char[dimension.first * dimension.second] }; + char *table = table_ptr.get(); + std::memset(table, 0, dimension.first * dimension.second); + + std::size_t lbp = 0; + + double precise_h = 0; + for (std::size_t u = 0; (u < dimension.second && i != end); ++u) + { + auto const block_height_px = static_cast(block_h + precise_h); + precise_h = (block_h + precise_h) - block_height_px; + + double precise_w = 0; + for (std::size_t v = 0; (v < dimension.first && i != end); ++v) + { + auto const epos = v + lbp; + if (table[epos]) + { + precise_w += block_w; + precise_w -= static_cast(precise_w); + continue; + } + + std::pair room{ 1, 1 }; + _m_find_collapse(static_cast(v), static_cast(u), room); + + const int pos_x = area.x + static_cast(v * (block_w + gap_size)); + const int pos_y = area.y + static_cast(u * (block_h + gap_size)); + + unsigned result_h; + if (room.first <= 1 && room.second <= 1) + { + precise_w += block_w; + result_h = block_height_px; + table[epos] = 1; + } + else + { + precise_w += block_w * room.first + (room.first - 1) * gap_size; + result_h = static_cast(block_h * room.second + precise_h + (room.second - 1) * gap_size); + + for (unsigned y = 0; y < room.second; ++y) + for (unsigned x = 0; x < room.first; ++x) + table[epos + x + y * dimension.first] = 1; + } + + unsigned result_w = static_cast(precise_w); + precise_w -= result_w; + + API::move_window(i->handle, rectangle{ pos_x, pos_y, result_w, result_h }); + ++i; + } + + lbp += dimension.first; } - lbp += dimension.first; } } + // Empty the size of windows that are out range of grid + for (; i != end; ++i) + API::window_size(i->handle, size{}); + for (auto & fsn : field->fastened) API::move_window(fsn.handle, area); } public: std::pair dimension; private: - void _m_find_collapse(int x, int y, std::pair& collapse) const + void _m_find_collapse(int x, int y, std::pair& collapse) const noexcept { for (auto & col : collapses_) { @@ -1302,21 +1635,37 @@ namespace nana int pixels; double scale; - div_block(division* d, int px) + div_block(division* d, int px) noexcept : div(d), pixels(px) {} }; enum{splitter_px = 4}; public: - div_splitter(place_parts::number_t init_weight) - : division(kind::splitter, std::string()), + div_splitter(const place_parts::number_t & init_weight, implement* impl) noexcept : + division(kind::splitter, std::string()), + impl_(impl), init_weight_(init_weight) { + impl->splitters.insert(this); + this->splitter_.set_renderer(impl_->split_renderer); + this->weight.assign(splitter_px); } - void direction(bool horizontal) + ~div_splitter() + { + impl_->splitters.erase(this); + } + + void set_renderer(const std::function & fn, bool update) + { + this->splitter_.set_renderer(fn); + if (update && this->splitter_.handle()) + API::refresh_window(this->splitter_); + } + + void direction(bool horizontal) noexcept { splitter_cursor_ = (horizontal ? cursor::size_we : cursor::size_ns); } @@ -1335,9 +1684,12 @@ namespace nana auto grab_fn = [this](const arg_mouse& arg) { - if (false == arg.left_button) + if ((false == arg.left_button) && (mouse::left_button != arg.button)) return; + auto const leaf_left = _m_leaf(true); + auto const leaf_right = _m_leaf(false); + if (event_code::mouse_down == arg.evt_code) { begin_point_ = splitter_.pos(); @@ -1345,8 +1697,8 @@ namespace nana auto px_ptr = &nana::rectangle::width; //Use field_area of leaf, not margin_area. Otherwise splitter would be at wrong position - auto area_left = _m_leaf_left()->field_area; - auto area_right = _m_leaf_right()->field_area; + auto const area_left = leaf_left->field_area; + auto const area_right = leaf_right->field_area; if (nana::cursor::size_we != splitter_cursor_) { @@ -1368,43 +1720,36 @@ namespace nana else if(event_code::mouse_up == arg.evt_code) { grabbed_ = false; + this->_m_update_div(impl_->div_text); } else if (event_code::mouse_move == arg.evt_code) { if(!grabbed_) return; - const bool vert = (::nana::cursor::size_we != splitter_cursor_); + auto const vert = (::nana::cursor::size_we != splitter_cursor_); + auto const delta = horz_point(vert, splitter_.pos() - begin_point_); + + const auto total_pixels = static_cast(left_pixels_ + right_pixels_); + + auto left_px = std::clamp(static_cast(left_pixels_) + delta, 0, total_pixels); + auto area_px = rectangle_rotator(vert, div_owner->margin_area()).w(); - int delta = (vert ? splitter_.pos().y - begin_point_.y : splitter_.pos().x - begin_point_.x); - - int total_pixels = static_cast(left_pixels_ + right_pixels_); - - auto left_px = static_cast(left_pixels_) + delta; - if (left_px > total_pixels) - left_px = total_pixels; - else if (left_px < 0) - left_px = 0; - double imd_rate = 100.0 / area_px; - left_px = static_cast(limit_px(_m_leaf_left(), left_px, area_px)); - _m_leaf_left()->weight.assign_percent(imd_rate * left_px); + left_px = static_cast(limit_px(leaf_left, left_px, area_px)); + leaf_left->weight.assign_percent(imd_rate * left_px); - auto right_px = static_cast(right_pixels_) - delta; - if (right_px > total_pixels) - right_px = total_pixels; - else if (right_px < 0) - right_px = 0; + auto right_px = std::clamp(static_cast(right_pixels_) - delta, 0, total_pixels); - right_px = static_cast(limit_px(_m_leaf_right(), right_px, area_px)); - _m_leaf_right()->weight.assign_percent(imd_rate * right_px); + right_px = static_cast(limit_px(leaf_right, right_px, area_px)); + leaf_right->weight.assign_percent(imd_rate * right_px); pause_move_collocate_ = true; div_owner->collocate(splitter_.parent()); //After the collocating, the splitter keeps the calculated weight of left division, //and clear the weight of right division. - _m_leaf_right()->weight.reset(); + leaf_right->weight.reset(); pause_move_collocate_ = false; } @@ -1422,18 +1767,15 @@ namespace nana { const bool vert = (::nana::cursor::size_we != splitter_cursor_); - auto leaf_left = _m_leaf_left(); - auto leaf_right = _m_leaf_right(); + auto leaf_left = _m_leaf(true); + auto leaf_right = _m_leaf(false); rectangle_rotator left(vert, leaf_left->field_area); rectangle_rotator right(vert, leaf_right->field_area); auto area_px = right.right() - left.x(); auto right_px = static_cast(limit_px(leaf_right, init_weight_.get_value(area_px), static_cast(area_px))); - auto pos = area_px - right_px - splitter_px; //New position of splitter - if (pos < limited_range.x()) - pos = limited_range.x(); - else if (pos > limited_range.right()) - pos = limited_range.right(); + //New position of splitter + const auto pos = std::clamp(static_cast(area_px - right_px - splitter_px), limited_range.x(), limited_range.right()); rectangle_rotator sp_r(vert, field_area); sp_r.x_ref() = pos; @@ -1466,14 +1808,162 @@ namespace nana splitter_.move(this->field_area); } private: - division * _m_leaf_left() const + static int _m_search_name(const division* div, std::string& name) noexcept { - return previous(); + if (div->name.size()) + { + name = div->name; + return 0; + } + + for (auto & child : div->children) + { + if (child->name.size()) + { + name = child->name; + return 1; + } + + auto depth = _m_search_name(child.get(), name); + if (depth >= 0) + return depth + 1; + } + + return -1; } - division * _m_leaf_right() const + static void _m_remove_attr(std::string& div, const char* attr) { - return div_next; + auto attr_pos = div.find(attr); + if (div.npos == attr_pos) + return; + + std::size_t off = 1; + while (true) + { + auto pos = div.find('<', off); + if (div.npos == pos) + break; + + if (attr_pos < pos) + break; + + int depth = 0; + off = pos + 1; + std::size_t endpos = 0; + while (true) + { + endpos = div.find_first_of("<>", off); + if (div.npos == endpos) + return; + + if ('<' == div[endpos]) + { + ++depth; + off = endpos + 1; + continue; + } + + if (0 == depth) + break; + + --depth; + off = endpos + 1; + } + + if (attr_pos < endpos) + { + attr_pos = div.find(attr, endpos + 1); + if (div.npos == attr_pos) + return; + } + + off = endpos + 1; + } + + auto len = std::strlen(attr); + + auto endpos = div.find_first_not_of(" ", attr_pos + len); + if (div.npos != endpos && div[endpos] == '=') + { + endpos = div.find_first_not_of(" 0123456789.%", endpos + 1); + if (div.npos == endpos) + throw std::runtime_error("please report an issue if throws"); + } + else + endpos = attr_pos + len; + + div.erase(attr_pos, endpos - attr_pos); + } + + void _m_update_div(std::string& div) + { + auto const leaf_left = _m_leaf(true); + auto const leaf_right = _m_leaf(false); + + std::string name; + bool left = true; + + //Search a name recursively from a specified leaf field. + //It returns the depth from the leaf div to the div which has a name. + auto depth = _m_search_name(leaf_left, name); + if (-1 == depth) + { + left = false; + depth = _m_search_name(leaf_right, name); + if (-1 == depth) + return; + } + + //Get the bound of field div-text through reverse recursion. + auto bound = get_field_bound(div, name.c_str(), depth); + if (bound.first == bound.second) + return; + + auto fieldstr = div.substr(bound.first, bound.second - bound.first); + _m_remove_attr(fieldstr, "weight"); + + std::string::size_type tag_pos{ left ? div.find('<', bound.second + 1) : div.rfind('>', bound.first - 1) }; + if (div.npos == tag_pos) + throw std::runtime_error("place report an issue if it throws"); + + auto other_bound = get_field_bound(div, tag_pos); + + auto other_fieldstr = div.substr(other_bound.first, other_bound.second - other_bound.first); + _m_remove_attr(other_fieldstr, "weight"); + + const bool vert = (::nana::cursor::size_we != splitter_cursor_); + + rectangle_rotator r_left(vert, leaf_left->field_area); + rectangle_rotator r_right(vert, leaf_right->field_area); + rectangle_rotator r_owner(vert, this->div_owner->field_area); + + double percent = double((left ? r_left : r_right).w()) / double(r_owner.w()); + + std::string weight = "weight=" + std::to_string(percent * 100) + "% "; + + fieldstr.insert(1, weight); + + //Replaces the 'right' field before 'left' in order to make the bound consistent + if (left) + { + if (other_fieldstr.length() != (other_bound.second - other_bound.first)) + div.replace(other_bound.first, other_bound.second - other_bound.first, other_fieldstr); + + div.replace(bound.first, bound.second - bound.first, fieldstr); + } + else + { + div.replace(bound.first, bound.second - bound.first, fieldstr); + + if (other_fieldstr.length() != (other_bound.second - other_bound.first)) + div.replace(other_bound.first, other_bound.second - other_bound.first, other_fieldstr); + } + } + + division * _m_leaf(bool left) const noexcept + { + return (left ? previous() : div_next); } rectangle_rotator _m_update_splitter_range() @@ -1482,8 +1972,8 @@ namespace nana rectangle_rotator area(vert, div_owner->margin_area()); - auto leaf_left = _m_leaf_left(); - auto leaf_right = _m_leaf_right(); + auto leaf_left = _m_leaf(true); + auto leaf_right = _m_leaf(false); rectangle_rotator left(vert, leaf_left->field_area); rectangle_rotator right(vert, leaf_right->field_area); @@ -1493,30 +1983,16 @@ namespace nana int endpos = right_base; if (!leaf_left->min_px.empty()) - { - auto v = leaf_left->min_px.get_value(area.w()); - pos += static_cast(v); - } + pos += static_cast(leaf_left->min_px.get_value(area.w())); + if (!leaf_left->max_px.empty()) - { - auto v = leaf_left->max_px.get_value(area.w()); - endpos = left_base + static_cast(v); - } + endpos = left_base + static_cast(leaf_left->max_px.get_value(area.w())); if (!leaf_right->min_px.empty()) - { - auto v = leaf_right->min_px.get_value(area.w()); - auto x = right_base - static_cast(v); - if (x < endpos) - endpos = x; - } + endpos = (std::min)(right_base - static_cast(leaf_right->min_px.get_value(area.w())), endpos); + if (!leaf_right->max_px.empty()) - { - auto v = leaf_right->max_px.get_value(area.w()); - auto x = right_base - static_cast(v); - if (x > pos) - pos = x; - } + pos = (std::max)(right_base - static_cast(leaf_right->max_px.get_value(area.w())), pos); area.x_ref() = pos; area.w_ref() = unsigned(endpos - pos + splitter_px); @@ -1526,8 +2002,9 @@ namespace nana return area; } private: + implement* const impl_; nana::cursor splitter_cursor_{nana::cursor::arrow}; - place_parts::splitter splitter_; + place_parts::splitter splitter_; nana::point begin_point_; int left_pos_, right_pos_; unsigned left_pixels_, right_pixels_; @@ -1541,7 +2018,7 @@ namespace nana : public division, public place_parts::dock_notifier_interface { public: - div_dockpane(std::string && name, implement* impl, direction pane_dir) + div_dockpane(std::string && name, implement* impl, direction pane_dir) noexcept : division(kind::dockpane, std::move(name)), impl_ptr_{impl} { @@ -1549,7 +2026,7 @@ namespace nana this->display = false; } - ~div_dockpane() + ~div_dockpane() noexcept { if (dockable_field) { @@ -1595,7 +2072,7 @@ namespace nana void notify_move() override { - if (!_m_indicator()) + if (!_m_hit_test(false)) //hit test on indicator { indicator_.docker.reset(); return; @@ -1632,7 +2109,7 @@ namespace nana }); } - if (_m_dockable()) + if (_m_hit_test(true)) //hit test on docker { if (!indicator_.dock_area) { @@ -1687,12 +2164,12 @@ namespace nana indicator_.graph.release(); } } - } void notify_move_stopped() override { - if (_m_dockable() && dockable_field && dockable_field->dockarea) + //hit test on docker + if (_m_hit_test(true) && dockable_field && dockable_field->dockarea) dockable_field->dockarea->dock(); indicator_.docker.reset(); @@ -1715,25 +2192,22 @@ namespace nana API::close_window(window_handle); } private: - bool _m_indicator() const + bool _m_hit_test(bool try_docker) const { - ::nana::point pos; - API::calc_screen_point(impl_ptr_->window_handle, pos); + window handle = nullptr; + if (try_docker) + { + if (!indicator_.docker) + return false; - rectangle r{ pos, API::window_size(impl_ptr_->window_handle) }; - return r.is_hit(API::cursor_position()); - } + handle = indicator_.docker->handle(); //hit test for docker + } + else + handle = impl_ptr_->window_handle; //hit test for indicator - bool _m_dockable() const - { - if (!indicator_.docker) - return false; - - ::nana::point pos; - API::calc_screen_point(indicator_.docker->handle(), pos); - - rectangle r{ pos, API::window_size(indicator_.docker->handle()) }; - return r.is_hit(API::cursor_position()); + point pos; + API::calc_screen_point(handle, pos); + return rectangle{ pos, API::window_size(handle) }.is_hit(API::cursor_position()); } public: field_dock * dockable_field{ nullptr }; @@ -1763,25 +2237,21 @@ namespace nana : panel(wd, true), dir_(dir), dock_dv_(dock_dv), pane_dv_(pane_dv) { this->bgcolor(colors::alice_blue); - this->cursor(_m_is_vert(dir_) ? ::nana::cursor::size_ns : ::nana::cursor::size_we); - + this->cursor(is_vert_dir(dir_) ? ::nana::cursor::size_ns : ::nana::cursor::size_we); auto grab_fn = [this, wd](const arg_mouse& arg) { + auto const is_vert = is_vert_dir(dir_); + if (event_code::mouse_down == arg.evt_code) //press mouse button { if (arg.button != ::nana::mouse::left_button) return; - bool is_vert = _m_is_vert(dir_); - this->set_capture(true); - auto basepos = API::cursor_position(); - base_pos_.x = (is_vert ? basepos.y : basepos.x); - - basepos = this->pos(); - base_pos_.y = (is_vert ? basepos.y : basepos.x); + base_pos_.x = horz_point(is_vert, API::cursor_position()); + base_pos_.y = horz_point(is_vert, this->pos()); base_px_ = (is_vert ? pane_dv_->field_area.height : pane_dv_->field_area.width); } @@ -1790,8 +2260,7 @@ namespace nana if (!arg.is_left_button()) return; - auto now_pos = API::cursor_position(); - int delta = (_m_is_vert(dir_) ? now_pos.y : now_pos.x) - base_pos_.x; + auto delta = horz_point(is_vert, API::cursor_position()) - base_pos_.x; int new_pos = base_pos_.y + delta; if (new_pos < range_.x) { @@ -1804,8 +2273,8 @@ namespace nana delta = new_pos - base_pos_.y; } - now_pos = this->pos(); - if (_m_is_vert(dir_)) + auto now_pos = this->pos(); + if (is_vert) now_pos.y = new_pos; else now_pos.x = new_pos; @@ -1832,7 +2301,7 @@ namespace nana break; } - auto dock_px = (_m_is_vert(dir_) ? dock_dv_->field_area.height : dock_dv_->field_area.width); + auto dock_px = (is_vert ? dock_dv_->field_area.height : dock_dv_->field_area.width); pane_dv_->weight.assign_percent(double(px) / double(dock_px) * 100); @@ -1848,7 +2317,7 @@ namespace nana evt.mouse_move.connect(grab_fn); } - void range(int begin, int end) + void range(int begin, int end) noexcept { range_.x = begin; range_.y = end; @@ -1863,11 +2332,11 @@ namespace nana }; public: - div_dock(std::string && name, implement* impl) + div_dock(std::string && name, implement* impl) noexcept : division(kind::dock, std::move(name)), impl_(impl) {} - division* front() const + division* front() const noexcept { for (auto & child : children) { @@ -1885,14 +2354,14 @@ namespace nana unsigned vert_count = 0, horz_count = 0; bool is_first = true; - bool prev_attr; + bool prev_attr = false; for (auto & child : children) { if (!child->display) continue; - const auto is_vert = _m_is_vert(child->dir); + const auto is_vert = is_vert_dir(child->dir); if (is_first) { is_first = false; @@ -1932,7 +2401,7 @@ namespace nana } auto child_dv = dynamic_cast(child.get()); - const bool is_vert = _m_is_vert(child->dir); + const bool is_vert = is_vert_dir(child->dir); auto room_px = (is_vert ? room.height : room.width); @@ -1962,7 +2431,7 @@ namespace nana child_dv->splitter.reset(); ::nana::rectangle child_r; - double split_range_begin = -1, split_range_end; + double split_range_begin = -1, split_range_end = 0; switch (child->dir) { default: @@ -2043,11 +2512,6 @@ namespace nana } } private: - static bool _m_is_vert(::nana::direction dir) - { - return (dir == ::nana::direction::north || dir == ::nana::direction::south); - } - static div_dockpane* _m_right(division* dv) { dv = dv->div_next; @@ -2085,6 +2549,8 @@ namespace nana if (root_division->field_area.empty()) return; + root_division->calc_weight_floor(); + root_division->collocate(window_handle); for (auto & field : fields) @@ -2105,15 +2571,14 @@ namespace nana } } - for (auto & el : field.second->elements) - API::show_window(el.handle, is_show); + field.second->visible(is_show); } } } //search_div_name //search a division with the specified name. - place::implement::division * place::implement::search_div_name(division* start, const std::string& name) + place::implement::division * place::implement::search_div_name(division* start, const std::string& name) noexcept { if (start) { @@ -2130,12 +2595,27 @@ namespace nana return nullptr; } + static int get_parameter(place_parts::tokenizer& tknizer, std::size_t pos) + { + auto & arg = tknizer.parameters()[pos]; + + if (arg.kind_of() == number_t::kind::integer) + return arg.integer(); + else if (arg.kind_of() == number_t::kind::real) + return static_cast(arg.real()); + + const char* pos_strs[] = { "1st", "2nd", "3rd", "4th" }; + throw std::invalid_argument("nana.place: the type of the " + std::string{pos_strs[pos]} +"th parameter for collapse should be integer."); + } + auto place::implement::scan_div(place_parts::tokenizer& tknizer) -> std::unique_ptr { typedef place_parts::tokenizer::token token; std::unique_ptr div; token div_type = token::eof; + auto fit = fit_policy::none; + place_parts::repeated_array fit_parameters; //These variables stand for the new division's attributes std::string name; @@ -2147,22 +2627,32 @@ namespace nana std::vector> children; ::nana::direction div_dir = ::nana::direction::west; - for (token tk = tknizer.read(); tk != token::eof; tk = tknizer.read()) + bool undisplayed = false; + bool invisible = false; + + for (token tk = tknizer.read(); (tk != token::eof && tk != token::div_end); tk = tknizer.read()) { - bool exit_for = false; switch (tk) { case token::dock: if (token::eof != div_type && token::dock != div_type) - throw std::invalid_argument("nana.place: conflict of div type at " + tknizer.pos_str()); + throw std::invalid_argument("nana.place: conflict of div type at " + std::to_string(tknizer.pos())); div_type = token::dock; break; + case token::fit: + fit = fit_policy::both; + break; + case token::hfit: + case token::vfit: + fit = (token::hfit == tk ? fit_policy::horz : fit_policy::vert); + fit_parameters = tknizer.reparray(); + break; case token::splitter: //Ignore the splitter when there is not a division. if (!children.empty() && (division::kind::splitter != children.back()->kind_of_division)) { - auto splitter = new div_splitter(tknizer.number()); + auto splitter = new div_splitter(tknizer.number(), this); children.back()->div_next = splitter; children.emplace_back(std::unique_ptr{ splitter }); } @@ -2203,29 +2693,13 @@ namespace nana if (tknizer.parameters().size() != 4) throw std::invalid_argument("nana.place: collapse requires 4 parameters."); - auto get_number = [](const number_t & arg, const std::string& nth) - { - if (arg.kind_of() == number_t::kind::integer) - return arg.integer(); - else if (arg.kind_of() == number_t::kind::real) - return static_cast(arg.real()); - - throw std::invalid_argument("nana.place: the type of the "+ nth +" parameter for collapse should be integer."); + ::nana::rectangle col{ + get_parameter(tknizer, 0), + get_parameter(tknizer, 1), + static_cast(get_parameter(tknizer, 2)), + static_cast(get_parameter(tknizer, 3)) }; - ::nana::rectangle col; - auto arg = tknizer.parameters().at(0); - col.x = get_number(arg, "1st"); - - arg = tknizer.parameters().at(1); - col.y = get_number(arg, "2nd"); - - arg = tknizer.parameters().at(2); - col.width = static_cast(get_number(arg, "3rd")); - - arg = tknizer.parameters().at(3); - col.height = static_cast(get_number(arg, "4th")); - //Check the collapse area. //Ignore this collapse if its area is less than 2(col.width * col.height < 2) if (!col.empty() && (col.width > 1 || col.height > 1) && (col.x >= 0 && col.y >= 0)) @@ -2274,7 +2748,7 @@ namespace nana switch (tknizer.read()) { case token::number: - margin.set_value(tknizer.number()); + margin.push(tknizer.number(), true); break; case token::array: margin.set_array(tknizer.array()); @@ -2291,9 +2765,6 @@ namespace nana break; } break; - case token::div_end: - exit_for = true; - break; case token::identifier: name = tknizer.idstr(); break; @@ -2305,10 +2776,12 @@ namespace nana div_dir = ::nana::direction::north; break; case token::bottom: div_dir = ::nana::direction::south; break; + case token::undisplayed: + undisplayed = true; break; + case token::invisible: + invisible = true; break; default: break; } - if (exit_for) - break; } field_gather * attached_field = nullptr; @@ -2370,16 +2843,20 @@ namespace nana max_px.reset(); } - //The weight will be ignored if one of min and max is specified. - if (min_px.empty() && max_px.empty()) - { - div->weight = weight; - } - else - { + if (!min_px.empty()) div->min_px = min_px; + + if (!max_px.empty()) div->max_px = max_px; - } + + if ((!min_px.empty()) && (!weight.empty()) && (weight.get_value(100) < min_px.get_value(100))) + weight.reset(); + + if ((!max_px.empty()) && (!weight.empty()) && (weight.get_value(100) > max_px.get_value(100))) + weight.reset(); + + if (!weight.empty()) + div->weight = weight; div->gap = std::move(gap); @@ -2417,10 +2894,10 @@ namespace nana } division * next = nullptr; - for (auto i = adjusted_children.rbegin(); i != adjusted_children.rend(); ++i) + for (int i = static_cast(adjusted_children.size()) - 1; i >= 0; --i) { - i->get()->div_next = next; - next = i->get(); + adjusted_children[i]->div_next = next; + next = adjusted_children[i].get(); } children.swap(adjusted_children); @@ -2430,6 +2907,12 @@ namespace nana div->children.swap(children); div->margin = std::move(margin); div->dir = div_dir; + + div->display = !undisplayed; + div->visible = !(undisplayed || invisible); + div->fit = fit; + div->fit_parameters = std::move(fit_parameters); + return div; } @@ -2468,6 +2951,8 @@ namespace nana if (!start) return; + check_unique(start); //may throw if there is a redefined name of field. + this->disconnect(); std::map docks_to_be_closed; @@ -2489,20 +2974,20 @@ namespace nana if (i != docks.end()) { docks_to_be_closed.erase(div->name); - auto pane = dynamic_cast(div); - pane->dockable_field = i->second; - auto old_pane = pane->dockable_field->attached; - if (old_pane) + auto const pane = dynamic_cast(div); + auto fd_dock = pane->dockable_field; + fd_dock = i->second; + + if (fd_dock->attached) { - //old div_dockpane will be deleted - old_pane->dockable_field = nullptr; - div->display = old_pane->display; + fd_dock->attached->dockable_field = nullptr; + div->display = fd_dock->attached->display; } - pane->dockable_field->attached = pane; - if (pane->dockable_field->dockarea) - pane->dockable_field->dockarea->set_notifier(pane); + fd_dock->attached = pane; + if (fd_dock->dockarea) + fd_dock->dockarea->set_notifier(pane); } } else @@ -2533,7 +3018,7 @@ namespace nana } } - void place::implement::disconnect() + void place::implement::disconnect() noexcept { for (auto & fd : fields) { @@ -2572,6 +3057,7 @@ namespace nana if (impl_->root_division) { impl_->root_division->field_area.dimension({ arg.width, arg.height }); + impl_->root_division->calc_weight_floor(); impl_->root_division->collocate(arg.window_handle); } }); @@ -2582,6 +3068,14 @@ namespace nana return impl_->window_handle; } + void place::splitter_renderer(std::function fn) + { + impl_->split_renderer.swap(fn); + + for (auto sp : impl_->splitters) + sp->set_renderer(impl_->split_renderer, true); + } + void place::div(const char* s) { place_parts::tokenizer tknizer(s); @@ -2589,10 +3083,10 @@ namespace nana auto div = impl_->scan_div(tknizer); try { - impl_->check_unique(div.get()); //may throw if there is a redefined name of field. - impl_->connect(div.get()); + impl_->connect(div.get()); //throws if there is a redefined name of field. impl_->root_division.reset(); //clear atachments div-fields impl_->root_division.swap(div); + impl_->div_text.assign(s); } catch (...) { @@ -2601,6 +3095,16 @@ namespace nana } } + const std::string& place::div() const noexcept + { + return impl_->div_text; + } + + //Contributed by dankan1890(PR#156) + enum class update_operation { erase = 0, insert, replace }; + + void update_div(std::string& div, const char* field, const char* attr, update_operation operation); + void place::modify(const char* name, const char* div_text) { if (nullptr == div_text) @@ -2622,7 +3126,6 @@ namespace nana throw std::invalid_argument(what); } - std::unique_ptr* replaced = nullptr; implement::division * div_owner = div_ptr->div_owner; @@ -2652,9 +3155,9 @@ namespace nana replaced->swap(modified); - impl_->check_unique(impl_->root_division.get()); - impl_->connect(impl_->root_division.get()); + impl_->connect(impl_->root_division.get()); //throws if there is a duplicate name impl_->tmp_replaced.reset(); + update_div(impl_->div_text, name, div_text, update_operation::replace); modified_ptr->div_owner = div_owner; modified_ptr->div_next = div_next; @@ -2698,6 +3201,71 @@ namespace nana return *p; } + void update_div(std::string& div, const char* field, const char* attr, update_operation operation) + { + const auto fieldname_pos = find_idstr(div, field); + if (div.npos == fieldname_pos) + return; + + auto bound = get_field_bound(div, field, 0); + + auto fieldstr = div.substr(bound.first + 1, bound.second - bound.first - 2); + //Search the attribute + std::size_t pos = 0; + int depth = 0; + for (; true; ++pos) + { + pos = find_idstr(fieldstr, attr, pos); + if (fieldstr.npos == pos) + break; + + //Check if the attr is belong to this field. + depth = 0; + std::size_t off = pos; + while (true) + { + off = fieldstr.find_last_of("<>", off); + if (fieldstr.npos == off) + break; + + if ('>' == fieldstr[off]) + ++depth; + else + --depth; + + if (0 == off) + break; + --off; + } + + if (0 == depth) + break; + } + + if (fieldstr.npos == pos) + { + //There is not an attribute + if (operation == update_operation::insert) + div.insert(fieldname_pos + std::strlen(field), " " + std::string(attr)); + else if (operation == update_operation::replace) + { + div.erase(bound.first + 1, fieldstr.length()); + div.insert(bound.first + 1, std::string(attr) + " " + std::string(field)); + } + } + else + { + //There is an attribute + if (operation == update_operation::erase) + { + div.erase(bound.first + pos + 1, std::strlen(attr)); + + if ((div[bound.first + pos] == div[bound.first + pos + 1]) && (' ' == div[bound.first + pos])) + div.erase(bound.first + pos, 1); + } + } + } + void place::field_visible(const char* name, bool vsb) { if (!name) name = ""; @@ -2707,7 +3275,10 @@ namespace nana auto div = impl_->search_div_name(impl_->root_division.get(), name); if (div) + { div->set_visible(vsb); + update_div(impl_->div_text, name, "invisible", !vsb ? update_operation::insert : update_operation::erase); + } } bool place::field_visible(const char* name) const @@ -2730,7 +3301,11 @@ namespace nana auto div = impl_->search_div_name(impl_->root_division.get(), name); if (div) + { + update_div(impl_->div_text, name, "invisible", update_operation::erase); + update_div(impl_->div_text, name, "undisplayed", !dsp ? update_operation::insert : update_operation::erase); div->set_display(dsp); + } } bool place::field_display(const char* name) const @@ -2839,7 +3414,7 @@ namespace nana dock_ptr->dockarea->move(dock_ptr->attached->field_area); } - return dock_ptr->dockarea->add_pane(i->second->factories[factory]); + return dock_ptr->dockarea->add_pane(dock_ptr->factories[factory]); } return nullptr; diff --git a/source/gui/place_parts.hpp b/source/gui/place_parts.hpp index 2bd29c1d..0b4f4e23 100644 --- a/source/gui/place_parts.hpp +++ b/source/gui/place_parts.hpp @@ -1,7 +1,7 @@ /* * Parts of Class Place * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -29,21 +29,77 @@ namespace nana virtual ~splitter_interface(){} }; - class splitter_dtrigger + class drawer_splitter : public drawer_trigger { - }; + public: + void set_renderer(const std::function& rd) + { + renderer_ = rd; + } + private: + void attached(widget_reference wdg, graph_reference) override + { + window_handle_ = wdg; + } - template + void refresh(graph_reference graph) override + { + API::dev::copy_transparent_background(window_handle_, graph); + if (renderer_) + renderer_(window_handle_, graph, API::mouse_action(window_handle_)); + } + + void mouse_enter(graph_reference graph, const arg_mouse&) override + { + refresh(graph); + API::dev::lazy_refresh(); + } + + void mouse_move(graph_reference graph, const arg_mouse&) override + { + refresh(graph); + API::dev::lazy_refresh(); + } + + void mouse_leave(graph_reference graph, const arg_mouse&) override + { + refresh(graph); + API::dev::lazy_refresh(); + } + + void mouse_down(graph_reference graph, const arg_mouse&) + { + refresh(graph); + API::dev::lazy_refresh(); + } + + void mouse_up(graph_reference graph, const arg_mouse&) + { + refresh(graph); + API::dev::lazy_refresh(); + } + private: + window window_handle_{nullptr}; + std::function renderer_; + }; + class splitter - : public widget_object ::type, splitter_dtrigger>, + : public widget_object, public splitter_interface { + public: + void set_renderer(const std::function& rd) + { + get_drawer_trigger().set_renderer(rd); + } private: void _m_complete_creation() override { this->caption("place-splitter"); - widget_object ::type, splitter_dtrigger>::_m_complete_creation(); + widget_object::_m_complete_creation(); + + API::effects_bground(*this, effects::bground_transparent(0), 0); } }; @@ -92,8 +148,8 @@ namespace nana { color xclr = colors::red; - if(x_state_ == ::nana::mouse_action::pressed) - xclr = xclr.blend(colors::white, 0.8); + if(x_state_ == ::nana::mouse_action::pressed) + xclr = xclr.blend(colors::white, 0.2); graph.rectangle(r, true, xclr); } @@ -144,11 +200,8 @@ namespace nana private: ::nana::rectangle _m_button_area() const { - ::nana::rectangle r{API::window_size(window_handle_)}; - - r.x = r.right() - 20; - r.width = 20; - return r; + auto sz = API::window_size(window_handle_); + return{static_cast(sz.width) - 20, 0, 20, sz.height}; } public: window window_handle_; @@ -161,12 +214,17 @@ namespace nana }; class dockarea_caption - : public widget_object < category::widget_tag, dockcaption_dtrigger > + : public widget_object { public: using widget_object::get_drawer_trigger; }; + static unsigned differ(unsigned x, unsigned y) noexcept + { + return (x > y ? x - y : 0); + } + class dockarea : public widget_object { @@ -183,6 +241,7 @@ namespace nana { notifier_ = notifier; } + void create(window parent) { host_window_ = parent; @@ -191,17 +250,14 @@ namespace nana caption_.create(*this, true); caption_.get_drawer_trigger().on_close([this] { - bool destroy_dockarea = true; - if (tabbar_) { tabbar_->erase(tabbar_->selected()); - - destroy_dockarea = (0 == tabbar_->length()); + if (tabbar_->length()) + return; } - if (destroy_dockarea) - notifier_->request_close(); + notifier_->request_close(); }); this->events().resized.connect([this](const arg_resized& arg) @@ -331,14 +387,11 @@ namespace nana private: widget* _m_add_pane(factory & fn) { - rectangle r{ point(), this->size() }; + rectangle r{ this->size() }; //get a rectangle excluding caption r.y = 20; - if (r.height > 20) - r.height -= 20; - else - r.height = 0; + r.height = differ(r.height, 20); if (!tabbar_) { @@ -426,7 +479,7 @@ namespace nana value_.integer = 0; } - void reset() + void reset() noexcept { kind_ = kind::none; value_.integer = 0; @@ -434,30 +487,21 @@ namespace nana bool is_negative() const { - switch (kind_) - { - case kind::integer: - return (value_.integer < 0); - case kind::real: - case kind::percent: - return (value_.real < 0); - default: - break; - } - return false; + return (((kind::integer == kind_) && (value_.integer < 0)) || + ((kind::real == kind_ || kind::percent == kind_) && (value_.real < 0))); } - bool empty() const throw() + bool empty() const noexcept { return (kind::none == kind_); } - kind kind_of() const + kind kind_of() const noexcept { return kind_; } - double get_value(int ref_percent) const + double get_value(int ref_percent) const noexcept { switch (kind_) { @@ -473,33 +517,33 @@ namespace nana return 0; } - int integer() const + int integer() const noexcept { if (kind::integer == kind_) return value_.integer; return static_cast(value_.real); } - double real() const + double real() const noexcept { if (kind::integer == kind_) return value_.integer; return value_.real; } - void assign(int i) + void assign(int i) noexcept { kind_ = kind::integer; value_.integer = i; } - void assign(double d) + void assign(double d) noexcept { kind_ = kind::real; value_.real = d; } - void assign_percent(double d) + void assign_percent(double d) noexcept { kind_ = kind::percent; value_.real = d / 100; @@ -532,15 +576,12 @@ namespace nana all_edges_ = true; margins_.clear(); } - - void push(const number_t& v) + + void push(const number_t& v, bool reset = false) { - margins_.emplace_back(v); - } + if (reset) + clear(); - void set_value(const number_t& v) - { - clear(); margins_.emplace_back(v); } @@ -550,6 +591,45 @@ namespace nana margins_ = v; } + number_t get_edge(std::size_t edge) const + { + int il{ -1 }, ir{ -1 }, it{ -1 }, ib{ -1 }; //index of four corners in margin + switch (margins_.size()) + { + case 0: break; + case 1: //top + il = ir = it = ib = 0; + break; + case 2://top,bottom and left,right + it = ib = 0; + il = ir = 1; + break; + default: + il = 3; //left + case 3: //top, right, bottom + it = 0; + ir = 1; + ib = 2; + } + + int pos = 0; + switch (edge) + { + case 0: //top + pos = it; break; + case 1: //right + pos = ir; break; + case 2: //bottom + pos = ib; break; + case 3: //left + pos = il; break; + default: + return number_t{}; + } + + return (-1 == pos ? number_t{} : margins_[pos]); + } + nana::rectangle area(const ::nana::rectangle& field_area) const { if (margins_.empty()) @@ -559,12 +639,11 @@ namespace nana if (all_edges_) { auto px = static_cast(margins_.back().get_value(static_cast(r.width))); - const auto dbl_px = static_cast(px << 1); r.x += px; - r.width = (r.width < dbl_px ? 0 : r.width - dbl_px); + r.width = differ(r.width, (static_cast(px) << 1)); r.y += px; - r.height = (r.height < dbl_px ? 0 : r.height - dbl_px); + r.height = differ(r.height, (static_cast(px) << 1)); } else { @@ -573,7 +652,7 @@ namespace nana { case 0: break; case 1: //top - it = 0; + il = ir = it = ib = 0; break; case 2://top,bottom and left,right it = ib = 0; @@ -587,49 +666,44 @@ namespace nana ib = 2; } - typedef decltype(r.height) px_type; - auto calc = [](px_type a, px_type b) - { - return (a > b ? a - b : 0); - }; + using px_type = decltype(r.height); if (0 == it) //top { auto px = static_cast(margins_[it].get_value(static_cast(field_area.height))); r.y += px; - r.height = calc(r.height, static_cast(px)); + r.height = differ(r.height, static_cast(px)); } if (-1 != ib) //bottom { auto px = static_cast(margins_[ib].get_value(static_cast(field_area.height))); - r.height = calc(r.height, static_cast(px)); + r.height = differ(r.height, static_cast(px)); } if (-1 != il) //left { auto px = static_cast(margins_[il].get_value(static_cast(field_area.width))); r.x += px; - r.width = calc(r.width, static_cast(px)); + r.width = differ(r.width, static_cast(px)); } if (-1 != ir) //right { auto px = static_cast(margins_[ir].get_value(static_cast(field_area.width))); - r.width = calc(r.width, static_cast(px)); + r.width = differ(r.width, static_cast(px)); } } return r; } private: - bool all_edges_ = true; + bool all_edges_{ true }; std::vector margins_; };//end class margin class repeated_array { public: - //A workaround for VC2013, becuase it does not generated an implicit declared move-constructor as defaulted. repeated_array() = default; @@ -678,15 +752,10 @@ namespace nana number_t at(std::size_t pos) const { - if (values_.empty()) - return{}; + if (values_.size() && (repeated_ || pos < values_.size())) + return values_[pos % values_.size()]; - if (repeated_) - pos %= values_.size(); - else if (pos >= values_.size()) - return{}; - - return values_[pos]; + return{}; } private: bool repeated_ = false; diff --git a/source/gui/programming_interface.cpp b/source/gui/programming_interface.cpp index 94446c8b..6f91756e 100644 --- a/source/gui/programming_interface.cpp +++ b/source/gui/programming_interface.cpp @@ -1,7 +1,7 @@ /* * Nana GUI Programming Interface Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -155,6 +155,12 @@ namespace API } } + void effects_bground(std::initializer_list wdgs, const effects::bground_factory_interface& factory, double fade_rate) + { + for (auto wd : wdgs) + effects_bground(wd, factory, fade_rate); + } + bground_mode effects_bground_mode(window wd) { auto const iwd = reinterpret_cast(wd); @@ -210,6 +216,14 @@ namespace API return (restrict::wd_manager().available(iwd) ? iwd->annex.scheme : nullptr); } + void set_measurer(window wd, ::nana::dev::widget_content_measurer_interface* measurer) + { + auto iwd = reinterpret_cast(wd); + internal_scope_guard lock; + if (restrict::wd_manager().available(iwd)) + iwd->annex.content_measurer = measurer; + } + void attach_drawer(widget& wd, drawer_trigger& dr) { const auto iwd = reinterpret_cast(wd.handle()); @@ -321,6 +335,32 @@ namespace API iwd->flags.space_click_enabled = enable; } + bool copy_transparent_background(window wd, paint::graphics& graph) + { + auto & buf = reinterpret_cast(wd)->other.glass_buffer; + internal_scope_guard lock; + + if (bground_mode::basic != API::effects_bground_mode(wd)) + return false; + + buf.paste(rectangle{ buf.size() }, graph, 0, 0); + + return true; + } + + bool copy_transparent_background(window wd, const rectangle& src_r, paint::graphics& graph, const point& dst_pt) + { + auto iwd = reinterpret_cast(wd); + internal_scope_guard lock; + + if (bground_mode::basic != API::effects_bground_mode(wd)) + return false; + + iwd->other.glass_buffer.paste(src_r, graph, dst_pt.x, dst_pt.y); + + return true; + } + void lazy_refresh() { restrict::bedrock.thread_context_lazy_refresh(); @@ -340,74 +380,15 @@ namespace API //close all windows in current thread void exit() { - std::vector v; - internal_scope_guard lock; - restrict::wd_manager().all_handles(v); - if(v.size()) - { - std::vector roots; - native_window_type root = nullptr; - unsigned tid = nana::system::this_thread_id(); - for(auto wd : v) - { - if((wd->thread_id == tid) && (wd->root != root)) - { - root = wd->root; - bool exists = false; - for (auto i = roots.cbegin(); i != roots.cend(); ++i) - { - if (*i == root) - { - exists = true; - break; - } - } - - if (!exists) - roots.emplace_back(root); - } - } - - for(auto i : roots) - interface_type::close_window(i); - } + restrict::bedrock.close_thread_window(nana::system::this_thread_id()); } + //close all windows void exit_all() { - std::vector v; - internal_scope_guard lock; - restrict::wd_manager().all_handles(v); - if (v.size()) - { - std::vector roots; - native_window_type root = nullptr; - //unsigned tid = nana::system::this_thread_id(); - for (auto wd : v) - { - if (/*(wd->thread_id == tid) &&*/ (wd->root != root)) - { - root = wd->root; - bool exists = false; - for (auto i = roots.cbegin(); i != roots.cend(); ++i) - { - if (*i == root) - { - exists = true; - break; - } - } - - if (!exists) - roots.emplace_back(root); - } - } - - for (auto i : roots) - interface_type::close_window(i); - } + restrict::bedrock.close_thread_window(0); } //transform_shortkey_text @@ -480,12 +461,13 @@ namespace API void window_icon_default(const paint::image& small_icon, const paint::image& big_icon) { - restrict::wd_manager().default_icon(small_icon, big_icon); + restrict::wd_manager().icon(nullptr, small_icon, big_icon); } void window_icon(window wd, const paint::image& small_icon, const paint::image& big_icon) { - restrict::wd_manager().icon(reinterpret_cast(wd), small_icon, big_icon); + if(nullptr != wd) + restrict::wd_manager().icon(reinterpret_cast(wd), small_icon, big_icon); } bool empty_window(window wd) @@ -520,6 +502,11 @@ namespace API } } + bool is_transparent_background(window wd) + { + return (bground_mode::basic == effects_bground_mode(wd)); + } + native_window_type root(window wd) { internal_scope_guard lock; @@ -992,24 +979,22 @@ namespace API void modal_window(window wd) { + auto const iwd = reinterpret_cast(wd); + internal_scope_guard isg; + + if (!restrict::wd_manager().available(iwd)) + return; + + if ((iwd->other.category == category::flags::root) && (iwd->flags.modal == false)) { - auto const iwd = reinterpret_cast(wd); - internal_scope_guard isg; - - if (!restrict::wd_manager().available(iwd)) - return; - - if ((iwd->other.category == category::flags::root) && (iwd->flags.modal == false)) - { - iwd->flags.modal = true; + iwd->flags.modal = true; #if defined(NANA_X11) - interface_type::set_modal(iwd->root); + interface_type::set_modal(iwd->root); #endif - restrict::wd_manager().show(iwd, true); - } - else - return; + restrict::wd_manager().show(iwd, true); } + else + return; //modal has to guarantee that does not lock the mutex of window_manager before invokeing the pump_event, //otherwise, the modal will prevent the other thread access the window. @@ -1018,7 +1003,8 @@ namespace API void wait_for(window wd) { - if (wd) + internal_scope_guard lock; + if (restrict::wd_manager().available(reinterpret_cast(wd))) restrict::bedrock.pump_event(wd, false); } @@ -1335,7 +1321,7 @@ namespace API ::nana::point clipos{pos}; interface_type::calc_window_point(wd, clipos); return reinterpret_cast( - restrict::wd_manager().find_window(wd, clipos.x, clipos.y)); + restrict::wd_manager().find_window(wd, clipos)); } return nullptr; } @@ -1395,6 +1381,7 @@ namespace API switch(iwd->flags.action) { case nana::mouse_action::normal: + case nana::mouse_action::normal_captured: return (is_focused ? nana::element_state::focus_normal : nana::element_state::normal); case nana::mouse_action::hovered: return (is_focused ? nana::element_state::focus_hovered : nana::element_state::hovered); @@ -1432,5 +1419,29 @@ namespace API { restrict::wd_manager().set_safe_place(reinterpret_cast(wd), std::move(fn)); } + + optional> content_extent(window wd, unsigned limited_px, bool limit_width) + { + auto iwd = reinterpret_cast(wd); + internal_scope_guard lock; + + if (restrict::wd_manager().available(iwd) && iwd->annex.content_measurer) + { + paint::graphics* graph = &iwd->drawer.graphics; + paint::graphics temp_graph; + if (graph->empty()) + { + temp_graph.make({ 1, 1 }); + temp_graph.typeface(graph->typeface()); + graph = &temp_graph; + } + + auto extent = iwd->annex.content_measurer->measure(*graph, limited_px, limit_width); + if (extent) + return std::make_pair(extent.value(), extent.value() + iwd->annex.content_measurer->extension()); + } + + return{}; + } }//end namespace API }//end namespace nana diff --git a/source/gui/timer.cpp b/source/gui/timer.cpp index bb165436..e961f6e9 100644 --- a/source/gui/timer.cpp +++ b/source/gui/timer.cpp @@ -28,7 +28,7 @@ #if defined(NANA_WINDOWS) #include #elif defined(NANA_LINUX) || defined(NANA_MACOS) -#include +#include "../detail/platform_spec_selector.hpp" #include #endif diff --git a/source/gui/tooltip.cpp b/source/gui/tooltip.cpp index 847fbab0..862a2720 100644 --- a/source/gui/tooltip.cpp +++ b/source/gui/tooltip.cpp @@ -1,7 +1,7 @@ /* * A Tooltip Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -210,24 +210,7 @@ namespace nana _m_get(wd).second = str; } - void show(const std::string& text) - { - if (nullptr == window_ || window_->tooltip_empty()) - { - auto fp = factory(); - - window_ = std::unique_ptr(fp->create(), [fp](tooltip_interface* ti) - { - fp->destroy(ti); - }); - } - - window_->duration(0); - window_->tooltip_text(text); - window_->tooltip_move(API::cursor_position(), true); - } - - void show_duration(window /*wd*/, point pos, const std::string& text, std::size_t duration) + void show(const std::string& text, const point* pos, std::size_t duration) { if (nullptr == window_ || window_->tooltip_empty()) { @@ -242,8 +225,10 @@ namespace nana window_->duration(duration); window_->tooltip_text(text); - pos = pos_by_screen(pos, window_->tooltip_size(), true); - window_->tooltip_move(pos, false); + if (pos) + window_->tooltip_move(pos_by_screen(*pos, window_->tooltip_size(), true), false); + else + window_->tooltip_move(API::cursor_position(), true); } void close() @@ -289,7 +274,7 @@ namespace nana { auto & pr = _m_get(arg.window_handle); if (pr.second.size()) - this->show(pr.second); + this->show(pr.second, nullptr, 0); } else this->close(); @@ -329,7 +314,7 @@ namespace nana { internal_scope_guard lock; API::calc_screen_point(wd, pos); - ctrl::instance()->show_duration(wd, pos, text, duration); + ctrl::instance()->show(text, &pos, duration); } void tooltip::close() diff --git a/source/gui/widgets/button.cpp b/source/gui/widgets/button.cpp index 241d3a63..01de4f7c 100644 --- a/source/gui/widgets/button.cpp +++ b/source/gui/widgets/button.cpp @@ -1,7 +1,7 @@ /* * A Button Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -11,12 +11,44 @@ */ #include +#include + #include namespace nana{ namespace drawerbase { namespace button { + class trigger::measurer + : public dev::widget_content_measurer_interface + { + public: + measurer(trigger* t) + : trigger_{ t } + {} + + optional measure(graph_reference graph, unsigned limit_pixels, bool /*limit_width*/) const override + { + //Button doesn't provide a support of vfit and hfit + if (limit_pixels) + return{}; + + wchar_t shortkey; + std::string::size_type shortkey_pos; + + auto str = to_wstring(API::transform_shortkey_text(trigger_->wdg_->caption(), shortkey, &shortkey_pos)); + auto text_sz = graph.text_extent_size(str); + + return size{ text_sz.width, text_sz.height }; + } + + size extension() const override + { + return { 14, 10}; + } + private: + trigger * trigger_; + }; //trigger //@brief: draw the button @@ -26,6 +58,8 @@ namespace nana{ namespace drawerbase attr_.omitted = attr_.focused = attr_.pushed = attr_.enable_pushed = attr_.keep_pressed = false; attr_.focus_color = true; attr_.icon = nullptr; + + measurer_.reset(new measurer{this}); } trigger::~trigger() @@ -44,6 +78,7 @@ namespace nana{ namespace drawerbase API::tabstop(wd); API::effects_edge_nimbus(wd, effects::edge_nimbus::active); API::effects_edge_nimbus(wd, effects::edge_nimbus::over); + API::dev::set_measurer(widget, measurer_.get()); } bool trigger::enable_pushed(bool eb) @@ -120,7 +155,7 @@ namespace nana{ namespace drawerbase if (false == cite_.draw(graph, attr_.bgcolor, attr_.fgcolor, ::nana::rectangle{ graph.size() }, e_state)) { - if (bground_mode::basic != API::effects_bground_mode(wdg_->handle())) + if (!API::is_transparent_background(*wdg_)) { _m_draw_background(graph); _m_draw_border(graph); @@ -251,17 +286,17 @@ namespace nana{ namespace drawerbase } else { - graph.palette(true, ::nana::color(colors::white)); + graph.palette(true, color{ colors::white }); if(attr_.omitted) { tr.render(point{ pos.x + 1, pos.y + 1 }, txtptr, txtlen, omitted_pixels, true); - graph.palette(true, ::nana::color(colors::gray)); + graph.palette(true, color{ colors::gray }); tr.render(pos, txtptr, txtlen, omitted_pixels, true); } else { graph.bidi_string(point{ pos.x + 1, pos.y + 1 }, txtptr, txtlen); - graph.palette(true, ::nana::color(colors::gray)); + graph.palette(true, color{ colors::gray }); graph.bidi_string(pos, txtptr, txtlen); } } @@ -276,8 +311,8 @@ namespace nana{ namespace drawerbase nana::rectangle r(graph.size()); r.pare_off(1); - auto from = attr_.bgcolor.blend(colors::white, 0.2); - auto to = attr_.bgcolor.blend(colors::black, 0.95); + auto from = attr_.bgcolor.blend(colors::white, 0.8); + auto to = attr_.bgcolor.blend(colors::black, 0.05); if (element_state::pressed == attr_.e_state) { @@ -457,7 +492,7 @@ namespace nana{ namespace drawerbase bool button::transparent() const { - return (bground_mode::basic == API::effects_bground_mode(*this)); + return API::is_transparent_background(*this); } button& button::edge_effects(bool enable) diff --git a/source/gui/widgets/categorize.cpp b/source/gui/widgets/categorize.cpp index cf29e267..b00bdf8c 100644 --- a/source/gui/widgets/categorize.cpp +++ b/source/gui/widgets/categorize.cpp @@ -1,7 +1,7 @@ /* * A Categorize Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -78,7 +78,7 @@ namespace nana if(ue.what == ue.none || (API::window_enabled(wd) == false)) { //the mouse is out of the widget. - style_.bgcolor = style_.bgcolor.blend(static_cast(0xa0c9f5), 0.9); + style_.bgcolor = style_.bgcolor.blend(static_cast(0xa0c9f5), 0.1); } graph.rectangle(r, true, style_.bgcolor); } @@ -343,7 +343,7 @@ namespace nana { if(tree_.get_root()->child) { - tree_.clear(); + tree_.clear(tree_.get_root()); return true; } return false; diff --git a/source/gui/widgets/checkbox.cpp b/source/gui/widgets/checkbox.cpp index b1a2fe88..a1b2fe5c 100644 --- a/source/gui/widgets/checkbox.cpp +++ b/source/gui/widgets/checkbox.cpp @@ -1,7 +1,7 @@ /* * A CheckBox Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -55,7 +55,7 @@ namespace nana{ namespace drawerbase auto wdg = impl_->widget_ptr; //draw background - if (bground_mode::basic != API::effects_bground_mode(*wdg)) + if (!API::dev::copy_transparent_background(*wdg, graph)) graph.rectangle(true, wdg->bgcolor()); //draw title @@ -193,7 +193,7 @@ namespace nana{ namespace drawerbase bool checkbox::transparent() const { - return (bground_mode::basic == API::effects_bground_mode(*this)); + return API::is_transparent_background(*this); } //end class checkbox diff --git a/source/gui/widgets/combox.cpp b/source/gui/widgets/combox.cpp index 4ec63f52..4204dbe8 100644 --- a/source/gui/widgets/combox.cpp +++ b/source/gui/widgets/combox.cpp @@ -1,7 +1,7 @@ /* * A Combox Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -17,8 +17,10 @@ #include #include #include +#include #include +#include namespace nana { @@ -80,6 +82,40 @@ namespace nana class drawer_impl { + class content_measurer + : public dev::widget_content_measurer_interface + { + public: + content_measurer(drawer_impl* drwimpl) + : drw_{ drwimpl } + {} + + optional measure(graph_reference graph, unsigned limit_pixels, bool /*limit_width*/) const override + { + //Combox doesn't provide a support of vfit and hfit + if (limit_pixels) + return{}; + + size content_size; + for (std::size_t i = 0; i < drw_->the_number_of_options(); ++i) + { + auto & m = drw_->at(i); + auto sz = graph.text_extent_size(m.item_text); + + content_size.width = (std::max)(content_size.width, sz.width); + content_size.height = (std::max)(content_size.height, sz.height); + } + + return content_size; + } + + size extension() const override + { + return{ 19, 4 }; + } + private: + drawer_impl* const drw_; + }; public: using graph_reference = paint::graphics&; using widget_reference = widget&; @@ -92,6 +128,8 @@ namespace nana state_.button_state = element_state::normal; state_.pointer_where = parts::none; state_.lister = nullptr; + + measurer_.reset(new content_measurer{this}); } void renderer(drawerbase::float_listbox::item_renderer* ir) @@ -111,6 +149,8 @@ namespace nana evt_agent_.reset(new event_agent{ static_cast(wd) }); editor_->textbase().set_event_agent(evt_agent_.get()); + + API::dev::set_measurer(wd, measurer_.get()); } void detached() @@ -139,7 +179,9 @@ namespace nana void text_area(const nana::size& s) { - nana::rectangle r(2, 2, s.width > 19 ? s.width - 19 : 0, s.height > 4 ? s.height - 4 : 0); + auto extension = measurer_->extension(); + + nana::rectangle r(2, 2, s.width > extension.width ? s.width - extension.width : 0, s.height > extension.height ? s.height - extension.height : 0); if(image_enabled_) { unsigned place = image_pixels_ + 2; @@ -147,6 +189,7 @@ namespace nana if(r.width > place) r.width -= place; } editor_->text_area(r); + editor_->render(state_.focused); } widgets::skeletons::text_editor * editor() const @@ -187,6 +230,14 @@ namespace nana } graph.gradual_rectangle(::nana::rectangle(graph.size()).pare_off(pare_off_px), clr_from, clr_to, true); + if (API::is_transparent_background(this->widget_ptr()->handle())) + { + paint::graphics trns_graph{ graph.size() }; + if (API::dev::copy_transparent_background(this->widget_ptr()->handle(), trns_graph)) + { + graph.blend(rectangle{ trns_graph.size() }, trns_graph, {}, 0.5); + } + } }; } else @@ -316,7 +367,6 @@ namespace nana if(editor_) { text_area(widget_->size()); - editor_->render(state_.focused); } _m_draw_push_button(enb); _m_draw_image(); @@ -351,7 +401,8 @@ namespace nana if (calc_where(*graph_, pos.x, pos.y)) state_.button_state = element_state::normal; - editor_->text(::nana::charset(items_[index]->item_text, ::nana::unicode::utf8), false); + editor_->text(to_wstring(items_[index]->item_text), false); + editor_->try_refresh(); _m_draw_push_button(widget_->enabled()); _m_draw_image(); @@ -465,9 +516,10 @@ namespace nana facade button; button.draw(*graph_, ::nana::color{ 3, 65, 140 }, colors::white, r, estate); - facade arrow("solid_triangle"); + facade arrow;// ("solid_triangle"); arrow.direction(::nana::direction::south); + r.x += 4; r.y += (r.height / 2) - 7; r.width = r.height = 16; arrow.draw(*graph_, {}, colors::white, r, element_state::normal); @@ -528,6 +580,8 @@ namespace nana unsigned image_pixels_{ 16 }; widgets::skeletons::text_editor * editor_{ nullptr }; std::unique_ptr evt_agent_; + + std::unique_ptr measurer_; struct state_type { bool focused; @@ -537,188 +591,192 @@ namespace nana nana::float_listbox * lister; std::size_t item_index_before_selection; }state_; - }; + + + }; //end class drawer_impl //class trigger - trigger::trigger() - : drawer_(new drawer_impl) - {} + trigger::trigger() : + drawer_(new drawer_impl) + { + } - trigger::~trigger() - { - delete drawer_; - } + trigger::~trigger() + { + delete drawer_; + } - drawer_impl& trigger::get_drawer_impl() - { - return *drawer_; - } + drawer_impl& trigger::get_drawer_impl() + { + return *drawer_; + } - const drawer_impl& trigger::get_drawer_impl() const - { - return *drawer_; - } + const drawer_impl& trigger::get_drawer_impl() const + { + return *drawer_; + } - void trigger::attached(widget_reference wdg, graph_reference graph) - { - wdg.bgcolor(colors::white); - drawer_->attached(wdg, graph); + void trigger::attached(widget_reference wdg, graph_reference graph) + { + wdg.bgcolor(colors::white); + drawer_->attached(wdg, graph); - API::effects_edge_nimbus(wdg, effects::edge_nimbus::active); - API::effects_edge_nimbus(wdg, effects::edge_nimbus::over); - } + API::effects_edge_nimbus(wdg, effects::edge_nimbus::active); + API::effects_edge_nimbus(wdg, effects::edge_nimbus::over); + } - void trigger::detached() - { - drawer_->detached(); - } + void trigger::detached() + { + drawer_->detached(); + } - void trigger::refresh(graph_reference) + void trigger::refresh(graph_reference) + { + drawer_->draw(); + } + + void trigger::focus(graph_reference, const arg_focus& arg) + { + drawer_->set_focused(arg.getting); + if(drawer_->widget_ptr()->enabled()) { drawer_->draw(); + drawer_->editor()->reset_caret(); + API::dev::lazy_refresh(); } + } - void trigger::focus(graph_reference, const arg_focus& arg) + void trigger::mouse_enter(graph_reference, const arg_mouse&) + { + drawer_->set_button_state(element_state::hovered, true); + if(drawer_->widget_ptr()->enabled()) { - drawer_->set_focused(arg.getting); - if(drawer_->widget_ptr()->enabled()) + drawer_->draw(); + API::dev::lazy_refresh(); + } + } + + void trigger::mouse_leave(graph_reference, const arg_mouse&) + { + drawer_->set_button_state(element_state::normal, true); + drawer_->editor()->mouse_enter(false); + if(drawer_->widget_ptr()->enabled()) + { + drawer_->draw(); + API::dev::lazy_refresh(); + } + } + + void trigger::mouse_down(graph_reference, const arg_mouse& arg) + { + drawer_->set_button_state(element_state::pressed, false); + if(drawer_->widget_ptr()->enabled()) + { + auto * editor = drawer_->editor(); + editor->mouse_pressed(arg); + drawer_->open_lister_if_push_button_positioned(); + + drawer_->draw(); + if(editor->attr().editable) + editor->reset_caret(); + + API::dev::lazy_refresh(); + } + } + + void trigger::mouse_up(graph_reference, const arg_mouse& arg) + { + if (drawer_->widget_ptr()->enabled() && !drawer_->has_lister()) + { + drawer_->editor()->mouse_pressed(arg); + drawer_->set_button_state(element_state::hovered, false); + drawer_->draw(); + API::dev::lazy_refresh(); + } + } + + void trigger::mouse_move(graph_reference graph, const arg_mouse& arg) + { + if(drawer_->widget_ptr()->enabled()) + { + bool redraw = drawer_->calc_where(graph, arg.pos.x, arg.pos.y); + redraw |= drawer_->editor()->mouse_move(arg.left_button, arg.pos); + + if(redraw) { drawer_->draw(); drawer_->editor()->reset_caret(); API::dev::lazy_refresh(); } } + } - void trigger::mouse_enter(graph_reference, const arg_mouse&) + void trigger::mouse_wheel(graph_reference, const arg_wheel& arg) + { + if(drawer_->widget_ptr()->enabled()) { - drawer_->set_button_state(element_state::hovered, true); - if(drawer_->widget_ptr()->enabled()) - { - drawer_->draw(); - API::dev::lazy_refresh(); - } - } - - void trigger::mouse_leave(graph_reference, const arg_mouse&) - { - drawer_->set_button_state(element_state::normal, true); - drawer_->editor()->mouse_enter(false); - if(drawer_->widget_ptr()->enabled()) - { - drawer_->draw(); - API::dev::lazy_refresh(); - } - } - - void trigger::mouse_down(graph_reference, const arg_mouse& arg) - { - //drawer_->set_mouse_press(true); - drawer_->set_button_state(element_state::pressed, false); - if(drawer_->widget_ptr()->enabled()) - { - auto * editor = drawer_->editor(); - editor->mouse_pressed(arg); - drawer_->open_lister_if_push_button_positioned(); - - drawer_->draw(); - if(editor->attr().editable) - editor->reset_caret(); - - API::dev::lazy_refresh(); - } - } - - void trigger::mouse_up(graph_reference, const arg_mouse& arg) - { - if (drawer_->widget_ptr()->enabled() && !drawer_->has_lister()) - { - drawer_->editor()->mouse_pressed(arg); - drawer_->set_button_state(element_state::hovered, false); - drawer_->draw(); - API::dev::lazy_refresh(); - } - } - - void trigger::mouse_move(graph_reference graph, const arg_mouse& arg) - { - if(drawer_->widget_ptr()->enabled()) - { - bool redraw = drawer_->calc_where(graph, arg.pos.x, arg.pos.y); - redraw |= drawer_->editor()->mouse_move(arg.left_button, arg.pos); - - if(redraw) - { - drawer_->draw(); - drawer_->editor()->reset_caret(); - API::dev::lazy_refresh(); - } - } - } - - void trigger::mouse_wheel(graph_reference, const arg_wheel& arg) - { - if(drawer_->widget_ptr()->enabled()) - { - if(drawer_->has_lister()) - drawer_->scroll_items(arg.upwards); - else - drawer_->move_items(arg.upwards, false); - } - } - - void trigger::key_press(graph_reference, const arg_keyboard& arg) - { - if(!drawer_->widget_ptr()->enabled()) - return; - - bool call_other_keys = false; - if(drawer_->editable()) - { - bool is_move_up = false; - switch(arg.key) - { - case keyboard::os_arrow_left: - case keyboard::os_arrow_right: - drawer_->editor()->respond_key(arg); - drawer_->editor()->reset_caret(); - break; - case keyboard::os_arrow_up: - is_move_up = true; - case keyboard::os_arrow_down: - drawer_->move_items(is_move_up, true); - break; - default: - call_other_keys = true; - } - } + if(drawer_->has_lister()) + drawer_->scroll_items(arg.upwards); else - { - bool is_move_up = false; - switch(arg.key) - { - case keyboard::os_arrow_left: - case keyboard::os_arrow_up: - is_move_up = true; - case keyboard::os_arrow_right: - case keyboard::os_arrow_down: - drawer_->move_items(is_move_up, true); - break; - default: - call_other_keys = true; - } - } - if (call_other_keys) - drawer_->editor()->respond_key(arg); - - API::dev::lazy_refresh(); + drawer_->move_items(arg.upwards, false); } + } - void trigger::key_char(graph_reference, const arg_keyboard& arg) + void trigger::key_press(graph_reference, const arg_keyboard& arg) + { + if(!drawer_->widget_ptr()->enabled()) + return; + + bool call_other_keys = false; + if(drawer_->editable()) { - if (drawer_->editor()->respond_char(arg)) - API::dev::lazy_refresh(); + bool is_move_up = false; + switch(arg.key) + { + case keyboard::os_arrow_left: + case keyboard::os_arrow_right: + drawer_->editor()->respond_key(arg); + drawer_->editor()->reset_caret(); + break; + case keyboard::os_arrow_up: + is_move_up = true; + case keyboard::os_arrow_down: + drawer_->move_items(is_move_up, true); + break; + default: + call_other_keys = true; + } } + else + { + bool is_move_up = false; + switch(arg.key) + { + case keyboard::os_arrow_left: + case keyboard::os_arrow_up: + is_move_up = true; + case keyboard::os_arrow_right: + case keyboard::os_arrow_down: + drawer_->move_items(is_move_up, true); + break; + default: + call_other_keys = true; + } + } + if (call_other_keys) + drawer_->editor()->respond_key(arg); + + drawer_->editor()->try_refresh(); + API::dev::lazy_refresh(); + } + + void trigger::key_char(graph_reference, const arg_keyboard& arg) + { + drawer_->editor()->respond_char(arg); + if (drawer_->editor()->try_refresh()) + API::dev::lazy_refresh(); + } //end class trigger //class item_proxy diff --git a/source/gui/widgets/date_chooser.cpp b/source/gui/widgets/date_chooser.cpp index aa824637..b2f743e4 100644 --- a/source/gui/widgets/date_chooser.cpp +++ b/source/gui/widgets/date_chooser.cpp @@ -1,7 +1,7 @@ /* * A date chooser Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -93,7 +93,8 @@ namespace nana r.y = static_cast(newbuf.height() - r.height) / 2; newbuf.stretch(nzbuf, r); - nzbuf.blend(::nana::rectangle{ nzbuf.size() }, dzbuf, nana::point(), fade * (count - i)); + dzbuf.blend(::nana::rectangle{ nzbuf.size() }, nzbuf, {}, 1 - fade * (count - i)); + graph.bitblt(refpos.x, refpos.y, dzbuf); API::update_window(window_handle); @@ -121,7 +122,8 @@ namespace nana nzbuf.rectangle(true, colors::white); newbuf.stretch(nzbuf, r); - nzbuf.blend(::nana::rectangle{ nzbuf.size() }, dzbuf, nana::point(), fade * (count - i)); + dzbuf.blend(::nana::rectangle{ nzbuf.size() }, nzbuf, {}, 1.0 - fade * (count - i)); + graph.bitblt(refpos.x, refpos.y, dzbuf); API::update_window(window_handle); diff --git a/source/gui/widgets/group.cpp b/source/gui/widgets/group.cpp index 91b1bd9a..ab8e9e73 100644 --- a/source/gui/widgets/group.cpp +++ b/source/gui/widgets/group.cpp @@ -1,7 +1,7 @@ /** * A group widget implementation * Nana C++ Library(http://www.nanaro.org) - * Copyright(C) 2015 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2015-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -12,6 +12,9 @@ * @Author: Stefan Pfeifer(st-321), Ariel Vina-Rodriguez (qPCR4vir) * * @brief group is a widget used to visually group and layout other widgets. + * + * @contributor: + * dankan1890(https://github.com/dankan1890) */ @@ -36,6 +39,8 @@ namespace nana{ unsigned gap{2}; std::string usr_div_str; + nana::size caption_dimension; + std::vector> options; radio_group * radio_logic{nullptr}; @@ -60,22 +65,24 @@ namespace nana{ void update_div() { - ::nana::size sz = caption.measure(1000); + caption_dimension = caption.measure(1000); - std::stringstream ss; - ss << "vert margin=[0," << gap << "," << gap + 5 << "," << gap << "]" - << " <" << field_title << " weight=" << sz.width + 1 << "> >" - << "<"; + std::string div = "vert margin=[0," + std::to_string(gap) + "," + std::to_string(gap + 5) + "," + std::to_string(gap) + "]"; + div += "<" + field_title + " weight=" + std::to_string(caption_dimension.width + 1) + ">>"; + div += "<"; if (!usr_div_str.empty()) - ss << "<" << usr_div_str << ">>"; + div += "<" + usr_div_str + ">>"; else - ss << ">"; + div += ">"; - place_content.div(ss.str().c_str()); + place_content.div(div.c_str()); if (options.empty()) place_content.field_display(field_options, false); + + if (caption.caption().empty()) + place_content.field_display(field_title, false); } }; @@ -202,25 +209,23 @@ namespace nana{ outter.collocate(); color pbg = API::bgcolor(this->parent()); - impl_->caption.bgcolor(pbg.blend(colors::black, 0.975)); - color bg = pbg.blend(colors::black, 0.950); - bgcolor(bg); + impl_->caption.bgcolor(pbg.blend(colors::black, 0.025)); + + this->bgcolor(pbg.blend(colors::black, 0.05)); drawing dw(*this); - ::nana::size sz = impl_->caption.measure(1000); - // This drawing function is owner by the onwer of dw (the outer panel of the group widget), not by dw !! - dw.draw([this, sz, bg, pbg](paint::graphics& graph) + dw.draw([this](paint::graphics& graph) { auto gap_px = impl_->gap - 1; - graph.rectangle(true, pbg); - graph.round_rectangle(rectangle(point(gap_px, sz.height / 2), - nana::size(graph.width() - 2 * gap_px, graph.height() - sz.height / 2 - gap_px) + graph.rectangle(true, API::bgcolor(this->parent())); + graph.round_rectangle(rectangle(point(gap_px, impl_->caption_dimension.height / 2), + nana::size(graph.width() - 2 * gap_px, graph.height() - impl_->caption_dimension.height / 2 - gap_px) ), - 3, 3, colors::gray_border, true, bg); + 3, 3, colors::gray_border, true, this->bgcolor()); }); } diff --git a/source/gui/widgets/label.cpp b/source/gui/widgets/label.cpp index 93a1d9a1..a4a8cef1 100644 --- a/source/gui/widgets/label.cpp +++ b/source/gui/widgets/label.cpp @@ -1,7 +1,7 @@ /* * A Label Control Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2013 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -13,8 +13,9 @@ */ #include -#include #include +#include +#include #include #include #include @@ -80,14 +81,14 @@ namespace nana { traceable_.clear(); - nana::paint::font ft = graph.typeface(); //used for restoring the font + auto pre_font = graph.typeface(); //used for restoring the font const unsigned def_line_pixels = graph.text_extent_size(L" ", 1).height; - font_ = ft; + font_ = pre_font; fblock_ = nullptr; - _m_set_default(ft, fgcolor); + _m_set_default(pre_font, fgcolor); _m_measure(graph); @@ -116,10 +117,10 @@ namespace nana if((tv != align_v::top) && extent_v_pixels < graph.height()) { + rs.pos.y = static_cast(graph.height() - extent_v_pixels); + if(align_v::center == tv) - rs.pos.y = static_cast(graph.height() - extent_v_pixels) >> 1; - else if(align_v::bottom == tv) - rs.pos.y = static_cast(graph.height() - extent_v_pixels); + rs.pos.y >>= 1; } else rs.pos.y = 0; @@ -145,10 +146,10 @@ namespace nana rs.pos.y += static_cast(rs.pixels.back().pixels); } - graph.typeface(ft); + graph.typeface(pre_font); } - bool find(int x, int y, std::wstring& target, std::wstring& url) const + bool find(int x, int y, std::wstring& target, std::wstring& url) const noexcept { for (auto & t : traceable_) { @@ -183,10 +184,10 @@ namespace nana rs.text_align = th; rs.text_align_v = tv; - for(auto i = dstream_.begin(), end = dstream_.end(); i != end; ++i) + for(auto & line: dstream_) { rs.pixels.clear(); - unsigned w = _m_line_pixels(*i, def_line_pixels, rs); + unsigned w = _m_line_pixels(line, def_line_pixels, rs); if(limited && (w > limited)) w = limited; @@ -202,7 +203,7 @@ namespace nana } private: //Manage the fblock for a specified rectangle if it is a traceable fblock. - void _m_inser_if_traceable(int x, int y, const nana::size& sz, widgets::skeletons::fblock* fbp) + void _m_insert_if_traceable(int x, int y, const nana::size& sz, widgets::skeletons::fblock* fbp) { if(fbp->target.size() || fbp->url.size()) { @@ -224,7 +225,7 @@ namespace nana def_.fgcolor = fgcolor; } - const ::nana::color& _m_fgcolor(nana::widgets::skeletons::fblock* fp) + const ::nana::color& _m_fgcolor(nana::widgets::skeletons::fblock* fp) noexcept { while(fp->fgcolor.invisible()) { @@ -235,9 +236,9 @@ namespace nana return fp->fgcolor; } - std::size_t _m_font_size(nana::widgets::skeletons::fblock* fp) + double _m_font_size(nana::widgets::skeletons::fblock* fp) noexcept { - while(fp->font_size == 0xFFFFFFFF) + while(fp->font_size < 0) { fp = fp->parent; if(nullptr == fp) @@ -246,7 +247,7 @@ namespace nana return fp->font_size; } - bool _m_bold(nana::widgets::skeletons::fblock* fp) + bool _m_bold(nana::widgets::skeletons::fblock* fp) noexcept { while(fp->bold_empty) { @@ -257,7 +258,7 @@ namespace nana return fp->bold; } - const std::string& _m_fontname(nana::widgets::skeletons::fblock* fp) + const std::string& _m_fontname(nana::widgets::skeletons::fblock* fp) noexcept { while(fp->font.empty()) { @@ -273,12 +274,14 @@ namespace nana if(fp != fblock_) { auto& name = _m_fontname(fp); - auto fontsize = static_cast(_m_font_size(fp)); + auto fontsize = _m_font_size(fp); bool bold = _m_bold(fp); if((fontsize != font_.size()) || bold != font_.bold() || name != font_.name()) { - font_.make(name, fontsize, bold); + paint::font::font_style fs; + fs.weight = (bold ? 800 : 400); + font_ = paint::font{ name, fontsize, fs }; graph.typeface(font_); } fblock_ = fp; @@ -304,7 +307,7 @@ namespace nana } } - void _m_align_x_base(const render_status& rs, pixel_tag & px, unsigned w) + void _m_align_x_base(const render_status& rs, pixel_tag & px, unsigned w) noexcept { switch(rs.text_align) { @@ -344,7 +347,7 @@ namespace nana std::vector line_values; - for(auto i = line.begin(), end = line.end(); i != end; ++i) + for(auto i = line.begin(); i != line.end(); ++i) { data * data_ptr = i->data_ptr; nana::size sz = data_ptr->size(); @@ -365,7 +368,8 @@ namespace nana sz.height = max_ascent + max_descent; } - if(w + sz.width <= rs.allowed_width) + //Check if the content is displayed in a new line. + if((0 == rs.allowed_width) || (w + sz.width <= rs.allowed_width)) { w += sz.width; @@ -376,12 +380,11 @@ namespace nana } else { + pixel_tag px; + _m_align_x_base(rs, px, (w ? w : sz.width)); + if(w) { - pixel_tag px; - - _m_align_x_base(rs, px, w); - if(max_ascent + max_descent > max_px) max_px = max_descent + max_ascent; else @@ -391,8 +394,6 @@ namespace nana px.baseline = max_ascent; px.values.swap(line_values); - rs.pixels.emplace_back(px); - w = sz.width; max_px = sz.height; max_ascent = as; @@ -401,18 +402,16 @@ namespace nana } else { - pixel_tag px; - - _m_align_x_base(rs, px, sz.width); px.pixels = sz.height; px.baseline = as; px.values.emplace_back(i); - rs.pixels.emplace_back(px); max_px = 0; max_ascent = max_descent = 0; } + + rs.pixels.emplace_back(px); } } @@ -442,49 +441,48 @@ namespace nana const int lastpos = static_cast(graph.height()) - 1; - for(auto i = rs.pixels.begin(), end = rs.pixels.end(); i != end; ++i) + for(auto & px : rs.pixels) { - for (auto & render_iterator : i->values) + for(auto & render_iterator: px.values) { auto & value = *render_iterator; - if(false == value.data_ptr->is_text()) - { - if(text.size()) - { - _m_draw_block(graph, text, block_start, rs); - if(lastpos <= rs.pos.y) - return false; - text.clear(); - } - nana::size sz = value.data_ptr->size(); - - pixel_tag px = rs.pixels[rs.index]; - if ((rs.allowed_width < rs.pos.x + sz.width) && (rs.pos.x != px.x_base)) - { - //Change a line. - rs.pos.y += static_cast(px.pixels); - px = rs.pixels[++rs.index]; - rs.pos.x = px.x_base; - } - - int y = rs.pos.y + _m_text_top(px, value.fblock_ptr, value.data_ptr); - - value.data_ptr->nontext_render(graph, rs.pos.x, y); - _m_inser_if_traceable(rs.pos.x, y, sz, value.fblock_ptr); - rs.pos.x += static_cast(sz.width); - - if(lastpos < y) - return false; - } - else + if (value.data_ptr->is_text()) { //hold the block while the text is empty, //it stands for the first block - if(text.empty()) + if (text.empty()) block_start = render_iterator; text += value.data_ptr->text(); + continue; } + + if(text.size()) + { + _m_draw_block(graph, text, block_start, rs); + if(lastpos <= rs.pos.y) + return false; + text.clear(); + } + nana::size sz = value.data_ptr->size(); + + pixel_tag px = rs.pixels[rs.index]; + if ((rs.allowed_width < rs.pos.x + sz.width) && (rs.pos.x != px.x_base)) + { + //Change a line. + rs.pos.y += static_cast(px.pixels); + px = rs.pixels[++rs.index]; + rs.pos.x = px.x_base; + } + + int y = rs.pos.y + _m_text_top(px, value.fblock_ptr, value.data_ptr); + + value.data_ptr->nontext_render(graph, rs.pos.x, y); + _m_insert_if_traceable(rs.pos.x, y, sz, value.fblock_ptr); + rs.pos.x += static_cast(sz.width); + + if(lastpos < y) + return false; } if(text.size()) @@ -496,7 +494,7 @@ namespace nana return (rs.pos.y <= lastpos); } - static bool _m_overline(const render_status& rs, int right, bool equal_required) + static bool _m_overline(const render_status& rs, int right, bool equal_required) noexcept { if(align::left == rs.text_align) return (equal_required ? right >= static_cast(rs.allowed_width) : right > static_cast(rs.allowed_width)); @@ -567,10 +565,13 @@ namespace nana else { auto str = data_ptr->text().substr(text_range.first, text_range.second); - graph.string({ rs.pos.x, y }, str, _m_fgcolor(fblock_ptr)); sz = graph.text_extent_size(str); + + graph.string({ rs.pos.x, y }, str, _m_fgcolor(fblock_ptr)); } - _m_inser_if_traceable(rs.pos.x, y, sz, fblock_ptr); + + + _m_insert_if_traceable(rs.pos.x, y, sz, fblock_ptr); rs.pos.x += static_cast(sz.width); if(text_range.second < len) @@ -584,7 +585,7 @@ namespace nana } } - std::pair _m_locate(dstream::linecontainer::iterator& i, std::size_t pos) + static std::pair _m_locate(dstream::linecontainer::iterator& i, std::size_t pos) { std::size_t n = i->data_ptr->text().length(); while(pos >= n) @@ -599,13 +600,13 @@ namespace nana dstream dstream_; bool format_enabled_ = false; ::nana::widgets::skeletons::fblock * fblock_ = nullptr; - std::deque traceable_; + ::std::deque traceable_; ::nana::paint::font font_; struct def_font_tag { ::std::string font_name; - std::size_t font_size; + double font_size; bool font_bold; ::nana::color fgcolor; }def_; @@ -613,13 +614,16 @@ namespace nana //class trigger //@brief: Draw the label - struct trigger::impl_t + struct trigger::implement { + class measurer; + widget * wd{nullptr}; paint::graphics * graph{nullptr}; + std::unique_ptr msr_ptr{ nullptr }; align text_align{align::left}; - align_v text_align_v; + align_v text_align_v{align_v::top}; class renderer renderer; @@ -643,16 +647,44 @@ namespace nana std::vector> listener_; }; + class trigger::implement::measurer + : public dev::widget_content_measurer_interface + { + public: + measurer(implement* impl) + : impl_{ impl } + {} + + optional measure(graph_reference graph, unsigned limit_pixels, bool limit_width) const override + { + //Label now doesn't support to measure content with a specified height. + if (graph && ((0 == limit_pixels) || limit_width)) + { + return impl_->renderer.measure(graph, limit_pixels, impl_->text_align, impl_->text_align_v); + } + return{}; + } + + size extension() const override + { + return{ 2, 2 }; + } + private: + implement * const impl_; + }; + trigger::trigger() - :impl_(new impl_t) - {} + :impl_(new implement) + { + impl_->msr_ptr.reset(new trigger::implement::measurer{impl_}); + } trigger::~trigger() { delete impl_; } - trigger::impl_t * trigger::impl() const + trigger::implement * trigger::impl() const { return impl_; } @@ -661,6 +693,7 @@ namespace nana { impl_->graph = &graph; impl_->wd = &widget; + API::dev::set_measurer(widget, impl_->msr_ptr.get()); } void trigger::mouse_move(graph_reference, const arg_mouse& arg) @@ -750,7 +783,7 @@ namespace nana if(nullptr == impl_->wd) return; window wd = impl_->wd->handle(); - if(bground_mode::basic != API::effects_bground_mode(wd)) + if (!API::dev::copy_transparent_background(wd, graph)) graph.rectangle(true, API::bgcolor(wd)); impl_->renderer.render(graph, API::fgcolor(wd), impl_->text_align, impl_->text_align_v); @@ -796,7 +829,7 @@ namespace nana bool label::transparent() const throw() { - return (bground_mode::basic == API::effects_bground_mode(*this)); + return API::is_transparent_background(*this); } label& label::format(bool f) diff --git a/source/gui/widgets/listbox.cpp b/source/gui/widgets/listbox.cpp index 0dcccdd0..53555952 100644 --- a/source/gui/widgets/listbox.cpp +++ b/source/gui/widgets/listbox.cpp @@ -1,7 +1,7 @@ /* * A List Box Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -14,28 +14,34 @@ * leobackes(pr#86,pr#97) * Benjamin Navarro(pr#81) * besh81(pr#130) - * + * dankan1890(pr#158) + * */ #include -#include #include //for inline widget #include #include #include +#include +#include +#include "skeletons/content_view.hpp" +#include #include #include #include -#include -#include -#include -#include #include namespace nana { + static void check_range(std::size_t pos, std::size_t size) + { + if (!(pos < size)) + throw std::out_of_range("listbox: invalid element position"); + } + namespace drawerbase { namespace listbox @@ -48,11 +54,11 @@ namespace nana model_lock_guard(model_interface* model) : model_ptr_(model) { - if (model_ptr_) - model_ptr_->lock(); + if (model) + model->lock(); } - ~model_lock_guard() + ~model_lock_guard() noexcept { if (model_ptr_) model_ptr_->unlock(); @@ -63,7 +69,7 @@ namespace nana //struct cell - cell::format::format(const ::nana::color& bgcolor, const ::nana::color& fgcolor) + cell::format::format(const ::nana::color& bgcolor, const ::nana::color& fgcolor) noexcept : bgcolor{ bgcolor }, fgcolor{ fgcolor } {} @@ -73,24 +79,19 @@ namespace nana {} //A workaround that VS2013 does not support to define an explicit default move constructor - cell::cell(cell&& other) + cell::cell(cell&& other) noexcept : text(std::move(other.text)), custom_format{ std::move(other.custom_format) } { } - cell::cell(std::string text) + cell::cell(std::string text) noexcept : text(std::move(text)) {} cell::cell(std::string text, const format& fmt) : text(std::move(text)), - custom_format(std::make_unique( fmt )) // or custom_format(new format{ fmt }) - {} - - cell::cell(std::string text, const ::nana::color& bgcolor, const ::nana::color& fgcolor) - : text(std::move(text)), - custom_format{std::make_unique( bgcolor, fgcolor ) } //custom_format{ new format{ bgcolor, fgcolor } } + custom_format(std::make_unique( fmt )) {} cell& cell::operator=(const cell& rhs) @@ -103,7 +104,7 @@ namespace nana return *this; } - cell& cell::operator=(cell&& other) + cell& cell::operator=(cell&& other) noexcept { if (this != &other) { @@ -121,12 +122,12 @@ namespace nana struct column : public column_interface { - native_string_type text; + native_string_type caption; unsigned width_px; std::pair range_width_px; bool visible_state{ true }; - /// Position of column when it was creating + /// Absolute position of column when it was creating size_type index; nana::align alignment{ nana::align::left }; @@ -136,14 +137,13 @@ namespace nana column() = default; - column(const column&) = default; column& operator=(const column& other) { if (this != &other) { - text = other.text; + caption = other.caption; width_px = other.width_px; range_width_px = other.range_width_px; visible_state = other.visible_state; @@ -156,7 +156,7 @@ namespace nana } column(column&& other): - text(std::move(other.text)), + caption(std::move(other.caption)), width_px(other.width_px), range_width_px(other.range_width_px), visible_state(other.visible_state), @@ -167,23 +167,8 @@ namespace nana { } - column& operator=(column&& other) - { - if (this != &other) - { - text = std::move(other.text); - width_px = other.width_px; - range_width_px = other.range_width_px; - visible_state = other.visible_state; - index = other.index; - alignment = other.alignment; - weak_ordering = std::move(other.weak_ordering); - } - return *this; - } - - column(essence* ess, native_string_type&& text, unsigned px, size_type pos) : - text(std::move(text)), + column(essence* ess, native_string_type&& text, unsigned px, size_type pos) noexcept : + caption(std::move(text)), width_px(px), index(pos), ess_(ess) @@ -191,7 +176,7 @@ namespace nana } private: //The definition is provided after essence - void _m_refresh(); + void _m_refresh() noexcept; private: essence* const ess_; public: @@ -219,18 +204,27 @@ namespace nana range_width_px.first = minimum; range_width_px.second = maximum; - if (width_px < range_width_px.first) + unsigned px = std::clamp(static_cast(width_px), static_cast(minimum), static_cast(maximum)); + if (width_px != px) { - width_px = range_width_px.first; - _m_refresh(); - } - else if (range_width_px.second < width_px) - { - width_px = range_width_px.second; + width_px = px; _m_refresh(); } } + size_type position(bool disp_order) const noexcept override; //The definition is provided after essence + + std::string text() const noexcept override + { + return to_utf8(caption); + } + + void text(std::string text_utf8) override + { + caption = to_nstring(std::move(text_utf8)); + _m_refresh(); + } + void text_align(::nana::align align) noexcept override { if (alignment != align) @@ -254,13 +248,13 @@ namespace nana _m_refresh(); } }; - + public: using container = std::vector; - export_options::columns_indexs all_headers(bool only_visibles) const + export_options::columns_indexs get_headers(bool only_visibles) const { export_options::columns_indexs idx; - for(const auto &col : cont()) + for(const auto &col : cont_) { if(col.visible_state || !only_visibles) idx.push_back(col.index); @@ -278,17 +272,17 @@ namespace nana first=false; else head_str += exp_opt.sep; - head_str += to_utf8(at(exp_opt.columns_order[idx]).text); + head_str += this->at(exp_opt.columns_order[idx]).text(); } return head_str; } - bool visible() const + bool visible() const noexcept { return visible_; } - bool visible(bool v) + bool visible(bool v) noexcept { if (visible_ == v) return false; @@ -297,12 +291,12 @@ namespace nana return true; } - bool sortable() const + bool sortable() const noexcept { return sortable_; } - void sortable(bool enable) + void sortable(bool enable) noexcept { sortable_ = enable; } @@ -313,7 +307,7 @@ namespace nana return cont_.back().index; } - unsigned pixels() const ///< the visible width of the whole header + unsigned pixels() const noexcept ///< the visible width of the whole header { unsigned pixels = 0; for(auto & col : cont_) @@ -329,7 +323,7 @@ namespace nana * @param width The width to be fittd * @return true if the ranged columns is adjusted for the width, false otherwise. */ - bool calc_ranged_columns(unsigned width) + bool calc_ranged_columns(unsigned width) noexcept { unsigned fixed_px = 0; unsigned minimal_px = 0; @@ -361,23 +355,24 @@ namespace nana return true; //Don't calculate the ranged columns if - //There isn't a ranged column while maximal_px == 0, or + //there isn't a ranged column while maximal_px == 0, or //the minimal ranged size is larger than width if ((0 == maximal_px) || (fixed_px + minimal_px > width)) return false; - if (ranged_px + fixed_px > width) + const bool beyond = (ranged_px + fixed_px > width); + unsigned delta_px = (beyond ? ranged_px + fixed_px - width : width - (ranged_px + fixed_px)); + + while (delta_px) { - auto delta_px = ranged_px + fixed_px - width; - - while (delta_px) + for (auto & col : cont_) { - for (auto & col : cont_) - { - if (0 == delta_px) - break; + if (0 == delta_px) + break; - if (col.visible_state && (col.range_width_px.first < col.range_width_px.second)) + if (col.visible_state && (col.range_width_px.first < col.range_width_px.second)) + { + if (beyond) { if (col.range_width_px.first < col.width_px) { @@ -385,20 +380,7 @@ namespace nana --delta_px; } } - } - } - } - else - { - auto delta_px = width - (ranged_px + fixed_px); - while (delta_px) - { - for (auto & col : cont_) - { - if (0 == delta_px) - break; - - if (col.visible_state && (col.range_width_px.first < col.range_width_px.second)) + else { if (col.width_px < col.range_width_px.second) { @@ -409,38 +391,65 @@ namespace nana } } } - + return true; } - const container& cont() const + const container& cont() const noexcept { return cont_; } - /// find and return a ref to the column that originaly was at position "pos" previous to any list reorganization. - column& at(size_type pos) + size_type cast(size_type pos, bool disp_order) const { - for(auto & m : cont_) + check_range(pos, cont_.size()); + + size_type order = 0; //order for display position + for (auto & m : cont_) { - if (m.index == pos) - return m; + if (!m.visible_state) + continue; + + if (disp_order) + { + if (0 == pos) + return m.index; + --pos; + } + else + { + if (m.index == pos) + return order; + ++order; + } } - throw std::out_of_range("Nana.GUI.Listbox: invalid header index."); + + throw std::invalid_argument("listbox: invalid header index"); } - const column& at(size_type pos) const + /// find and return a ref to the column that originaly was at position "pos" previous to any list reorganization. + column& at(size_type pos, bool disp_order = false) + { + check_range(pos, cont_.size()); + + if (!disp_order) + pos = this->cast(pos, false); + + return cont_[pos]; + } + + const column& at(size_type pos, bool disp_order = false) const { - for(const auto & m : cont_) - { - if (m.index == pos) - return m; - } - throw std::out_of_range("Nana.GUI.Listbox: invalid header index."); + check_range(pos, cont_.size()); + + if (!disp_order) + pos = this->cast(pos, false); + + return cont_[pos]; } /// Returns the position(original index when it is creating) of the current column at point x - size_type column_from_point(int x) const + size_type column_from_point(int x) const noexcept { for (const auto & col : cont_) { @@ -476,60 +485,60 @@ namespace nana } /// return the original index of the visible col currently before(in front of) or after the col originaly at index "index" - size_type neighbor(size_type index, bool front) const + size_type next(size_type index) const noexcept { - size_type n = npos; + bool found_me = false; for(auto i = cont_.cbegin(); i != cont_.cend(); ++i) // in current order { - if(i->index == index) + if (!found_me) { - if(front) return n; - for(++i; i != cont_.cend(); ++i) - { - if(i->visible_state) return i->index; - } - break; + if (i->index == index) + found_me = true; } else if(i->visible_state) - n = i->index; - } - return npos; - } - - /// return the original index of the currently first visible col - size_type begin() const - { - for(const auto & m : cont_) - if(m.visible_state) return m.index; - - return npos; - } - - /// return the original index of the currently last visible col - size_type last() const - { - for(auto i = cont_.rbegin(); i != cont_.rend(); ++i) - { - if(i->visible_state) return i->index; } return npos; } - - /// move the col originaly at "index" to the position currently in front (or after) the col originaly at index "to" invalidating some current index - void move(size_type index, size_type to, bool front) throw() - { - if ((index == to) || (index >= cont_.size()) || (to >= cont_.size())) - return; + /// Returns the absolute position of the first/last visible column. + size_type boundary(bool get_front) const noexcept + { + size_type pos = npos; + for (const auto & m : cont_) + { + if (m.visible_state) + { + if (get_front) + return m.index; + else + pos = m.index; + } + } + + return pos; + } + + /// move the col originaly at "from" to the position currently in front (or after) the col originaly at index "to" invalidating some current index + void move(size_type from, size_type to, bool front) noexcept + { + if ((from == to) || (from >= cont_.size()) || (to >= cont_.size())) + return; + for (auto i = cont_.begin(); i != cont_.end(); ++i) { - if (index == i->index) + if (from == i->index) { auto col_from = std::move(*i); cont_.erase(i); + //A workaround for old libstdc++, that some operations of vector + //don't accept const iterator. +#ifdef _MSC_VER + for (auto u = cont_.cbegin(); u != cont_.cend(); ++u) +#else for (auto u = cont_.begin(); u != cont_.end(); ++u) +#endif { if (to == u->index) { @@ -558,7 +567,7 @@ namespace nana paint::image img; nana::size img_show_size; - struct flags_tag + struct inner_flags { bool selected :1; bool checked :1; @@ -566,7 +575,7 @@ namespace nana mutable std::unique_ptr anyobj; - item_data() + item_data() noexcept { flags.selected = flags.checked = false; } @@ -593,15 +602,6 @@ namespace nana cells->emplace_back(std::move(s)); } - item_data(std::string&& s, const nana::color& bg, const nana::color& fg): - cells(std::make_unique()), - bgcolor(bg), - fgcolor(fg) - { - flags.selected = flags.checked = false; - cells->emplace_back(std::move(s)); - } - item_data& operator=(const item_data& r) { if (this != &r) @@ -632,10 +632,7 @@ namespace nana item_str += exp_opt.sep; //Use the model cells instead if model cells is avaiable - if (model_cells) - item_str += model_cells->operator[](col).text; - else - item_str += (*cells)[col].text; + item_str += (model_cells ? model_cells : cells.get())->operator[](col).text; } return item_str; @@ -662,13 +659,11 @@ namespace nana std::deque>> factories; std::deque> indicators; - category_t() = default; - - category_t(native_string_type str) + category_t(native_string_type str = {}) noexcept :text(std::move(str)) {} - bool selected() const + bool selected() const noexcept { for (auto & m : items) { @@ -677,6 +672,21 @@ namespace nana } return !items.empty(); } + + void make_sort_order() + { + sorted.clear(); + for (std::size_t i = 0; i < items.size(); ++i) + sorted.push_back(i); + } + + std::vector cells(size_type pos) const + { + if (model_ptr) + return model_ptr->container()->to_cells(pos); + + return *(items.at(pos).cells); + } }; struct inline_pane @@ -693,25 +703,35 @@ namespace nana { public: using container = std::list; - using item_type = item_data; std::function(std::size_t) > fetch_ordering_comparer; + struct sort_attributes + { + std::size_t column; ///< The position of the column to be sorted + bool resort; + bool reverse; + }; + es_lister() { //#0 is a default category categories_.emplace_back(); + + sort_attrs_.column = npos; + sort_attrs_.resort = true; + sort_attrs_.reverse = false; } - void bind(essence* ess, widget& wd) + void bind(essence* ess, widget& wd) noexcept { ess_ = ess; widget_ = dynamic_cast(&wd); } - nana::listbox* wd_ptr() const + nana::listbox* wd_ptr() const noexcept { return widget_; } @@ -728,7 +748,7 @@ namespace nana if (allocate_if_empty) { - item.anyobj.reset(new any); + item.anyobj.reset(new ::nana::any); return item.anyobj.get(); } } @@ -750,217 +770,200 @@ namespace nana return panes; } - void emit_checked(index_pair pos) + void emit_cs(const index_pair& pos, bool for_selection) { item_proxy i(ess_, pos); arg_listbox arg{ i }; - wd_ptr()->events().checked.emit(arg, wd_ptr()->handle()); + + auto & events = wd_ptr()->events(); + + if (for_selection) + events.selected.emit(arg, wd_ptr()->handle()); + else + events.checked.emit(arg, wd_ptr()->handle()); auto panes = get_inline_pane(pos); for (auto p : panes) - p->inline_ptr->notify_status(inline_widget_status::checking, i.checked()); - } - - void emit_selected(index_pair pos) - { - item_proxy i(ess_, pos); - arg_listbox arg{ i }; - wd_ptr()->events().selected.emit(arg, wd_ptr()->handle()); - - auto panes = get_inline_pane(pos); - for (auto p : panes) - p->inline_ptr->notify_status(inline_widget_status::selecting, i.selected()); + { + if(for_selection) + p->inline_ptr->notify_status(inline_widget_status::selecting, i.selected()); + else + p->inline_ptr->notify_status(inline_widget_status::checking, i.checked()); + } } // Definition is provided after struct essence unsigned column_content_pixels(size_type pos) const; + const sort_attributes& sort_attrs() const noexcept + { + return sort_attrs_; + } + /// each sort() ivalidate any existing reference from display position to absolute item, that is after sort() display offset point to different items void sort() { - if((sorted_index_ == npos) || (!resort_)) + if((npos == sort_attrs_.column) || (!sort_attrs_.resort)) return; - auto weak_ordering_comp = fetch_ordering_comparer(sorted_index_); + auto weak_ordering_comp = fetch_ordering_comparer(sort_attrs_.column); if(weak_ordering_comp) { for (auto & cat : categories_) { - auto bi = std::begin(cat.sorted); - auto ei = std::end(cat.sorted); + const bool use_model = (cat.model_ptr != nullptr); - if (cat.model_ptr) - { - std::stable_sort(bi, ei, [&cat, &weak_ordering_comp, this](std::size_t x, std::size_t y){ - //The predicate must be a strict weak ordering. - //!comp(x, y) != comp(x, y) + std::stable_sort(cat.sorted.begin(), cat.sorted.end(), [&cat, &weak_ordering_comp, use_model, this](std::size_t x, std::size_t y){ + //The predicate must be a strict weak ordering. + //!comp(x, y) != comp(x, y) + if (use_model) + { auto & mx = cat.items[x]; auto & my = cat.items[y]; auto mx_cells = cat.model_ptr->container()->to_cells(x); auto my_cells = cat.model_ptr->container()->to_cells(y); - if (mx_cells.size() <= sorted_index_ || my_cells.size() <= sorted_index_) + if (mx_cells.size() <= sort_attrs_.column || my_cells.size() <= sort_attrs_.column) { std::string a; - if (mx_cells.size() > sorted_index_) - a = mx_cells[sorted_index_].text; + if (mx_cells.size() > sort_attrs_.column) + a = mx_cells[sort_attrs_.column].text; std::string b; - if (my_cells.size() > sorted_index_) - b = my_cells[sorted_index_].text; + if (my_cells.size() > sort_attrs_.column) + b = my_cells[sort_attrs_.column].text; - return weak_ordering_comp(a, mx.anyobj.get(), b, my.anyobj.get(), sorted_reverse_); + return weak_ordering_comp(a, mx.anyobj.get(), b, my.anyobj.get(), sort_attrs_.reverse); } - return weak_ordering_comp(mx_cells[sorted_index_].text, mx.anyobj.get(), my_cells[sorted_index_].text, my.anyobj.get(), sorted_reverse_); - }); - } - else - { - std::stable_sort(bi, ei, [&cat, &weak_ordering_comp, this](std::size_t x, std::size_t y){ - //The predicate must be a strict weak ordering. - //!comp(x, y) != comp(x, y) + return weak_ordering_comp(mx_cells[sort_attrs_.column].text, mx.anyobj.get(), my_cells[sort_attrs_.column].text, my.anyobj.get(), sort_attrs_.reverse); + } + + auto & mx = cat.items[x]; + auto & my = cat.items[y]; - auto & mx = cat.items[x]; - auto & my = cat.items[y]; + if (mx.cells->size() <= sort_attrs_.column || my.cells->size() <= sort_attrs_.column) + { + std::string a; + if (mx.cells->size() > sort_attrs_.column) + a = (*mx.cells)[sort_attrs_.column].text; - if (mx.cells->size() <= sorted_index_ || my.cells->size() <= sorted_index_) - { - std::string a; - if (mx.cells->size() > sorted_index_) - a = (*mx.cells)[sorted_index_].text; + std::string b; + if (my.cells->size() > sort_attrs_.column) + b = (*my.cells)[sort_attrs_.column].text; - std::string b; - if (my.cells->size() > sorted_index_) - b = (*my.cells)[sorted_index_].text; + return weak_ordering_comp(a, mx.anyobj.get(), b, my.anyobj.get(), sort_attrs_.reverse); + } - return weak_ordering_comp(a, mx.anyobj.get(), b, my.anyobj.get(), sorted_reverse_); - } - - return weak_ordering_comp((*mx.cells)[sorted_index_].text, mx.anyobj.get(), (*my.cells)[sorted_index_].text, my.anyobj.get(), sorted_reverse_); - }); - } + return weak_ordering_comp((*mx.cells)[sort_attrs_.column].text, mx.anyobj.get(), (*my.cells)[sort_attrs_.column].text, my.anyobj.get(), sort_attrs_.reverse); + }); } } else { //No user-defined comparer is provided, and default comparer is applying. for (auto & cat : categories_) { - if (cat.model_ptr) - { - std::stable_sort(std::begin(cat.sorted), std::end(cat.sorted), [&cat, this](std::size_t x, std::size_t y){ + const bool use_model = (cat.model_ptr != nullptr); + + std::stable_sort(cat.sorted.begin(), cat.sorted.end(), [this, &cat, use_model](std::size_t x, std::size_t y){ + //The predicate must be a strict weak ordering. + //!comp(x, y) != comp(x, y) + if (use_model) + { auto mx_cells = cat.model_ptr->container()->to_cells(x); auto my_cells = cat.model_ptr->container()->to_cells(y); - if (mx_cells.size() <= sorted_index_ || my_cells.size() <= sorted_index_) + if (mx_cells.size() <= sort_attrs_.column || my_cells.size() <= sort_attrs_.column) { std::string a; - if (mx_cells.size() > sorted_index_) - a = mx_cells[sorted_index_].text; + if (mx_cells.size() > sort_attrs_.column) + a = mx_cells[sort_attrs_.column].text; std::string b; - if (my_cells.size() > sorted_index_) - b = my_cells[sorted_index_].text; + if (my_cells.size() > sort_attrs_.column) + b = my_cells[sort_attrs_.column].text; - return (sorted_reverse_ ? a > b : a < b); + return (sort_attrs_.reverse ? a > b : a < b); } - auto & a = mx_cells[sorted_index_].text; - auto & b = my_cells[sorted_index_].text; - return (sorted_reverse_ ? a > b : a < b); - }); - } - else - { - std::stable_sort(std::begin(cat.sorted), std::end(cat.sorted), [&cat, this](std::size_t x, std::size_t y){ + auto & a = mx_cells[sort_attrs_.column].text; + auto & b = my_cells[sort_attrs_.column].text; + return (sort_attrs_.reverse ? a > b : a < b); + } - auto & mx = cat.items[x]; - auto & my = cat.items[y]; + auto & mx = cat.items[x]; + auto & my = cat.items[y]; - if (mx.cells->size() <= sorted_index_ || my.cells->size() <= sorted_index_) - { - std::string a; - if (mx.cells->size() > sorted_index_) - a = (*mx.cells)[sorted_index_].text; + if (mx.cells->size() <= sort_attrs_.column || my.cells->size() <= sort_attrs_.column) + { + std::string a; + if (mx.cells->size() > sort_attrs_.column) + a = (*mx.cells)[sort_attrs_.column].text; - std::string b; - if (my.cells->size() > sorted_index_) - b = (*my.cells)[sorted_index_].text; + std::string b; + if (my.cells->size() > sort_attrs_.column) + b = (*my.cells)[sort_attrs_.column].text; - return (sorted_reverse_ ? a > b : a < b); - } + return (sort_attrs_.reverse ? a > b : a < b); + } - auto & a = (*mx.cells)[sorted_index_].text; - auto & b = (*my.cells)[sorted_index_].text; - return (sorted_reverse_ ? a > b : a < b); - }); - } + auto & a = (*mx.cells)[sort_attrs_.column].text; + auto & b = (*my.cells)[sort_attrs_.column].text; + return (sort_attrs_.reverse ? a > b : a < b); + }); } } - scroll_refresh(); } - void scroll_refresh(); - - /// sort() and ivalidate any existing reference from display position to absolute item, that is after sort() display offset point to different items - bool sort_index(size_type index) + /// Sorts the specified column + /** + * It sorts the specified column and invalidates all existing item reference from display position to absolute position. + * The side effect of this method is that all display positions point to different absolute positions. + * @param pos The position of the specified column. + * @param reverse A pointer to a boolean which indicates whether to reverse sort. If this parameter is nullptr, the sort is negated to the current reverse state. + * @return true if the column is sorted, false otherwise. + */ + bool sort_column(std::size_t pos, const bool * reverse) { - if (npos == index) + if (npos == pos) { - sorted_index_ = npos; - scroll_refresh(); + sort_attrs_.column = npos; return false; } - if(index != sorted_index_) + if (reverse) { - sorted_index_ = index; - sorted_reverse_ = false; + if (pos != sort_attrs_.column || *reverse != sort_attrs_.reverse) + { + sort_attrs_.column = pos; + sort_attrs_.reverse = *reverse; + + sort(); + } } else - sorted_reverse_ = !sorted_reverse_; - - sort(); - return true; - } - - /// sort() and ivalidate any existing reference from display position to absolute item, that is: after sort() display offset point to different items - bool set_sort_index(std::size_t index, bool reverse) - { - if (npos == index) { - sorted_index_ = npos; - scroll_refresh(); - return false; - } + if (pos != sort_attrs_.column) + { + sort_attrs_.column = pos; + sort_attrs_.reverse = false; + } + else + sort_attrs_.reverse = !sort_attrs_.reverse; - if(index != sorted_index_ || reverse != sorted_reverse_) - { - sorted_index_ = index; - sorted_reverse_ = reverse; sort(); } return true; } - std::size_t sort_index() const + bool active_sort(bool resort) noexcept { - return sorted_index_; - } - - bool active_sort(bool resort) - { - bool prstatus = resort; - resort_ = resort; + bool prstatus = sort_attrs_.resort; + sort_attrs_.resort = resort; return prstatus; } - bool sort_reverse() const - { - return sorted_reverse_; - } - void scroll(const index_pair& pos, bool to_bottom); /// Append a new category with a specified name and return a pointer to it. @@ -970,11 +973,16 @@ namespace nana return &categories_.back(); } - /// will use the key to insert new cat before the first cat with compare less than the key, or at the end of the list of cat and return a ref to that new cat. ? - category_t* create_cat(std::shared_ptr& ptr) + category_t* create_cat(std::shared_ptr& ptr) { + //A workaround for old version of libstdc++ + //Some operations of vector provided by libstdc++ don't accept const iterator. +#ifdef _MSC_VER + for (auto i = categories_.cbegin(); i != categories_.cend(); ++i) +#else for (auto i = categories_.begin(); i != categories_.end(); ++i) +#endif { if (i->key_ptr) { @@ -985,16 +993,16 @@ namespace nana } else if (ptr->compare(i->key_ptr.get())) { - i = categories_.emplace(i); - i->key_ptr = ptr; - return &(*i); + auto & catobj = *categories_.emplace(i); + catobj.key_ptr = ptr; + return &catobj; } } } categories_.emplace_back(); categories_.back().key_ptr = ptr; - return &categories_.back(); + return &(categories_.back()); } /// add a new cat created at "pos" and return a ref to it @@ -1004,16 +1012,15 @@ namespace nana } /// Insert before item in absolute "pos" a new item with "text" in column 0, and place it in last display position of this cat - void insert(const index_pair& pos, std::string&& text, std::size_t columns) + void insert(const index_pair& pos, std::string&& text, const std::size_t columns) { auto & catobj = *get(pos.cat); - const auto n = catobj.items.size(); - if (pos.item > n) - throw std::out_of_range("listbox: insert an item at invalid position"); + const auto item_count = catobj.items.size(); - catobj.sorted.push_back(n); + check_range(pos.item, item_count); + catobj.sorted.push_back(item_count); if (catobj.model_ptr) { @@ -1021,7 +1028,7 @@ namespace nana auto container = catobj.model_ptr->container(); std::size_t item_index; // - if (pos.item < n) + if (pos.item < item_count) { catobj.items.emplace(catobj.items.begin() + pos.item); container->emplace(pos.item); @@ -1034,43 +1041,62 @@ namespace nana container->emplace_back(); } - std::vector cells{ columns }; - cells[0] = std::move(text); + std::vector cells; + cells.emplace_back(std::move(text)); + cells.resize(columns); container->assign(item_index, cells); + + return; } - else + + catobj.items.emplace(catobj.items.begin() + (pos.item < item_count ? pos.item : item_count), std::move(text)); + } + + /// Converts an index between display position and absolute real position. + /** + * @param from An index to be converted + * @param from_display_order If this parameter is true, the method convert a display position to the absolute position. If the parameter + * is false, the method converts an absolute position to the display position. + * @return A display position or absolute position that are depending on from_display_order. + */ + index_pair index_cast(const index_pair& from, bool from_display_order) const + { + auto cat = get(from.cat); + if (from.item < cat->sorted.size()) { - if (pos.item < n) - catobj.items.emplace(catobj.items.begin() + pos.item, std::move(text)); - else - catobj.items.emplace_back(std::move(text)); + if (from_display_order) + return index_pair{ from.cat, static_cast(cat->sorted[from.item]) }; - catobj.items.back().cells->emplace_back(std::move(text)); + for (size_type i = 0; i < cat->sorted.size(); ++i) + { + if (from.item == cat->sorted[i]) + return index_pair{ from.cat, i }; + } } + throw std::out_of_range("listbox: invalid item position"); } - - /// convert from display order to absolute (find the real item in that display pos) but without check from current active sorting, in fact using just the last sorting !!! - size_type index_by_display_order(size_type cat, size_type display_order_pos) const + + index_pair index_cast_noexcpt(const index_pair& from, bool from_display_order, const index_pair& default_value = index_pair{npos, npos}) const noexcept { - auto & catobj = *get(cat); - if (display_order_pos >= catobj.sorted.size()) - throw std::out_of_range("listbox: Invalid item position."); + if (from.cat < categories_.size()) + { + auto i = categories_.cbegin(); + std::advance(i, from.cat); - return catobj.sorted[display_order_pos]; - } + auto & cat = *i; + if (from.item < cat.sorted.size()) + { + if (from_display_order) + return index_pair{ from.cat, static_cast(cat.sorted[from.item]) }; - /// find display order for the real item but without check from current active sorting, in fact using just the last sorting !!! - size_type display_order(size_type cat, size_type pos) const - { - auto& catobj = *get(cat); - if (pos >= catobj.sorted.size()) - throw std::out_of_range("listbox: Invalid item position."); - - for (size_type i=0; imodel_ptr) { throw_if_immutable_model(i->model_ptr.get()); - i->model_ptr->container()->assign(pos.item, cells); } } } - bool have_model(const index_pair& pos) const + bool has_model(const index_pair& pos) const { return (get(pos.cat)->model_ptr != nullptr); } @@ -1132,12 +1157,20 @@ namespace nana /// return a ref to the real item object at display!!! position pos using current sorting only if it is active, and at absolute position if no sorting is currently active. category_t::container::value_type& at(const index_pair& pos) { - auto index = pos.item; + auto acc_pos = pos.item; + if (npos != sort_attrs_.column) + acc_pos = index_cast(pos, true).item; //convert display position to absolute position - if (sorted_index_ != npos) - index = absolute(pos); + return get(pos.cat)->items.at(acc_pos); + } - return get(pos.cat)->items.at(index); + const category_t::container::value_type& at(const index_pair& pos) const + { + auto acc_pos = pos.item; + if (npos != sort_attrs_.column) + acc_pos = index_cast(pos, true).item; //convert display position to absolute position + + return get(pos.cat)->items.at(acc_pos); } std::vector at_model(const index_pair& pos) const @@ -1148,22 +1181,11 @@ namespace nana model_lock_guard lock(model_ptr); - auto index = pos.item; + auto acc_pos = pos.item; + if (npos != sort_attrs_.column) + acc_pos = index_cast(pos, true).item; //convert display position to absolute position - if (sorted_index_ != npos) - index = absolute(pos); - - return model_ptr->container()->to_cells(index); - } - - const category_t::container::value_type& at(const index_pair& pos) const - { - auto index = pos.item; - - if (sorted_index_ != npos) - index = absolute(pos); - - return get(pos.cat)->items.at(index); + return model_ptr->container()->to_cells(acc_pos); } void append_active_panes(inline_pane* p) @@ -1196,70 +1218,81 @@ namespace nana // Clears all items in all cat, but not the container of cat self. void clear() { - // Check whether there is a immutable model + // Check whether there is a immutable model before performing clear. for (auto & cat : categories_) throw_if_immutable_model(cat.model_ptr.get()); - auto n = categories_.size(); - for (decltype(n) i = 0; i < n; ++i) - { + for (size_type i = 0; i < categories_.size(); ++i) clear(i); - } } - index_pair advance(index_pair from, size_type offset) // <------------- index + index_pair advance(const index_pair& pos, int n) const { - index_pair dpos{npos, npos}; - if(from.cat >= size_categ() || (from.item != npos && from.item >= size_item(from.cat))) - return dpos; + const auto cat_size = categories_.size(); + index_pair dpos{ npos, npos }; + + if (pos.cat >= cat_size || (pos.item != npos && pos.item >= size_item(pos.cat))) + return dpos; - dpos = from; + if ((0 == pos.cat && npos == pos.item) || (!expand(pos.cat) && (npos != pos.item))) + return dpos; - while(offset) + if (0 == n) + return pos; + + dpos = pos; + if (0 < n) { - if(dpos.item == npos) + //Forward + std::size_t index = (npos == pos.item ? 0 : pos.item + 1); + + while (n) { - if(expand(dpos.cat) == false) - { - if(dpos.cat + 1 == size_categ()) - break; - ++dpos.cat; - } - else - dpos.item = 0; - --offset; + std::size_t end = 1; + if (expand(dpos.cat)) + end += size_item(dpos.cat); + + if (n < static_cast(end - index)) + return index_pair{ dpos.cat, index + n - 1 }; + + ++dpos.cat; + if (cat_size == dpos.cat) + return index_pair{ npos, npos }; + + n -= static_cast(end - index); + index = 0; } - else + return index_pair{ dpos.cat, npos }; + } + + //Backward + dpos = pos; + if (good(dpos.cat)) + { + auto count = static_cast(dpos.is_category() ? 1 : pos.item + 2); + auto i = get(pos.cat); + while (true) { - size_type rest = size_item(dpos.cat) - dpos.item - 1; - if(rest == 0) + if (count > n) { - if(dpos.cat + 1 == size_categ()) - break; - ++dpos.cat; - dpos.item = npos; - --offset; + count -= n; + dpos.item = (count == 1 ? npos : count - 2); + return dpos; } - else if(rest < offset) - { - offset -= rest; - if(dpos.cat + 1 >= size_categ()) - { - dpos.item += rest; - break; - } - dpos.item = npos; - ++dpos.cat; - } - else - { - dpos.item += offset; + + n -= count; + + if (i == categories_.cbegin()) break; - } + + --i; + --dpos.cat; + count = static_cast(i->expand ? i->items.size() : 0) + 1; } } - return dpos; + return index_pair{npos, npos}; } + /// change to index arg size_type distance(index_pair from, index_pair to) const { @@ -1268,55 +1301,36 @@ namespace nana if(to.cat == from.cat) { if(from.item > to.item && from.item != npos) - std::swap(from.item, to.item); + std::swap(from, to); return (from.item == npos ? to.item + 1 : to.item - from.item); } else if(to.cat < from.cat) std::swap(from, to); - size_type n = 0; - auto i = get(from.cat); - if(from.item == npos) + std::size_t count = 1; + for (auto i = get(from.cat); i != get(to.cat); ++i) { - if(i->expand) - n = i->items.size(); - } - else - n = i->items.size() - (from.item + 1); - - for(++i, ++from.cat; i != categories_.end(); ++i, ++from.cat) - { - ++n; //this is a category - if(from.cat != to.cat) - { - if(i->expand) - n += i->items.size(); - } + if (i->expand) + count += i->items.size() + 1; else - { - if(to.item != npos) - n += (to.item + 1); - break; - } + ++count; } - return n; + + if (npos != to.item) + count += (1 + to.item); + + if (npos != from.item) + count -= (1 + from.item); + else if (0 == from.cat) + --count; + + return count - 1; } - std::vector get_cells(category_t * cat, std::size_t pos) const + void text(category_t* cat, size_type pos, size_type abs_col, cell&& cl, size_type columns) { - if (!cat) - throw std::out_of_range("nana::listbox: category is null"); - - if (cat->model_ptr) - return cat->model_ptr->container()->to_cells(pos); - - return *(cat->items.at(pos).cells); - } - - void text(category_t* cat, size_type pos, size_type col, cell&& cl, size_type columns) - { - if ((col < columns) && (pos < cat->items.size())) + if ((abs_col < columns) && (pos < cat->items.size())) { std::vector model_cells; @@ -1329,16 +1343,16 @@ namespace nana auto & cells = (cat->model_ptr ? model_cells : *(cat->items[pos].cells)); - if (col < cells.size()) + if (abs_col < cells.size()) { - cells[col] = std::move(cl); - if (sorted_index_ == col) + cells[abs_col] = std::move(cl); + if (sort_attrs_.column == abs_col) sort(); } else { //If the index of specified sub item is over the number of sub items that item contained, //it fills the non-exist items. - cells.resize(col); + cells.resize(abs_col); cells.emplace_back(std::move(cl)); } @@ -1347,9 +1361,9 @@ namespace nana } } - void text(category_t* cat, size_type pos, size_type col, std::string&& str, size_type columns) + void text(category_t* cat, size_type pos, size_type abs_col, std::string&& str, size_type columns) { - if ((col < columns) && (pos < cat->items.size())) + if ((abs_col < columns) && (pos < cat->items.size())) { std::vector model_cells; @@ -1362,16 +1376,16 @@ namespace nana auto & cells = (cat->model_ptr ? model_cells : *(cat->items[pos].cells)); - if (col < cells.size()) + if (abs_col < cells.size()) { - cells[col].text.swap(str); - if (sorted_index_ == col) + cells[abs_col].text = std::move(str); + if (sort_attrs_.column == abs_col) sort(); } else { //If the index of specified sub item is over the number of sub items that item contained, //it fills the non-exist items. - cells.resize(col); + cells.resize(abs_col); cells.emplace_back(std::move(str)); } @@ -1405,24 +1419,24 @@ namespace nana void erase() { //Do not remove the first category. - auto i = categories_.begin(); - if (i->model_ptr) - { - throw_if_immutable_model(i->model_ptr.get()); - i->model_ptr->container()->clear(); - } - - i->items.clear(); - i->sorted.clear(); + this->erase(0); if (categories_.size() > 1) - categories_.erase(++i, categories_.end()); + { + //A workaround for old version of libstdc++ + //Some operations of vector provided by libstdc++ don't accept const iterator. +#ifdef _MSC_VER + categories_.erase(++categories_.cbegin(), categories_.cend()); +#else + categories_.erase(++categories_.begin(), categories_.end()); +#endif + } } - bool expand(size_type cat, bool exp) + bool expand(size_type cat, bool exp) noexcept { - if(good(cat)) + if(good(cat) && cat) { auto & expanded = get(cat)->expand; if(expanded != exp) @@ -1434,23 +1448,23 @@ namespace nana return false; } - bool expand(size_type cat) const + bool expand(size_type cat) const noexcept { return (good(cat) ? get(cat)->expand : false); } - container& cat_container() + container& cat_container() noexcept { return categories_; } - const container& cat_container() const + const container& cat_container() const noexcept { return categories_; } //Enable/Disable the ordered categories - bool enable_ordered(bool enb) + bool enable_ordered(bool enb) noexcept { if (ordered_categories_ != enb) { @@ -1478,12 +1492,12 @@ namespace nana return true; } - bool enable_ordered() const + bool enable_ordered() const noexcept { return ordered_categories_; } - size_type the_number_of_expanded() const + size_type the_number_of_expanded() const noexcept { size_type n = categories_.size() - 1; for (auto & i : categories_) @@ -1494,98 +1508,91 @@ namespace nana return n; } - void check_for_all(bool ck) - { - index_pair pos; - for (auto & cat : categories_) - { - pos.item = 0; - for(auto & m : cat.items) - { - if(m.flags.checked != ck) - { - m.flags.checked = ck; - emit_checked(pos); - } - ++pos.item; - } - ++pos.cat; - } - } - - void select_range(index_pair fr, index_pair to, bool sel) - { - if (fr > to) - std::swap(fr, to); - - for (; fr != to; forward(fr, 1, fr)) - { - if (fr.is_item()) - item_proxy(ess_, fr).select(sel); - } - - if (to.is_item()) - item_proxy(ess_, to).select(sel); - } - - void select_display_range(index_pair fr_abs, index_pair to_dpl, bool sel) + template + std::vector> select_display_range_if(index_pair fr_abs, index_pair to_dpl, bool unselect_others, Pred pred) { const auto already_selected = this->pick_items(true); - index_pair fr_dpl (fr_abs.cat, this->display_order(fr_abs.cat, fr_abs.item)); + auto fr_dpl = this->index_cast(fr_abs, false); //Converts an absolute position to display position if (fr_dpl > to_dpl) std::swap(fr_dpl, to_dpl); const auto begin = fr_dpl; const auto last = to_dpl; - for (; fr_dpl != to_dpl; forward(fr_dpl, 1, fr_dpl)) + //pair first: index in the range of [begin, last] + //pair second: indicates whether the index is selected before selection. + std::vector> pairs; + + for (; fr_dpl != to_dpl; fr_dpl = advance(fr_dpl, 1)) { - if (fr_dpl.is_item()) - item_proxy(ess_, index_pair(fr_dpl.cat, absolute( fr_dpl ) )).select(sel); + if (!fr_dpl.is_category()) + { + auto abs_pos = index_cast(fr_dpl, true); //convert display position to absolute position + item_proxy m{ ess_, abs_pos }; + pairs.emplace_back(abs_pos, m.selected()); + + if (pred(abs_pos)) + m.select(true); + } } - if (to_dpl.is_item()) - item_proxy(ess_, index_pair(to_dpl.cat, absolute( to_dpl ) )).select(sel); - - //Unselects the already selected which is out of range [begin, last] - for (auto index : already_selected) + if (!to_dpl.is_category()) { - index_pair disp_order{ index.cat, this->display_order(index.cat, index.item) }; - if (begin > disp_order || disp_order > last) - item_proxy(ess_, index_pair(index.cat, absolute(disp_order))).select(false); + auto abs_pos = index_cast(to_dpl, true); //convert display position to absolute position + + item_proxy m(ess_, abs_pos); + pairs.emplace_back(abs_pos, m.selected()); + + if (pred(abs_pos)) + m.select(true); } + + if (unselect_others) + { + //Unselects the already selected which is out of range [begin, last] + for (auto index : already_selected) + { + auto disp_order = this->index_cast(index, false); //converts an absolute position to a display position + if (begin > disp_order || disp_order > last) + item_proxy{ ess_, index }.select(false); + } + } + + return pairs; } - bool select_for_all(bool sel) + bool select_for_all(bool sel, const index_pair& except = index_pair{npos, npos}) { bool changed = false; - index_pair i; + index_pair pos; for (auto & cat : categories_) { - i.item = 0; + pos.item = 0; for(auto & m : cat.items) { - if(m.flags.selected != sel) + if (except != pos) { - changed = true; - m.flags.selected = sel; + if (m.flags.selected != sel) + { + changed = true; + m.flags.selected = sel; - this->emit_selected(i); + this->emit_cs(pos, true); - if (m.flags.selected) - last_selected_abs = i; - else if (last_selected_abs == i) - last_selected_abs.set_both(npos); //make empty + if (m.flags.selected) + last_selected_abs = pos; + else if (last_selected_abs == pos) + last_selected_abs.set_both(npos); //make empty + } } - ++i.item; + ++pos.item; } - ++i.cat; + ++pos.cat; } return changed; } - /// return absolute positions, no relative to display index_pairs pick_items(bool for_selection) const { @@ -1606,23 +1613,6 @@ namespace nana return results; } - index_pair find_first_selected() - { - index_pair id; - for (auto & cat : categories_) - { - id.item = 0; - for(auto & m : cat.items) - { - if(m.flags.selected) - return id; // absolute positions, no relative to display - ++id.item; - } - ++id.cat; - } - return index_pair{npos,npos}; - } - /// return absolute positions, no relative to display bool item_selected_all_checked(index_pairs& vec) const { @@ -1650,30 +1640,45 @@ namespace nana ///emit_cs(item_pos, for_selection); + } + }; void cancel_others_if_single_enabled(bool for_selection, const index_pair& except) { if (!(for_selection ? single_selection_ : single_check_)) return; - auto pred = [for_selection](category_t::container::value_type & m){ - return (for_selection ? m.flags.selected : m.flags.checked); - }; - - auto do_cancel = [this, for_selection](category_t::container::value_type& m, const index_pair& item_pos) - { - if (for_selection) - { - m.flags.selected = false; - this->emit_selected(item_pos); - } - else - { - m.flags.checked = false; - this->emit_checked(item_pos); - } - }; + pred_cancel pred{ for_selection }; + emit_cancel do_cancel{ this, for_selection }; if (for_selection ? single_selection_category_limited_ : single_check_category_limited_) { @@ -1690,42 +1695,26 @@ namespace nana } else { - std::size_t cat_pos = 0; + index_pair cancel_pos; for (auto & cat : categories_) { - if (cat_pos != except.cat) + for (auto & m : cat.items) { - std::size_t item_pos = 0; - for (auto & m : cat.items) - { - if (pred(m)) - do_cancel(m, index_pair{ cat_pos, item_pos }); - ++item_pos; - } + if ((cancel_pos != except) && pred(m)) + do_cancel(m, cancel_pos); + + ++cancel_pos.item; } - else - { - std::size_t item_pos = 0; - for (auto & m : cat.items) - { - if ((item_pos != except.item) && pred(m)) - do_cancel(m, index_pair{ cat_pos, item_pos }); - ++item_pos; - } - } - ++cat_pos; + + ++cancel_pos.cat; + cancel_pos.item = 0; } } } - bool single_selection() const + bool single_status(bool for_selection) const noexcept { - return single_selection_; - } - - bool single_check() const - { - return single_check_; + return (for_selection ? single_selection_ : single_check_); } void enable_single(bool for_selection, bool category_limited) @@ -1739,152 +1728,90 @@ namespace nana single = true; limited = category_limited; - auto pred = [for_selection](category_t::container::value_type & m){ - return (for_selection ? m.flags.selected : m.flags.checked); - }; - - auto cancel = [this, for_selection](category_t::container::value_type& m, const index_pair& item_pos) - { - if (for_selection) - { - m.flags.selected = false; - this->emit_selected(item_pos); - } - else - { - m.flags.checked = false; - this->emit_checked(item_pos); - } - }; + pred_cancel pred{ for_selection }; + emit_cancel cancel{ this, for_selection }; std::size_t cat_pos = 0; - if (category_limited) - { - for (auto & cat : categories_) - { - auto i = std::find_if(cat.items.begin(), cat.items.end(), pred); - if (i != cat.items.end()) - { - ++i; - for (auto end = cat.items.end(); i != end; ++i) - { - if (pred(*i)) - cancel(*i, index_pair{ cat_pos, static_cast(i - cat.items.begin()) }); - } - } - ++cat_pos; - } - } - else - { - bool selected = false; - for (auto & cat : categories_) - { - if(!selected) - { - const auto end = cat.items.end(); - auto i = std::find_if(cat.items.begin(), end, pred); - if (i != end) + bool selected = false; + + for (auto & cat : categories_) + { + if ((category_limited) || (!selected)) + { + bool ignore = true; //Ignore the first matched item + std::size_t pos = 0; + for (auto & m : cat.items) + { + if (pred(m)) { selected = true; - for (++i; i != end; ++i) - { - if (pred(*i)) - cancel(*i, index_pair{ cat_pos, static_cast(i - cat.items.begin()) }); - } + if (ignore) + ignore = false; + else + cancel(m, index_pair{ cat_pos, pos }); } + ++pos; } - else + ++cat_pos; + } + else + { + std::size_t skip_cat = 0; + for (auto & cat : categories_) { - for (auto & cat : categories_) - { - std::size_t item_pos = 0; - for (auto & m : cat.items) - { - if (pred(m)) - cancel(m, index_pair{ cat_pos, item_pos }); + if (skip_cat++ < cat_pos) + continue; - ++item_pos; - } + std::size_t pos = 0; + for (auto & m : cat.items) + { + if (pred(m)) + cancel(m, index_pair{ cat_pos, pos }); + ++pos; } ++cat_pos; } + break; } } } - void disable_single(bool for_selection) + void disable_single(bool for_selection) noexcept { (for_selection ? single_selection_ : single_check_) = false; } - size_type size_categ() const - { - return categories_.size(); - } - size_type size_item(size_type cat) const { return get(cat)->items.size(); } - bool categ_checked(size_type cat) const + bool cat_status(size_type pos, bool for_selection) const { - auto& items = get(cat)->items; - for(auto & m : items) + auto & items = get(pos)->items; + for (auto & m : items) { - if(m.flags.checked == false) + if ((for_selection ? m.flags.selected : m.flags.checked) == false) return false; } return true; } - bool categ_checked(size_type cat, bool ck) - { - bool changed = false; - auto & items = get(cat)->items; - size_type index = 0; - for(auto & m : items) - { - if(m.flags.checked != ck) - { - m.flags.checked = ck; - this->emit_checked(index_pair{cat, index}); - changed = true; - } - ++index; - } - return changed; - } + bool cat_status(size_type pos, bool for_selection, bool value); - bool categ_checked_reverse(size_type cat_index) + bool cat_status_reverse(size_type pos, bool for_selection) noexcept { - if (categories_.size() > cat_index) - return categ_checked(cat_index, !categ_checked(cat_index)); + if (pos < categories_.size()) + { + return cat_status(pos, !cat_status(pos, for_selection)); + } return false; } - bool categ_selected(size_type cat) const - { - auto & items = get(cat)->items; - for(auto & m : items) - if(m.flags.selected == false) - return false; - return true; - } - - /// set all items in cat to selection sel, emiting events, actualizing last_selected_abs, but not check for single_selection_ - void categ_selected(size_type cat, bool sel); - - void reverse_categ_selected(size_type categ) - { - categ_selected(categ, ! categ_selected(categ)); - } - /// can be used as the absolute position of the last absolute item, or as the display pos of the last displayed item - index_pair last() const + index_pair last() const noexcept { index_pair i{ categories_.size() - 1, categories_.back().items.size() }; @@ -1901,37 +1828,26 @@ namespace nana return i; } - /// absolute position of the last displayed item - index_pair last_displ() const - { - return index_pair{ absolute(last()) }; - } - /// can be used as the absolute position of the first absolute item, or as the display pos of the first displayed item - index_pair first() const + index_pair first() const noexcept { index_pair fst{0,npos}; good_item(fst,fst); return fst; } - /// absolute position of the first displayed item - index_pair first_displ() const - { - return index_pair{ absolute(first()) }; - } - bool good(size_type cat) const + bool good(size_type cat) const noexcept { return (cat < categories_.size()); } - bool good(const index_pair& pos) const + bool good(const index_pair& pos) const noexcept { return ((pos.cat < categories_.size()) && (pos.item < size_item(pos.cat))); } /// if good return the same item (in arg item), or just the next cat and true, but If fail return false - bool good_item(index_pair pos, index_pair& item) const + bool good_item(const index_pair& pos, index_pair& item) const noexcept { if (!good(pos.cat)) return false; // cat out of range @@ -1952,7 +1868,7 @@ namespace nana return true; } - if (++i == categories_.end()) // item out of range and no more cat + if (++i == categories_.cend()) // item out of range and no more cat return false; item.cat = pos.cat + 1; // select the next cat @@ -1960,137 +1876,10 @@ namespace nana return true; } - ///Translate relative position (position in display) into absolute position (original data order) - size_type absolute(const index_pair& display_pos) const - { - if(sorted_index_ == npos || display_pos.item == npos) - return display_pos.item ; - - auto & catobj = *get(display_pos.cat); - if(catobj.items.empty()) - return (display_pos == index_pair{0,0} ? 0 : npos); - - return (display_pos.item < catobj.sorted.size() ? catobj.sorted[display_pos.item] : npos); - } - - index_pair absolute_pair(const index_pair& display_pos) const - { - //Returns an empty pos if item pos npos - auto item_pos = absolute(display_pos); - return index_pair{item_pos != npos ? display_pos.cat : npos, item_pos}; - } - - ///Translate absolute position (original data order) into relative position (position in display) - size_type relative(const index_pair& pos) const - { - if (sorted_index_ == npos) - return pos.item ; - - auto& catobj = *get(pos.cat); - - for (size_type i=0; iexpand ? cat->items.size() : 0); - - if (from.is_category()) - items_left += 1; //add 1 category bar - else if (items_left >= from.item) - items_left -= from.item; - else - return false; //invalid argument - - while (offs) - { - if (items_left > offs) - { - item.cat = from.cat; - item.item = (npos == from.item ? offs - 1 : from.item + offs); - return true; - } - - offs -= items_left; - if (++cat == cat_end) - return false; - - ++from.cat; - from.item = npos; - items_left = (cat->expand ? cat->items.size() + 1 : 1); - } - - item = from; - return true; - } - - /// all arg are relative to display order, or all are absolute, but not mixed - bool backward(index_pair from, size_type offs, index_pair& item) const - { - if(offs == 0) - item = from; - - if(good(from.cat)) - { - auto i = get(from.cat); - size_type n = (from.is_category() ? 1 : from.item + 2); // ?? - - if (n > offs) - { - n -=offs; - item.cat = from.cat; - item.item = (n == 1 ? npos : n - 2); - return true; - } - - offs -= n; - while (i != categories_.cbegin()) - { - --i; - --from.cat; - - n = (i->expand ? i->items.size() : 0) + 1; - - if(n > offs) - { - n -=offs; - item.cat = from.cat; - item.item = (n == 1 ? npos : n - 2); - return true; - } - else - offs -= n; - } - } - return false; - } - /// categories iterator container::iterator get(size_type pos) { - if (pos >= categories_.size()) - throw std::out_of_range("nana::listbox: invalid category index"); + check_range(pos, categories_.size()); auto i = categories_.begin(); std::advance(i, pos); @@ -2099,8 +1888,7 @@ namespace nana container::const_iterator get(size_type pos) const { - if (pos >= categories_.size()) - throw std::out_of_range("nana::listbox: invalid category index"); + check_range(pos, categories_.size()); auto i = categories_.cbegin(); std::advance(i, pos); @@ -2111,9 +1899,9 @@ namespace nana private: essence * ess_{nullptr}; nana::listbox * widget_{nullptr}; - std::size_t sorted_index_{npos}; ///< The index of the column used to sort - bool resort_{true}; - bool sorted_reverse_{false}; + + sort_attributes sort_attrs_; //Attributes of sort + bool ordered_categories_{false}; ///< A switch indicates whether the categories are ordered. /// The ordered categories always creates a new category at a proper position(before the first one which is larger than it). container categories_; @@ -2131,7 +1919,7 @@ namespace nana struct essence { enum class item_state{normal, highlighted, pressed, grabbed, floated}; - enum class parts{unknown = -1, header, lister, checker}; + enum class parts{unknown = -1, header, list, list_blank, checker}; ::nana::listbox* listbox_ptr{nullptr}; ::nana::listbox::scheme_type* scheme_ptr{nullptr}; @@ -2139,6 +1927,7 @@ namespace nana bool auto_draw{true}; bool checkable{false}; bool if_image{false}; + unsigned text_height; ::nana::listbox::export_options def_exp_options; @@ -2151,20 +1940,23 @@ namespace nana //if where == lister || where == checker, 'second' indicates the offset to the scroll offset_y which stands for the first item displayed in lister. //if where == unknown, 'second' ignored. - struct scroll_part + std::unique_ptr content_view; + + struct mouse_selection_part { - static const unsigned scale = 16; // ? + bool started{ false }; + bool reverse_selection{ false }; - unsigned x_offset() const - { - return static_cast(h.empty() ? 0 : h.value()); - } + point screen_pos; + point begin_position; ///< Logical position to the + point end_position; + index_pairs already_selected; + index_pairs selections; - index_pair offset_y_abs, offset_y_dpl; //cat stands for category, item stands for item. "item == npos" means that is a category. - // need to be abs??? to see the same item after sort() ?? - nana::scroll v; - nana::scroll h; - }scroll; + bool scroll_direction; + unsigned scroll_step{ 1 }; + unsigned mouse_move_timestamp{ 0 }; + }mouse_selection; std::map>> inline_table, inline_buffered_table; @@ -2175,318 +1967,256 @@ namespace nana lister.fetch_ordering_comparer = [this](std::size_t pos) -> std::function { - try - { - return header.at(pos).weak_ordering; - } - catch (...) - { - } - - return {}; + return header.at(pos).weak_ordering; }; } + void resize_disp_area() + { + auto head_px = this->header_visible_px(); + + auto r = this->content_area(); + r.y += head_px; + + if (r.height <= head_px) + r.height = 0; + else + r.height -= head_px; + + this->content_view->disp_area(r, { -1, 0 }, { 1, -static_cast(head_px) }, { 2, head_px }); + } + + size_type column_from_pos(int screen_x) const + { + return header.column_from_point(screen_x - content_area().x + content_view->origin().x); + } + std::string to_string(const export_options& exp_opt) const { return header.to_string(exp_opt) + exp_opt.endl + lister.to_string(exp_opt) ; } - const index_pair& scroll_y_abs() const + int content_position(const index_pair& pos) const { - return scroll.offset_y_abs; - } - const index_pair& scroll_y_dpl() const - { - return scroll.offset_y_dpl; - } - const index_pair& scroll_y_dpl_refresh() - { - return scroll.offset_y_dpl = lister.relative_pair(scroll.offset_y_abs); + return static_cast(lister.distance(lister.first(), pos) * this->item_height()); } - void scroll_y_abs(const index_pair& pos_abs) + index_pair first_display() const noexcept { - if (!lister.good(pos_abs.cat)) + auto offset_display = content_view->origin().y / item_height(); + return lister.advance(lister.first(), offset_display); + } + + unsigned item_height() const noexcept + { + auto px = (std::max)(scheme_ptr->item_height_ex + text_height, unsigned(1)); + content_view->step(px, false); + return px; + } + + point coordinate_cast(const point& from, bool from_screen) noexcept + { + rectangle orignal; + if (!rect_lister(orignal)) + return{}; + + const auto origin = this->content_view->origin(); + + if (from_screen) + return from - orignal.position() + origin; + + return from + orignal.position() - origin; + } + + struct pred_mouse_selection + { + index_pair pos_; + pred_mouse_selection(index_pair pos) noexcept + : pos_(pos) + {} + + bool operator()(const std::pair& m) const noexcept + { + return (pos_ == m.first); + } + }; + + std::pair columns_range() const + { + rectangle r; + if (!rect_header(r)) + return{}; + + auto origin = content_view->origin(); + return{ r.x - origin.x, r.x - origin.x + static_cast(header.pixels()) }; + } + + void start_mouse_selection(const point& screen_pos) + { + auto logic_pos = coordinate_cast(screen_pos, true); + + mouse_selection.started = true; + mouse_selection.begin_position = logic_pos; + mouse_selection.end_position = logic_pos; + + mouse_selection.already_selected = lister.pick_items(true); + + API::set_capture(*listbox_ptr, true); + } + + void update_mouse_selection(const point& screen_pos) + { + mouse_selection.screen_pos = screen_pos; + + auto logic_pos = coordinate_cast(screen_pos, true); + auto imd_area = content_view->view_area(); + + if (logic_pos.y > mouse_selection.begin_position.y) + { + //The top of logic_pos shouldn't be less than the top of imd_area. + //This is a feature that listbox always shows the first displayed item on the screen as a selected item when + //move the cursor upwards. + logic_pos.y = (std::max)(logic_pos.y, coordinate_cast(imd_area.position(), true).y); + } + + mouse_selection.end_position = logic_pos; + + auto content_x = coordinate_cast({ columns_range().first, 0 }, true).x; + if ((std::max)(mouse_selection.end_position.x, mouse_selection.begin_position.x) <= content_x || + (std::min)(mouse_selection.end_position.x, mouse_selection.begin_position.x) >= content_x + static_cast(header.pixels()) + ) + { + lister.select_for_all(false); return; - - scroll.offset_y_abs.cat = pos_abs.cat; - - size_type number = lister.size_item(pos_abs.cat); - if(pos_abs.item < number) - scroll.offset_y_abs.item = pos_abs.item; - else if(number) - scroll.offset_y_abs.item = number - 1; - else - { - scroll.offset_y_abs.item = (pos_abs.cat > 0 ? npos : 0); - scroll.offset_y_dpl = scroll.offset_y_abs ; - return ; - } - scroll_y_dpl_refresh() ; - } - - /// directly set a tested relative display pos - void set_scroll_y_dpl(index_pair pos_dpl) - { - if (lister.first() != pos_dpl) - { - //check the pos_dpl to make sure the last item is at bottom of listbox - const auto numbers = this->number_of_lister_items(false); - const auto distance = lister.distance(pos_dpl, lister.last()); - if (numbers > 1 && distance < numbers) - lister.backward(lister.last(), numbers - 1, pos_dpl); } - scroll.offset_y_dpl = pos_dpl; - if (pos_dpl.is_category()) - scroll.offset_y_abs = pos_dpl; - else - scroll.offset_y_abs = lister.absolute_pair(pos_dpl); + auto begin_off = (std::max)((std::min)(mouse_selection.begin_position.y, mouse_selection.end_position.y), 0) / item_height(); + auto last_off = (std::max)(mouse_selection.begin_position.y, mouse_selection.end_position.y) / item_height(); - if (scroll.offset_y_abs.empty()) - throw std::invalid_argument("nana.listbox.set_scroll_y_dpl's exception is due to invalid item, please report a bug"); - } + auto begin = lister.advance(lister.first(), begin_off); + auto last = lister.advance(lister.first(), last_off); + if (!lister.good(last)) + last = lister.last(); - //number_of_lister_item - /// @brief Returns the number of items that are contained on screen. - /// @param with_rest: Means whether including extra one item that is not completely contained in reset pixels. - size_type number_of_lister_items(bool with_rest) const - { - unsigned lister_s = graph->height() - 2 - header_visible_px() - (scroll.h.empty() ? 0 : scroll.scale); - return (lister_s / scheme_ptr->item_height) + (with_rest && (lister_s % scheme_ptr->item_height) ? 1 : 0); - } + if (lister.good(begin) && ((mouse_selection.end_position.y < 0) || (lister.distance(lister.first(), begin) == begin_off))) + { + auto selections = lister.select_display_range_if(begin, last, false, [this](const index_pair& abs_pos) { + if (this->mouse_selection.reverse_selection) + { + if(mouse_selection.already_selected.cend() != std::find(mouse_selection.already_selected.cbegin(), mouse_selection.already_selected.cend(), abs_pos)) + { + item_proxy{ this, abs_pos }.select(false); + return false; + } + } + return true; + }); - //keep the first selected item in the display area: the distances are in display positions! - void trace_item_dpl( index_pair dpl_pos ) - { - if( dpl_pos.cat < scroll.offset_y_dpl.cat // in prevoious cat ---------------- up ----> we need to move - || ((dpl_pos.cat == scroll.offset_y_dpl.cat) && ( scroll.offset_y_dpl.item != npos) // is our cat, where we are an item - && (dpl_pos.item == npos || dpl_pos.item < scroll.offset_y_dpl.item))) - // problem!!!!!! - { - if(lister.expand(dpl_pos.cat) == false) + for (auto & pair : selections) { - if(lister.categ_selected(dpl_pos.cat)) - dpl_pos.item = static_cast(npos); - else - lister.expand(dpl_pos.cat, true); + if (pair.second) + continue; + + if (mouse_selection.selections.cend() == + std::find(mouse_selection.selections.cbegin(), mouse_selection.selections.cend(), pair.first)) + { + mouse_selection.selections.push_back(pair.first); + } + } + +#ifdef _MSC_VER + for(auto i = mouse_selection.selections.cbegin(); i != mouse_selection.selections.cend();) +#else + for(auto i = mouse_selection.selections.begin(); i != mouse_selection.selections.end();) + +#endif + { + if (selections.cend() == std::find_if(selections.cbegin(), selections.cend(), pred_mouse_selection{*i})) + { + item_proxy{ this, *i }.select(false); + i = mouse_selection.selections.erase(i); + } + else + ++i; + } + + if (mouse_selection.reverse_selection) + { + for (auto & abs_pos : mouse_selection.already_selected) + { + if (selections.cend() == std::find_if(selections.cbegin(), selections.cend(), pred_mouse_selection{abs_pos})) + { + item_proxy{ this, abs_pos }.select(true); + } + } } - set_scroll_y_dpl(dpl_pos); // <------------------------- set scroll.offset_y_dpl & scroll.offset_y_abs } else { - size_type numbers = number_of_lister_items(false); // revise ... ok - size_type off = lister.distance(scroll.offset_y_dpl, dpl_pos); - if(numbers > off) return; - index_pair n_off = lister.advance(scroll.offset_y_dpl, (off - numbers) + 1); + for (auto & pos : mouse_selection.selections) + { + item_proxy{ this, pos }.select(false); + } - if(n_off.cat != npos) // <------------------------- set scroll.offset_y_dpl & scroll.offset_y_abs - set_scroll_y_dpl(n_off); + mouse_selection.selections.clear(); + + if (mouse_selection.reverse_selection) + { + for (auto & abs_pos : mouse_selection.already_selected) + { + item_proxy{ this, abs_pos }.select(true); + } + } } + } - adjust_scroll_life(); // call adjust_scroll_value(); //adjust_scroll_value(); // again? - } - - void trace_last_selected_item( ) - { - if (lister.last_selected_abs.item == npos && - lister.last_selected_abs.cat == scroll.offset_y_abs.cat && - scroll.offset_y_abs.item == npos) // if item==off y and is a cat - return; - - trace_item_dpl(lister.relative_pair(lister.last_selected_abs)); // ??? scroll_y_dpl_refresh() ; - } - - void update() + void stop_mouse_selection() noexcept { - if(auto_draw && lister.wd_ptr()) + mouse_selection.started = false; + API::release_capture(*listbox_ptr); + mouse_selection.begin_position = mouse_selection.end_position; + mouse_selection.already_selected.clear(); + mouse_selection.selections.clear(); + } + + /// Returns the number of items that are contained on on screen + /** + * @pram with_rest Indicates whether the extra one item which is not completely contained in rest pixels to be included. + * @return The number of items + */ + size_type count_of_exposed(bool with_rest) const + { + auto lister_s = this->content_view->view_area().height; + return (lister_s / item_height()) + (with_rest && (lister_s % item_height()) ? 1 : 0); + } + + void update(bool ignore_auto_draw = false) noexcept + { + if((auto_draw || ignore_auto_draw) && lister.wd_ptr()) { - adjust_scroll_life(); + calc_content_size(false); API::refresh_window(lister.wd_ptr()->handle()); } } - void adjust_scroll_value() + ::nana::size calc_content_size(bool try_update = true) { - const auto graph_size = graph->size(); - if(scroll.h.empty() == false) - { - const auto ext_px = (4 + (scroll.v.empty() ? 0 : scroll.scale - 1)); - if (ext_px > graph_size.width) - return; + size ctt_size( + this->header.pixels() + 5, + static_cast(this->lister.the_number_of_expanded()) + ); - const auto header_px = header.pixels(); - const unsigned window_px = graph_size.width - ext_px; - - auto offset_x = scroll.x_offset(); - if (header_px < window_px + offset_x) - offset_x = header_px - window_px; + ctt_size.height *= this->item_height(); - scroll.h.amount(header_px); - scroll.h.range(window_px); - scroll.h.value(offset_x); - scroll.h.step(graph->text_extent_size(L"W").width); - } + this->content_view->content_size(ctt_size, try_update); - if(scroll.v.empty() == false) - { - const auto ext_px = 2 + (scroll.h.empty() ? 0 : scroll.scale); - if (ext_px >= graph_size.height) - return; - - const auto items = lister.the_number_of_expanded(); - const auto disp_items = number_of_lister_items(false); - - size_type off = lister.distance(index_pair{ 0, 0 }, scroll.offset_y_dpl); - - if (items < disp_items + off) - { - index_pair pos; - if (lister.forward(index_pair{ 0, 0 }, items - disp_items, pos)) - { - off = items - disp_items; - set_scroll_y_dpl(pos); - } - } - - scroll.v.amount(lister.the_number_of_expanded()); - scroll.v.range(number_of_lister_items(false)); - scroll.v.value(off); - } + return ctt_size; } - void adjust_scroll_life() // at end call adjust_scroll_value(); + nana::rectangle checkarea(int x, int y) const noexcept /// move to scheme ?? 16 ? { - internal_scope_guard lock; - - const unsigned border_px = 1; - const unsigned border_px_twice = (border_px << 1); - - const nana::size sz = graph->size(); - - if ((sz.width <= border_px_twice) || (sz.height <= border_px_twice)) - { - scroll.h.close(); - scroll.v.close(); - return; - } - - // Adjust the ranged column assume the vertical scrollbar is enabled. - auto range_adjusted = this->header.calc_ranged_columns(sz.width - border_px_twice - scroll.scale); - auto columns_pixels = header.pixels(); - - //H scroll enabled - //If range_adjusted is true, it indicates no horzontal scroll bar is enabled. - bool enable_horz = ((!range_adjusted) && (columns_pixels + 4 > sz.width)); // 4px = left and right borders(2px) + left and right gaps(2px) - - unsigned head_scroll = 2 + header_visible_px() + (enable_horz ? scroll.scale : 0); // 2px left and right gaps(2px) - unsigned lister_s = sz.height > head_scroll ? sz.height - head_scroll : 0; - size_type screen_number = (lister_s / scheme_ptr->item_height); - - //V scroll enabled - auto enable_vert = (lister.the_number_of_expanded() > screen_number); - - if (enable_vert) - { - if (!enable_horz) - enable_horz = ((columns_pixels + 2 + scroll.scale) > sz.width); - } - else if (range_adjusted) - { - //No vertical scrollbar, then re-adjust the range columns for a new width that excludes vert scroll. - this->header.calc_ranged_columns(sz.width - border_px_twice); - } - - //event hander for scrollbars - auto evt_fn = [this](const arg_scroll& arg) - { - if (scroll.h.empty() || (scroll.h.handle() != arg.window_handle)) - { - index_pair item; - if (!lister.forward(item, scroll.v.value(), item)) return; - - if (item == scroll.offset_y_dpl) - return; - - set_scroll_y_dpl(item); - } - - API::refresh_window(this->lister.wd_ptr()->handle()); - }; - - unsigned horz_px = sz.width - border_px_twice; - if (enable_vert) - { - if (horz_px < scroll.scale) - horz_px = 0; - else - horz_px -= scroll.scale; - } - - unsigned vert_px = sz.height - border_px_twice; - if (enable_horz) - { - if (vert_px < scroll.scale) - vert_px = 0; - else - vert_px -= scroll.scale; - } - - const auto wd_handle = lister.wd_ptr()->handle(); - if (enable_horz && horz_px) - { - rectangle r(border_px, static_cast(sz.height - border_px) - static_cast(scroll.scale), horz_px, scroll.scale); - if(scroll.h.empty()) - { - scroll.h.create(wd_handle, r); - API::take_active(scroll.h.handle(), false, wd_handle); - scroll.h.events().value_changed.connect_unignorable(evt_fn); - } - else - scroll.h.move(r); - } - else if(!scroll.h.empty()) - scroll.h.close(); - - if (enable_vert && vert_px) - { - rectangle r(static_cast(sz.width - border_px) - static_cast(scroll.scale), border_px, scroll.scale, vert_px); - if(scroll.v.empty()) - { - scroll.v.create(wd_handle, r); - API::take_active(scroll.v.handle(), false, wd_handle); - scroll.v.events().value_changed.connect_unignorable(evt_fn); - } - else - scroll.v.move(r); - - } - else if(!scroll.v.empty()) - { - scroll.v.close(); - set_scroll_y_dpl(index_pair{0,0}); - } - adjust_scroll_value(); - } - - void set_auto_draw(bool ad) - { - if(auto_draw != ad) - { - auto_draw = ad; - if(ad) - { - adjust_scroll_life(); - API::refresh_window(lister.wd_ptr()->handle()); - } - } - } - - nana::rectangle checkarea(int x, int y) const /// move to scheme ?? 16 ? - { - return nana::rectangle(x + 4, y + (static_cast(scheme_ptr->item_height) - 16) / 2, 16, 16); + return nana::rectangle(x + 4, y + (static_cast(item_height()) - 16) / 2, 16, 16); } int item_xpos(const nana::rectangle& r) const @@ -2496,49 +2226,65 @@ namespace nana if (seq.empty()) return 0; - return (header.position(seq[0], nullptr) - static_cast(scroll.x_offset()) + r.x); + return (header.position(seq[0], nullptr) - this->content_view->origin().x + r.x); } - std::pair where(int x, int y) + //Returns the absolute coordinate of the specified item in the window + point item_coordinate(const index_pair& pos) const { - std::pair new_where; + auto top = static_cast(this->lister.distance(index_pair{}, pos) * item_height()) - content_view->origin().y; - if(2 < x && x < static_cast(graph->width()) - 2 && 1 < y && y < static_cast(graph->height()) - 1) + rectangle r; + if (rect_lister(r)) + top += r.y; + + return{ top, top + static_cast(item_height()) }; + } + + std::pair where(const nana::point& pos) const noexcept + { + std::pair new_where{ parts::unknown, npos }; + + const auto area = this->content_area(); + + if(area.is_hit(pos)) { /// we are inside + auto const origin = content_view->origin(); - if(header.visible() && y < static_cast(scheme_ptr->header_height + 1)) + if(header.visible() && (pos.y < static_cast(scheme_ptr->header_height) + area.y)) { /// we are in the header - x += static_cast(scroll.x_offset()) - 2; new_where.first = parts::header; - new_where.second = header.column_from_point(x); + new_where.second = this->column_from_pos(pos.x); } - else + else if (area.x <= pos.x + origin.x && pos.x + origin.x < area.x + static_cast(header.pixels())) { - new_where.second = ((y + 1) - header_visible_px()) / scheme_ptr->item_height; // y>1 ! - new_where.first = parts::lister; - if(checkable) + new_where.first = parts::list; + + auto const item_h = item_height(); + //don't combine the following formula into the (pos.y - area.y - header_visible_px()) / item_h + new_where.second = ((pos.y - area.y - header_visible_px() + origin.y) / item_h) - (origin.y / item_h); + + if (checkable) { nana::rectangle r; - if(rect_lister(r)) + if (rect_lister(r)) { - auto top = new_where.second * scheme_ptr->item_height + header_visible_px(); - if(checkarea(item_xpos(r), static_cast(top)).is_hit(x, y)) + auto top = new_where.second * item_h + header_visible_px(); + if (checkarea(item_xpos(r), static_cast(top)).is_hit(pos)) new_where.first = parts::checker; } } } + else + new_where.first = parts::list_blank; } - else - { - new_where.first = parts::unknown; - new_where.second = npos; - } - return new_where; + + return new_where; } - bool calc_where(const point& pos) + bool calc_where(const point& pos) noexcept { - auto new_where = where(pos.x, pos.y); + auto new_where = where(pos); if (new_where == pointer_where) return false; @@ -2546,35 +2292,41 @@ namespace nana return true; } - void widget_to_header(nana::point& pos) + void widget_to_header(nana::point& pos) noexcept { --pos.y; - - pos.x += static_cast(scroll.x_offset()) - 2; + pos.x += this->content_view->origin().x - 2; } - bool rect_header(nana::rectangle& r) const + void draw_peripheral() { - if(header.visible()) - { - if (lister.wd_ptr()->borderless()) - { - r.dimension(graph->size()); - r.height = scheme_ptr->header_height; - return !r.empty(); - } + auto ctt_area = this->content_area(); - const unsigned ex_width = 4 + (scroll.v.empty() ? 0 : scroll.scale - 1); - if(graph->width() > ex_width) - { - r.x = 2; - r.y = 1; - r.width = graph->width() - ex_width; - r.height = scheme_ptr->header_height; - return true; - } + if (!API::widget_borderless(*lister.wd_ptr())) + { + //Draw Border + graph->rectangle(false, static_cast(0x9cb6c5)); + + graph->line({ ctt_area.x - 1, ctt_area.y }, { ctt_area.x - 1, ctt_area.bottom() - 1 }, colors::white); + graph->line({ ctt_area.right(), ctt_area.y }, { ctt_area.right(), ctt_area.bottom() - 1 }); } - return false; + + this->content_view->draw_corner(*graph); + } + + rectangle content_area() const + { + rectangle r{ graph->size() }; + + if (!this->listbox_ptr->borderless()) + { + r.x = 2; + r.width -= (r.width > 4 ? 4 : r.width); + + r.y = 1; + r.height -= (r.height > 2 ? 2 : r.height); + } + return r; } unsigned header_visible_px() const @@ -2582,59 +2334,56 @@ namespace nana return (header.visible() ? scheme_ptr->header_height : 0); } + bool rect_header(nana::rectangle& r) const + { + if(header.visible()) + { + r = this->content_area(); + + r.height = scheme_ptr->header_height; + + if (lister.wd_ptr()->borderless()) + return !r.empty(); + + auto exs = this->content_view->extra_space(false); + const unsigned ex_width = (exs ? exs - 1 : 0); + + if(r.width > ex_width) + { + r.width -= ex_width; + return true; + } + } + return false; + } + bool rect_lister(nana::rectangle& r) const { auto head_pixels = header_visible_px(); - unsigned width = (scroll.v.empty() ? 0 : scroll.scale - 1); - unsigned height = (scroll.h.empty() ? 0 : scroll.scale) + head_pixels; - if (!lister.wd_ptr()->borderless()) - { - width += 4; - height += 2; + auto exs_vert = this->content_view->extra_space(false); + auto exs_horz = this->content_view->extra_space(true); - r.x = 2; - r.y = head_pixels + 1; - } - else - { - r.x = 0; - r.y = head_pixels; - } + unsigned extr_w = (exs_vert ? exs_vert - 1 : 0); + unsigned extr_h = exs_horz + head_pixels; - nana::size gsz = graph->size(); - if(gsz.width <= width || gsz.height <= height) return false; + r = this->content_area(); + + if (r.width <= extr_w || r.height <= extr_h) + return false; + + r.y += head_pixels; + + r.width -= extr_w; + r.height -= extr_h; - r.width = gsz.width - width; - r.height = gsz.height - height; return true; } - bool wheel(bool upwards) - { - if(scroll.v.empty() || !scroll.v.scrollable(upwards)) - return false; - - index_pair target; - if (upwards == false) - { - if (!lister.forward(scroll.offset_y_dpl, this->scheme_ptr->mouse_wheel.lines, target)) - return false; - } - else - lister.backward(scroll.offset_y_dpl, this->scheme_ptr->mouse_wheel.lines, target); - - if (target == scroll.offset_y_dpl) - return false; - - set_scroll_y_dpl ( target ); - return true; - } - - std::vector header_seq(unsigned lister_w)const + std::vector header_seq(unsigned lister_w) const { std::vector seqs; - int x = -static_cast(scroll.x_offset()); + int x = -content_view->origin().x; for (const auto& col : header.cont()) { @@ -2682,7 +2431,7 @@ namespace nana }; //definition of iresolver/oresolver - oresolver::oresolver(essence* ess) + oresolver::oresolver(essence* ess) noexcept : ess_(ess) {} @@ -2799,12 +2548,12 @@ namespace nana return *this; } - std::vector&& oresolver::move_cells() + std::vector&& oresolver::move_cells() noexcept { return std::move(cells_); } - ::nana::listbox& oresolver::listbox() + ::nana::listbox& oresolver::listbox() noexcept { return *ess_->listbox_ptr; } @@ -2870,6 +2619,7 @@ namespace nana n = std::stoull(cells_[pos_++].text); return *this; } + iresolver& iresolver::operator>>(float& f) { if (pos_ < cells_.size()) @@ -2907,7 +2657,7 @@ namespace nana return *this; } - iresolver::iresolver(std::vector cl) + iresolver::iresolver(std::vector cl) noexcept : cells_(std::move(cl)) {} @@ -2918,7 +2668,7 @@ namespace nana return *this; } - iresolver& iresolver::operator>>(std::nullptr_t) + iresolver& iresolver::operator>>(std::nullptr_t) noexcept { ++pos_; return *this; @@ -2930,40 +2680,41 @@ namespace nana unsigned max_px = 0; for (auto & cat : categories_) { - if (cat.model_ptr) + for (std::size_t i = 0; i < cat.items.size(); ++i) { - for (std::size_t i = 0; i < cat.items.size(); ++i) + unsigned content_px = 0; + if (cat.model_ptr) { auto model_cells = cat.model_ptr->container()->to_cells(i); if (pos >= model_cells.size()) continue; - auto content_px = ess_->graph->text_extent_size(model_cells[pos].text).width; - if (content_px > max_px) - max_px = content_px; + content_px = ess_->graph->text_extent_size(model_cells[pos].text).width; } - } - else - { - for (auto & m : cat.items) + else { - if (pos >= m.cells->size()) + if (pos >= cat.items[i].cells->size()) continue; - auto content_px = ess_->graph->text_extent_size((*m.cells)[pos].text).width; - if (content_px > max_px) - max_px = content_px; + content_px = ess_->graph->text_extent_size((*cat.items[i].cells)[pos].text).width; } + + if (content_px > max_px) + max_px = content_px; } } return max_px; } //es_header::column member functions - void es_header::column::_m_refresh() + void es_header::column::_m_refresh() noexcept { - ess_->adjust_scroll_life(); - API::refresh_window(ess_->lister.wd_ptr()->handle()); + ess_->update(true); + } + + size_type es_header::column::position(bool disp_order) const noexcept + { + return (disp_order ? ess_->header.cast(index, false) : index); } void es_header::column::fit_content(unsigned maximize) noexcept @@ -3025,7 +2776,7 @@ namespace nana panes_.emplace_back(std::make_pair(pos, pane)); } - void detach() + void detach() noexcept { panes_.clear(); } @@ -3046,7 +2797,7 @@ namespace nana ess_->lister.throw_if_immutable_model(pos); auto model_cells = ess_->lister.at_model_abs(pos); - auto & cells = ess_->lister.have_model(pos) ? model_cells : (*ess_->lister.at_abs(pos).cells); + auto & cells = ess_->lister.has_model(pos) ? model_cells : (*ess_->lister.at_abs(pos).cells); if (cells.size() <= column_pos_) cells.resize(column_pos_ + 1); @@ -3070,13 +2821,13 @@ namespace nana cat_proxy(ess_, pos.cat).at(pos.item).select(true); } - void hovered(index_type pos) override + void hovered(index_type /*pos*/) override { - auto offset = ess_->lister.distance(ess_->scroll.offset_y_dpl, pos); + auto offset = ess_->content_view->origin().y / ess_->item_height(); - if (ess_->pointer_where.first != parts::lister || ess_->pointer_where.second != offset) + if (ess_->pointer_where.first != parts::list || ess_->pointer_where.second != offset) { - ess_->pointer_where.first = parts::lister; + ess_->pointer_where.first = parts::list; ess_->pointer_where.second = offset; ess_->update(); } @@ -3097,42 +2848,10 @@ namespace nana if (!cat.expand) { this->expand(pos.cat, true); - ess_->adjust_scroll_life(); + ess_->calc_content_size(); } - //The number of items can be displayed on screen - auto view_items = ess_->number_of_lister_items(false) - 1; - - index_pair start_pos; - if (to_bottom) - { - //start_pos will be (0,0) if backward fails - backward(pos, view_items, start_pos); - } - else - { - if (forward(pos, view_items, start_pos)) - start_pos = pos; - else - { - index_pair last(categories_.size() - 1); - - if (categories_.back().expand) - { - if (categories_.back().items.empty()) - last.item = nana::npos; - else - last.item = categories_.back().items.size() - 1; - } - else - last.item = nana::npos; - - backward(last, view_items, start_pos); - } - } - - ess_->set_scroll_y_dpl(start_pos); - ess_->adjust_scroll_value(); + ess_->content_view->turn_page(to_bottom, false); } void es_lister::erase(const index_pair& pos) @@ -3153,14 +2872,10 @@ namespace nana } } - void es_lister::scroll_refresh() + void es_lister::move_select(bool upwards, bool unselect_previous, bool /*trace_selected*/) noexcept { - ess_->scroll_y_dpl_refresh(); - } + auto next_selected_dpl = index_cast_noexcpt(last_selected_abs, false); //convert absolute position to display position - void es_lister::move_select(bool upwards, bool unselect_previous, bool trace_selected) - { - auto next_selected_dpl = relative_pair ( last_selected_abs); if (next_selected_dpl.empty()) // has no cat ? (cat == npos) => beging from first cat { bool good = false; @@ -3192,7 +2907,7 @@ namespace nana else { next_selected_dpl.item = 0; - if (size_categ() > next_selected_dpl.cat + 1) + if (categories_.size() > next_selected_dpl.cat + 1) ++next_selected_dpl.cat; else next_selected_dpl.cat = 0; @@ -3209,7 +2924,7 @@ namespace nana do { if (0 == next_selected_dpl.cat) - next_selected_dpl.cat = size_categ() - 1; + next_selected_dpl.cat = categories_.size() - 1; else --next_selected_dpl.cat; @@ -3227,14 +2942,11 @@ namespace nana if (good(next_selected_dpl)) { - if (unselect_previous && !single_selection_ ) - select_for_all(false); + if (unselect_previous && !single_selection_) + select_for_all(false); - /// is ignored if no change (maybe set last_selected anyway??), but if change emit event, deselect others if need ans set/unset last_selected - item_proxy::from_display(ess_, next_selected_dpl).select(true); - - if (trace_selected) - ess_->trace_item_dpl(next_selected_dpl); + /// is ignored if no change (maybe set last_selected anyway??), but if change emit event, deselect others if need ans set/unset last_selected + item_proxy::from_display(ess_, next_selected_dpl).select(true); } break; } @@ -3273,15 +2985,37 @@ namespace nana return list_str ; } - void es_lister::categ_selected(size_type cat, bool sel) + bool es_lister::cat_status(size_type pos, bool for_selection, bool value) { - cat_proxy cpx{ess_,cat}; - for (item_proxy &it : cpx ) - { - if (it.selected() != sel) - it.select(sel); - } - last_selected_abs = index_pair{cat, npos}; + bool changed = false; + + if (for_selection) + { + cat_proxy cpx{ ess_, pos }; + for (item_proxy &it : cpx) + it.select(value); + + last_selected_abs.cat = pos; + last_selected_abs.item = npos; + + return true; + } + else + { + auto & items = get(pos)->items; + size_type index = 0; + for (auto & m : items) + { + if (m.flags.checked != value) + { + m.flags.checked = value; + this->emit_cs(index_pair{ pos, index }, false); + changed = true; + } + ++index; + } + } + return changed; } class drawer_header_impl @@ -3291,24 +3025,24 @@ namespace nana using item_state = essence::item_state; using parts = essence::parts; - drawer_header_impl(essence* es): essence_(es){} + drawer_header_impl(essence* es) noexcept: essence_(es){} - size_type splitter() const + size_type splitter() const noexcept { return grabs_.splitter; } - void cancel_splitter() + void cancel_splitter() noexcept { grabs_.splitter = npos; } // Detects a header spliter, return true if x is in the splitter area after that header item (column) - bool detect_splitter(const nana::rectangle& r, int x) + bool detect_splitter(const nana::rectangle& r, int x) noexcept { if(essence_->ptr_state == item_state::highlighted) { - x -= r.x - static_cast(essence_->scroll.x_offset()); + x -= r.x - essence_->content_view->origin().x; for(auto & col : essence_->header.cont()) // in current order { @@ -3339,7 +3073,7 @@ namespace nana if(grabs_.splitter != npos) // resize header item, not move it grabs_.item_width = essence_->header.at(grabs_.splitter).width_px; } - else if(grab_terminal_.index != npos && grab_terminal_.index != essence_->pointer_where.second) + else if((grab_terminal_.index != npos) && (grab_terminal_.index != essence_->pointer_where.second)) essence_->header.move(essence_->pointer_where.second, grab_terminal_.index, grab_terminal_.place_front); } @@ -3367,10 +3101,7 @@ namespace nana if (col.range_width_px.first != col.range_width_px.second) { //Column ranged width - if (new_w < col.range_width_px.first) - new_w = col.range_width_px.first; - else if (new_w > col.range_width_px.second) - new_w = col.range_width_px.second; + new_w = std::clamp(static_cast(new_w), static_cast(col.range_width_px.first), static_cast(col.range_width_px.second)); } else { @@ -3381,7 +3112,7 @@ namespace nana if(col.width_px != new_w) { col.width_px = new_w; - essence_->adjust_scroll_life(); + essence_->calc_content_size(); return true; } } @@ -3390,10 +3121,9 @@ namespace nana void draw(graph_reference graph, const nana::rectangle& r) { - const auto border_color = essence_->scheme_ptr->header_bgcolor.get_color().blend(colors::black, 0.8); + const auto border_color = essence_->scheme_ptr->header_bgcolor.get_color().blend(colors::black, 0.2); - int text_top = (r.height - essence_->scheme_ptr->text_height) / 2 + r.y; - auto text_color = essence_->lister.wd_ptr()->fgcolor(); + auto text_color = essence_->scheme_ptr->header_fgcolor.get_color(); auto state = item_state::normal; //check whether grabing an item, if item_spliter_ != npos, that indicates the grab item is a spliter. @@ -3401,7 +3131,7 @@ namespace nana state = essence_->ptr_state; rectangle column_r{ - r.x - static_cast(essence_->scroll.x_offset()), r.y, + r.x - essence_->content_view->origin().x, r.y, 0, r.height - 1 }; @@ -3416,8 +3146,8 @@ namespace nana //Make sure the column is in the display area. if (right_pos > r.x) { - _m_draw_header_item(graph, column_r, text_top, text_color, col, (col.index == essence_->pointer_where.second ? state : item_state::normal)); - graph.line({ right_pos - 1, r.y }, { right_pos - 1, r.bottom() - 2 }, /*_m_border_color()*/ border_color); + _m_draw_header_item(graph, column_r, text_color, col, (col.index == essence_->pointer_where.second ? state : item_state::normal)); + graph.line({ right_pos - 1, r.y }, { right_pos - 1, r.bottom() - 2 }, border_color); } column_r.x = right_pos; @@ -3426,15 +3156,18 @@ namespace nana } } - //If the last rendered column's right is less than r.right, fill the spare space. + //If the last rendered column's right point doesn't reach at r.right, fill the spare space. if (column_r.x < r.right()) { column_r.width = (r.right() - column_r.x); - graph.rectangle(column_r, true, essence_->scheme_ptr->header_bgcolor); + if(API::dev::copy_transparent_background(essence_->listbox_ptr->handle(), column_r, graph, column_r.position())) + graph.blend(column_r, essence_->scheme_ptr->header_bgcolor, 0.5); + else + graph.rectangle(column_r, true, essence_->scheme_ptr->header_bgcolor); } const int y = r.bottom() - 1; - graph.line({ r.x, y }, { r.right(), y }, /*_m_border_color()*/ border_color); + graph.line({ r.x, y }, { r.right(), y }, border_color); if (options_.grab_column) { @@ -3450,17 +3183,15 @@ namespace nana size_type _m_target_strip(int x, const nana::rectangle& rect, size_type grab, bool& place_front) { //convert x to header logic coordinate. - const int x_offset = static_cast(essence_->scroll.x_offset()); - if (x < x_offset) - x = x_offset; - else if (x > x_offset + static_cast(rect.width)) - x = x_offset + static_cast(rect.width); + auto const x_offset = essence_->content_view->origin().x; + + x = std::clamp(x, x_offset, x_offset + static_cast(rect.width)); auto i = essence_->header.column_from_point(x); + if(i == npos) - { - i = (essence_->header.position(grab, nullptr) < x ? essence_->header.last() : essence_->header.begin()); - } + i = essence_->header.boundary(essence_->header.position(grab, nullptr) >= x); + if(grab != i) { unsigned item_pixels = 0; @@ -3469,7 +3200,7 @@ namespace nana //Get the item pos //if mouse pos is at left of an item middle, the pos of itself otherwise the pos of the next. place_front = (x <= (item_x + static_cast(item_pixels / 2))); - x = (place_front ? item_x : essence_->header.position(essence_->header.neighbor(i, false), nullptr)); + x = (place_front ? item_x : essence_->header.position(essence_->header.next(i), nullptr)); if (npos != i) essence_->graph->rectangle({x - x_offset + rect.x, rect.y, 2, rect.height}, true, colors::red); @@ -3479,9 +3210,10 @@ namespace nana return npos; } - void _m_draw_header_item(graph_reference graph, const rectangle& column_r, int text_top, const ::nana::color& fgcolor, const es_header::column& column, item_state state) + void _m_draw_header_item(graph_reference graph, const rectangle& column_r, const ::nana::color& fgcolor, const es_header::column& column, item_state state) { ::nana::color bgcolor; + switch(state) { case item_state::normal: bgcolor = essence_->scheme_ptr->header_bgcolor.get_color(); break; @@ -3491,7 +3223,15 @@ namespace nana case item_state::floated: bgcolor = essence_->scheme_ptr->header_floated.get_color(); break; } - graph.gradual_rectangle(column_r, bgcolor.blend(colors::white, 0.9), bgcolor.blend(colors::black, 0.9), true); + if(API::dev::copy_transparent_background(essence_->listbox_ptr->handle(), column_r, graph, column_r.position())) + { + paint::graphics grad_graph{column_r.dimension()}; + grad_graph.gradual_rectangle(rectangle{column_r.dimension()}, bgcolor.blend(colors::white, 0.1), bgcolor.blend(colors::black, 0.1), true); + + graph.blend(column_r, grad_graph, {}, 0.8); + } + else + graph.gradual_rectangle(column_r, bgcolor.blend(colors::white, 0.1), bgcolor.blend(colors::black, 0.1), true); paint::aligner text_aligner{ graph, column.alignment, column.alignment }; @@ -3501,20 +3241,22 @@ namespace nana { graph.palette(true, fgcolor); - point text_pos{ column_r.x, text_top }; + point text_pos{ column_r.x, (static_cast(essence_->scheme_ptr->header_height) - static_cast(essence_->text_height)) / 2 }; if (align::left == column.alignment) text_pos.x += text_margin; else if (align::center == column.alignment) text_margin = 0; - text_aligner.draw(column.text, text_pos, column_r.width - text_margin); + text_aligner.draw(column.caption, text_pos, column_r.width - text_margin); } - if (column.index == essence_->lister.sort_index()) + auto & sort = essence_->lister.sort_attrs(); + + if (column.index == sort.column) { facade arrow("hollow_triangle"); - arrow.direction(essence_->lister.sort_reverse() ? ::nana::direction::south : ::nana::direction::north); + arrow.direction(sort.reverse ? ::nana::direction::south : ::nana::direction::north); arrow.draw(graph, {}, colors::black, { column_r.x + (static_cast(column_r.width) - 16) / 2, -4, 16, 16 }, element_state::normal); // geometric scheme? } } @@ -3527,16 +3269,14 @@ namespace nana fl_graph.typeface(essence_->graph->typeface()); - int text_top = (essence_->scheme_ptr->header_height - essence_->scheme_ptr->text_height) / 2; - _m_draw_header_item(fl_graph, rectangle{ fl_graph.size()}, text_top, colors::white, col, item_state::floated); + _m_draw_header_item(fl_graph, rectangle{ fl_graph.size()}, colors::white, col, item_state::floated); auto xpos = essence_->header.position(col.index, nullptr) + pos.x - grabs_.start_pos; - - fl_graph.blend(rectangle{ fl_graph.size() }, *(essence_->graph), point{xpos - static_cast(essence_->scroll.x_offset()) + rect.x, rect.y}, 0.5); + essence_->graph->blend(rectangle{ point{ xpos - essence_->content_view->origin().x + rect.x, rect.y } , fl_graph.size() }, fl_graph, {}, 0.5); } private: - essence * essence_; + essence * const essence_; struct grab_variables { @@ -3566,7 +3306,7 @@ namespace nana using parts = essence::parts; using status_type = inline_notifier_interface::status_type; - drawer_lister_impl(essence * es) + drawer_lister_impl(essence * es) noexcept :essence_(es) {} @@ -3578,151 +3318,181 @@ namespace nana essence_->lister.append_active_panes(nullptr); //The count of items to be drawn - auto item_count = essence_->number_of_lister_items(true); + auto item_count = essence_->count_of_exposed(true); if (0 == item_count) return; - widget * wdptr = essence_->lister.wd_ptr(); - auto bgcolor = wdptr->bgcolor(); - auto fgcolor = wdptr->fgcolor(); + auto const self = essence_->lister.wd_ptr(); + auto const bgcolor = self->bgcolor(); + auto const fgcolor = self->fgcolor(); essence_->graph->palette(false, bgcolor); - const auto header_w = essence_->header.pixels(); - const auto x_offset = essence_->scroll.x_offset(); - if (header_w < x_offset + rect.width) - essence_->graph->rectangle(rectangle{ point{ rect.x + static_cast(header_w) - static_cast(x_offset), rect.y }, - size{rect.width + x_offset - header_w, rect.height} }, true); + auto const header_w = essence_->header.pixels(); + auto const item_height_px = essence_->item_height(); - es_lister & lister = essence_->lister; - //The Tracker indicates the item where mouse placed. - index_pair tracker(npos, npos); - auto & ptr_where = essence_->pointer_where; - - //if where == lister || where == checker, 'second' indicates the offset to the relative display-order pos of the scroll offset_y which stands for the first item to be displayed in lister. - if((ptr_where.first == parts::lister || ptr_where.first == parts::checker) && ptr_where.second != npos) - lister.forward(essence_->scroll.offset_y_dpl, ptr_where.second, tracker); - - auto subitems = essence_->header_seq(rect.width); - - if(subitems.empty()) - return; - - int x = essence_->item_xpos(rect); - int y = rect.y; - int txtoff = (essence_->scheme_ptr->item_height - essence_->scheme_ptr->text_height) / 2; - - auto i_categ = lister.get(essence_->scroll.offset_y_dpl.cat); - - auto idx = essence_->scroll.offset_y_dpl; - - auto state = item_state::normal; - - essence_->inline_buffered_table.swap(essence_->inline_table); - - for(auto & cat : lister.cat_container()) - for (auto & ind : cat.indicators) - { - if (ind) - ind->detach(); - } - - //Here we draw the root categ (0) or a first item if the first drawing is not a categ.(item!=npos)) - if(idx.cat == 0 || !idx.is_category()) + auto origin = essence_->content_view->origin(); + if (header_w < origin.x + rect.width) { - if (idx.cat == 0 && idx.is_category()) // the 0 cat - { - essence_->scroll.offset_y_dpl.item = 0; // no, we draw the first item of cat 0, not the 0 cat itself - idx.item = 0; - } - - std::size_t size = i_categ->items.size(); - index_pair item_index{ idx.cat, 0 }; - - for (std::size_t offs = essence_->scroll.offset_y_dpl.item; offs < size; ++offs, ++idx.item) - { - if (0 == item_count--) - break; - - state = (tracker == idx ? item_state::highlighted : item_state::normal); - - item_index.item = offs; - item_index = lister.absolute_pair(item_index); - - _m_draw_item(*i_categ, item_index, x, y, txtoff, header_w, rect, subitems, bgcolor,fgcolor, state); - y += essence_->scheme_ptr->item_height; - } - - ++i_categ; - ++idx.cat; + rectangle r{ point{ rect.x + static_cast(header_w)-origin.x, rect.y }, + size{ rect.width + origin.x - header_w, rect.height } }; + + if (!API::dev::copy_transparent_background(essence_->listbox_ptr->handle(), r, *essence_->graph, r.position())) + essence_->graph->rectangle(r, true); } - if (item_count > 0) + es_lister & lister = essence_->lister; + + auto & ptr_where = essence_->pointer_where; + + int item_top = rect.y - (origin.y % item_height_px); + auto first_disp = essence_->first_display(); + + // The first display is empty when the listbox is empty. + if (!first_disp.empty()) { + index_pair hoverred_pos(npos, npos); //the hoverred item. + + //if where == lister || where == checker, 'second' indicates the offset to the relative display-order pos of the scroll offset_y which stands for the first item to be displayed in lister. + if ((ptr_where.first == parts::list || ptr_where.first == parts::checker) && ptr_where.second != npos) + { + hoverred_pos = lister.advance(first_disp, static_cast(ptr_where.second)); + } + + auto subitems = essence_->header_seq(rect.width); + + if (subitems.empty()) + return; + + int txtoff = essence_->scheme_ptr->item_height_ex / 2; + + auto i_categ = lister.get(first_disp.cat); + + auto idx = first_disp; + + essence_->inline_buffered_table.swap(essence_->inline_table); + + for (auto & cat : lister.cat_container()) + for (auto & ind : cat.indicators) + { + if (ind) + ind->detach(); + } + + const int x = essence_->item_xpos(rect); + + //Here we draw the root categ (0) or a first item if the first drawing is not a categ.(item!=npos)) + if (idx.cat == 0 || !idx.is_category()) + { + if (idx.cat == 0 && idx.is_category()) // the 0 cat + { + first_disp.item = 0; + idx.item = 0; + } + + std::size_t size = i_categ->items.size(); + for (std::size_t offs = first_disp.item; offs < size; ++offs, ++idx.item) + { + if (item_top >= rect.bottom()) + break; + + auto item_pos = lister.index_cast(index_pair{ idx.cat, offs }, true); //convert display position to absolute position + + _m_draw_item(*i_categ, item_pos, x, item_top, txtoff, header_w, rect, subitems, bgcolor, fgcolor, + (hoverred_pos == idx ? item_state::highlighted : item_state::normal) + ); + + item_top += item_height_px; + } + + ++i_categ; + ++idx.cat; + } + for (; i_categ != lister.cat_container().end(); ++i_categ, ++idx.cat) { - if (0 == item_count--) + if (item_top > rect.bottom()) break; idx.item = 0; - state = (tracker.is_category() && (idx.cat == tracker.cat) ? item_state::highlighted : item_state::normal); - - _m_draw_categ(*i_categ, rect.x - static_cast(x_offset), y, txtoff, header_w, rect, bgcolor, state); - y += essence_->scheme_ptr->item_height; + _m_draw_categ(*i_categ, rect.x - origin.x, item_top, txtoff, header_w, rect, bgcolor, + (hoverred_pos.is_category() && (idx.cat == hoverred_pos.cat) ? item_state::highlighted : item_state::normal) + ); + item_top += item_height_px; if (false == i_categ->expand) continue; - if (item_count > 0) + auto size = i_categ->items.size(); + for (decltype(size) pos = 0; pos < size; ++pos) { - auto size = i_categ->items.size(); - index_pair item_pos{ idx.cat, 0 }; - for (decltype(size) pos = 0; pos < size; ++pos) - { - if (0 == item_count--) - break; + if (item_top > rect.bottom()) + break; - state = (idx == tracker ? item_state::highlighted : item_state::normal); + auto item_pos = lister.index_cast(index_pair{ idx.cat, pos }, true); //convert display position to absolute position - item_pos.item = pos; - item_pos.item = lister.absolute(item_pos); + _m_draw_item(*i_categ, item_pos, x, item_top, txtoff, header_w, rect, subitems, bgcolor, fgcolor, + (idx == hoverred_pos ? item_state::highlighted : item_state::normal) + ); - _m_draw_item(*i_categ, item_pos, x, y, txtoff, header_w, rect, subitems, bgcolor, fgcolor, state); - y += essence_->scheme_ptr->item_height; - if (y >= rect.bottom()) - break; + item_top += item_height_px; + if (item_top >= rect.bottom()) + break; - ++idx.item; - } + ++idx.item; } } + + essence_->inline_buffered_table.clear(); } - essence_->inline_buffered_table.clear(); + if (item_top < rect.bottom()) + { + rectangle bground_r{ rect.x, item_top, rect.width, static_cast(rect.bottom() - item_top) }; + if (!API::dev::copy_transparent_background(essence_->listbox_ptr->handle(), bground_r, *essence_->graph, bground_r.position())) + essence_->graph->rectangle(bground_r, true, bgcolor); + } - if (y < rect.bottom()) - essence_->graph->rectangle(rectangle{ rect.x, y, rect.width, static_cast(rect.bottom() - y) }, true, bgcolor); + //Draw mouse selection + //Check if the mouse selection box is present. + if (essence_->mouse_selection.begin_position != essence_->mouse_selection.end_position) + { + point box_position{ + std::min(essence_->mouse_selection.begin_position.x, essence_->mouse_selection.end_position.x), + std::min(essence_->mouse_selection.begin_position.y, essence_->mouse_selection.end_position.y) + }; + size box_size{ + static_cast(std::abs(essence_->mouse_selection.begin_position.x - essence_->mouse_selection.end_position.x)), + static_cast(std::abs(essence_->mouse_selection.begin_position.y - essence_->mouse_selection.end_position.y)) + }; + + paint::graphics box_graph{ box_size }; + box_graph.rectangle(true, essence_->scheme_ptr->selection_box.get_color().blend(colors::white, 0.6)); + box_graph.rectangle(false, essence_->scheme_ptr->selection_box.get_color()); + + essence_->graph->blend(rectangle{ essence_->coordinate_cast(box_position, false), box_size }, box_graph, {}, 0.5); + } } private: - void _m_draw_categ(const category_t& categ, int x, int y, int txtoff, unsigned width, const nana::rectangle& r, nana::color bgcolor, item_state state) const + void _m_draw_categ(const category_t& categ, int x, int y, int txtoff, unsigned width, const nana::rectangle& r, nana::color bgcolor, item_state state) { - const bool sel = categ.selected(); - if (sel && (categ.expand == false)) - bgcolor = static_cast(0xD5EFFC); - - if (state == item_state::highlighted) - bgcolor = bgcolor.blend(static_cast(0x99defd), 0.8); + const auto item_height = essence_->item_height(); + rectangle bground_r{ x, y, width, item_height }; auto graph = essence_->graph; - graph->rectangle(rectangle{ x, y, width, essence_->scheme_ptr->item_height }, true, bgcolor); + + item_data item; + item.flags.selected = categ.selected(); + + this->_m_draw_item_bground(bground_r, bgcolor, {}, state, item); color txt_color{ static_cast(0x3399) }; facade arrow("double"); arrow.direction(categ.expand ? ::nana::direction::north : ::nana::direction::south); arrow.draw( *graph, {}, txt_color, - { x + 5, y + static_cast(essence_->scheme_ptr->item_height - 16) / 2, 16, 16 }, + { x + 5, y + static_cast(item_height - 16) / 2, 16, 16 }, element_state::normal); graph->string({ x + 20, y + txtoff }, categ.text, txt_color); @@ -3734,21 +3504,64 @@ namespace nana graph->string({ x + 25 + static_cast(text_s), y + txtoff }, str); - if (x + 35 + static_cast(extend_text_w) < x + static_cast(width)) + if (35 + extend_text_w < width) { - ::nana::point pos{ x + 30 + static_cast(extend_text_w), y + static_cast(essence_->scheme_ptr->item_height) / 2 }; - graph->line(pos, { x + static_cast(width)-5, pos.y }, txt_color); + ::nana::point pos{ x + 30 + static_cast(extend_text_w), y + static_cast(item_height) / 2 }; + + graph->line(pos, { x + static_cast(width) - 5, pos.y }, + txt_color); } //Draw selecting inner rectangle - if (sel && (categ.expand == false)) + if (item.flags.selected && (categ.expand == false)) + _m_draw_item_border(r.x, y, (std::min)(r.width, width - essence_->content_view->origin().x)); + } + + color _m_draw_item_bground(const rectangle& bground_r, color bgcolor, color cell_color, item_state state, const item_data& item) + { + auto graph = essence_->graph; + + auto const is_transparent = API::dev::copy_transparent_background(essence_->listbox_ptr->handle(), bground_r, *graph, bground_r.position()); + + if (is_transparent) + bgcolor = color{}; + + if (item.flags.selected) { - _m_draw_border(r.x, y, (std::min)(r.width, width - essence_->scroll.x_offset())); + bgcolor = essence_->scheme_ptr->item_selected; + + if (!cell_color.invisible()) + bgcolor = bgcolor.blend(cell_color, 0.5); } + else if (!cell_color.invisible()) + bgcolor = cell_color; + else if (!item.bgcolor.invisible()) + bgcolor = item.bgcolor; + + if (item_state::highlighted == state) + { + if (item.flags.selected) + bgcolor = bgcolor.blend(essence_->scheme_ptr->item_highlighted, 0.5); + else + bgcolor = bgcolor.blend(essence_->scheme_ptr->item_highlighted, 0.7); + } + + if (is_transparent) + { + if(!bgcolor.invisible()) + graph->blend(bground_r, bgcolor, 0.2); + } + else + { + graph->rectangle(bground_r, true, bgcolor); + + } + + return bgcolor; } /// Draws an item - void _m_draw_item(const category_t& cat, + void _m_draw_item(const category_t& cat, const index_pair& item_pos, const int x, ///< left coordinate ? const int y, ///< top coordinate @@ -3771,28 +3584,16 @@ namespace nana auto & cells = (cat.model_ptr ? model_cells : *item.cells); - if (item.flags.selected) // fetch the "def" colors - bgcolor = essence_->scheme_ptr->item_selected; - else if (!item.bgcolor.invisible()) - bgcolor = item.bgcolor; - if(!item.fgcolor.invisible()) fgcolor = item.fgcolor; - if (item_state::highlighted == state) // and blend it if "highlighted" - { - if (item.flags.selected) - bgcolor = bgcolor.blend(colors::black, 0.98); // or "selected" - else - bgcolor = bgcolor.blend(essence_->scheme_ptr->item_selected, 0.7); /// \todo create a parametre for amount of blend - } - - unsigned show_w = (std::min)(content_r.width, width - essence_->scroll.x_offset()); + const unsigned show_w = (std::min)(content_r.width, width - essence_->content_view->origin().x); auto graph = essence_->graph; //draw the background for the whole item - graph->rectangle(rectangle{ content_r.x, y, show_w, essence_->scheme_ptr->item_height }, true, bgcolor); + rectangle bground_r{ content_r.x, y, show_w, essence_->item_height() }; + auto const state_bgcolor = this->_m_draw_item_bground(bground_r, bgcolor, {}, state, item); int column_x = x; @@ -3800,11 +3601,12 @@ namespace nana { const auto column_pos = seqs[display_order]; const auto & col = essence_->header.at(column_pos); // deduce the corresponding header which is in a kind of dislay order - auto it_bgcolor = bgcolor; if (col.width_px > essence_->scheme_ptr->text_margin) { int content_pos = 0; + element_state estate = element_state::normal; + nana::rectangle img_r; //Draw the image in the 1st column in display order if (0 == display_order) @@ -3813,7 +3615,6 @@ namespace nana { content_pos += 18; // checker width, geom scheme? - element_state estate = element_state::normal; if (essence_->pointer_where.first == parts::checker) { switch (state) @@ -3828,7 +3629,6 @@ namespace nana using state = facade::state; crook_renderer_.check(item.flags.checked ? state::checked : state::unchecked); - crook_renderer_.draw(*graph, bgcolor, fgcolor, essence_->checkarea(column_x, y), estate); } if (essence_->if_image) @@ -3836,10 +3636,10 @@ namespace nana //Draw the image in the 1st column in display order if (item.img) { - nana::rectangle img_r(item.img_show_size); + nana::rectangle imgt(item.img_show_size); + img_r = imgt; img_r.x = content_pos + column_x + (16 - static_cast(item.img_show_size.width)) / 2; // center in 16 - geom scheme? - img_r.y = y + (static_cast(essence_->scheme_ptr->item_height) - static_cast(item.img_show_size.height)) / 2; // center - item.img.stretch(rectangle{ item.img.size() }, *graph, img_r); + img_r.y = y + (static_cast(essence_->item_height()) - static_cast(item.img_show_size.height)) / 2; // center } content_pos += 18; // image width, geom scheme? } @@ -3852,13 +3652,14 @@ namespace nana auto inline_wdg = _m_get_inline_pane(cat, column_pos); if (inline_wdg) { - //Make sure the user-define inline widgets in right visible rectangle. + //Make sure the user-define inline widgets is in the right visible rectangle. rectangle pane_r; - auto wdg_x = column_x + content_pos; - auto wdg_w = col.width_px - static_cast(content_pos); + + const auto wdg_x = column_x + content_pos; + const auto wdg_w = col.width_px - static_cast(content_pos); bool visible_state = true; - if (::nana::overlap(content_r, { wdg_x, y, wdg_w, essence_->scheme_ptr->item_height }, pane_r)) + if (::nana::overlap(content_r, { wdg_x, y, wdg_w, essence_->item_height() }, pane_r)) { ::nana::point pane_pos; if (wdg_x < content_r.x) @@ -3873,14 +3674,13 @@ namespace nana else visible_state = false; - draw_column = inline_wdg->inline_ptr->whether_to_draw(); inline_wdg->item_pos = item_pos; inline_wdg->column_pos = column_pos; inline_wdg->inline_ptr->activate(*inline_wdg->indicator, item_pos); - ::nana::size sz{ wdg_w, essence_->scheme_ptr->item_height }; + ::nana::size sz{ wdg_w, essence_->item_height() }; inline_wdg->pane_widget.size(sz); inline_wdg->inline_ptr->resize(sz); @@ -3901,25 +3701,23 @@ namespace nana } } + auto col_bgcolor = bgcolor; + auto col_fgcolor = fgcolor; + if (cells.size() > column_pos) // process only if the cell is visible { - auto cell_txtcolor = fgcolor; - auto & m_cell = cells[column_pos]; review_utf8(m_cell.text); - if (m_cell.custom_format && (!m_cell.custom_format->bgcolor.invisible())) // adapt to costum format if need + if (m_cell.custom_format) // adapt to costum format if need { - it_bgcolor = m_cell.custom_format->bgcolor; - if (item.flags.selected) - it_bgcolor = it_bgcolor.blend(bgcolor, 0.5); - if (item_state::highlighted == state) - it_bgcolor = it_bgcolor.blend(static_cast(0x99defd), 0.8); + col_fgcolor = m_cell.custom_format->fgcolor; - graph->rectangle(rectangle{ column_x, y, col.width_px, essence_->scheme_ptr->item_height }, true, it_bgcolor); - - cell_txtcolor = m_cell.custom_format->fgcolor; + bground_r = rectangle{ column_x, y, col.width_px, essence_->item_height() }; + col_bgcolor = this->_m_draw_item_bground(bground_r, bgcolor, m_cell.custom_format->bgcolor, state, item); } + else + col_bgcolor = state_bgcolor; if (draw_column) { @@ -3931,12 +3729,20 @@ namespace nana else if (align::right == col.alignment) text_margin_right = essence_->scheme_ptr->text_margin; - graph->palette(true, cell_txtcolor); + graph->palette(true, col_fgcolor); text_aligner.draw(m_cell.text, { column_x + content_pos, y + txtoff }, col.width_px - content_pos - text_margin_right); } } - graph->line({ column_x - 1, y }, { column_x - 1, y + static_cast(essence_->scheme_ptr->item_height) - 1 }, static_cast(0xEBF4F9)); + if (0 == display_order) + { + if (essence_->checkable) + crook_renderer_.draw(*graph, col_bgcolor, col_fgcolor, essence_->checkarea(column_x, y), estate); + if (item.img) + item.img.stretch(rectangle{ item.img.size() }, *graph, img_r); + } + + graph->line({ column_x - 1, y }, { column_x - 1, y + static_cast(essence_->item_height()) - 1 }, static_cast(0xEBF4F9)); } column_x += col.width_px; @@ -3944,7 +3750,7 @@ namespace nana //Draw selecting inner rectangle if(item.flags.selected) - _m_draw_border(content_r.x, y, show_w); + _m_draw_item_border(content_r.x, y, show_w); } inline_pane * _m_get_inline_pane(const category_t& cat, std::size_t column_pos) const @@ -3983,10 +3789,10 @@ namespace nana return nullptr; } - void _m_draw_border(int x, int y, unsigned width) const + void _m_draw_item_border(int x, int y, unsigned width) const { //Draw selecting inner rectangle - rectangle r{ x, y, width, essence_->scheme_ptr->item_height }; + rectangle r{ x, y, width, essence_->item_height() }; essence_->graph->rectangle(r, false, static_cast(0x99defd)); @@ -4014,45 +3820,26 @@ namespace nana delete essence_; } - essence& trigger::ess() const + essence& trigger::ess() const noexcept { return *essence_; } - void trigger::_m_draw_border() - { - if (API::widget_borderless(*essence_->lister.wd_ptr())) - return; - - auto & graph = *essence_->graph; - - int right = static_cast(graph.width()) - 1; - int bottom = static_cast(graph.height()) - 1; - - //Draw Border - graph.rectangle(false, static_cast(0x9cb6c5)); - - graph.line({ 1, 1 }, { 1, bottom - 1}, colors::white); - graph.line({ right - 1, 1 }, { right - 1, bottom - 1 }); - - if ((essence_->scroll.h.empty() == false) && (essence_->scroll.v.empty() == false)) - graph.rectangle({ right - static_cast(essence_->scroll.scale), - bottom - static_cast(essence_->scroll.scale), - essence_->scroll.scale, - essence_->scroll.scale }, - true, colors::button_face); - } - void trigger::attached(widget_reference widget, graph_reference graph) { essence_->listbox_ptr = static_cast(&widget); essence_->scheme_ptr = static_cast<::nana::listbox::scheme_type*>(API::dev::get_scheme(widget)); essence_->graph = &graph; - typeface_changed(graph); essence_->lister.bind(essence_, widget); widget.bgcolor(colors::white); - + + essence_->content_view.reset(new widgets::skeletons::content_view{ widget.handle() }); + essence_->resize_disp_area(); + + essence_->content_view->events().hover_outside = [this](const point& cur_pos) { + essence_->update_mouse_selection(cur_pos); + }; } void trigger::detached() @@ -4063,9 +3850,15 @@ namespace nana void trigger::typeface_changed(graph_reference graph) { - essence_->scheme_ptr->text_height = graph.text_extent_size(L"jHWn0123456789/scheme_ptr->item_height = essence_->scheme_ptr->text_height + essence_->scheme_ptr->item_height_ex; + //essence_->text_height = graph.text_extent_size(L"jHWn0123456789/text_height = 0; + unsigned as, ds, il; + if (graph.text_metrics(as, ds, il)) + essence_->text_height = as + ds; + essence_->scheme_ptr->suspension_width = graph.text_extent_size("...").width; + essence_->calc_content_size(true); } void trigger::refresh(graph_reference graph) @@ -4075,11 +3868,13 @@ namespace nana nana::rectangle r; - if (essence_->header.visible() && essence_->rect_header(r)) - drawer_header_->draw(graph, r); if (essence_->rect_lister(r)) drawer_lister_->draw(r); - _m_draw_border(); + + if (essence_->header.visible() && essence_->rect_header(r)) + drawer_header_->draw(graph, r); + + essence_->draw_peripheral(); } void trigger::mouse_move(graph_reference graph, const arg_mouse& arg) @@ -4089,16 +3884,17 @@ namespace nana bool need_refresh = false; + point pos_in_header = arg.pos; + essence_->widget_to_header(pos_in_header); + if(essence_->ptr_state == item_state::pressed) { if(essence_->pointer_where.first == parts::header) { // moving a pressed header : grab it essence_->ptr_state = item_state::grabbed; - nana::point pos = arg.pos; - essence_->widget_to_header(pos); //Start to move a header column or resize a header column(depends on item_spliter_) - drawer_header_->grab(pos, true); + drawer_header_->grab(pos_in_header, true); essence_->lister.wd_ptr()->set_capture(true); need_refresh = true; @@ -4106,10 +3902,9 @@ namespace nana } if(essence_->ptr_state == item_state::grabbed) - { // moving a grabbed header - nana::point pos = arg.pos; - essence_->widget_to_header(pos); - need_refresh = drawer_header_->grab_move(pos); + { + // moving a grabbed header + need_refresh = drawer_header_->grab_move(pos_in_header); } else if(essence_->calc_where(arg.pos)) { @@ -4141,6 +3936,12 @@ namespace nana } } + if (essence_->mouse_selection.started) + { + essence_->update_mouse_selection(arg.pos); + need_refresh = true; + } + if (need_refresh) { refresh(graph); @@ -4152,6 +3953,7 @@ namespace nana { using item_state = essence::item_state; using parts = essence::parts; + if((essence_->pointer_where.first != parts::unknown) || (essence_->ptr_state != item_state::normal)) { if (essence_->ptr_state != item_state::grabbed) @@ -4170,31 +3972,53 @@ namespace nana using item_state = essence::item_state; using parts = essence::parts; bool update = false; + + essence_->mouse_selection.reverse_selection = false; + + auto & lister = essence_->lister; + + rectangle head_r, list_r; + auto const good_head_r = essence_->rect_header(head_r); + auto const good_list_r = essence_->rect_lister(list_r); + auto & ptr_where = essence_->pointer_where; if((ptr_where.first == parts::header) && (ptr_where.second != npos || (drawer_header_->splitter() != npos))) { essence_->ptr_state = item_state::pressed; - nana::rectangle r; - if(essence_->rect_header(r)) + if(good_head_r) { - drawer_header_->draw(graph, r); + drawer_header_->draw(graph, head_r); update = true; } } - else if(ptr_where.first == parts::lister || ptr_where.first == parts::checker) + else if(ptr_where.first == parts::list || ptr_where.first == parts::checker) { - auto & lister = essence_->lister; - index_pair item_pos; - if (lister.forward(essence_->scroll.offset_y_dpl, ptr_where.second, item_pos)) + index_pair item_pos = lister.advance(essence_->first_display(), static_cast(ptr_where.second)); + + if ((essence_->column_from_pos(arg.pos.x) != npos) && !item_pos.empty()) { - auto * item_ptr = (item_pos.is_item() ? &lister.at(item_pos) : nullptr); + auto * item_ptr = (item_pos.is_category() ? nullptr : &lister.at(item_pos)); - const index_pair abs_item_pos{ item_pos.cat, lister.absolute(item_pos) }; + const auto abs_item_pos = lister.index_cast_noexcpt(item_pos, true, item_pos); //convert display position to absolute position - if(ptr_where.first == parts::lister) + if(ptr_where.first == parts::list) { + //adjust the display of selected into the list rectangle if the part of the item is beyond the top/bottom edge + if (good_list_r) + { + auto item_coord = this->essence_->item_coordinate(abs_item_pos); //item_coord.x = top, item_coord.y = bottom + if (item_coord.x < list_r.y && list_r.y < item_coord.y) + essence_->content_view->move_origin({ 0, item_coord.x - list_r.y }); + else if (item_coord.x < list_r.bottom() && list_r.bottom() < item_coord.y) + essence_->content_view->move_origin({ 0, item_coord.y - list_r.bottom() }); + + essence_->content_view->sync(false); + } + bool sel = true; - if (!lister.single_selection()) + + //no single selected + if (!lister.single_status(true)) { if (arg.shift) { @@ -4205,14 +4029,33 @@ namespace nana auto before = lister.last_selected_abs; - lister.select_display_range(lister.last_selected_abs, item_pos, sel); + lister.select_display_range_if(lister.last_selected_abs, item_pos, true, [](const index_pair&) + { + return true; + }); lister.last_selected_abs = before; } else if (arg.ctrl) + { + essence_->mouse_selection.reverse_selection = true; sel = !item_proxy(essence_, abs_item_pos).selected(); + } else - lister.select_for_all(false); //cancel all selections + { + if (nana::mouse::right_button == arg.button) + { + //Unselects all selected items if the current item is not selected before selecting. + auto selected = lister.pick_items(true); + if (selected.cend() == std::find(selected.cbegin(), selected.cend(), item_pos)) + lister.select_for_all(false, item_pos); + } + else + { + //Unselects all selected items except current item if right button clicked. + lister.select_for_all(false, item_pos); //cancel all selections + } + } } else { @@ -4227,8 +4070,7 @@ namespace nana if (item_ptr->flags.selected != sel) { item_ptr->flags.selected = sel; - - lister.emit_selected(abs_item_pos); + lister.emit_cs(abs_item_pos, true); if (item_ptr->flags.selected) { @@ -4239,45 +4081,72 @@ namespace nana essence_->lister.last_selected_abs.set_both(npos); } } - else if(!lister.single_selection()) - lister.categ_selected(item_pos.cat, true); + else + { + lister.cat_status(item_pos.cat, true, true); + } } else { - if(item_ptr) + if (item_ptr) { - item_ptr->flags.checked = ! item_ptr->flags.checked; - - lister.emit_checked(abs_item_pos); + item_ptr->flags.checked = !item_ptr->flags.checked; + lister.emit_cs(abs_item_pos, false); if (item_ptr->flags.checked) lister.cancel_others_if_single_enabled(false, abs_item_pos); } - else if (! lister.single_check()) - lister.categ_checked_reverse(item_pos.cat); + else if (!lister.single_status(false)) //not single checked + lister.cat_status_reverse(item_pos.cat, false); } update = true; } else - update = lister.select_for_all(false); //unselect all items due to the blank area being clicked + { + //Blank area is clicked + + bool unselect_all = true; + if (!lister.single_status(true)) //not single selected + { + if (arg.ctrl || arg.shift) + { + essence_->mouse_selection.reverse_selection = arg.ctrl; + unselect_all = false; + } + } + + if(unselect_all) + update = lister.select_for_all(false); //unselect all items due to the blank area being clicked + } if(update) { - nana::rectangle r; - update = essence_->rect_lister(r); - if(update) - drawer_lister_->draw(r); + if (good_list_r) + { + drawer_lister_->draw(list_r); + if (good_head_r) + drawer_header_->draw(graph, head_r); + } + else + update = false; } } + else if (ptr_where.first == parts::list_blank) //not selected + { + //Start box selection if mulit-selection is enabled + if (arg.is_left_button() && (!lister.single_status(true))) + essence_->start_mouse_selection(arg.pos); + + lister.select_for_all(false); + } if(update) { - _m_draw_border(); + essence_->draw_peripheral(); API::dev::lazy_refresh(); } } - void trigger::mouse_up(graph_reference graph, const arg_mouse& arg) { using item_state = essence::item_state; @@ -4285,36 +4154,32 @@ namespace nana auto prev_state = essence_->ptr_state; essence_->ptr_state = item_state::highlighted; + + bool need_refresh = false; //Do sort if (essence_->header.sortable() && essence_->pointer_where.first == parts::header && prev_state == item_state::pressed) { if(essence_->pointer_where.second < essence_->header.cont().size()) - { - if(essence_->lister.sort_index(essence_->pointer_where.second)) - { - essence_->trace_item_dpl(index_pair{0,0}); - refresh(graph); - API::dev::lazy_refresh(); - } - } + need_refresh = essence_->lister.sort_column(essence_->pointer_where.second, nullptr); } - else if (prev_state == item_state::grabbed) + else if (item_state::grabbed == prev_state) { nana::point pos = arg.pos; essence_->widget_to_header(pos); drawer_header_->grab(pos, false); - refresh(graph); - API::dev::lazy_refresh(); + need_refresh = true; essence_->lister.wd_ptr()->release_capture(); } - } - void trigger::mouse_wheel(graph_reference graph, const arg_wheel& arg) - { - if(essence_->wheel(arg.upwards)) + if (essence_->mouse_selection.started) + { + essence_->stop_mouse_selection(); + need_refresh = true; + } + + if (need_refresh) { refresh(graph); - essence_->adjust_scroll_value(); API::dev::lazy_refresh(); } } @@ -4339,14 +4204,14 @@ namespace nana } } - if (parts::lister != essence_->pointer_where.first) + if (parts::list != essence_->pointer_where.first) return; - index_pair item_pos; - auto & offset_y = essence_->scroll.offset_y_dpl; auto & lister = essence_->lister; //Get the item which the mouse is placed. - if (lister.forward(offset_y, essence_->pointer_where.second, item_pos)) + + auto item_pos = lister.advance(essence_->first_display(), static_cast(essence_->pointer_where.second)); + if (!item_pos.empty()) { if (!item_pos.is_category()) //being the npos of item.second is a category return; @@ -4359,23 +4224,18 @@ namespace nana bool do_expand = (lister.expand(item_pos.cat) == false); lister.expand(item_pos.cat, do_expand); - if(false == do_expand) - { - auto last = lister.last(); - size_type n = essence_->number_of_lister_items(false); - if (lister.backward(last, n, last)) - offset_y = last; - } - essence_->adjust_scroll_life(); - refresh(graph); - API::dev::lazy_refresh(); + essence_->calc_content_size(false); + essence_->content_view->sync(false); + refresh(graph); + API::dev::lazy_refresh(); } } } void trigger::resized(graph_reference graph, const arg_resized&) { - essence_->adjust_scroll_life(); + essence_->resize_disp_area(); + refresh(graph); API::dev::lazy_refresh(); } @@ -4384,7 +4244,7 @@ namespace nana { bool up = false; - if (essence_->lister.size_categ()==1 && essence_->lister.size_item(0)==0) + if (essence_->lister.cat_container().size() == 1 && essence_->lister.size_item(0)==0) return ; switch(arg.key) @@ -4402,26 +4262,25 @@ namespace nana item_proxy(essence_, i).check(ck); } break; - case keyboard::os_pageup : up = true; case keyboard::os_pagedown: { - auto& scrl = essence_->scroll.v; - if (! scrl.make_page_scroll(!up)) + //Turns page, then returns if no change occurs + if (!essence_->content_view->turn_page(!up, false)) return; + essence_->lister.select_for_all(false); - index_pair idx{essence_->scroll_y_dpl()}; + auto idx = essence_->first_display(); + if (!up) - essence_->lister.forward(idx, scrl.range()-1, idx); + idx = essence_->lister.advance(idx, static_cast(essence_->count_of_exposed(false)) - 1); - if (idx.is_item()) + if (!idx.is_category()) item_proxy::from_display(essence_, idx).select(true); - else if(!essence_->lister.single_selection()) - essence_->lister.categ_selected(idx.cat, true); - - essence_->trace_last_selected_item (); + else if (!essence_->lister.single_status(true)) //not selected + essence_->lister.cat_status(idx.cat, true, true); break; } @@ -4430,18 +4289,16 @@ namespace nana essence_->lister.select_for_all(false); index_pair frst{essence_->lister.first()}; - if (frst.is_item()) + if (! frst.is_category()) item_proxy::from_display(essence_, frst).select(true); - else if(!essence_->lister.single_selection()) - essence_->lister.categ_selected(frst.cat, true); + else if (!essence_->lister.single_status(true)) //not selected + essence_->lister.cat_status(frst.cat, true, true); - essence_->trace_last_selected_item (); break; } case keyboard::os_end: essence_->lister.select_for_all(false); item_proxy::from_display(essence_, essence_->lister.last()).select(true); - essence_->trace_last_selected_item (); break; default: return; @@ -4456,10 +4313,10 @@ namespace nana { case keyboard::copy: { - export_options exp_opt {essence_->def_exp_options}; - exp_opt.columns_order = essence_->header.all_headers(true); + auto exp_opt = essence_->def_exp_options; + exp_opt.columns_order = essence_->header.get_headers(true); exp_opt.only_selected_items = true; - ::nana::system::dataexch().set(essence_->to_string(exp_opt)); + ::nana::system::dataexch().set(essence_->to_string(exp_opt), API::root(essence_->listbox_ptr->handle())); return; } case keyboard::select_all : @@ -4471,54 +4328,70 @@ namespace nana return; } } - //end class trigger //class item_proxy - - item_proxy::item_proxy(essence * ess) - : ess_(ess) - {} - item_proxy::item_proxy(essence * ess, const index_pair& pos) : ess_(ess), pos_(pos) { //get the cat of the item specified by pos - if (ess) + if (ess && !pos.empty()) cat_ = &(*ess->lister.get(pos.cat)); } /// the main porpose of this it to make obvious that item_proxy operate with absolute positions, and dont get moved during sort() item_proxy item_proxy::from_display(essence *ess, const index_pair &relative) { - return item_proxy{ess, ess->lister.absolute_pair(relative)}; + return item_proxy{ ess, ess->lister.index_cast(relative, true) }; } item_proxy item_proxy::from_display(const index_pair &relative) const { - return item_proxy{ess_, ess_->lister.absolute_pair(relative)}; + return item_proxy{ess_, ess_->lister.index_cast(relative, true)}; } /// posible use: last_selected_display = last_selected.to_display().item; use with caution, it get invalidated after a sort() index_pair item_proxy::to_display() const { - return ess_->lister.relative_pair(pos_); + return ess_->lister.index_cast(pos_, false); //convert absolute position to display position } - bool item_proxy::empty() const + bool item_proxy::displayed() const + { + if (!ess_->lister.get(pos_.cat)->expand) + return false; + + auto pos = to_display(); + if (ess_->first_display() > pos) + return false; + + auto last = ess_->lister.advance(ess_->first_display(), static_cast(ess_->count_of_exposed(false))); + + return (last > pos || last == pos); + } + + bool item_proxy::empty() const noexcept { return !ess_; } - item_proxy & item_proxy::check(bool ck) + item_proxy & item_proxy::check(bool ck, bool scroll_view) { internal_scope_guard lock; auto & m = cat_->items.at(pos_.item); if(m.flags.checked != ck) { m.flags.checked = ck; - ess_->lister.emit_checked(pos_); + ess_->lister.emit_cs(pos_, false); + if (scroll_view) + { + if (ess_->lister.get(pos_.cat)->expand) + ess_->lister.get(pos_.cat)->expand = false; + + if (!this->displayed()) + ess_->lister.scroll(pos_, !(ess_->first_display() > this->to_display())); + } ess_->update(); } @@ -4531,16 +4404,18 @@ namespace nana } /// is ignored if no change (maybe set last_selected anyway??), but if change emit event, deselect others if need ans set/unset last_selected - item_proxy & item_proxy::select(bool s) + item_proxy & item_proxy::select(bool s, bool scroll_view) { internal_scope_guard lock; //pos_ never represents a category if this item_proxy is available. auto & m = cat_->items.at(pos_.item); // a ref to the real item - if(m.flags.selected == s) return *this; // ignore if no change + if(m.flags.selected == s) + return *this; // ignore if no change + m.flags.selected = s; // actually change selection - ess_->lister.emit_selected(this->pos_); + ess_->lister.emit_cs(this->pos_, true); if (m.flags.selected) { @@ -4550,6 +4425,15 @@ namespace nana else if (ess_->lister.last_selected_abs == pos_) ess_->lister.last_selected_abs.set_both(npos); + if (scroll_view) + { + if (ess_->lister.get(pos_.cat)->expand) + ess_->lister.get(pos_.cat)->expand = false; + + if (!this->displayed()) + ess_->lister.scroll(pos_, !(ess_->first_display() > this->to_display())); + } + ess_->update(); return *this; } @@ -4583,11 +4467,16 @@ namespace nana return cat_->items.at(pos_.item).fgcolor; } - std::size_t item_proxy::columns() const + std::size_t item_proxy::columns() const noexcept { return ess_->header.cont().size(); } + size_type item_proxy::column_cast(size_type pos, bool disp_order) const + { + return ess_->header.cast(pos, disp_order); + } + item_proxy& item_proxy::text(size_type col, cell cl) { ess_->lister.text(cat_, pos_.item, col, std::move(cl), columns()); @@ -4611,7 +4500,7 @@ namespace nana std::string item_proxy::text(size_type col) const { - return ess_->lister.get_cells(cat_, pos_.item).at(col).text; + return cat_->cells(pos_.item).at(col).text; } void item_proxy::icon(const nana::paint::image& img) @@ -4627,7 +4516,6 @@ namespace nana } } - //Behavior of Iterator's value_type bool item_proxy::operator==(const char * s) const { @@ -4641,12 +4529,12 @@ namespace nana bool item_proxy::operator==(const std::string& s) const { - return (ess_->lister.get_cells(cat_, pos_.item).at(0).text == s); + return (text(pos_.item) == s); } bool item_proxy::operator==(const std::wstring& s) const { - return (ess_->lister.get_cells(cat_, pos_.item).at(0).text == to_utf8(s)); + return (text(pos_.item) == to_utf8(s)); } item_proxy & item_proxy::operator=(const item_proxy& rhs) @@ -4720,19 +4608,19 @@ namespace nana } //Undocumented methods - essence * item_proxy::_m_ess() const + essence * item_proxy::_m_ess() const noexcept { return ess_; } - index_pair item_proxy::pos() const + index_pair item_proxy::pos() const noexcept { return pos_; } auto item_proxy::_m_cells() const -> std::vector { - return ess_->lister.get_cells(cat_, pos_.item); + return cat_->cells(pos_.item); } nana::any * item_proxy::_m_value(bool alloc_if_empty) @@ -4749,14 +4637,14 @@ namespace nana //class cat_proxy //the member cat_ is used for fast accessing to the category - cat_proxy::cat_proxy(essence * ess, size_type pos) + cat_proxy::cat_proxy(essence * ess, size_type pos) noexcept : ess_(ess), pos_(pos) { _m_cat_by_pos(); } - cat_proxy::cat_proxy(essence* ess, category_t* cat) + cat_proxy::cat_proxy(essence* ess, category_t* cat) noexcept : ess_(ess), cat_(cat) { @@ -4779,7 +4667,7 @@ namespace nana void cat_proxy::append(std::initializer_list arg) { const auto items = columns(); - push_back(std::string{}); + push_back({}); item_proxy ip{ ess_, index_pair(pos_, size() - 1) }; size_type pos = 0; for (auto & txt : arg) @@ -4793,7 +4681,7 @@ namespace nana void cat_proxy::append(std::initializer_list arg) { const auto items = columns(); - push_back(std::string{}); + push_back({}); item_proxy ip{ ess_, index_pair(pos_, size() - 1) }; size_type pos = 0; for (auto & txt : arg) @@ -4833,7 +4721,7 @@ namespace nana internal_scope_guard lock; if (text != cat_->text) { - cat_->text.swap(text); + cat_->text = std::move(text); ess_->update(); } return *this; @@ -4845,7 +4733,7 @@ namespace nana internal_scope_guard lock; if (text != cat_->text) { - cat_->text.swap(text); + cat_->text = std::move(text); ess_->update(); } return *this; @@ -4857,20 +4745,6 @@ namespace nana return to_utf8(cat_->text); } - bool assign_colors_for_last(essence* ess, category_t* cat) - { - auto wd = ess->lister.wd_ptr(); - if (wd && !API::empty_window(wd->handle())) - { - auto & m = cat->items.back(); - m.bgcolor = wd->bgcolor(); - m.fgcolor = wd->fgcolor(); - - return true; - } - return false; - } - void cat_proxy::push_back(std::string s) { internal_scope_guard lock; @@ -4881,22 +4755,22 @@ namespace nana if (cat_->model_ptr) { - auto pos = cat_->model_ptr->container()->size(); - cat_->model_ptr->container()->emplace_back(); - auto cells = cat_->model_ptr->container()->to_cells(pos); + const auto cont = cat_->model_ptr->container(); + auto pos = cont->size(); + cont->emplace_back(); + auto cells = cont->to_cells(pos); if (cells.size()) - cells.front().text.swap(s); + cells.front().text = std::move(s); else cells.emplace_back(std::move(s)); - cat_->model_ptr->container()->assign(pos, cells); + cont->assign(pos, cells); cat_->items.emplace_back(); } else cat_->items.emplace_back(std::move(s)); - if (assign_colors_for_last(ess_, cat_)) - ess_->update(); + ess_->update(); } //Behavior of a container @@ -4929,8 +4803,7 @@ namespace nana item_proxy cat_proxy::at(size_type pos_abs) const { - if(pos_abs >= size()) - throw std::out_of_range("listbox.cat_proxy.at() invalid position"); + check_range(pos_abs, size()); return item_proxy(ess_, index_pair(pos_, pos_abs)); } @@ -4942,16 +4815,9 @@ namespace nana return item_proxy(ess_, index_pair(pos_, cat_->items.size() - 1)); } - /// convert from display order to absolute (find the real item in that display pos) but without check from current active sorting, in fact using just the last sorting !!! - size_type cat_proxy::index_by_display_order(size_type display_order_pos) const + size_type cat_proxy::index_cast(size_type from, bool from_display_order) const { - return ess_->lister.index_by_display_order(pos_, display_order_pos); - } - - /// find display order for the real item but without check from current active sorting, in fact using just the last sorting !!! - size_type cat_proxy::display_order(size_type pos) const - { - return ess_->lister.display_order(pos_, pos); + return ess_->lister.index_cast(index_pair{ pos_, from }, from_display_order).item; } size_type cat_proxy::position() const @@ -5039,8 +4905,7 @@ namespace nana void cat_proxy::inline_factory(size_type column, pat::cloneable> factory) { - if (column >= ess_->header.cont().size()) - throw std::out_of_range("listbox.cat_proxy.inline_factory: invalid column index"); + check_range(column, this->columns()); if (column >= cat_->factories.size()) { @@ -5080,40 +4945,31 @@ namespace nana } else { - cat_->sorted.push_back(cat_->items.size()); cells.resize(columns()); cat_->items.emplace_back(std::move(cells)); } - assign_colors_for_last(ess_, cat_); + cat_->sorted.push_back(cat_->items.size() - 1); } void cat_proxy::_m_try_append_model(const const_virtual_pointer& dptr) { + //Throws when appends an object to a listbox which should have a model. if (!cat_->model_ptr) - { - //Throws when appends an object to a listbox which should have a model. throw std::runtime_error("nana::listbox hasn't a model"); - } ess_->lister.throw_if_immutable_model(cat_->model_ptr.get()); - if (cat_->model_ptr->container()->push_back(dptr)) - { - cat_->sorted.push_back(cat_->items.size()); - cat_->items.emplace_back(); - - assign_colors_for_last(ess_, cat_); - } - else - { + if (!cat_->model_ptr->container()->push_back(dptr)) throw std::invalid_argument("nana::listbox, the type of operand object is mismatched with model container value_type"); - } + + cat_->sorted.push_back(cat_->items.size()); + cat_->items.emplace_back(); } - void cat_proxy::_m_cat_by_pos() + void cat_proxy::_m_cat_by_pos() noexcept { - if (pos_ >= ess_->lister.size_categ()) + if (pos_ >= ess_->lister.cat_container().size()) { ess_ = nullptr; cat_ = nullptr; @@ -5126,7 +4982,7 @@ namespace nana //A fix for auto_draw, to make sure the inline widget set() issued after value() and value_ptr() are actually set. //Fixed by leobackes(pr#86) - void cat_proxy::_m_update() + void cat_proxy::_m_update() noexcept { ess_->update(); } @@ -5137,19 +4993,13 @@ namespace nana { cat_->model_ptr.reset(p); cat_->items.clear(); - cat_->sorted.clear(); cat_->items.resize(cat_->model_ptr->container()->size()); - const auto item_size = cat_->items.size(); - cat_->sorted.reserve(item_size + 100); - for (std::size_t pos = 0; pos != item_size; ++pos) - cat_->sorted.push_back(pos); - + cat_->make_sort_order(); ess_->lister.sort(); - ess_->adjust_scroll_life(); - API::refresh_window(ess_->listbox_ptr->handle()); + ess_->update(true); } } //end class cat_proxy @@ -5185,22 +5035,26 @@ namespace nana { internal_scope_guard lock; - auto & ess = _m_ess(); - if (ess.lister.enable_ordered(enable)) - ess.update(); + if (_m_ess().lister.enable_ordered(enable)) + _m_ess().update(); return true; } - void listbox::auto_draw(bool ad) + void listbox::auto_draw(bool enabled) noexcept { - _m_ess().set_auto_draw(ad); + auto & ess = _m_ess(); + if (ess.auto_draw != enabled) + { + ess.auto_draw = enabled; + ess.update(); + } } void listbox::scroll(bool to_bottom, size_type cat_pos) { auto & ess = _m_ess(); - auto cats = ess.lister.size_categ(); + auto cats = this->size_categ(); if (::nana::npos != cat_pos) { @@ -5214,10 +5068,7 @@ namespace nana if (to_bottom) { auto items = ess.lister.size_item(cat_pos); - if (0 == items) - pos.item = ::nana::npos; - else - pos.item = items - 1; + pos.item = (0 == items ? ::nana::npos : items - 1); } else pos.item = ess.lister.size_item(cat_pos) ? 0 : ::nana::npos; @@ -5228,9 +5079,8 @@ namespace nana void listbox::scroll(bool to_bottom, const index_pair& pos) { - auto & ess = _m_ess(); - ess.lister.scroll(pos, to_bottom); - ess.update(); + _m_ess().lister.scroll(pos, to_bottom); + _m_ess().update(); } listbox::size_type listbox::append_header(std::string s, unsigned width) @@ -5270,22 +5120,22 @@ namespace nana return cat_proxy{ &ess, new_cat_ptr }; } - void listbox::append(std::initializer_list args) + void listbox::append(std::initializer_list categories) { internal_scope_guard lock; auto & ess = _m_ess(); - for (auto & arg : args) + for (auto & arg : categories) ess.lister.create_cat(native_string_type(to_nstring(arg))); ess.update(); } - void listbox::append(std::initializer_list args) + void listbox::append(std::initializer_list categories) { internal_scope_guard lock; auto & ess = _m_ess(); - for (auto & arg : args) + for (auto & arg : categories) ess.lister.create_cat(native_string_type(to_nstring(arg))); ess.update(); } @@ -5311,7 +5161,7 @@ namespace nana { internal_scope_guard lock; auto & ess = _m_ess(); - ess.lister.insert(pos, std::move(text), ess.header.cont().size()); + ess.lister.insert(pos, std::move(text), this->column_size()); if (!empty()) { @@ -5329,20 +5179,14 @@ namespace nana listbox::cat_proxy listbox::at(size_type pos) { - auto & ess = _m_ess(); - if (pos >= ess.lister.size_categ()) - throw std::out_of_range("Nana.Listbox.at(): invalid position"); - - return{ &ess, pos }; + check_range(pos, size_categ()); + return{ &_m_ess(), pos }; } const listbox::cat_proxy listbox::at(size_type pos) const { - auto & ess = _m_ess(); - if(pos >= ess.lister.size_categ()) - throw std::out_of_range("Nana.Listbox.at(): invalid position"); - - return{ &ess, pos }; + check_range(pos, size_categ()); + return{ &_m_ess(), pos }; } listbox::item_proxy listbox::at(const index_pair& abs_pos) @@ -5359,24 +5203,22 @@ namespace nana listbox::index_pair listbox::cast( const point& pos ) const { auto & ess=_m_ess(); - auto _where=ess.where(pos.x, pos.y); - index_pair item_pos{npos,npos}; - if(_where.first==drawerbase::listbox::essence::parts::lister) - { - auto & offset_y = ess.scroll.offset_y_dpl; - ess.lister.forward(offset_y, _where.second, item_pos); - } - return item_pos; + auto _where = ess.where(pos); + + if (drawerbase::listbox::essence::parts::list == _where.first) + return ess.lister.advance(ess.first_display(), static_cast(_where.second)); + + return index_pair{ npos, npos }; } - auto listbox::column_at(size_type pos) -> column_interface& + auto listbox::column_at(size_type pos, bool disp_order) -> column_interface& { - return _m_ess().header.at(pos); + return _m_ess().header.at(pos, disp_order); } - auto listbox::column_at(size_type pos) const -> const column_interface& + auto listbox::column_at(size_type pos, bool disp_order) const -> const column_interface& { - return _m_ess().header.at(pos); + return _m_ess().header.at(pos, disp_order); } auto listbox::column_size() const ->size_type @@ -5385,10 +5227,9 @@ namespace nana } //Contributed by leobackes(pr#97) - listbox::size_type listbox::column_from_pos ( const point& pos ) + listbox::size_type listbox::column_from_pos ( const point& pos ) const { - auto & ess=_m_ess(); - return ess.header.column_from_point(pos.x - 2 - static_cast(ess.scroll.x_offset())); + return _m_ess().column_from_pos(pos.x); } void listbox::checkable(bool chkable) @@ -5409,26 +5250,36 @@ namespace nana void listbox::clear(size_type cat) { auto & ess = _m_ess(); + + auto origin = ess.content_view->origin(); + + int new_pos = origin.y; + + auto start_pos = static_cast(ess.lister.distance(ess.lister.first(), index_pair{cat, npos}) * ess.item_height()); + auto count = static_cast(ess.lister.size_item(cat) * ess.item_height()); + if (start_pos + count <= origin.y) + new_pos = origin.y - static_cast(count); + else if (start_pos < origin.y && origin.y < start_pos + count) + new_pos = start_pos + ess.item_height(); + ess.lister.clear(cat); - // from current display position - // move to the cat self if not in first cat - // move to first item ?? if in first cat - ess.scroll_y_abs(ess.scroll_y_abs()); - + ess.calc_content_size(false); + ess.content_view->change_position(new_pos, false, false); + ess.content_view->sync(false); ess.update(); } void listbox::clear() { auto & ess = _m_ess(); + ess.lister.clear(); unsort(); // apperar to be espected - // from current display position - // move to the cat self if not in first cat - // move to first item ?? if in first cat - ess.scroll_y_abs(ess.scroll_y_abs()); + ess.calc_content_size(false); + ess.content_view->change_position(0, false, false); + ess.content_view->sync(false); ess.update(); } @@ -5436,20 +5287,24 @@ namespace nana void listbox::erase(size_type cat) { auto & ess = _m_ess(); + + auto origin = ess.content_view->origin(); + + auto start_pos = ess.content_position(index_pair{ cat, npos }); + + int new_pos = origin.y; + auto count = static_cast((ess.lister.size_item(cat) + 1) * ess.item_height()); + if (start_pos + count <= origin.y) + new_pos = origin.y - static_cast(count); + else if (start_pos < origin.y && origin.y < start_pos + count) + new_pos = start_pos; + ess.lister.erase(cat); - if(cat) - { - auto pos = ess.scroll_y_dpl(); - if(cat <= pos.cat) - { - if(pos.cat == ess.lister.size_categ()) - --pos.cat; - pos.item = npos; - ess.set_scroll_y_dpl(pos); - } - } - else - ess.set_scroll_y_dpl(index_pair()); + + ess.calc_content_size(false); + ess.content_view->change_position(new_pos, false, false); + ess.content_view->sync(false); + ess.update(); } @@ -5457,7 +5312,54 @@ namespace nana { auto & ess = _m_ess(); ess.lister.erase(); - ess.scroll_y_abs(index_pair()); + //ess.first_display(ess.lister.first()); + ess.calc_content_size(); + ess.update(); + } + + void listbox::erase(index_pairs indexes) + { + std::sort(indexes.begin(), indexes.end(), [](const index_pair& pos1, const index_pair& pos2) + { + return (pos1 > pos2); + }); + + auto & ess = _m_ess(); + + auto const origin = ess.content_view->origin(); + + for (auto & pos : indexes) + { + auto & cat = *ess.lister.get(pos.cat); + if (pos.item < cat.items.size()) + { + if (cat.model_ptr) + { + drawerbase::listbox::es_lister::throw_if_immutable_model(cat.model_ptr.get()); + cat.model_ptr->container()->erase(pos.item); + } + + cat.items.erase(cat.items.begin() + pos.item); + } + } + + //Rebuild the sorted order + std::size_t this_cat = this->size_categ(); + for (auto & pos : indexes) + { + if (this_cat != pos.cat) + { + this_cat = pos.cat; + ess.lister.get(this_cat)->make_sort_order(); + } + } + + ess.calc_content_size(false); + ess.content_view->change_position(origin.y, false, false); + ess.content_view->sync(false); + + ess.lister.sort(); + ess.update(); } @@ -5469,29 +5371,15 @@ namespace nana auto * ess = ip._m_ess(); auto _where = ip.pos(); - auto pos_before = ess->scroll_y_dpl(); - ess->lister.erase(_where); - auto pos = ess->scroll_y_dpl(); - if (!pos.empty()) - { - if ((pos.cat == _where.cat) && (_where.item <= pos.item)) - { - if (pos.item == 0) - { - if (ess->lister.size_item(_where.cat) == 0) - pos.item = (pos.cat > 0 ? npos : 0); - } - else - --pos.item; - ess->set_scroll_y_dpl(pos); - } - } - else - { - if (pos_before.item) - --pos_before.item; - ess->set_scroll_y_dpl(pos_before); - } + auto origin = ess->content_view->origin(); + auto start_pos = ess->content_position(_where); + if (start_pos < origin.y) + origin.y -= ess->item_height(); + + ess->calc_content_size(false); + ess->content_view->change_position(origin.y, false, false); + ess->content_view->sync(false); + ess->update(); if(_where.item < ess->lister.size_item(_where.cat)) return ip; @@ -5516,18 +5404,18 @@ namespace nana /// sort() and ivalidate any existing reference from display position to absolute item, that is: after sort() display offset point to different items void listbox::sort_col(size_type col, bool reverse) { - _m_ess().lister.set_sort_index(col, reverse); + _m_ess().lister.sort_column(col, &reverse); } auto listbox::sort_col() const -> size_type { - return _m_ess().lister.sort_index(); + return _m_ess().lister.sort_attrs().column; } /// potencially ivalidate any existing reference from display position to absolute item, that is: after sort() display offset point to different items void listbox::unsort() { - _m_ess().lister.set_sort_index(npos, false); + this->sort_col(npos, false); } bool listbox::freeze_sort(bool freeze) @@ -5542,9 +5430,8 @@ namespace nana void listbox::show_header(bool sh) { - auto & ess = _m_ess(); - ess.header.visible(sh); - ess.update(); + _m_ess().header.visible(sh); + _m_ess().update(); } bool listbox::visible_header() const @@ -5554,14 +5441,13 @@ namespace nana void listbox::move_select(bool upwards) /// +#include + #include #include #include #include #include //introduces tolower #include +#include namespace nana { @@ -26,6 +30,20 @@ namespace nana { namespace menu { + + struct menu_type + { + using item_type = menu_item_type; + using item_container = std::vector>; + using iterator = item_container::iterator; + + std::vector owner; + item_container items; + unsigned max_pixels; + unsigned item_pixels; + nana::point gaps; + }; + //A helper function to check the style parameter inline bool good_checks(checks s) { @@ -68,6 +86,11 @@ namespace nana return item_.flags.checked; } + std::string menu_item_type::item_proxy::text() const + { + return item_.text; + } + std::size_t menu_item_type::item_proxy::index() const { return index_; @@ -82,8 +105,8 @@ namespace nana flags.checked = false; } - menu_item_type::menu_item_type(std::string text, const event_fn_t& f) - : text(std::move(text)), functor(f) + menu_item_type::menu_item_type(std::string text, const event_fn_t& fn) + : text(std::move(text)), event_handler(fn) { flags.enabled = true; flags.splitter = false; @@ -152,10 +175,10 @@ namespace nana //Stretchs menu icon only when it doesn't fit, center it otherwise. //Contributed by kmribti(pr#102) - nana::point ipos = pos; - ipos.x += (image_px - img.size().width ) / 2; - ipos.y += (image_px - img.size().height) / 2; - img.paste(graph, ipos); + img.paste(graph, { + pos.x + static_cast(image_px - img.size().width) / 2, + pos.y + static_cast(image_px - img.size().height) / 2 + }); } void item_text(graph_reference graph, const nana::point& pos, const std::string& text, unsigned text_pixels, const attr& at) @@ -182,11 +205,9 @@ namespace nana : noncopyable { public: - typedef menu_item_type item_type; - - typedef menu_type::item_container::value_type::event_fn_t event_fn_t; - typedef menu_type::item_container::iterator iterator; - typedef menu_type::item_container::const_iterator const_iterator; + using item_type = menu_item_type; + using event_fn_t = item_type::event_fn_t; + using iterator = menu_type::item_container::iterator; menu_builder() { @@ -205,40 +226,42 @@ namespace nana if(good_checks(s)) { if(root_.items.size() > index) - root_.items[index].style = s; + root_.items[index]->style = s; } } - void checked(std::size_t index, bool check) + void checked(std::size_t pos, bool check) { - if (root_.items.size() <= index) + if (root_.items.size() <= pos) return; - item_type & m = root_.items[index]; + item_type & m = *(root_.items[pos]); + if(check && (checks::option == m.style)) { - if(index) + //find a splitter in front of pos + if (pos > 0) { - std::size_t i = index; do { - item_type& el = root_.items[--i]; - if(el.flags.splitter) break; - - if(checks::option == el.style) - el.flags.checked = false; - }while(i); + if (root_.items[--pos]->flags.splitter) + { + ++pos; + break; + } + }while(pos > 0); } - for(std::size_t i = index + 1; i < root_.items.size(); ++i) + while (pos < root_.items.size()) { - item_type & el = root_.items[i]; + item_type & el = *(root_.items[pos++]); if(el.flags.splitter) break; if(checks::option == el.style) el.flags.checked = false; } } + m.flags.checked = check; } @@ -247,25 +270,12 @@ namespace nana return root_; } - const menu_type& data() const - { - return root_; - } - - void insert(std::size_t pos, std::string&& text, const event_fn_t& fn) - { - if(pos < root_.items.size()) - root_.items.emplace(root_.items.begin() + pos, std::move(text), std::ref(fn)); - else - root_.items.emplace_back(std::move(text), std::ref(fn)); - } - bool set_sub_menu(std::size_t pos, menu_type &sub) { if(root_.items.size() > pos) { - menu_item_type & item = root_.items[pos]; - if(item.sub_menu == nullptr) + auto & item = *(root_.items[pos]); + if(!item.sub_menu) { item.sub_menu = ⊂ sub.owner.emplace_back(&root_); @@ -280,17 +290,17 @@ namespace nana for(auto i : root_.owner) for(auto & m : i->items) { - if(m.sub_menu == &root_) - m.sub_menu = nullptr; + if(m->sub_menu == &root_) + m->sub_menu = nullptr; } for(auto & m : root_.items) { - if(m.sub_menu) - for(auto i = m.sub_menu->owner.begin(); i != m.sub_menu->owner.end();) + if(m->sub_menu) + for(auto i = m->sub_menu->owner.begin(); i != m->sub_menu->owner.end();) { if((*i) == &root_) - i = m.sub_menu->owner.erase(i); + i = m->sub_menu->owner.erase(i); else ++i; } @@ -317,8 +327,6 @@ namespace nana public: using item_proxy = menu_item_type::item_proxy; - renderer_interface * renderer; - menu_drawer() { state_.active = npos; @@ -328,9 +336,16 @@ namespace nana detail_.border.x = detail_.border.y = 2; } - void close_menu_tree(std::function && fn) + void set_run(menu_builder& mbuilder, menu_type& menu, std::function && menu_tree_destroyer) { - fn_close_tree_ = std::move(fn); + mbuilder_ = &mbuilder; + menu_ = &menu; + fn_close_tree_ = std::move(menu_tree_destroyer); + } + + menu_builder& mbuilder() + { + return *mbuilder_; } void attached(widget_reference widget, graph_reference graph) @@ -363,10 +378,12 @@ namespace nana void refresh(graph_reference graph) { - if (nullptr == menu_) return; + if (!(mbuilder_ && menu_)) + return; _m_adjust_window_size(); + auto renderer = mbuilder_->renderer().get(); renderer->background(graph, *widget_); const unsigned item_h_px = _m_item_height(); @@ -375,12 +392,13 @@ namespace nana unsigned strpixels = item_r.width - 60; - int text_top_off = (item_h_px - graph.text_extent_size(L"jh({[").height) / 2; + int text_top_off = static_cast(item_h_px - graph.text_extent_size(L"jh({[").height) / 2; std::size_t pos = 0; for (auto & m : menu_->items) { - if (m.flags.splitter) + auto item_ptr = m.get(); + if (item_ptr->flags.splitter) { graph_->line({ item_r.x + 40, item_r.y }, { static_cast(graph.width()) - 1, item_r.y }, colors::gray_border); item_r.y += 2; @@ -388,24 +406,24 @@ namespace nana continue; } - renderer_interface::attr attr = _m_make_renderer_attr(pos == state_.active, m); + renderer_interface::attr attr = _m_make_renderer_attr(pos == state_.active, *item_ptr); //Draw item background renderer->item(*graph_, item_r, attr); //Draw text, the text is transformed from orignal for hotkey character wchar_t hotkey; std::string::size_type hotkey_pos; - auto text = API::transform_shortkey_text(m.text, hotkey, &hotkey_pos); + auto text = API::transform_shortkey_text(item_ptr->text, hotkey, &hotkey_pos); - if (m.image.empty() == false) - renderer->item_image(graph, nana::point(item_r.x + 5, item_r.y + static_cast(item_h_px - image_px) / 2 - 1), image_px, m.image); + if (item_ptr->image.empty() == false) + renderer->item_image(graph, nana::point(item_r.x + 5, item_r.y + static_cast(item_h_px - image_px) / 2 - 1), image_px, item_ptr->image); renderer->item_text(graph, nana::point(item_r.x + 40, item_r.y + text_top_off), text, strpixels, attr); if (hotkey) { - m.hotkey = hotkey; - if (m.flags.enabled) + item_ptr->hotkey = hotkey; + if (item_ptr->flags.enabled) { auto off_px = (hotkey_pos ? graph.text_extent_size(text.c_str(), hotkey_pos).width : 0); auto hotkey_px = graph.text_extent_size(text.c_str() + hotkey_pos, 1).width; @@ -420,7 +438,7 @@ namespace nana } } - if (m.sub_menu) + if (item_ptr->sub_menu) renderer->sub_arrow(graph, nana::point(graph_->width() - 20, item_r.y), item_h_px, attr); item_r.y += item_r.height + 1; @@ -437,11 +455,14 @@ namespace nana bool goto_next(bool forword) { state_.nullify_mouse = true; - if (menu_->items.empty()) + + auto & items = menu_->items; + + if (items.empty()) return false; auto pos = state_.active; - const auto lastpos = menu_->items.size() - 1; + const auto lastpos = items.size() - 1; bool end = false; while(true) @@ -476,7 +497,8 @@ namespace nana --pos; } - if(! menu_->items.at(pos).flags.splitter && menu_->items.at(pos).flags.enabled) + auto item_ptr = items.at(pos).get(); + if (!item_ptr->flags.splitter && item_ptr->flags.enabled) break; } @@ -496,13 +518,14 @@ namespace nana { if (!state_.nullify_mouse) { + auto & items = menu_->items; std::size_t index = _m_get_index_by_pos(pos.x, pos.y); if (index != state_.active) { - if ((index == npos) && menu_->items.at(state_.active).sub_menu && state_.sub_window) + if ((index == npos) && items.at(state_.active)->sub_menu && state_.sub_window) return false; - state_.active = (index != npos && menu_->items.at(index).flags.splitter) ? npos : index; + state_.active = (index != npos && items.at(index)->flags.splitter) ? npos : index; state_.active_timestamp = nana::system::timestamp(); return true; } @@ -511,11 +534,6 @@ namespace nana return false; } - void data(menu_type & menu) - { - menu_ = & menu; - } - menu_type* data() const { return menu_; @@ -531,19 +549,20 @@ namespace nana if (npos == state_.active) return nullptr; - auto sub = menu_->items.at(state_.active).sub_menu; + auto & items = menu_->items; + auto sub = items.at(state_.active)->sub_menu; if (sub) { pos.x = static_cast(graph_->width()) - 2; pos.y = 2; auto index = state_.active; - for (auto & m : menu_->items) + for (auto & m : items) { if (0 == index--) break; - if (m.flags.splitter) + if (m->flags.splitter) { pos.y += 2; continue; @@ -565,15 +584,16 @@ namespace nana std::size_t index = 0; for(auto & m : menu_->items) { - if (std::tolower(m.hotkey) != key) + auto item_ptr = m.get(); + if (std::tolower(m->hotkey) != key) { ++index; continue; } - if(!m.flags.splitter) + if (!item_ptr->flags.splitter) { - if(m.sub_menu) + if (item_ptr->sub_menu) { state_.active = index; state_.active_timestamp = nana::system::timestamp(); @@ -581,13 +601,13 @@ namespace nana API::refresh_window(*widget_); return 2; } - else if(m.flags.enabled) + else if (item_ptr->flags.enabled) { fn_close_tree_(); - if (m.functor) + if (item_ptr->event_handler) { - item_proxy ip(index, m); - m.functor.operator()(ip); + item_proxy ip(index, *item_ptr); + item_ptr->event_handler.operator()(ip); } return 1; } @@ -617,9 +637,9 @@ namespace nana int pos = detail_.border.y; std::size_t index = 0; - for(auto & m : menu_->items) + for (auto & m : menu_->items) { - unsigned h = (m.flags.splitter ? 1 : _m_item_height()); + unsigned h = (m->flags.splitter ? 1 : _m_item_height()); if(pos <= y && y < static_cast(pos + h)) return index; else if(y < pos) @@ -640,13 +660,13 @@ namespace nana { nana::size size; - if(menu_->items.size()) + if (menu_->items.size()) { - for(auto & m : menu_->items) + for (auto & m : menu_->items) { - if(false == m.flags.splitter) + if(false == m->flags.splitter) { - nana::size item_size = graph_->text_extent_size(m.text); + nana::size item_size = graph_->text_extent_size(m->text); if(size.width < item_size.width) size.width = item_size.width; } @@ -658,7 +678,7 @@ namespace nana size.height = static_cast(menu_->items.size() - size.height) * _m_item_height() + size.height + static_cast(menu_->items.size() - 1); } - if(size.width > menu_->max_pixels) + if (size.width > menu_->max_pixels) size.width = menu_->max_pixels; return size; @@ -687,14 +707,15 @@ namespace nana pos.y = scr_area.bottom() - static_cast(size.height); if(pos.y < scr_area.y) pos.y = scr_area.y; - auto owner = API::get_owner_window(*widget_); - API::calc_window_point(owner, pos); + API::calc_window_point(API::get_owner_window(*widget_), pos); widget_->move(pos.x, pos.y); } private: widget *widget_{nullptr}; paint::graphics *graph_{nullptr}; - menu_type *menu_{nullptr}; + + menu_builder* mbuilder_{ nullptr }; + menu_type* menu_{ nullptr }; std::function fn_close_tree_; @@ -722,16 +743,16 @@ namespace nana using item_type = menu_builder::item_type; - menu_window(window wd, bool is_wd_parent_menu, const point& pos, renderer_interface * rdptr) + menu_window(window wd, bool is_wd_parent_menu, const point& pos, menu_builder& mbuilder, menu_type& menu_data) //add a is_wd_parent_menu to determine whether the menu wants the focus. //if a submenu gets the focus, the program may cause a crash error when the submenu is being destroyed : base_type(wd, false, rectangle(pos, nana::size(2, 2)), appear::bald()), - want_focus_{ (!wd) || ((!is_wd_parent_menu) && (API::focus_window() != wd)) }, + want_focus_{ (!wd) || ((!is_wd_parent_menu) && (API::root(API::focus_window()) != API::root(wd))) }, event_focus_{ nullptr } { caption("nana menu window"); - get_drawer_trigger().close_menu_tree([this]{ this->_m_close_all(); }); - get_drawer_trigger().renderer = rdptr; + get_drawer_trigger().set_run(mbuilder, menu_data, [this]{ this->_m_close_all(); }); + state_.owner_menubar = state_.self_submenu = false; state_.auto_popup_submenu = true; @@ -739,7 +760,7 @@ namespace nana submenu_.object = nullptr; state_.mouse_pos = API::cursor_position(); - events().mouse_move.connect_unignorable([this]{ + events().mouse_move.connect_unignorable([this](const arg_mouse&){ nana::point pos = API::cursor_position(); if (pos != state_.mouse_pos) { @@ -753,10 +774,8 @@ namespace nana }); } - void popup(menu_type& menu, bool owner_menubar) + void popup(bool owner_menubar) { - get_drawer_trigger().data(menu); - if (!want_focus_) { API::activate_window(this->parent()); @@ -770,7 +789,7 @@ namespace nana } auto & events = this->events(); - events.destroy.connect_unignorable([this]{ + events.destroy.connect_unignorable([this](const arg_destroy&){ _m_destroy(); }); @@ -880,7 +899,7 @@ namespace nana if ((npos == active) || !menu) return; - menu_item_type & item = menu->items.at(active); + menu_item_type & item = *(menu->items.at(active)); if ((!item.flags.enabled) || item.flags.splitter || item.sub_menu) return; @@ -891,35 +910,18 @@ namespace nana } else if (checks::option == item.style) { - //Forward Looks for a splitter - auto pos = active; - while (pos) - { - if (menu->items.at(--pos).flags.splitter) - break; - } - - for (; pos < menu->items.size(); ++pos) - { - menu_item_type & im = menu->items.at(pos); - if (im.flags.splitter) break; - - if ((checks::option == im.style) && im.flags.checked) - im.flags.checked = false; - } - - item.flags.checked = true; + get_drawer_trigger().mbuilder().checked(active, true); } this->_m_close_all(); //means deleting this; - //The deleting operation has moved here, because item.functor.operator()(ip) + //The deleting operation has moved here, because item.event_handler.operator()(ip) //may create a window, which make a killing focus for menu window, if so the close_all - //operation preformences after item.functor.operator()(ip), that would be deleting this object twice! + //operation preformences after item.event_handler.operator()(ip), that would be deleting this object twice! - if (item.functor) + if (item.event_handler) { item_type::item_proxy ip(active, item); - item.functor.operator()(ip); + item.event_handler.operator()(ip); } } private: @@ -989,6 +991,12 @@ namespace nana case keyboard::enter: this->pick(); break; + case keyboard::escape: + //Leave sub menu. But if the sub menu doesn't exist, + //close the menu. + if (!this->submenu(false)) + close(); + break; default: if (2 != send_shortkey(arg.key)) { @@ -1034,7 +1042,7 @@ namespace nana menu_ptr->gaps = drawer.data()->gaps; pos += menu_ptr->gaps; - menu_window & mwnd = form_loader()(handle(), true, pos, drawer.renderer); + menu_window & mwnd = form_loader()(handle(), true, pos, drawer.mbuilder(), *menu_ptr); mwnd.state_.self_submenu = true; submenu_.child = &mwnd; submenu_.child->submenu_.parent = this; @@ -1042,7 +1050,7 @@ namespace nana API::set_window_z_order(handle(), mwnd.handle(), z_order_action::none); - mwnd.popup(*menu_ptr, state_.owner_menubar); + mwnd.popup(state_.owner_menubar); drawer.set_sub_window(true); if (forced) mwnd.goto_next(true); @@ -1099,7 +1107,7 @@ namespace nana }; drawerbase::menu::menu_builder mbuilder; - drawerbase::menu::menu_window * uiobj; + drawerbase::menu::menu_window * window_ptr; std::function destroy_answer; std::map sub_container; }; @@ -1107,7 +1115,7 @@ namespace nana menu::menu() :impl_(new implement) { - impl_->uiobj = nullptr; + impl_->window_ptr = nullptr; } menu::~menu() @@ -1120,34 +1128,56 @@ namespace nana delete impl_; } - auto menu::append(const std::string& text, const menu::event_fn_t& f) -> item_proxy + auto menu::append(std::string text_utf8, const menu::event_fn_t& handler) -> item_proxy { - impl_->mbuilder.data().items.emplace_back(text, f); - return item_proxy(size() - 1, impl_->mbuilder.data().items.back()); + std::unique_ptr item{ new item_type{ std::move(text_utf8), handler } }; + impl_->mbuilder.data().items.emplace_back(item.get()); + return item_proxy(size() - 1, *item.release()); } void menu::append_splitter() { - impl_->mbuilder.data().items.emplace_back(); + impl_->mbuilder.data().items.emplace_back(new item_type); + } + + auto menu::insert(std::size_t pos, std::string text_utf8, const event_fn_t& handler) -> item_proxy + { + auto & items = impl_->mbuilder.data().items; + if (pos > items.size()) + throw std::out_of_range("menu: a new item inserted to an invalid position"); + + std::unique_ptr item{ new item_type{ std::move(text_utf8), handler } }; + + items.emplace( +#ifdef _MSC_VER + items.cbegin() + pos, +#else + items.begin() + pos, +#endif + item.get()); + + return item_proxy{ pos, *item.release() }; } void menu::clear() { + internal_scope_guard lock; impl_->mbuilder.data().items.clear(); } void menu::enabled(std::size_t index, bool enable) { - impl_->mbuilder.data().items.at(index).flags.enabled = enable; + impl_->mbuilder.data().items.at(index)->flags.enabled = enable; } bool menu::enabled(std::size_t index) const { - return impl_->mbuilder.data().items.at(index).flags.enabled; + return impl_->mbuilder.data().items.at(index)->flags.enabled; } void menu::erase(std::size_t index) { + internal_scope_guard lock; auto & items = impl_->mbuilder.data().items; if(index < items.size()) items.erase(items.begin() + index); @@ -1155,7 +1185,12 @@ namespace nana void menu::image(std::size_t index, const paint::image& img) { - impl_->mbuilder.data().items.at(index).image = img; + impl_->mbuilder.data().items.at(index)->image = img; + } + + void menu::text(std::size_t index, std::string text_utf8) + { + impl_->mbuilder.data().items.at(index)->text.swap(text_utf8); } bool menu::link(std::size_t index, menu& menu_obj) @@ -1196,22 +1231,22 @@ namespace nana void menu::popup(window wd, int x, int y) { - _m_popup(wd, x, y, false); + _m_popup(wd, { x, y }, false); } void menu::popup_await(window wd, int x, int y) { - _m_popup(wd, x, y, false); - if (impl_->uiobj) - API::wait_for(impl_->uiobj->handle()); + _m_popup(wd, { x, y }, false); + if (impl_->window_ptr) + API::wait_for(impl_->window_ptr->handle()); } void menu::close() { - if(impl_->uiobj) + if (impl_->window_ptr) { - impl_->uiobj->close(); - impl_->uiobj = nullptr; + impl_->window_ptr->close(); + impl_->window_ptr = nullptr; } } @@ -1227,17 +1262,17 @@ namespace nana bool menu::checked(std::size_t index) const { - return impl_->mbuilder.data().items.at(index).flags.checked; + return impl_->mbuilder.data().items.at(index)->flags.checked; } void menu::answerer(std::size_t index, const event_fn_t& fn) { - impl_->mbuilder.data().items.at(index).functor = fn; + impl_->mbuilder.data().items.at(index)->event_handler = fn; } - void menu::destroy_answer(const std::function& fn) + void menu::destroy_answer(std::function fn) { - impl_->destroy_answer = fn; + impl_->destroy_answer = std::move(fn); } void menu::gaps(const nana::point& pos) @@ -1247,18 +1282,18 @@ namespace nana void menu::goto_next(bool forward) { - if(impl_->uiobj) - impl_->uiobj->goto_next(forward); + if (impl_->window_ptr) + impl_->window_ptr->goto_next(forward); } bool menu::goto_submen() { - return (impl_->uiobj ? impl_->uiobj->submenu(true) : false); + return (impl_->window_ptr ? impl_->window_ptr->submenu(true) : false); } bool menu::exit_submenu() { - return (impl_->uiobj ? impl_->uiobj->submenu(false) : false); + return (impl_->window_ptr ? impl_->window_ptr->submenu(false) : false); } std::size_t menu::size() const @@ -1268,13 +1303,13 @@ namespace nana int menu::send_shortkey(wchar_t key) { - return (impl_->uiobj ? impl_->uiobj->send_shortkey(key) : 0); + return (impl_->window_ptr ? impl_->window_ptr->send_shortkey(key) : 0); } void menu::pick() { - if (impl_->uiobj) - impl_->uiobj->pick(); + if (impl_->window_ptr) + impl_->window_ptr->pick(); } menu& menu::max_pixels(unsigned px) @@ -1309,19 +1344,19 @@ namespace nana impl_->mbuilder.renderer(rd); } - void menu::_m_popup(window wd, int x, int y, bool called_by_menubar) + void menu::_m_popup(window wd, const point& pos, bool called_by_menubar) { if (impl_->mbuilder.data().items.size()) { close(); - impl_->uiobj = &(form_loader()(wd, false, point(x, y), &(*impl_->mbuilder.renderer()))); - impl_->uiobj->events().destroy.connect_unignorable([this]{ - impl_->uiobj = nullptr; + impl_->window_ptr = &(form_loader()(wd, false, pos, impl_->mbuilder, impl_->mbuilder.data())); + impl_->window_ptr->events().destroy.connect_unignorable([this](const arg_destroy&){ + impl_->window_ptr = nullptr; if (impl_->destroy_answer) impl_->destroy_answer(); }); - impl_->uiobj->popup(impl_->mbuilder.data(), called_by_menubar); + impl_->window_ptr->popup(called_by_menubar); } } //end class menu diff --git a/source/gui/widgets/menubar.cpp b/source/gui/widgets/menubar.cpp index 3d39b1f9..9b17d1e7 100644 --- a/source/gui/widgets/menubar.cpp +++ b/source/gui/widgets/menubar.cpp @@ -1,7 +1,7 @@ /* * A Menubar implementation * Nana C++ Library(http://www.nanapro.org) -* Copyright(C) 2009-2015 Jinhao(cnjinhao@hotmail.com) +* Copyright(C) 2009-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -20,7 +20,7 @@ namespace nana public: static void popup(menu& m, window wd, int x, int y) { - m._m_popup(wd, x, y, true); + m._m_popup(wd, { x, y }, true); } }; @@ -85,24 +85,24 @@ namespace nana //class item_renderer item_renderer::item_renderer(window wd, graph_reference graph) - :handle_(wd), graph_(graph) + :handle_(wd), graph_(graph), scheme_ptr_(static_cast(API::dev::get_scheme(wd))) {} void item_renderer::background(const nana::point& pos, const nana::size& size, state item_state) { - auto bground = API::fgcolor(handle_); + auto bground = scheme_ptr_->text_fgcolor; ::nana::color border, body, corner; switch (item_state) { case state::highlighted: - border = colors::highlight; - body.from_rgb(0xC0, 0xDD, 0xFC); + border = scheme_ptr_->border_highlight; + body = scheme_ptr_->body_highlight; corner = body.blend(bground, 0.5); break; case state::selected: - border = colors::dark_border; - body = colors::white; + border = scheme_ptr_->border_selected; + body = scheme_ptr_->body_selected; corner = body.blend(bground, 0.5); break; default: //Don't process other states. @@ -120,14 +120,16 @@ namespace nana void item_renderer::caption(const point& pos, const native_string_type& text) { - graph_.string(pos, text, colors::black); + graph_.string(pos, text, scheme_ptr_->text_fgcolor); + } //end class item_renderer //class trigger trigger::trigger() - : items_(new itembase) - {} + : widget_(nullptr) + , graph_(nullptr) + , items_(new itembase) {} trigger::~trigger() { @@ -202,7 +204,7 @@ namespace nana { int x = item_pos.x + item_s.width; int y1 = item_pos.y + 2, y2 = item_pos.y + item_s.height - 1; - graph.line({ x, y1 }, { x, y2 }, bgcolor.blend(colors::gray_border, 0.4)); + graph.line({ x, y1 }, { x, y2 }, bgcolor.blend(colors::gray_border, 0.6)); graph.line({ x + 1, y1 }, { x + 1, y2 }, bgcolor.blend(colors::button_face_shadow_end, 0.5)); } @@ -213,13 +215,13 @@ namespace nana if (hotkey) { unsigned off_w = (hotkey_pos ? graph.text_extent_size(text.c_str(), hotkey_pos).width : 0); - nana::size hotkey_size = graph.text_extent_size(text.c_str() + hotkey_pos, 1); + auto hotkey_size = graph.text_extent_size(text.c_str() + hotkey_pos, 1); unsigned ascent, descent, inleading; graph.text_metrics(ascent, descent, inleading); int x = item_pos.x + 8 + off_w; int y = item_pos.y + text_top_off + ascent + 1; - graph.line({ x, y }, { x + static_cast(hotkey_size.width) - 1, y }, ::nana::colors::black); + graph.line({ x, y }, { x + static_cast(hotkey_size.width) - 1, y }, ird.scheme_ptr()->text_fgcolor); } item_pos.x += i->size.width; @@ -235,7 +237,7 @@ namespace nana bool popup = false; if(state_.behavior == state_type::behavior_focus) { - std::size_t index = _m_item_by_pos(arg.pos); + auto index = _m_item_by_pos(arg.pos); if(index != npos && state_.active != index) { state_.active = index; @@ -362,6 +364,7 @@ namespace nana case 2: //GOTO SUBMENU state_.menu->goto_submen(); break; + default: break; } break; } @@ -390,7 +393,7 @@ namespace nana } break; default: - std::size_t index = items_->find(arg.key); + auto index = items_->find(arg.key); if(index != npos) { state_.active = index; @@ -418,7 +421,7 @@ namespace nana else { state_.behavior = state_type::behavior_none; - nana::point pos = API::cursor_position(); + auto pos = API::cursor_position(); API::calc_window_point(widget_->handle(), pos); state_.active = _m_item_by_pos(pos); } @@ -433,7 +436,7 @@ namespace nana { API::focus_window(widget_->handle()); - std::size_t index = items_->find(arg.key); + auto index = items_->find(arg.key); if(index != npos && (index != state_.active || nullptr == state_.menu)) { _m_close_menu(); @@ -455,7 +458,7 @@ namespace nana if(items_->cont().empty()) return; const std::size_t last_pos = items_->cont().size() - 1; - std::size_t index = state_.active; + auto index = state_.active; if(to_left) { --index; @@ -564,7 +567,7 @@ namespace nana { if(state_.nullify_mouse == false) { - std::size_t which = _m_item_by_pos(pos); + auto which = _m_item_by_pos(pos); if((which != state_.active) && (which != npos || (false == state_.menu_active))) { state_.active = which; @@ -602,7 +605,7 @@ namespace nana void menubar::create(window wd) { - widget_object + widget_object ::create(wd, rectangle(nana::size(API::window_size(wd).width, 28))); API::dev::set_menubar(handle(), true); @@ -621,7 +624,7 @@ namespace nana menu& menubar::at(std::size_t index) const { - menu* p = get_drawer_trigger().at(index); + auto p = get_drawer_trigger().at(index); if(nullptr == p) throw std::out_of_range("menubar::at, out of range"); return *p; diff --git a/source/gui/widgets/panel.cpp b/source/gui/widgets/panel.cpp index 5c1fdee1..2592a3dd 100644 --- a/source/gui/widgets/panel.cpp +++ b/source/gui/widgets/panel.cpp @@ -1,6 +1,6 @@ /* * A Panel Implementation - * Copyright(C) 2003-2013 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -31,7 +31,7 @@ namespace nana void drawer::refresh(graph_reference graph) { - if(bground_mode::basic != API::effects_bground_mode(window_)) + if (!API::dev::copy_transparent_background(window_, graph)) graph.rectangle(true, API::bgcolor(window_)); } //end class drawer diff --git a/source/gui/widgets/picture.cpp b/source/gui/widgets/picture.cpp index bf739693..b1e40aae 100644 --- a/source/gui/widgets/picture.cpp +++ b/source/gui/widgets/picture.cpp @@ -1,7 +1,7 @@ /* * A Picture Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -16,6 +16,7 @@ #include #include #include +#include namespace nana { @@ -23,11 +24,13 @@ namespace nana { namespace picture { + class content_measurer; + struct implement { widget* wdg_ptr{nullptr}; paint::graphics* graph_ptr{nullptr}; - + std::unique_ptr measurer; struct gradual_bground_tag { @@ -47,9 +50,37 @@ namespace nana }backimg; }; + class content_measurer + : public dev::widget_content_measurer_interface + { + public: + content_measurer(implement* impl) + : impl_{impl} + {} + + optional measure(graph_reference /*graph*/, unsigned limit_pixels, bool /*limit_width*/) const override + { + //Picture doesn't provide a support of vfit and hfit + if (!limit_pixels) + { + if (impl_->backimg.valid_area.empty()) + return impl_->backimg.image.size(); + } + return{}; + } + + size extension() const override + { + return{}; + } + private: + implement* const impl_; + }; + //class drawer drawer::drawer() :impl_(new implement) { + impl_->measurer.reset(new content_measurer{impl_}); } drawer::~drawer() @@ -61,6 +92,7 @@ namespace nana { impl_->wdg_ptr = &wdg; impl_->graph_ptr = &graph; + API::dev::set_measurer(wdg, impl_->measurer.get()); } void drawer::refresh(graph_reference graph) @@ -162,9 +194,9 @@ namespace nana { auto graph = impl_->graph_ptr; - if (graph && (bground_mode::basic != API::effects_bground_mode(*impl_->wdg_ptr))) + if (graph && (!API::dev::copy_transparent_background(*impl_->wdg_ptr, *graph))) { - if (w < graph->size().width || h < graph->size().height /* .width ??? */ || impl_->backimg.image.alpha()) + if (w < graph->size().width || h < graph->size().height || impl_->backimg.image.alpha()) { auto & bground = impl_->gradual_bground; if (bground.gradual_from.invisible() || bground.gradual_to.invisible()) @@ -288,7 +320,7 @@ namespace nana bool picture::transparent() const { - return (bground_mode::basic == API::effects_bground_mode(*this)); + return API::is_transparent_background(*this); } //end class picture }//end namespace nana diff --git a/source/gui/widgets/skeletons/content_view.cpp b/source/gui/widgets/skeletons/content_view.cpp new file mode 100644 index 00000000..4dfc49a0 --- /dev/null +++ b/source/gui/widgets/skeletons/content_view.cpp @@ -0,0 +1,511 @@ +/* +* A Content View Implementation +* Nana C++ Library(http://www.nanapro.org) +* Copyright(C) 2017 Jinhao(cnjinhao@hotmail.com) +* +* Distributed under the Boost Software License, Version 1.0. +* (See accompanying file LICENSE_1_0.txt or copy at +* http://www.boost.org/LICENSE_1_0.txt) +* +* @file: nana/gui/widgets/skeletons/content_view.hpp +* @author: Jinhao +*/ + +#include "content_view.hpp" +#include +#include + +namespace nana { + namespace widgets { + namespace skeletons + { + struct content_view::implementation + { + content_view& view; + window const window_handle; + nana::rectangle disp_area; + nana::size content_size; + + point skew_horz; + point skew_vert; + nana::size extra_px; + + bool enable_update{ true }; + bool drag_started{ false }; + point origin; + + nana::scroll horz; + nana::scroll vert; + + timer tmr; + + events_type events; + + struct conf_provider + { + std::function wheel_speed; + }provider; + + implementation(content_view& v, window handle) : + view(v), + window_handle(handle) + { + API::events(handle).mouse_wheel.connect_unignorable([this](const arg_wheel& arg) { + scroll_interface * scroll = nullptr; + switch (arg.which) + { + case arg_wheel::wheel::vertical: + scroll = | + break; + case arg_wheel::wheel::horizontal: + scroll = &horz; + break; + default: + //Other button is not unsupported. + return; + } + + if (!API::empty_window(arg.window_handle)) + { + auto align_px = (scroll->value() % scroll->step()); + if (align_px) + { + auto new_value = scroll->value() - align_px; + if (!arg.upwards) + new_value += scroll->step(); + + scroll->value(new_value); + } + else + { + unsigned speed = 1; + if (provider.wheel_speed) + { + speed = provider.wheel_speed(); + if (0 == speed) + speed = 1; + } + scroll->make_step(!arg.upwards, speed); + } + } + }); + + auto mouse_evt = [this](const arg_mouse& arg) + { + if (event_code::mouse_move == arg.evt_code) + { + if (!arg.is_left_button()) + return; + + if ((!this->drag_started) && this->view.view_area().is_hit(arg.pos)) + this->drag_started = true; + + if (this->drag_started && this->drive(arg.pos)) + { + tmr.interval(16); + tmr.start(); + } + } + else if (event_code::mouse_up == arg.evt_code) + { + this->drag_started = false; + tmr.stop(); + } + }; + + API::events(handle).mouse_move.connect_unignorable(mouse_evt); + API::events(handle).mouse_up.connect_unignorable(mouse_evt); + + tmr.elapse([this](const arg_elapse&) + { + auto curs = ::nana::API::cursor_position(); + ::nana::API::calc_window_point(window_handle, curs); + + if (this->drive(curs)) + { + if (events.hover_outside) + events.hover_outside(curs); + + API::refresh_window(window_handle); + view.sync(false); + } + else + tmr.stop(); + }); + } + + bool drive(const point& cursor_pos) + { + auto const area = view.view_area(); + + point skew; + + if (disp_area.x > cursor_pos.x) + skew.x = cursor_pos.x - disp_area.x; + else if (cursor_pos.x > disp_area.x + static_cast(area.width)) + skew.x = cursor_pos.x - (disp_area.x + static_cast(area.width)); + + if (disp_area.y > cursor_pos.y) + skew.y = cursor_pos.y - disp_area.y; + else if (cursor_pos.y > disp_area.y + static_cast(area.height)) + skew.y = cursor_pos.y - (disp_area.y + static_cast(area.height)); + + if (skew.x == 0 && skew.y == 0) + return false; + + auto speed_horz = 0; + if (skew.x) + speed_horz = skew.x / (std::max)(1, static_cast(horz.step())) + (skew.x < 0 ? -1 : 1); + + auto speed_vert = 0; + if (skew.y) + speed_vert = skew.y / (std::max)(1, static_cast(vert.step())) + (skew.y < 0 ? -1 : 1); + + speed_horz = (std::min)(5, (std::max)(speed_horz, -5)); + speed_vert = (std::min)(5, (std::max)(speed_vert, -5)); + + view.move_origin({ + speed_horz, speed_vert + }); + + return true; + } + + void size_changed(bool try_update) + { + auto imd_area = view.view_area(); + + //event hander for scrollbars + auto event_fn = [this](const arg_scroll& arg) + { + if (arg.window_handle == this->vert.handle()) + origin.y = static_cast(this->vert.value()); + else + origin.x = static_cast(this->horz.value()); + + if (this->events.scrolled) + this->events.scrolled(); + + if (this->enable_update) + API::refresh_window(this->window_handle); + }; + + this->enable_update = try_update; + + if (imd_area.width != disp_area.width) + { + if (vert.empty()) + { + vert.create(window_handle); + vert.events().value_changed.connect_unignorable(event_fn); + API::take_active(vert, false, window_handle); + this->enable_update = false; + } + + vert.move({ + disp_area.x + static_cast(imd_area.width) + skew_vert.x, + disp_area.y + skew_vert.y, + space(), + imd_area.height + extra_px.height + }); + + vert.amount(content_size.height); + vert.range(imd_area.height); + vert.value(origin.y); + } + else + { + vert.close(); + origin.y = 0; + } + + if (imd_area.height != disp_area.height) + { + if (horz.empty()) + { + horz.create(window_handle); + horz.events().value_changed.connect_unignorable(event_fn); + API::take_active(horz, false, window_handle); + this->enable_update = false; + } + + horz.move({ + disp_area.x + skew_horz.x, + disp_area.y + static_cast(imd_area.height) + skew_horz.y, + imd_area.width + extra_px.width, + space() + }); + + horz.amount(content_size.width); + horz.range(imd_area.width); + horz.value(origin.x); + } + else + { + horz.close(); + origin.x = 0; + } + + this->enable_update = true; + } + }; + + content_view::content_view(window handle) + : impl_{ new implementation{*this, handle} } + { + } + + content_view::~content_view() + { + delete impl_; + } + + content_view::events_type& content_view::events() + { + return impl_->events; + } + + void content_view::step(unsigned step_value, bool horz) + { + if (horz) + impl_->horz.step(step_value); + else + impl_->vert.step(step_value); + } + + bool content_view::scroll(bool forwards, bool horz) + { + unsigned speed = 1; + if (impl_->provider.wheel_speed) + { + speed = impl_->provider.wheel_speed(); + if (0 == speed) + speed = 1; + } + + if (horz) + return impl_->horz.make_step(forwards, speed); + + return impl_->vert.make_step(forwards, speed); + } + + bool content_view::turn_page(bool forwards, bool horz) + { + if (horz) + return impl_->horz.make_page_scroll(forwards); + else + return impl_->vert.make_page_scroll(forwards); + } + + void content_view::disp_area(const rectangle& da, const point& skew_horz, const point& skew_vert, const size& extra_px, bool try_update) + { + if (impl_->disp_area != da) + { + impl_->disp_area = da; + impl_->skew_horz = skew_horz; + impl_->skew_vert = skew_vert; + impl_->extra_px = extra_px; + + auto imd_area = this->view_area(); + if (static_cast(impl_->content_size.width) - impl_->origin.x < static_cast(imd_area.width)) + impl_->origin.x = (std::max)(0, static_cast(impl_->content_size.width) - static_cast(imd_area.width)); + + if (static_cast(impl_->content_size.height) - impl_->origin.y < static_cast(imd_area.height)) + impl_->origin.y = (std::max)(0, static_cast(impl_->content_size.height) - static_cast(imd_area.height)); + + impl_->size_changed(try_update); + } + } + + void content_view::content_size(const size& sz, bool try_update) + { + if (sz.height < impl_->content_size.height) + { + if (impl_->origin.y + impl_->disp_area.height > sz.height) + { + if (impl_->disp_area.height > sz.height) + impl_->origin.y = 0; + else + impl_->origin.y = sz.height - impl_->disp_area.height; + } + } + + if (sz.width < impl_->content_size.width) + { + if (impl_->origin.x + impl_->disp_area.width > sz.width) + { + if (impl_->disp_area.width > sz.width) + impl_->origin.x = 0; + else + impl_->origin.x = sz.width - impl_->disp_area.width; + } + } + + + impl_->content_size = sz; + + impl_->size_changed(try_update); + } + + const size& content_view::content_size() const + { + return impl_->content_size; + } + + const point& content_view::origin() const + { + return impl_->origin; + } + + rectangle content_view::corner() const + { + rectangle r; + + auto imd_area = this->view_area(); + + r.x = impl_->disp_area.x + static_cast(imd_area.width) + impl_->skew_vert.x; + r.y = impl_->disp_area.y + static_cast(imd_area.height) + impl_->skew_horz.y; + + + unsigned extra_horz = (impl_->disp_area.width < impl_->content_size.width ? space() : 0); + unsigned extra_vert = (impl_->disp_area.height < impl_->content_size.height + extra_horz ? space() : 0); + + if ((0 == extra_horz) && extra_vert) + extra_horz = (impl_->disp_area.width < impl_->content_size.width + extra_vert ? space() : 0); + + r.width = extra_horz; + r.height = extra_vert; + + return r; + } + + void content_view::draw_corner(graph_reference graph) + { + auto r = corner(); + if(!r.empty()) + graph.rectangle(r, true, colors::button_face); + } + + rectangle content_view::view_area() const + { + unsigned extra_horz = (impl_->disp_area.width < impl_->content_size.width ? space() : 0); + unsigned extra_vert = (impl_->disp_area.height < impl_->content_size.height + extra_horz ? space() : 0); + + if ((0 == extra_horz) && extra_vert) + extra_horz = (impl_->disp_area.width < impl_->content_size.width + extra_vert ? space() : 0); + + return rectangle{ + impl_->disp_area.position(), + size{ + impl_->disp_area.width > extra_vert ? impl_->disp_area.width - extra_vert : 0, + impl_->disp_area.height > extra_horz ? impl_->disp_area.height - extra_horz : 0 + } + }; + } + + unsigned content_view::extra_space(bool horz) const + { + return ((horz ? impl_->horz.empty() : impl_->vert.empty()) ? 0 : space()); + } + + void content_view::change_position(int pos, bool aligned, bool horz) + { + if (aligned) + pos -= (pos % static_cast(horz ? impl_->horz.step() : impl_->vert.step())); + + auto imd_size = this->view_area(); + + if (horz) + { + if (pos + imd_size.width > impl_->content_size.width) + pos = static_cast(impl_->content_size.width) - static_cast(imd_size.width); + + if (pos < 0) pos = 0; + + impl_->origin.x = pos; + } + else + { + if (pos + imd_size.height > impl_->content_size.height) + pos = static_cast(impl_->content_size.height) - static_cast(imd_size.height); + + if (pos < 0) pos = 0; + + impl_->origin.y = pos; + } + } + + void content_view::move_origin(const point& skew) + { + auto imd_area = this->view_area(); + + impl_->origin.x += skew.x; + if (impl_->origin.x + imd_area.width > impl_->content_size.width) + impl_->origin.x = static_cast(impl_->content_size.width) - static_cast(imd_area.width); + + if (impl_->origin.x < 0) impl_->origin.x = 0; + + + impl_->origin.y += skew.y; + if (impl_->origin.y + imd_area.height > impl_->content_size.height) + impl_->origin.y = static_cast(impl_->content_size.height) - static_cast(imd_area.height); + + if (impl_->origin.y < 0) impl_->origin.y = 0; + } + + void content_view::sync(bool try_update) + { + impl_->enable_update = try_update; + impl_->horz.value(impl_->origin.x); + impl_->vert.value(impl_->origin.y); + impl_->enable_update = true; + } + + void content_view::pursue(const point& cursor) + { + if (impl_->disp_area.is_hit(cursor)) + return; + + int delta = 0; + if (cursor.x < impl_->disp_area.x) + delta = cursor.x - impl_->disp_area.x; + else if (cursor.x > impl_->disp_area.right()) + delta = cursor.x - impl_->disp_area.right(); + + impl_->origin.x += delta; + if (impl_->origin.x < 0) + impl_->origin.x = 0; + + if (cursor.y < impl_->disp_area.y) + delta = cursor.y - impl_->disp_area.y; + else if (cursor.y > impl_->disp_area.bottom()) + delta = cursor.y - impl_->disp_area.bottom(); + + impl_->origin.y += delta; + if (impl_->origin.y < 0) + impl_->origin.y = 0; + + bool changed = false; + if (!impl_->horz.empty() && (static_cast(impl_->horz.value()) != impl_->origin.x)) + { + impl_->horz.value(impl_->origin.x); + changed = true; + } + + if ((!impl_->vert.empty()) && (static_cast(impl_->vert.value()) != impl_->origin.y)) + { + impl_->vert.value(impl_->origin.y); + changed = true; + } + + if (changed) + API::refresh_window(impl_->window_handle); + } + + void content_view::set_wheel_speed(std::function fn) + { + impl_->provider.wheel_speed = std::move(fn); + } + } + } +} diff --git a/source/gui/widgets/skeletons/content_view.hpp b/source/gui/widgets/skeletons/content_view.hpp new file mode 100644 index 00000000..2575fe29 --- /dev/null +++ b/source/gui/widgets/skeletons/content_view.hpp @@ -0,0 +1,92 @@ +/* + * A Content View Implementation + * Nana C++ Library(http://www.nanapro.org) + * Copyright(C) 2017 Jinhao(cnjinhao@hotmail.com) + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * @file: nana/gui/widgets/skeletons/content_view.hpp + * @author: Jinhao + */ + +#ifndef NANA_WIDGETS_SKELETONS_CONTENT_VIEW_INCLUDED +#define NANA_WIDGETS_SKELETONS_CONTENT_VIEW_INCLUDED + +#include +#include + +namespace nana +{ + namespace paint + { + class graphics; + } +} + +namespace nana { namespace widgets { +namespace skeletons +{ + class content_view + { + struct implementation; + + content_view(const content_view&) = delete; + content_view& operator=(const content_view&) = delete; + + content_view(content_view&&) = delete; + content_view& operator=(content_view&&) = delete; + public: + using graph_reference = paint::graphics&; + + struct events_type + { + ::std::function hover_outside; + ::std::function scrolled; + }; + + content_view(window handle); + ~content_view(); + + events_type& events(); + + void step(unsigned step_value, bool horz); + bool scroll(bool forwards, bool horz); + bool turn_page(bool forwards, bool horz); + + void disp_area(const rectangle& da, const point& skew_horz_bar, const point& skew_vert_bar, const size& extra_px, bool try_update = true); + + void content_size(const size& sz, bool try_update = true); + const size& content_size() const; + + const point& origin() const; + rectangle corner() const; + void draw_corner(graph_reference); + + rectangle view_area() const; + + unsigned extra_space(bool horz) const; + + void change_position(int pos, bool aligned, bool horz); + + void move_origin(const point& skew); + + void sync(bool try_update); + + void pursue(const point& cursor); + + void set_wheel_speed(std::function fn); + + static constexpr unsigned space() + { + return 16; + } + private: + implementation * const impl_; + }; +} +} +} + +#endif \ No newline at end of file diff --git a/source/gui/widgets/skeletons/text_editor.cpp b/source/gui/widgets/skeletons/text_editor.cpp index 28429f8f..282a08b5 100644 --- a/source/gui/widgets/skeletons/text_editor.cpp +++ b/source/gui/widgets/skeletons/text_editor.cpp @@ -1,7 +1,7 @@ /* * A text editor implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -13,9 +13,10 @@ #include #include #include -#include #include #include +#include +#include "content_view.hpp" #include #include @@ -347,11 +348,204 @@ namespace nana{ namespace widgets nana::upoint dest_a_, dest_b_; }; + struct text_editor::text_section + { + const wchar_t* begin{ nullptr }; + const wchar_t* end{ nullptr }; + unsigned pixels{ 0 }; + + text_section() = default; + text_section(const wchar_t* ptr, const wchar_t* endptr, unsigned px) + : begin(ptr), end(endptr), pixels(px) + {} + }; + + + struct keyword_scheme + { + ::nana::color fgcolor; + ::nana::color bgcolor; + }; + + struct keyword_desc + { + std::wstring text; + std::string scheme; + bool case_sensitive; + bool whole_word_matched; + + keyword_desc(const std::wstring& txt, const std::string& schm, bool cs, bool wwm) + : text(txt), scheme(schm), case_sensitive(cs), whole_word_matched(wwm) + {} + }; + + struct entity + { + const wchar_t* begin; + const wchar_t* end; + const keyword_scheme * scheme; + }; + + enum class sync_graph + { + none, + refresh, + lazy_refresh + }; + + colored_area_access_interface::~colored_area_access_interface(){} + + class colored_area_access + : public colored_area_access_interface + { + public: + void set_window(window handle) + { + window_handle_ = handle; + } + + std::shared_ptr find(std::size_t pos) const + { + for (auto i = colored_areas_.cbegin(); i != colored_areas_.cend(); ++i) + { + if (i->get()->begin <= pos && pos < i->get()->begin + i->get()->count) + return *i; + else if (i->get()->begin > pos) + break; + } + return{}; + } + public: + //Overrides methods of colored_area_access_interface + std::shared_ptr get(std::size_t line_pos) override + { +#ifdef _MSC_VER + auto i = colored_areas_.cbegin(); + for (; i != colored_areas_.cend(); ++i) +#else + auto i = colored_areas_.begin(); + for (; i != colored_areas_.end(); ++i) +#endif + { + auto & area = *(i->get()); + if (area.begin <= line_pos && line_pos < area.begin + area.count) + return *i; + + if (area.begin > line_pos) + break; + } + + return *colored_areas_.emplace(i, + std::make_shared(colored_area_type{line_pos, 1, color{}, color{}}) + ); + } + + bool clear() + { + if (colored_areas_.empty()) + return false; + + colored_areas_.clear(); + API::refresh_window(window_handle_); + return true; + } + + bool remove(std::size_t pos) override + { + bool changed = false; +#ifdef _MSC_VER + for (auto i = colored_areas_.cbegin(); i != colored_areas_.cend();) +#else + for (auto i = colored_areas_.begin(); i != colored_areas_.end();) +#endif + { + if (i->get()->begin <= pos && pos < i->get()->begin + i->get()->count) + { + i = colored_areas_.erase(i); + changed = true; + } + else if (i->get()->begin > pos) + break; + } + if (changed) + API::refresh_window(window_handle_); + + return changed; + } + + std::size_t size() const override + { + return colored_areas_.size(); + } + + std::shared_ptr at(std::size_t index) override + { + return colored_areas_.at(index); + } + private: + window window_handle_; + std::vector> colored_areas_; + }; + + struct text_editor::implementation + { + undoable undo; //undo command + renderers customized_renderers; + std::vector text_position; //positions of text since last rendering. + + skeletons::textbase textbase; + + sync_graph try_refresh{ sync_graph::none }; + + colored_area_access colored_area; + + struct inner_capacities + { + editor_behavior_interface * behavior; + + accepts acceptive{ accepts::no_restrict }; + std::function pred_acceptive; + }capacities; + + struct inner_counterpart + { + bool enabled{ false }; + paint::graphics buffer; //A offscreen buffer which keeps the background that painted by external part. + }counterpart; + + struct indent_rep + { + bool enabled{ false }; + std::function generator; + }indent; + + struct inner_keywords + { + std::map> schemes; + std::deque base; + }keywords; + + std::unique_ptr cview; + }; + + class text_editor::editor_behavior_interface { public: + using row_coordinate = std::pair; ///< A coordinate type for line position. first: the absolute line position of text. second: the secondary line position of a part of line. + virtual ~editor_behavior_interface() = default; + /// Returns the text sections of a specified line + /** + * @param pos The absolute line number. + * @return The text sections of this line. + */ + virtual std::vector line(std::size_t pos) const = 0; + virtual row_coordinate text_position_from_screen(int top) const = 0; + + virtual unsigned max_pixels() const = 0; + /// Deletes lines between first and second, and then, second line will be merged into first line. virtual void merge_lines(std::size_t first, std::size_t second) = 0; //Calculates how many lines the specified line of text takes with a specified pixels of width. @@ -361,12 +555,6 @@ namespace nana{ namespace widgets virtual std::size_t take_lines() const = 0; /// Returns the number of lines that the line of text specified by pos takes. virtual std::size_t take_lines(std::size_t pos) const = 0; - - virtual void update_line(std::size_t textline, std::size_t secondary_before) = 0; - virtual std::vector<::nana::upoint> render(const ::nana::color& fgcolor) = 0; - virtual nana::point caret_to_screen(upoint) = 0; - virtual nana::upoint screen_to_caret(point scrpos) = 0; - virtual bool move_caret_ns(bool to_north) = 0; virtual bool adjust_caret_into_screen() = 0; }; @@ -384,10 +572,109 @@ namespace nana{ namespace widgets : editor_(editor) {} - void merge_lines(std::size_t, std::size_t) override{} - void add_lines(std::size_t, std::size_t) override{} - void pre_calc_line(std::size_t, unsigned) override{} - void pre_calc_lines(unsigned) override{} + std::vector line(std::size_t pos) const override + { + //Every line of normal behavior only has one text_section + std::vector sections; + sections.emplace_back(this->sections_[pos]); + return sections; + } + + row_coordinate text_position_from_screen(int top) const override + { + const std::size_t textlines = editor_.textbase().lines(); + const auto line_px = static_cast(editor_.line_height()); + if ((0 == textlines) || (0 == line_px)) + return{}; + + if (top < editor_.text_area_.area.y) + top = (std::max)(editor_._m_text_topline() - 1, 0); + else + top = (top - editor_.text_area_.area.y + editor_.impl_->cview->origin().y) / line_px; + + return{ (textlines <= static_cast(top) ? textlines - 1 : static_cast(top)), + 0 }; + } + + unsigned max_pixels() const override + { + unsigned px = editor_.width_pixels(); + for (auto & sct : sections_) + { + if (sct.pixels > px) + px = sct.pixels; + } + return px; + } + + void merge_lines(std::size_t first, std::size_t second) override + { + if (first > second) + std::swap(first, second); + + if (second < this->sections_.size()) +#ifdef _MSC_VER + this->sections_.erase(this->sections_.cbegin() + (first + 1), this->sections_.cbegin() + second); +#else + this->sections_.erase(this->sections_.begin() + (first + 1), this->sections_.begin() + second); +#endif + pre_calc_line(first, 0); + + //textbase is implement by using deque, and the linemtr holds the text pointers + //If the textbase is changed, it will check the text pointers. + std::size_t line = 0; + for (auto & sct : this->sections_) + { + auto const& text = editor_.textbase().getline(line); + if (sct.begin < text.c_str() || (text.c_str() + text.size() < sct.begin)) + pre_calc_line(line, 0); + + ++line; + } + } + + void add_lines(std::size_t pos, std::size_t line_size) override + { + if (pos < this->sections_.size()) + { + for (std::size_t i = 0; i < line_size; ++i) +#ifdef _MSC_VER + this->sections_.emplace(this->sections_.cbegin() + (pos + i)); +#else + this->sections_.emplace(this->sections_.begin() + (pos + i)); +#endif + //textbase is implement by using deque, and the linemtr holds the text pointers + //If the textbase is changed, it will check the text pointers. + std::size_t line = 0; + for (auto & sct : this->sections_) + { + if (line < pos || (pos + line_size) <= line) + { + auto const & text = editor_.textbase().getline(line); + if (sct.begin < text.c_str() || (text.c_str() + text.size() < sct.begin)) + pre_calc_line(line, 0); + } + ++line; + } + } + } + + void pre_calc_line(std::size_t pos, unsigned) override + { + auto const & text = editor_.textbase().getline(pos); + auto& txt_section = this->sections_[pos]; + txt_section.begin = text.c_str(); + txt_section.end = text.c_str() + text.size(); + txt_section.pixels = editor_._m_text_extent_size(text.c_str(), text.size()).width; + } + + void pre_calc_lines(unsigned) override + { + auto const line_count = editor_.textbase().lines(); + this->sections_.resize(line_count); + for (std::size_t i = 0; i < line_count; ++i) + pre_calc_line(i, 0); + } std::size_t take_lines() const override { @@ -399,135 +686,6 @@ namespace nana{ namespace widgets return 1; } - void update_line(std::size_t textline, std::size_t) override - { - int top = editor_._m_text_top_base() + static_cast(editor_.line_height() * (textline - editor_.points_.offset.y)); - editor_.graph_.rectangle({ editor_.text_area_.area.x, top, editor_.text_area_.area.width, editor_.line_height() }, true, API::bgcolor(editor_.window_)); - editor_._m_draw_string(top, API::fgcolor(editor_.window_), nana::upoint(0, editor_.points_.caret.y), editor_.textbase().getline(textline), true); - } - - std::vector render(const ::nana::color& fgcolor) override - { - std::vector line_index; - - ::nana::upoint str_pos(0, static_cast(editor_.points_.offset.y)); - - const auto scrlines = (std::min)(editor_.screen_lines() + str_pos.y, static_cast(editor_.textbase().lines())); - - int top = editor_._m_text_top_base(); - const unsigned pixels = editor_.line_height(); - - while( str_pos.y < scrlines) - { - editor_._m_draw_string(top, fgcolor, str_pos, editor_.textbase().getline(str_pos.y), true); - line_index.emplace_back(str_pos); - ++str_pos.y; - top += pixels; - } - - return line_index; - } - - nana::point caret_to_screen(nana::upoint pos) override - { - pos.y = (std::min)(pos.y, static_cast(editor_.textbase().lines())); - - auto text_ptr = &editor_.textbase().getline(pos.y); - - std::wstring mask_str; - if (editor_.mask_char_) - { - mask_str.resize(text_ptr->size(), editor_.mask_char_); - text_ptr = &mask_str; - } - - if (pos.x > text_ptr->size()) - pos.x = static_cast(text_ptr->size()); - - pos.x = editor_._m_pixels_by_char(*text_ptr, pos.x) + editor_.text_area_.area.x; - int pos_y = static_cast((pos.y - editor_.points_.offset.y) * editor_.line_height() + editor_._m_text_top_base()); - - return{ static_cast(pos.x - editor_.points_.offset.x), pos_y }; - } - - nana::upoint screen_to_caret(point scrpos) override - { - nana::upoint res{ 0, static_cast(_m_textline_from_screen(scrpos.y)) }; - - //Convert the screen point to text caret point - - auto text_ptr = &editor_.textbase().getline(res.y); - - std::wstring mask_str; - if (editor_.mask_char_) - { - mask_str.resize(text_ptr->size(), editor_.mask_char_); - text_ptr = &mask_str; - } - - if (text_ptr->size() > 0) - { - scrpos.x += (editor_.points_.offset.x - editor_.text_area_.area.x); - if (scrpos.x > 0) - { - std::vector reordered; - unicode_bidi{}.linestr(text_ptr->c_str(), text_ptr->size(), reordered); - - for (auto & ent : reordered) - { - std::size_t len = ent.end - ent.begin; - auto str_px = static_cast(editor_._m_text_extent_size(ent.begin, len).width); - if (scrpos.x < str_px) - { - res.x = editor_._m_char_by_pixels(ent, static_cast(scrpos.x)); - res.x += static_cast(ent.begin - text_ptr->c_str()); - return res; - } - scrpos.x -= str_px; - } - - res.x = static_cast(text_ptr->size()); - } - } - - return res; - } - - bool move_caret_ns(bool to_north) override - { - auto & points = editor_.points_; - - auto & textbase = editor_.textbase(); - - if (to_north) //North - { - if (points.caret.y) - { - points.caret.x = (std::min)(points.xpos, - static_cast(textbase.getline(--points.caret.y).size()) - ); - - bool out_of_screen = (static_cast(points.caret.y) < points.offset.y); - if (out_of_screen) - editor_._m_offset_y(static_cast(points.caret.y)); - - return (adjust_caret_into_screen() || out_of_screen); - } - } - else //South - { - if (points.caret.y + 1 < textbase.lines()) - { - points.caret.x = (std::min)(points.xpos, - static_cast(textbase.getline(++points.caret.y).size()) - ); - - return adjust_caret_into_screen(); - } - } - return false; - } - //adjust_caret_into_screen //@brief: Adjust the text offset in order to moving caret into visible area if it is out of the visible area //@note: the function assumes the points_.caret is correct @@ -537,101 +695,100 @@ namespace nana{ namespace widgets if (0 == scrlines) return false; + auto const pre_origin = editor_.impl_->cview->origin(); + auto origin = pre_origin; + auto& points = editor_.points_; auto& textbase = editor_.textbase(); - editor_._m_get_scrollbar_size(); - auto& lnstr = textbase.getline(points.caret.y); - const auto x = (points.caret.x > lnstr.size() ? static_cast(lnstr.size()) : points.caret.x); + const auto x = (std::min)(points.caret.x, static_cast(lnstr.size())); auto const text_w = editor_._m_pixels_by_char(lnstr, x); - unsigned area_w = editor_._m_text_area().width; + auto area_w = editor_.impl_->cview->view_area().width; - bool adjusted_cond = true; - if (static_cast(text_w) < points.offset.x) + if (static_cast(text_w) < origin.x) { auto delta_pixels = editor_._m_text_extent_size(L" ", 4).width; - points.offset.x = (text_w > delta_pixels ? text_w - delta_pixels : 0); + origin.x = (text_w > delta_pixels ? text_w - delta_pixels : 0); } - else if (area_w && (text_w >= points.offset.x + area_w)) - points.offset.x = text_w - area_w + 2; - else - adjusted_cond = false; + else if (area_w && (text_w >= origin.x + area_w)) + origin.x = text_w - area_w + 2; - bool adjusted_cond2 = true; - int value = points.offset.y; - if (scrlines && (points.caret.y >= points.offset.y + scrlines)) + int row = origin.y / static_cast(editor_.line_height()); + if (points.caret.y >= row + scrlines) //implicit condition scrlines > 0 { - value = static_cast(points.caret.y - scrlines) + 1; + row = static_cast(points.caret.y - scrlines) + 1; } - else if (static_cast(points.caret.y) < points.offset.y) + else if (static_cast(points.caret.y) < row) { - if (scrlines >= static_cast(points.offset.y)) - value = 0; + if (scrlines >= static_cast(row)) + row = 0; else - value = static_cast(points.offset.y - scrlines); + row = static_cast(row - scrlines); } - else if (points.offset.y && (textbase.lines() <= scrlines)) - value = 0; - else - adjusted_cond2 = false; + else if (row && (textbase.lines() <= scrlines)) + row = 0; - editor_._m_offset_y(value); - editor_._m_scrollbar(); - return (adjusted_cond || adjusted_cond2); - } + if(row != origin.y / static_cast(editor_.line_height())) + origin.y = row * editor_.line_height(); - private: - std::size_t _m_textline_from_screen(int y) const - { - const std::size_t textlines = editor_.textbase().lines(); - const auto line_px = static_cast(editor_.line_height()); - if ((0 == textlines) || (0 == line_px)) - return 0; - - const int offset_top = editor_.points_.offset.y; - const int text_area_top = editor_.text_area_.area.y; - - if (y < text_area_top) - y = offset_top ? offset_top - 1 : 0; - else - y = (y - text_area_top) / line_px + offset_top; - - return (textlines <= static_cast(y) ? textlines - 1 : static_cast(y)); + if (pre_origin != origin) + { + editor_.impl_->cview->move_origin(origin - pre_origin); + editor_.impl_->cview->sync(true); + return true; + } + return false; } private: text_editor& editor_; + std::vector sections_; }; //end class behavior_normal class text_editor::behavior_linewrapped : public text_editor::editor_behavior_interface { - struct text_section - { - const wchar_t* begin; - const wchar_t* end; - unsigned pixels; - - text_section(const wchar_t* ptr, const wchar_t* endptr, unsigned px) - : begin(ptr), end(endptr), pixels(px) - {} - }; - struct line_metrics { std::size_t take_lines; //The number of lines that text of this line takes. std::vector line_sections; }; public: - /// A coordinate type for line position. first: the absolute line position of text. second: the secondary line position of a part of line. - using row_coordinate = std::pair; - behavior_linewrapped(text_editor& editor) : editor_(editor) {} + std::vector line(std::size_t pos) const override + { + return linemtr_[pos].line_sections; + } + + row_coordinate text_position_from_screen(int top) const override + { + row_coordinate coord; + const auto line_px = static_cast(editor_.line_height()); + + if ((0 == editor_.textbase().lines()) || (0 == line_px)) + return coord; + + auto screen_rows = (top - editor_.text_area_.area.y + editor_.impl_->cview->origin().y) / line_px; + + coord = _m_textline(static_cast(screen_rows)); + if (linemtr_.size() <= coord.first) + { + coord.first = linemtr_.size() - 1; + coord.second = linemtr_.back().line_sections.size() - 1; + } + return coord; + } + + unsigned max_pixels() const override + { + return editor_.width_pixels(); + } + void merge_lines(std::size_t first, std::size_t second) override { if (first > second) @@ -708,7 +865,7 @@ namespace nana{ namespace widgets const unsigned str_w = editor_._m_text_extent_size(ts.begin, ts.end - ts.begin).width; text_px += str_w; - if (text_px > pixels) + if (text_px >= pixels) { if (text_px != str_w) { @@ -765,7 +922,7 @@ namespace nana{ namespace widgets void pre_calc_lines(unsigned pixels) override { - const auto lines = editor_.textbase().lines(); + auto const lines = editor_.textbase().lines(); linemtr_.resize(lines); for (std::size_t i = 0; i < lines; ++i) @@ -786,204 +943,6 @@ namespace nana{ namespace widgets return (pos < linemtr_.size() ? linemtr_[pos].take_lines : 0); } - void update_line(std::size_t textline, std::size_t secondary_before) override - { - if (take_lines(textline) == secondary_before) - { - int top = caret_to_screen(upoint{ 0, static_cast(textline) }).y; - - const unsigned pixels = editor_.line_height(); - editor_.graph_.rectangle({ editor_.text_area_.area.x, top, editor_.width_pixels(), static_cast(pixels * secondary_before) }, true, API::bgcolor(editor_.window_)); - - auto fgcolor = API::fgcolor(editor_.window_); - auto text_ptr = editor_.textbase().getline(textline).c_str(); - - for (std::size_t pos = 0; pos < secondary_before; ++pos, top+=pixels) - { - auto & sct = linemtr_[textline].line_sections[pos]; - editor_._m_draw_string(top, fgcolor, nana::upoint(static_cast(sct.begin - text_ptr), editor_.points_.caret.y), std::wstring(sct.begin, sct.end), true); - } - } - else - editor_.render(API::is_focus_ready(editor_.window_)); - } - - std::vector render(const ::nana::color& fgcolor) override - { - std::vector line_index; - - auto row = _m_textline_from_screen(0); - if (row.first >= linemtr_.size() || row.second >= linemtr_[row.first].line_sections.size()) - return line_index; - - nana::upoint str_pos(0, static_cast(row.first)); - str_pos.x = static_cast(linemtr_[row.first].line_sections[row.second].begin - editor_.textbase().getline(row.first).c_str()); - - int top = editor_._m_text_top_base(); - const unsigned pixels = editor_.line_height(); - - const std::size_t scrlines = editor_.screen_lines(); - for (std::size_t pos = 0; pos < scrlines; ++pos, top += pixels) - { - if ((row.first < linemtr_.size()) && (row.second < linemtr_[row.first].line_sections.size())) - { - auto & mtr = linemtr_[row.first]; - auto & section = mtr.line_sections[row.second]; - - std::wstring text(section.begin, section.end); - editor_._m_draw_string(top, fgcolor, str_pos, text, true); - line_index.emplace_back(str_pos); - ++row.second; - if (row.second >= mtr.line_sections.size()) - { - ++row.first; - row.second = 0; - str_pos.x = 0; - ++str_pos.y; - } - else - str_pos.x += static_cast(text.size()); - } - else - break; - } - - return line_index; - } - - nana::point caret_to_screen(upoint pos) override - { - const auto & mtr = linemtr_[pos.y]; - - std::size_t lines = 0; //lines before the caret line; - for (auto & v : linemtr_) - { - if (pos.y) - { - lines += v.take_lines; - --pos.y; - } - else - break; - } - - nana::point scrpos; - if (0 != pos.x) - { - std::wstring str; - for (auto & sec : mtr.line_sections) - { - std::size_t chsize = sec.end - sec.begin; - str.clear(); - if (editor_.mask_char_) - str.append(chsize, editor_.mask_char_); - else - str.append(sec.begin, sec.end); - - if (pos.x < chsize) - { - scrpos.x = editor_._m_pixels_by_char(str, pos.x); - break; - } - else if (pos.x == chsize) - { - scrpos.x = editor_._m_text_extent_size(str.c_str(), sec.end - sec.begin).width; - break; - } - else - { - pos.x -= static_cast(chsize); - ++lines; - } - } - } - - scrpos.x += editor_.text_area_.area.x; - scrpos.y = editor_.text_area_.area.y + static_cast((lines - editor_.points_.offset.y) * editor_.line_height()); - return scrpos; - } - - nana::upoint screen_to_caret(point scrpos) override - { - const auto row = _m_textline_from_screen(scrpos.y); - - auto & mtr = linemtr_[row.first]; - if (mtr.line_sections.empty()) - return{ 0, static_cast(row.first) }; - - //First of all, find the text of secondary. - auto real_str = mtr.line_sections[row.second]; - - auto text_ptr = real_str.begin; - const auto text_size = real_str.end - real_str.begin; - - std::wstring mask_str; - if (editor_.mask_char_) - { - mask_str.resize(text_size, editor_.mask_char_); - text_ptr = mask_str.c_str(); - } - - std::vector reordered; - unicode_bidi{}.linestr(text_ptr, text_size, reordered); - - nana::upoint res(static_cast(real_str.begin - mtr.line_sections.front().begin), static_cast(row.first)); - scrpos.x -= editor_.text_area_.area.x; - if (scrpos.x < 0) - scrpos.x = 0; - - for (auto & ent : reordered) - { - auto str_px = static_cast(editor_._m_text_extent_size(ent.begin, ent.end - ent.begin).width); - if (scrpos.x < str_px) - { - res.x += editor_._m_char_by_pixels(ent, scrpos.x); - res.x += static_cast(ent.begin - text_ptr); - return res; - } - scrpos.x -= str_px; - } - res.x = static_cast(editor_.textbase().getline(res.y).size()); - return res; - } - - bool move_caret_ns(bool to_north) override - { - auto & points = editor_.points_; - - nana::upoint secondary_pos; - _m_pos_secondary(points.caret, secondary_pos); - - if (to_north) //North - { - if (0 == secondary_pos.y) - { - if (0 == points.caret.y) - return false; - - --points.caret.y; - secondary_pos.y = static_cast(take_lines(points.caret.y)) - 1; - } - else - --secondary_pos.y; - } - else //South - { - ++secondary_pos.y; - if (secondary_pos.y >= take_lines(points.caret.y)) - { - secondary_pos.y = 0; - if (points.caret.y + 1 >= editor_.textbase().lines()) - return false; - - ++points.caret.y; - } - } - - _m_pos_from_secondary(points.caret.y, secondary_pos, points.caret.x); - return adjust_caret_into_screen(); - } - bool adjust_caret_into_screen() override { const auto scrlines = editor_.screen_lines(); @@ -991,22 +950,16 @@ namespace nana{ namespace widgets return false; const auto & points = editor_.points_; - editor_._m_get_scrollbar_size(); + auto off_coord = _m_textline(static_cast(editor_._m_text_topline())); - auto off_coord = _m_textline(points.offset.y); - - unsigned caret_secondary; - { - nana::upoint secondpos; - _m_pos_secondary(points.caret, secondpos); - caret_secondary = secondpos.y; - } + nana::upoint caret_secondary; + editor_._m_pos_secondary(points.caret, caret_secondary); //Use the caret line for the offset line when caret is in front of current offset line. - if (off_coord.first > points.caret.y || ((off_coord.first == points.caret.y) && (off_coord.second > caret_secondary))) + if (off_coord.first > points.caret.y || ((off_coord.first == points.caret.y) && (off_coord.second > caret_secondary.y))) { //Use the line which was specified by points.caret for the first line. - _m_set_offset_by_secondary(row_coordinate(points.caret.y, caret_secondary)); + _m_set_offset_by_secondary(row_coordinate(points.caret.y, caret_secondary.y)); return true; } @@ -1017,10 +970,10 @@ namespace nana{ namespace widgets return false; //Do not adjust the offset line if the caret line does not reach the bottom line. - if (points.caret.y < bottom.first || ((points.caret.y == bottom.first) && (caret_secondary <= bottom.second))) + if (points.caret.y < bottom.first || ((points.caret.y == bottom.first) && (caret_secondary.y <= bottom.second))) return false; - _m_advance_secondary(row_coordinate(points.caret.y, caret_secondary), -static_cast(scrlines - 1), bottom); + _m_advance_secondary(row_coordinate(points.caret.y, caret_secondary.y), -static_cast(scrlines - 1), bottom); _m_set_offset_by_secondary(bottom); return true; @@ -1066,7 +1019,9 @@ namespace nana{ namespace widgets for (auto i = linemtr_.begin(), end = linemtr_.begin() + row.first; i != end; ++i) row.second += i->take_lines; - editor_.points_.offset.y = static_cast(row.second); + auto origin = editor_.impl_->cview->origin(); + origin.y = static_cast(row.second * editor_.line_height()); + editor_.impl_->cview->move_origin(origin - editor_.impl_->cview->origin()); } bool _m_advance_secondary(row_coordinate row, int distance, row_coordinate& new_row) @@ -1143,46 +1098,6 @@ namespace nana{ namespace widgets return false; } - bool _m_pos_from_secondary(std::size_t textline, const nana::upoint& secondary, unsigned & pos) - { - if (textline >= linemtr_.size()) - return false; - - auto & mtr = linemtr_[textline]; - if (secondary.y >= mtr.line_sections.size() || mtr.line_sections.size() != mtr.take_lines) - return false; - - auto & section = mtr.line_sections[secondary.y]; - - auto chptr = section.begin + (std::min)(secondary.x, static_cast(section.end - section.begin)); - pos = static_cast(chptr - editor_.textbase().getline(textline).c_str()); - return true; - } - - bool _m_pos_secondary(const nana::upoint& charpos, nana::upoint& secondary_pos) const - { - if (charpos.y >= linemtr_.size()) - return false; - - secondary_pos.x = charpos.x; - secondary_pos.y = 0; - - unsigned len = 0; - for (auto & ts : linemtr_[charpos.y].line_sections) - { - len = static_cast(ts.end - ts.begin); - if (len >= secondary_pos.x) - return true; - - ++secondary_pos.y; - secondary_pos.x -= len; - } - --secondary_pos.y; - secondary_pos.x = len; - return true; - } - - row_coordinate _m_textline(std::size_t scrline) const { row_coordinate coord; @@ -1200,116 +1115,11 @@ namespace nana{ namespace widgets } return coord; } - - //secondary, index of line that the text was splitted into multilines. - row_coordinate _m_textline_from_screen(int y) const - { - row_coordinate coord; - const auto line_px = static_cast(editor_.line_height()); - - if ((0 == editor_.textbase().lines()) || (0 == line_px)) - return coord; - - const int text_area_top = editor_.text_area_.area.y; - const int offset_top = editor_.points_.offset.y; - - auto screen_line = (text_area_top - y) / line_px; - if ((y < text_area_top) && (screen_line > offset_top)) - screen_line = 0; - else - screen_line = offset_top - screen_line; - - coord = _m_textline(static_cast(screen_line)); - if (linemtr_.size() <= coord.first) - { - coord.first = linemtr_.size() - 1; - coord.second = linemtr_.back().line_sections.size() - 1; - } - return coord; - } private: text_editor& editor_; std::vector linemtr_; }; //end class behavior_linewrapped - - struct keyword_scheme - { - ::nana::color fgcolor; - ::nana::color bgcolor; - }; - - struct keyword_desc - { - std::wstring text; - std::string scheme; - bool case_sensitive; - bool whole_word_matched; - - keyword_desc(const std::wstring& txt, const std::string& schm, bool cs, bool wwm) - : text(txt), scheme(schm), case_sensitive(cs), whole_word_matched(wwm) - {} - }; - - /* - struct text_editor::keywords - { - std::map> schemes; - std::deque kwbase; - }; - */ - - struct entity - { - const wchar_t* begin; - const wchar_t* end; - const keyword_scheme * scheme; - }; - - - struct text_editor::implementation - { - undoable undo; //undo command - renderers customized_renderers; - std::vector text_position; //positions of text since last rendering. - - skeletons::textbase textbase; - - struct inner_capacities - { - editor_behavior_interface * behavior; - - accepts acceptive{ accepts::no_restrict }; - std::function pred_acceptive; - }capacities; - - struct inner_counterpart - { - bool enabled{ false }; - paint::graphics buffer; //A offscreen buffer which keeps the background that painted by external part. - }counterpart; - - - struct indent_rep - { - bool enabled{ false }; - std::function generator; - }indent; - - - struct inner_keywords - { - std::map> schemes; - std::deque base; - }keywords; - - struct inner_widgets - { - std::unique_ptr> vscroll; - std::unique_ptr> hscroll; - }widgets; - }; - class text_editor::keyword_parser { public: @@ -1333,12 +1143,6 @@ namespace nana{ namespace widgets pos = text.find(ds.text, pos); if (pos == text.npos) break; - - if (ds.whole_word_matched) - { - if (!_m_whole_word(text, pos, ds.text.size())) - continue; - } } else { @@ -1348,16 +1152,13 @@ namespace nana{ namespace widgets pos = cistr.find(ds.text.c_str(), pos); if (pos == cistr.npos) break; - - if (ds.whole_word_matched) - { - if (!_m_whole_word(text, pos, ds.text.size())) - continue; - } } + if (ds.whole_word_matched && (!_m_whole_word(text, pos, ds.text.size()))) + continue; + auto ki = keywords.schemes.find(ds.scheme); - if (ki != keywords.schemes.end() && ki->second) + if ((ki != keywords.schemes.end()) && ki->second) { entities.emplace_back(); auto & last = entities.back(); @@ -1428,15 +1229,12 @@ namespace nana{ namespace widgets impl_->capacities.behavior = new behavior_normal(*this); text_area_.area.dimension(graph.size()); - text_area_.captured = false; - text_area_.tab_space = 4; - text_area_.scroll_pixels = 16; - text_area_.hscroll = text_area_.vscroll = 0; - select_.behavior = text_focus_behavior::select_if_tabstop_or_click; - select_.move_to_end = false; - select_.mode_selection = selection::mode::no_selected; - select_.ignore_press = false; + impl_->cview.reset(new content_view{ wd }); + impl_->cview->disp_area(text_area_.area, {}, {}, {}); + impl_->cview->events().scrolled = [this] { + this->reset_caret(); + }; API::create_caret(wd, { 1, line_height() }); API::bgcolor(wd, colors::white); @@ -1497,6 +1295,11 @@ namespace nana{ namespace widgets } } } + + colored_area_access_interface& text_editor::colored_area() + { + return impl_->colored_area; + } void text_editor::set_accept(std::function pred) { @@ -1553,6 +1356,7 @@ namespace nana{ namespace widgets put(key); } reset_caret(); + impl_->try_refresh = sync_graph::refresh; return true; } return false; @@ -1574,12 +1378,14 @@ namespace nana{ namespace widgets _m_handle_move_key(arg); break; case keyboard::os_del: - if (this->attr().editable) + // send delete to set_accept function + if (this->attr().editable && (!impl_->capacities.pred_acceptive || impl_->capacities.pred_acceptive(key))) del(); break; default: return false; } + impl_->try_refresh = sync_graph::refresh; return true; } @@ -1590,10 +1396,6 @@ namespace nana{ namespace widgets void text_editor::indent(bool enb, std::function generator) { - /* - indent_.enabled = enb; - indent_.generator = std::move(generator); - */ impl_->indent.enabled = enb; impl_->indent.generator = std::move(generator); } @@ -1603,39 +1405,6 @@ namespace nana{ namespace widgets event_handler_ = ptr; } - bool text_editor::line_wrapped() const - { - return attributes_.line_wrapped; - } - - bool text_editor::line_wrapped(bool autl) - { - if (autl != attributes_.line_wrapped) - { - attributes_.line_wrapped = autl; - - delete impl_->capacities.behavior; - if (autl) - { - impl_->capacities.behavior = new behavior_linewrapped(*this); - text_area_.vscroll = text_area_.scroll_pixels; - text_area_.hscroll = 0; - impl_->capacities.behavior->pre_calc_lines(width_pixels()); - } - else - impl_->capacities.behavior = new behavior_normal(*this); - - points_.offset.x = 0; - _m_offset_y(0); - move_caret(upoint{}); - - _m_scrollbar(); - render(API::is_focus_ready(window_)); - return true; - } - return false; - } - bool text_editor::load(const char* fs) { if (!impl_->textbase.load(fs)) @@ -1644,11 +1413,19 @@ namespace nana{ namespace widgets _m_reset(); impl_->capacities.behavior->pre_calc_lines(width_pixels()); - render(API::is_focus_ready(window_)); - _m_scrollbar(); + impl_->try_refresh = sync_graph::refresh; + _m_reset_content_size(); return true; } + void text_editor::text_align(::nana::align alignment) + { + this->attributes_.alignment = alignment; + this->reset_caret(); + impl_->try_refresh = sync_graph::refresh; + _m_reset_content_size(); + } + bool text_editor::text_area(const nana::rectangle& r) { if(text_area_.area == r) @@ -1659,8 +1436,9 @@ namespace nana{ namespace widgets if (impl_->counterpart.enabled) impl_->counterpart.buffer.make(r.dimension()); - impl_->capacities.behavior->pre_calc_lines(width_pixels()); - _m_scrollbar(); + impl_->cview->disp_area(r, { -1, 1 }, { 1, -1 }, { 2, 2 }); + if (impl_->cview->content_size().empty() || this->attributes_.line_wrapped) + _m_reset_content_size(true); move_caret(points_.caret); return true; @@ -1668,7 +1446,7 @@ namespace nana{ namespace widgets rectangle text_editor::text_area(bool including_scroll) const { - return (including_scroll ? _m_text_area() : text_area_.area); + return (including_scroll ? impl_->cview->view_area() : text_area_.area); } bool text_editor::tip_string(::std::string&& str) @@ -1680,11 +1458,34 @@ namespace nana{ namespace widgets return true; } - const text_editor::attributes& text_editor::attr() const + const text_editor::attributes& text_editor::attr() const noexcept { return attributes_; } + bool text_editor::line_wrapped(bool autl) + { + if (autl != attributes_.line_wrapped) + { + attributes_.line_wrapped = autl; + + delete impl_->capacities.behavior; + if (autl) + impl_->capacities.behavior = new behavior_linewrapped(*this); + else + impl_->capacities.behavior = new behavior_normal(*this); + + _m_reset_content_size(true); + + impl_->cview->move_origin(point{} -impl_->cview->origin()); + move_caret(upoint{}); + + impl_->try_refresh = sync_graph::refresh; + return true; + } + return false; + } + bool text_editor::multi_lines(bool ml) { if((ml == false) && attributes_.multi_lines) @@ -1702,7 +1503,7 @@ namespace nana{ namespace widgets if (!ml) line_wrapped(false); - _m_scrollbar(); + _m_reset_content_size(); return true; } @@ -1746,6 +1547,13 @@ namespace nana{ namespace widgets return impl_->undo.max_steps(); } + void text_editor::clear_undo() + { + auto size = impl_->undo.max_steps(); + impl_->undo.max_steps(0); + impl_->undo.max_steps(size); + } + auto text_editor::customized_renderers() -> renderers& { return impl_->customized_renderers; @@ -1754,18 +1562,27 @@ namespace nana{ namespace widgets unsigned text_editor::line_height() const { unsigned ascent, descent, internal_leading; + unsigned px = 0; if (graph_.text_metrics(ascent, descent, internal_leading)) - return ascent + descent; + px = ascent + descent; - return 0; + impl_->cview->step(px, false); + return px; } - unsigned text_editor::screen_lines() const + unsigned text_editor::screen_lines(bool completed_line) const { - if(graph_ && (text_area_.area.height > text_area_.hscroll)) + auto const line_px = line_height(); + if (line_px) { - auto lines = (text_area_.area.height - text_area_.hscroll) / line_height(); - return (lines ? lines : 1); + auto h = impl_->cview->view_area().height; + if (graph_ && h) + { + if (completed_line) + return (h / line_px); + + return (h / line_px + (h % line_px ? 1 : 0)); + } } return 0; } @@ -1797,7 +1614,7 @@ namespace nana{ namespace widgets if (select_all) { select(true); - move_caret_end(); + move_caret_end(false); renderred = true; //If the text widget is focused by clicking mouse button, the selected text will be cancelled @@ -1832,28 +1649,30 @@ namespace nana{ namespace widgets if(left_button) { - mouse_caret(scrpos); + mouse_caret(scrpos, false); if (selection::mode::mouse_selected == select_.mode_selection || selection::mode::method_selected == select_.mode_selection) - set_end_caret(); + set_end_caret(false); + else if (selection::mode::move_selected == select_.mode_selection) + select_.mode_selection = selection::mode::move_selected_take_effect; - _m_draw_border(); + impl_->try_refresh = sync_graph::refresh; return true; } return false; } - bool text_editor::mouse_pressed(const arg_mouse& arg) + void text_editor::mouse_pressed(const arg_mouse& arg) { if(!attributes_.enable_caret) - return false; + return; if (event_code::mouse_down == arg.evt_code) { if (select_.ignore_press || (!hit_text_area(arg.pos))) { select_.ignore_press = false; - return false; + return; } if (::nana::mouse::left_button == arg.button) @@ -1861,8 +1680,7 @@ namespace nana{ namespace widgets API::set_capture(window_, true); text_area_.captured = true; - - if (this->hit_select_area(impl_->capacities.behavior->screen_to_caret(arg.pos), true)) + if (this->hit_select_area(_m_screen_to_caret(arg.pos), true)) { //The selected of text can be moved only if it is editable if (attributes_.editable) @@ -1871,7 +1689,7 @@ namespace nana{ namespace widgets else { //Set caret pos by screen point and get the caret pos. - mouse_caret(arg.pos); + mouse_caret(arg.pos, true); if (arg.shift) { if (points_.shift_begin_caret != points_.caret) @@ -1885,7 +1703,7 @@ namespace nana{ namespace widgets if (!select(false)) { select_.a = points_.caret; //Set begin caret - set_end_caret(); + set_end_caret(true); } points_.shift_begin_caret = points_.caret; } @@ -1893,24 +1711,32 @@ namespace nana{ namespace widgets } } - _m_draw_border(); - return true; + impl_->try_refresh = sync_graph::refresh; } else if (event_code::mouse_up == arg.evt_code) { select_.ignore_press = false; - bool updated = false; if (select_.mode_selection == selection::mode::mouse_selected) { select_.mode_selection = selection::mode::no_selected; - set_end_caret(); + set_end_caret(true); } - else if (selection::mode::move_selected == select_.mode_selection) + else if (selection::mode::move_selected == select_.mode_selection || selection::mode::move_selected_take_effect == select_.mode_selection) { - if (!move_select()) + //move_selected indicates the mouse is pressed on the selected text, but the mouse has not moved. So the text_editor should cancel the selection. + //move_selected_take_effect indicates the text_editor should try to move the selection. + + if ((selection::mode::move_selected == select_.mode_selection) || !move_select()) + { + //no move occurs select(false); - updated = true; + + move_caret(_m_screen_to_caret(arg.pos)); + } + + select_.mode_selection = selection::mode::no_selected; + impl_->try_refresh = sync_graph::refresh; } API::release_capture(window_); @@ -1918,12 +1744,7 @@ namespace nana{ namespace widgets text_area_.captured = false; if (hit_text_area(arg.pos) == false) API::window_cursor(window_, nana::cursor::arrow); - - _m_draw_border(); - - return updated; } - return false; } textbase & text_editor::textbase() @@ -1936,6 +1757,19 @@ namespace nana{ namespace widgets return impl_->textbase; } + bool text_editor::try_refresh() + { + if (sync_graph::none != impl_->try_refresh) + { + if (sync_graph::refresh == impl_->try_refresh) + render(API::is_focus_ready(window_)); + + impl_->try_refresh = sync_graph::none; + return true; + } + return false; + } + bool text_editor::getline(std::size_t pos, std::wstring& text) const { if (impl_->textbase.lines() <= pos) @@ -1951,7 +1785,8 @@ namespace nana{ namespace widgets impl_->textbase.erase_all(); _m_reset(); - impl_->capacities.behavior->pre_calc_lines(width_pixels()); + _m_reset_content_size(true); + if (!end_caret) { auto undo_ptr = std::unique_ptr{ new undo_input_text(*this, str) }; @@ -1965,9 +1800,7 @@ namespace nana{ namespace widgets { impl_->capacities.behavior->adjust_caret_into_screen(); reset_caret(); - render(API::is_focus_ready(window_)); - _m_scrollbar(); - + impl_->try_refresh = sync_graph::refresh; points_.xpos = 0; } } @@ -1991,16 +1824,15 @@ namespace nana{ namespace widgets return str; } - //move_caret - //Set caret position through text coordinate bool text_editor::move_caret(const upoint& crtpos, bool reset_caret) { const unsigned line_pixels = line_height(); - auto pos = impl_->capacities.behavior->caret_to_screen(crtpos); + + auto pos = _m_caret_to_screen(crtpos); const int line_bottom = pos.y + static_cast(line_pixels); if (reset_caret) - points_.caret = this->impl_->capacities.behavior->screen_to_caret(pos); + points_.caret = _m_screen_to_caret(pos); if (!API::is_focus_ready(window_)) return false; @@ -2008,7 +1840,8 @@ namespace nana{ namespace widgets auto caret = API::open_caret(window_, true); bool visible = false; - auto text_area = _m_text_area(); + auto text_area = impl_->cview->view_area(); + if (text_area.is_hit(pos) && (line_bottom > text_area.y)) { visible = true; @@ -2029,34 +1862,37 @@ namespace nana{ namespace widgets if (reset_caret && (!hit_text_area(pos))) { impl_->capacities.behavior->adjust_caret_into_screen(); - render(true); + impl_->try_refresh = sync_graph::refresh; caret->visible(true); return true; } return false; } - void text_editor::move_caret_end() + void text_editor::move_caret_end(bool update) { points_.caret.y = static_cast(impl_->textbase.lines()); if(points_.caret.y) --points_.caret.y; points_.caret.x = static_cast(impl_->textbase.getline(points_.caret.y).size()); + + if (update) + this->move_caret(points_.caret, false); } void text_editor::reset_caret_pixels() const { - API::open_caret(window_, true)->dimension({ 1, line_height() }); + API::open_caret(window_, true).get()->dimension({ 1, line_height() }); } - void text_editor::reset_caret() + void text_editor::reset_caret(bool stay_in_view) { - move_caret(points_.caret); + move_caret(points_.caret, stay_in_view); } void text_editor::show_caret(bool isshow) { if(isshow == false || API::is_focus_ready(window_)) - API::open_caret(window_, true)->visible(isshow); + API::open_caret(window_, true).get()->visible(isshow); } bool text_editor::selected() const @@ -2064,39 +1900,7 @@ namespace nana{ namespace widgets return (select_.a != select_.b); } - void text_editor::set_end_caret() - { - bool new_sel_end = (select_.b != points_.caret); - select_.b = points_.caret; - points_.xpos = points_.caret.x; - - if (new_sel_end || impl_->capacities.behavior->adjust_caret_into_screen()) - render(true); - } - - bool text_editor::select(bool yes) - { - if(yes) - { - select_.a.x = select_.a.y = 0; - select_.b.y = static_cast(impl_->textbase.lines()); - if(select_.b.y) --select_.b.y; - select_.b.x = static_cast(impl_->textbase.getline(select_.b.y).size()); - select_.mode_selection = selection::mode::method_selected; - render(true); - return true; - } - - select_.mode_selection = selection::mode::no_selected; - if (_m_cancel_select(0)) - { - render(true); - return true; - } - return false; - } - - bool text_editor::get_select_points(nana::upoint& a, nana::upoint& b) const + bool text_editor::get_selected_points(nana::upoint &a, nana::upoint &b) const { if (select_.a == select_.b) return false; @@ -2115,15 +1919,47 @@ namespace nana{ namespace widgets return true; } + bool text_editor::select(bool yes) + { + if(yes) + { + select_.a.x = select_.a.y = 0; + select_.b.y = static_cast(impl_->textbase.lines()); + if(select_.b.y) --select_.b.y; + select_.b.x = static_cast(impl_->textbase.getline(select_.b.y).size()); + select_.mode_selection = selection::mode::method_selected; + impl_->try_refresh = sync_graph::refresh; + return true; + } + + select_.mode_selection = selection::mode::no_selected; + if (_m_cancel_select(0)) + { + impl_->try_refresh = sync_graph::refresh; + return true; + } + return false; + } + + void text_editor::set_end_caret(bool stay_in_view) + { + bool new_sel_end = (select_.b != points_.caret); + select_.b = points_.caret; + points_.xpos = points_.caret.x; + + if(new_sel_end || (stay_in_view && impl_->capacities.behavior->adjust_caret_into_screen())) + impl_->try_refresh = sync_graph::refresh; + } + bool text_editor::hit_text_area(const point& pos) const { - return _m_text_area().is_hit(pos); + return impl_->cview->view_area().is_hit(pos); } bool text_editor::hit_select_area(nana::upoint pos, bool ignore_when_select_all) const { nana::upoint a, b; - if(get_select_points(a, b)) + if (get_selected_points(a, b)) { if (ignore_when_select_all) { @@ -2148,7 +1984,7 @@ namespace nana{ namespace widgets points_.caret = select_.b; if (impl_->capacities.behavior->adjust_caret_into_screen()) - render(true); + impl_->try_refresh = sync_graph::refresh; reset_caret(); return true; @@ -2157,7 +1993,7 @@ namespace nana{ namespace widgets if (_m_move_select(true)) { impl_->capacities.behavior->adjust_caret_into_screen(); - render(true); + impl_->try_refresh = sync_graph::refresh; return true; } return false; @@ -2174,11 +2010,10 @@ namespace nana{ namespace widgets unsigned text_editor::width_pixels() const { - unsigned exclude_px; + unsigned exclude_px = API::open_caret(window_, true).get()->dimension().width; + if (attributes_.line_wrapped) - exclude_px = text_area_.vscroll; - else - exclude_px = API::open_caret(window_, true)->dimension().width; + exclude_px += impl_->cview->extra_space(false); return (text_area_.area.width > exclude_px ? text_area_.area.width - exclude_px : 0); } @@ -2205,11 +2040,7 @@ namespace nana{ namespace widgets void text_editor::draw_corner() { - if(text_area_.vscroll && text_area_.hscroll) - { - graph_.rectangle(rectangle{ text_area_.area.right() - static_cast(text_area_.vscroll), text_area_.area.bottom() - static_cast(text_area_.hscroll), text_area_.vscroll, text_area_.hscroll }, - true, colors::button_face); - } + impl_->cview->draw_corner(graph_); } void text_editor::render(bool has_focus) @@ -2224,8 +2055,11 @@ namespace nana{ namespace widgets graph_.rectangle(false, bgcolor); //Draw background - if(attributes_.enable_background) - graph_.rectangle(text_area_.area, true, bgcolor); + if (!API::dev::copy_transparent_background(window_, graph_)) + { + if (attributes_.enable_background) + graph_.rectangle(text_area_.area, true, bgcolor); + } if (impl_->customized_renderers.background) impl_->customized_renderers.background(graph_, text_area_.area, bgcolor); @@ -2237,7 +2071,7 @@ namespace nana{ namespace widgets //otherwise draw the tip string. if ((false == textbase().empty()) || has_focus) { - auto text_pos = impl_->capacities.behavior->render(fgcolor); + auto text_pos = _m_render_text(fgcolor); if (text_pos.empty()) text_pos.emplace_back(upoint{}); @@ -2250,14 +2084,15 @@ namespace nana{ namespace widgets } } else //Draw tip string - graph_.string({ text_area_.area.x - points_.offset.x, text_area_.area.y }, attributes_.tip_string, static_cast(0x787878)); + { + graph_.string({ text_area_.area.x - impl_->cview->origin().x, text_area_.area.y }, attributes_.tip_string, static_cast(0x787878)); + } if (impl_->text_position.empty()) impl_->text_position.emplace_back(upoint{}); - draw_corner(); - _m_draw_border(); + impl_->try_refresh = sync_graph::none; } //public: void text_editor::put(std::wstring text) @@ -2282,9 +2117,8 @@ namespace nana{ namespace widgets { impl_->capacities.behavior->adjust_caret_into_screen(); reset_caret(); - render(API::is_focus_ready(window_)); - _m_scrollbar(); - + impl_->try_refresh = sync_graph::refresh; + _m_reset_content_size(true); points_.xpos = points_.caret.x; } } @@ -2310,13 +2144,16 @@ namespace nana{ namespace widgets points_.caret.x ++; - if (refresh || _m_update_caret_line(secondary_before)) - render(true); + + if (!refresh) + { + if (!_m_update_caret_line(secondary_before)) + draw_corner(); + } else - draw_corner(); - - _m_scrollbar(); + impl_->try_refresh = sync_graph::refresh; + _m_reset_content_size(); points_.xpos = points_.caret.x; } @@ -2324,7 +2161,7 @@ namespace nana{ namespace widgets { auto text = _m_make_select_string(); if (!text.empty()) - nana::system::dataexch().set(text); + nana::system::dataexch().set(text, API::root(this->window_)); } void text_editor::cut() @@ -2335,24 +2172,32 @@ namespace nana{ namespace widgets void text_editor::paste() { - std::wstring text; - nana::system::dataexch().get(text); + auto text = system::dataexch{}.wget(); - //If it is required check the acceptable - if (accepts::no_restrict != impl_->capacities.acceptive) + if ((accepts::no_restrict == impl_->capacities.acceptive) || !impl_->capacities.pred_acceptive) { - for (auto i = text.begin(); i != text.end(); ++i) - { - if (!_m_accepts(*i)) - { - text.erase(i, text.end()); - break; - } - } + put(move(text)); + return; } - if (!text.empty()) - put(std::move(text)); + //Check if the input is acceptable + for (auto i = text.begin(); i != text.end(); ++i) + { + if (_m_accepts(*i)) + { + if (accepts::no_restrict == impl_->capacities.acceptive) + put(*i); + + continue; + } + + if (accepts::no_restrict != impl_->capacities.acceptive) + { + text.erase(i, text.end()); + put(move(text)); + } + break; + } } void text_editor::enter(bool record_undo) @@ -2396,9 +2241,11 @@ namespace nana{ namespace widgets points_.caret.x = 0; - if(points_.offset.x || (points_.caret.y < textbase.lines()) || textbase.getline(points_.caret.y).size()) + auto origin = impl_->cview->origin(); + if (origin.x || (points_.caret.y < textbase.lines()) || textbase.getline(points_.caret.y).size()) { - points_.offset.x = 0; + origin.x = -origin.x; + impl_->cview->move_origin(origin); need_refresh = true; } @@ -2419,10 +2266,11 @@ namespace nana{ namespace widgets } } + impl_->cview->move_origin(origin - impl_->cview->origin()); if (impl_->capacities.behavior->adjust_caret_into_screen() || need_refresh) - render(true); + impl_->cview->sync(true); - _m_scrollbar(); + _m_reset_content_size(); } void text_editor::del() @@ -2446,7 +2294,7 @@ namespace nana{ namespace widgets if(has_erase) backspace(); - _m_scrollbar(); + _m_reset_content_size(); points_.xpos = points_.caret.x; } @@ -2472,8 +2320,7 @@ namespace nana{ namespace widgets if(_m_move_offset_x_while_over_border(-2) == false) { - impl_->capacities.behavior->update_line(points_.caret.y, secondary); - draw_corner(); + _m_update_line(points_.caret.y, secondary); has_to_redraw = false; } } @@ -2500,13 +2347,13 @@ namespace nana{ namespace widgets if (record_undo) impl_->undo.push(std::move(undo_ptr)); + _m_reset_content_size(has_to_redraw); + if(has_to_redraw) { - impl_->capacities.behavior->pre_calc_lines(width_pixels()); impl_->capacities.behavior->adjust_caret_into_screen(); - render(true); + impl_->try_refresh = sync_graph::refresh; } - _m_scrollbar(); } void text_editor::undo(bool reverse) @@ -2516,10 +2363,10 @@ namespace nana{ namespace widgets else impl_->undo.undo(); - impl_->capacities.behavior->pre_calc_lines(width_pixels()); + _m_reset_content_size(true); + impl_->capacities.behavior->adjust_caret_into_screen(); - render(true); - _m_scrollbar(); + impl_->try_refresh = sync_graph::refresh; } void text_editor::set_undo_queue_length(std::size_t len) @@ -2530,9 +2377,8 @@ namespace nana{ namespace widgets void text_editor::move_ns(bool to_north) { const bool redraw_required = _m_cancel_select(0); - if (impl_->capacities.behavior->move_caret_ns(to_north) || redraw_required) - render(true); - _m_scrollbar(); + if (_m_move_caret_ns(to_north) || redraw_required) + impl_->try_refresh = sync_graph::refresh; } void text_editor::move_left() @@ -2547,7 +2393,7 @@ namespace nana{ namespace widgets pending = false; bool adjust_y = (attributes_.line_wrapped && impl_->capacities.behavior->adjust_caret_into_screen()); if (_m_move_offset_x_while_over_border(-2) || adjust_y) - render(true); + impl_->try_refresh = sync_graph::refresh; } else if (points_.caret.y) //Move to previous line points_.caret.x = static_cast(textbase().getline(--points_.caret.y).size()); @@ -2556,9 +2402,8 @@ namespace nana{ namespace widgets } if (pending && impl_->capacities.behavior->adjust_caret_into_screen()) - render(true); + impl_->try_refresh = sync_graph::refresh; - _m_scrollbar(); points_.xpos = points_.caret.x; } @@ -2586,9 +2431,8 @@ namespace nana{ namespace widgets do_render = impl_->capacities.behavior->adjust_caret_into_screen(); if (do_render) - render(true); + impl_->try_refresh = sync_graph::refresh; - _m_scrollbar(); points_.xpos = points_.caret.x; } @@ -2597,6 +2441,9 @@ namespace nana{ namespace widgets if (arg.shift && (select_.a == select_.b)) select_.a = select_.b = points_.caret; + auto origin = impl_->cview->origin(); + origin.y = _m_text_topline(); + bool changed = false; nana::upoint caret = points_.caret; wchar_t key = arg.key; @@ -2610,7 +2457,7 @@ namespace nana{ namespace widgets case keyboard::os_home: if (caret.y != 0) { caret.y = 0; - points_.offset.y = 0; + origin.y = 0; changed = true; } break; @@ -2669,13 +2516,14 @@ namespace nana{ namespace widgets case keyboard::os_arrow_up: case keyboard::os_arrow_down: { - auto screen_pt = impl_->capacities.behavior->caret_to_screen(caret); + auto screen_pt = _m_caret_to_screen(caret); int offset = line_height(); if (key == keyboard::os_arrow_up) { offset = -offset; } screen_pt.y += offset; - auto new_caret = impl_->capacities.behavior->screen_to_caret(screen_pt); + + auto const new_caret = _m_screen_to_caret(screen_pt); if (new_caret != caret) { caret = new_caret; if (screen_pt.y < 0) { @@ -2698,15 +2546,15 @@ namespace nana{ namespace widgets } break; case keyboard::os_pageup: - if (caret.y >= screen_lines() && points_.offset.y >= static_cast(screen_lines())) { - points_.offset.y -= screen_lines(); + if (caret.y >= screen_lines() && origin.y >= static_cast(screen_lines())) { + origin.y -= screen_lines(); caret.y -= screen_lines(); changed = true; } break; case keyboard::os_pagedown: if (caret.y + screen_lines() <= impl_->capacities.behavior->take_lines()) { - points_.offset.y += screen_lines(); + origin.y += static_cast(screen_lines()); caret.y += screen_lines(); changed = true; } @@ -2737,13 +2585,25 @@ namespace nana{ namespace widgets select_.a = caret; } points_.caret = caret; - impl_->capacities.behavior->adjust_caret_into_screen(); - render(true); - _m_scrollbar(); + + origin.y *= line_height(); + impl_->cview->move_origin(origin - impl_->cview->origin()); + impl_->cview->sync(true); points_.xpos = points_.caret.x; + impl_->try_refresh = sync_graph::refresh; } } + unsigned text_editor::_m_width_px(bool include_vs) const + { + unsigned exclude_px = API::open_caret(window_, true).get()->dimension().width; + + if (!include_vs) + exclude_px += impl_->cview->space(); + + return (text_area_.area.width > exclude_px ? text_area_.area.width - exclude_px : 0); + } + void text_editor::_m_draw_border() { if (!API::widget_borderless(this->window_)) @@ -2757,15 +2617,24 @@ namespace nana{ namespace widgets ::nana::facade facade; facade.draw(graph_, _m_bgcolor(), API::fgcolor(this->window_), ::nana::rectangle{ API::window_size(this->window_) }, API::element_state(this->window_)); } + + if (!attributes_.line_wrapped) + { + auto exclude_px = API::open_caret(window_, true).get()->dimension().width; + int x = this->text_area_.area.x + static_cast(width_pixels()); + graph_.rectangle(rectangle{ x, this->text_area_.area.y, exclude_px, text_area_.area.height }, true, _m_bgcolor()); + } } + + draw_corner(); } - const upoint& text_editor::mouse_caret(const point& scrpos) //From screen position + const upoint& text_editor::mouse_caret(const point& scrpos, bool stay_in_view) //From screen position { - points_.caret = impl_->capacities.behavior->screen_to_caret(scrpos); + points_.caret = _m_screen_to_caret(scrpos); - if (impl_->capacities.behavior->adjust_caret_into_screen()) - render(true); + if (stay_in_view && impl_->capacities.behavior->adjust_caret_into_screen()) + impl_->try_refresh = sync_graph::refresh; move_caret(points_.caret); return points_.caret; @@ -2778,21 +2647,99 @@ namespace nana{ namespace widgets point text_editor::caret_screen_pos() const { - return impl_->capacities.behavior->caret_to_screen(points_.caret); + return _m_caret_to_screen(points_.caret); } bool text_editor::scroll(bool upwards, bool vert) { - if(vert && impl_->widgets.vscroll) + impl_->cview->scroll(!upwards, !vert); + return false; + } + + color text_editor::_m_draw_colored_area(paint::graphics& graph, const std::pair& row, bool whole_line) + { + auto area = impl_->colored_area.find(row.first); + if (area) { - impl_->widgets.vscroll->make_step(!upwards, this->scheme_->mouse_wheel.lines); - if(_m_scroll_text(true)) + if (!area->bgcolor.invisible()) { - render(true); - return true; + auto const height = line_height(); + + auto top = _m_caret_to_screen(upoint{ 0, static_cast(row.first) }).y; + std::size_t lines = 1; + + if (whole_line) + lines = impl_->capacities.behavior->take_lines(row.first); + else + top += height * row.second; + + const rectangle area_r = { text_area_.area.x, top, width_pixels(), static_cast(height * lines) }; + + if (API::is_transparent_background(this->window_)) + graph.blend(area_r, area->bgcolor, 1); + else + graph.rectangle(area_r, true, area->bgcolor); + } + + return area->fgcolor; + } + return{}; + } + + std::vector text_editor::_m_render_text(const color& text_color) + { + std::vector line_indexes; + auto const behavior = this->impl_->capacities.behavior; + auto const line_count = textbase().lines(); + + auto row = behavior->text_position_from_screen(impl_->cview->view_area().y); + + if (row.first >= line_count || graph_.empty()) + return line_indexes; + + auto sections = behavior->line(row.first); + if (row.second < sections.size()) + { + nana::upoint str_pos(0, static_cast(row.first)); + str_pos.x = static_cast(sections[row.second].begin - textbase().getline(row.first).c_str()); + + int top = _m_text_top_base() - (impl_->cview->origin().y % line_height()); + const unsigned pixels = line_height(); + + const std::size_t scrlines = screen_lines() + 1; + for (std::size_t pos = 0; pos < scrlines; ++pos, top += pixels) + { + if (row.first >= line_count) + break; + + auto fgcolor = _m_draw_colored_area(graph_, row, false); + if (fgcolor.invisible()) + fgcolor = text_color; + + sections = behavior->line(row.first); + if (row.second < sections.size()) + { + auto const & sct = sections[row.second]; + + _m_draw_string(top, fgcolor, str_pos, sct, true); + line_indexes.emplace_back(str_pos); + ++row.second; + if (row.second >= sections.size()) + { + ++row.first; + row.second = 0; + str_pos.x = 0; + ++str_pos.y; + } + else + str_pos.x += static_cast(sct.end - sct.begin); + } + else + break; } } - return false; + + return line_indexes; } void text_editor::_m_pre_calc_lines(std::size_t line_off, std::size_t lines) @@ -2802,11 +2749,240 @@ namespace nana{ namespace widgets this->impl_->capacities.behavior->pre_calc_line(pos, width_px); } + nana::point text_editor::_m_caret_to_screen(nana::upoint pos) const + { + auto const behavior = impl_->capacities.behavior; + auto const sections = behavior->line(pos.y); + + std::size_t lines = 0; //lines before the caret line; + for (std::size_t i = 0; i < pos.y; ++i) + { + lines += behavior->line(i).size(); + } + + const text_section * sct_ptr = nullptr; + nana::point scrpos; + if (0 != pos.x) + { + std::wstring str; + for (auto & sct : sections) + { + std::size_t chsize = sct.end - sct.begin; + str.clear(); + if (mask_char_) + str.append(chsize, mask_char_); + else + str.append(sct.begin, sct.end); + + if (pos.x <= chsize) + { + sct_ptr = &sct; + if (pos.x == chsize) + scrpos.x = _m_text_extent_size(str.c_str(), sct.end - sct.begin).width; + else + scrpos.x = _m_pixels_by_char(str, pos.x); + break; + } + else + { + pos.x -= static_cast(chsize); + ++lines; + } + } + } + + if (!sct_ptr) + { + if (sections.empty()) + scrpos.x += _m_text_x({}); + else + scrpos.x += _m_text_x(sections.front()); + } + else + scrpos.x += _m_text_x(*sct_ptr); + + scrpos.y = static_cast(lines * line_height()) - impl_->cview->origin().y + this->_m_text_top_base(); + return scrpos; + } + + upoint text_editor::_m_screen_to_caret(point scrpos) const + { + auto const behavior = impl_->capacities.behavior; + auto const row = behavior->text_position_from_screen(scrpos.y); + + auto sections = behavior->line(row.first); + if (sections.empty()) + return{ 0, static_cast(row.first) }; + + //First of all, find the text of secondary. + auto real_str = sections[row.second]; + + auto text_ptr = real_str.begin; + const auto text_size = real_str.end - real_str.begin; + + std::wstring mask_str; + if (mask_char_) + { + mask_str.resize(text_size, mask_char_); + text_ptr = mask_str.c_str(); + } + + std::vector reordered; + unicode_bidi{}.linestr(text_ptr, text_size, reordered); + + nana::upoint res(static_cast(real_str.begin - sections.front().begin), static_cast(row.first)); + scrpos.x -= _m_text_x(sections[row.second]); + + if (scrpos.x < 0) + scrpos.x = 0; + + for (auto & ent : reordered) + { + auto str_px = static_cast(_m_text_extent_size(ent.begin, ent.end - ent.begin).width); + if (scrpos.x < str_px) + { + res.x += _m_char_by_pixels(ent, scrpos.x); + res.x += static_cast(ent.begin - text_ptr); + return res; + } + scrpos.x -= str_px; + } + res.x = static_cast(textbase().getline(res.y).size()); + return res; + } + + bool text_editor::_m_pos_from_secondary(std::size_t textline, const nana::upoint& secondary, unsigned & pos) + { + if (textline >= textbase().lines()) + return false; + + auto sections = impl_->capacities.behavior->line(textline); + + if (secondary.y >= sections.size()) + return false; + + auto const & sct = sections[secondary.y]; + + auto chptr = sct.begin + (std::min)(secondary.x, static_cast(sct.end - sct.begin)); + pos = static_cast(chptr - textbase().getline(textline).c_str()); + return true; + } + + + bool text_editor::_m_pos_secondary(const nana::upoint& charpos, nana::upoint& secondary_pos) const + { + if (charpos.y >= textbase().lines()) + return false; + + secondary_pos.x = charpos.x; + secondary_pos.y = 0; + + auto sections = impl_->capacities.behavior->line(charpos.y); + unsigned len = 0; + for (auto & sct : sections) + { + len = static_cast(sct.end - sct.begin); + if (len >= secondary_pos.x) + return true; + + ++secondary_pos.y; + secondary_pos.x -= len; + } + --secondary_pos.y; + secondary_pos.x = len; + return true; + } + + bool text_editor::_m_move_caret_ns(bool to_north) + { + auto const behavior = impl_->capacities.behavior; + + nana::upoint secondary_pos; + _m_pos_secondary(points_.caret, secondary_pos); + + if (to_north) //North + { + if (0 == secondary_pos.y) + { + if (0 == points_.caret.y) + return false; + + --points_.caret.y; + secondary_pos.y = static_cast(behavior->take_lines(points_.caret.y)) - 1; + } + else + { + //Test if out of screen + if (static_cast(points_.caret.y) < _m_text_topline()) + { + auto origin = impl_->cview->origin(); + origin.y = static_cast(points_.caret.y) * line_height(); + impl_->cview->move_origin(origin - impl_->cview->origin()); + } + + --secondary_pos.y; + } + } + else //South + { + ++secondary_pos.y; + if (secondary_pos.y >= behavior->take_lines(points_.caret.y)) + { + secondary_pos.y = 0; + if (points_.caret.y + 1 >= textbase().lines()) + return false; + + ++points_.caret.y; + } + } + + _m_pos_from_secondary(points_.caret.y, secondary_pos, points_.caret.x); + return behavior->adjust_caret_into_screen(); + } + + void text_editor::_m_update_line(std::size_t pos, std::size_t secondary_count_before) + { + auto behavior = impl_->capacities.behavior; + if (behavior->take_lines(pos) == secondary_count_before) + { + auto top = _m_caret_to_screen(upoint{ 0, static_cast(pos) }).y; + + const unsigned pixels = line_height(); + const rectangle update_area = { text_area_.area.x, top, width_pixels(), static_cast(pixels * secondary_count_before) }; + + if (!API::dev::copy_transparent_background(window_, update_area, graph_, update_area.position())) + { + _m_draw_colored_area(graph_, { pos, 0 }, true); + graph_.rectangle(update_area, true, API::bgcolor(window_)); + } + else + _m_draw_colored_area(graph_, { pos, 0 }, true); + + auto fgcolor = API::fgcolor(window_); + auto text_ptr = textbase().getline(pos).c_str(); + + auto sections = behavior->line(pos); + for (auto & sct : sections) + { + _m_draw_string(top, fgcolor, nana::upoint(static_cast(sct.begin - text_ptr), points_.caret.y), sct, true); + top += pixels; + } + + _m_draw_border(); + impl_->try_refresh = sync_graph::lazy_refresh; + } + else + impl_->try_refresh = sync_graph::refresh; + } bool text_editor::_m_accepts(char_type ch) const { - if(accepts::no_restrict == impl_->capacities.acceptive) + if (accepts::no_restrict == impl_->capacities.acceptive) + { + if (impl_->capacities.pred_acceptive) + return impl_->capacities.pred_acceptive(ch); return true; + } //Checks the input whether it meets the requirement for a numeric. auto str = text(); @@ -2826,173 +3002,60 @@ namespace nana{ namespace widgets return (!API::window_enabled(window_) ? static_cast(0xE0E0E0) : API::bgcolor(window_)); } - bool text_editor::_m_scroll_text(bool vert) + void text_editor::_m_reset_content_size(bool calc_lines) { - if (vert) + size csize; + + if (this->attributes_.line_wrapped) { - if (impl_->widgets.vscroll) + if (calc_lines) { - auto sv = static_cast(impl_->widgets.vscroll->value()); - if (sv != points_.offset.y) + //detect if vertical scrollbar is required + auto const max_lines = screen_lines(true); + auto text_lines = textbase().lines(); + + if (text_lines <= max_lines) { - _m_offset_y(sv); - return true; + std::size_t lines = 0; + for (std::size_t i = 0; i < text_lines; ++i) + { + impl_->capacities.behavior->pre_calc_line(i, csize.width); + lines += impl_->capacities.behavior->take_lines(i); + + if (lines > max_lines) + { + text_lines = lines; + break; + } + } } + + //enable vertical scrollbar when text_lines > max_lines + csize.width = _m_width_px(text_lines <= max_lines); + + impl_->capacities.behavior->pre_calc_lines(csize.width); } - } - else if(impl_->widgets.hscroll) - { - auto sv = static_cast(impl_->widgets.hscroll->value()); - if(sv != points_.offset.x) - { - points_.offset.x = sv; - return true; - } - } - return false; - } - - void text_editor::_m_scrollbar() - { - _m_get_scrollbar_size(); - auto tx_area = _m_text_area(); - - auto scroll_fn = [this](const arg_mouse& arg) - { - if ((arg.evt_code == event_code::mouse_move) && (arg.left_button == false)) - return; - - bool vert = (impl_->widgets.vscroll && (impl_->widgets.vscroll->handle() == arg.window_handle)); - if (_m_scroll_text(vert)) - { - render(true); - reset_caret(); - API::update_window(window_); - } - }; - - if (text_area_.vscroll) - { - const int x = text_area_.area.x + static_cast(tx_area.width); - auto wdptr = impl_->widgets.vscroll.get(); - if (!wdptr) - { - impl_->widgets.vscroll.reset(new nana::scroll); - wdptr = impl_->widgets.vscroll.get(); - wdptr->create(window_, nana::rectangle(x, text_area_.area.y, text_area_.vscroll, tx_area.height)); - - auto & evts = wdptr->events(); - evts.mouse_down(scroll_fn); - evts.mouse_move(scroll_fn); - evts.mouse_wheel(scroll_fn); - - API::take_active(wdptr->handle(), false, window_); - } - - if (impl_->capacities.behavior->take_lines() != wdptr->amount()) - wdptr->amount(static_cast(impl_->capacities.behavior->take_lines())); - - if (screen_lines() != wdptr->range()) - wdptr->range(screen_lines()); - - if (points_.offset.y != static_cast(wdptr->value())) - wdptr->value(points_.offset.y); - - wdptr->move(rectangle{ x, text_area_.area.y, text_area_.vscroll, tx_area.height }); + else + csize.width = impl_->cview->content_size().width; } else - impl_->widgets.vscroll.reset(); - - //HScroll - if(text_area_.hscroll) { - auto wdptr = impl_->widgets.hscroll.get(); - int y = text_area_.area.y + static_cast(tx_area.height); - if(nullptr == wdptr) - { - impl_->widgets.hscroll.reset(new nana::scroll); - wdptr = impl_->widgets.hscroll.get(); - wdptr->create(window_, nana::rectangle(text_area_.area.x, y, tx_area.width, text_area_.hscroll)); + if (calc_lines) + impl_->capacities.behavior->pre_calc_lines(0); - auto & evts = wdptr->events(); - evts.mouse_down(scroll_fn); - evts.mouse_move(scroll_fn); - evts.mouse_wheel(scroll_fn); - - wdptr->step(20); - API::take_active(wdptr->handle(), false, window_); - } auto maxline = textbase().max_line(); - nana::size text_size = _m_text_extent_size(textbase().getline(maxline.first).c_str(), maxline.second); - - text_size.width += 1; - if(text_size.width > wdptr->amount()) - wdptr->amount(text_size.width); - - if(tx_area.width != wdptr->range()) wdptr->range(tx_area.width); - if(points_.offset.x != static_cast(wdptr->value())) - wdptr->value(points_.offset.x); - - wdptr->move(rectangle{ text_area_.area.x, y, tx_area.width, text_area_.hscroll }); - } - else - impl_->widgets.hscroll.reset(); - } - - rectangle text_editor::_m_text_area() const - { - auto area = text_area_.area; - if (area.width > text_area_.vscroll) - area.width -= text_area_.vscroll; - else - area.width = 0; - - if (area.height > text_area_.hscroll) - area.height -= text_area_.hscroll; - else - area.height = 0; - - return area; - } - - void text_editor::_m_get_scrollbar_size() - { - text_area_.hscroll = 0; - - if (attributes_.line_wrapped) - { - text_area_.vscroll = text_area_.scroll_pixels; - return; + csize.width = _m_text_extent_size(textbase().getline(maxline.first).c_str(), maxline.second).width; } - //No scrollbar if it is not multi-line - if (!attributes_.multi_lines) - { - text_area_.vscroll = 0; - return; - } + csize.height = static_cast(impl_->capacities.behavior->take_lines() * line_height()); - const bool text_overflow = (textbase().lines() > screen_lines()); - - text_area_.vscroll = (text_overflow ? text_area_.scroll_pixels : 0); - - auto max_line = textbase().max_line(); - if(max_line.second) - { - if(points_.offset.x || _m_text_extent_size(textbase().getline(max_line.first).c_str(), max_line.second).width > _m_text_area().width) - { - text_area_.hscroll = text_area_.scroll_pixels; - if((text_area_.vscroll == 0) && text_overflow) - text_area_.vscroll = text_area_.scroll_pixels; - } - } + impl_->cview->content_size(csize); } void text_editor::_m_reset() { points_.caret.x = points_.caret.y = 0; - points_.offset.x = 0; - _m_offset_y(0); + impl_->cview->move_origin(point{} -impl_->cview->origin()); select_.a = select_.b; } @@ -3045,7 +3108,7 @@ namespace nana{ namespace widgets nana::upoint text_editor::_m_erase_select() { nana::upoint a, b; - if (get_select_points(a, b)) + if (get_selected_points(a, b)) { auto & textbase = this->textbase(); if(a.y != b.y) @@ -3076,7 +3139,7 @@ namespace nana{ namespace widgets std::wstring text; nana::upoint a, b; - if (get_select_points(a, b)) + if (get_selected_points(a, b)) { auto & textbase = this->textbase(); if (a.y != b.y) @@ -3167,7 +3230,7 @@ namespace nana{ namespace widgets bool text_editor::_m_cancel_select(int align) { nana::upoint a, b; - if (get_select_points(a, b)) + if (get_selected_points(a, b)) { switch(align) { @@ -3221,25 +3284,32 @@ namespace nana{ namespace widgets const auto count = static_cast(std::abs(many)); if(many < 0) { - if(points_.offset.x && (points_.offset.x >= static_cast(width))) + auto origin = impl_->cview->origin(); + + if (origin.x && (origin.x >= static_cast(width))) { //Out of screen text area - if(points_.caret.x > count) - points_.offset.x = static_cast(width - _m_text_extent_size(lnstr.c_str() + points_.caret.x - count, count).width); + if (points_.caret.x > count) + origin.x = static_cast(width - _m_text_extent_size(lnstr.c_str() + points_.caret.x - count, count).width); else - points_.offset.x = 0; + origin.x = 0; + + impl_->cview->move_origin(origin - impl_->cview->origin()); return true; } } else { - const int right_pos = _m_text_area().right(); - + auto const right_pos = impl_->cview->view_area().right(); width += text_area_.area.x; - if(static_cast(width) - points_.offset.x >= right_pos) + + auto origin = impl_->cview->origin(); + if (static_cast(width) - origin.x >= right_pos) { //Out of screen text area - points_.offset.x = static_cast(width) - right_pos + 1; + origin.x = static_cast(width) - right_pos + 1; auto rest_size = lnstr.size() - points_.caret.x; - points_.offset.x += static_cast(_m_text_extent_size(lnstr.c_str() + points_.caret.x, (rest_size >= static_cast(many) ? static_cast(many) : rest_size)).width); + origin.x += static_cast(_m_text_extent_size(lnstr.c_str() + points_.caret.x, (rest_size >= static_cast(many) ? static_cast(many) : rest_size)).width); + + impl_->cview->move_origin(origin - impl_->cview->origin()); return true; } } @@ -3262,7 +3332,7 @@ namespace nana{ namespace widgets const bool at_left = (select_.b < select_.a); nana::upoint a, b; - get_select_points(a, b); + get_selected_points(a, b); if (caret.y < a.y || (caret.y == a.y && caret.x < a.x)) {//forward undo_ptr->set_caret_pos(); @@ -3314,6 +3384,31 @@ namespace nana{ namespace widgets return text_area_.area.y; } + int text_editor::_m_text_topline() const + { + auto px = static_cast(line_height()); + return (px ? (impl_->cview->origin().y / px) : px); + } + + int text_editor::_m_text_x(const text_section& sct) const + { + auto const behavior = impl_->capacities.behavior; + int left = this->text_area_.area.x; + + if (::nana::align::left != this->attributes_.alignment) + { + auto blank_px = behavior->max_pixels() - sct.pixels; + + if (::nana::align::center == this->attributes_.alignment) + left += static_cast(blank_px) / 2; + else + left += static_cast(blank_px); + } + + return left - impl_->cview->origin().x; + } + + void text_editor::_m_draw_parse_string(const keyword_parser& parser, bool rtl, ::nana::point pos, const ::nana::color& fgcolor, const wchar_t* str, std::size_t len) const { graph_.palette(true, fgcolor); @@ -3381,34 +3476,33 @@ namespace nana{ namespace widgets class text_editor::helper_pencil { public: - helper_pencil(paint::graphics& graph, const text_editor& editor, const color_proxy& selection_fgcolor, const color_proxy& selection_bgcolor, keyword_parser& parser): + helper_pencil(paint::graphics& graph, const text_editor& editor, keyword_parser& parser): graph_( graph ), editor_( editor ), parser_( parser ), - selection_fgcolor_( selection_fgcolor ), - selection_bgcolor_( selection_bgcolor ), line_px_( editor.line_height() ) {} - void write_selection(const point& text_pos, unsigned text_px, const wchar_t* text, std::size_t len) + void write_selection(const point& text_pos, unsigned text_px, const wchar_t* text, std::size_t len, bool has_focused) { - graph_.palette(true, selection_fgcolor_.get_color()); - graph_.rectangle(::nana::rectangle{ text_pos, { text_px, line_px_ } }, true, selection_bgcolor_); + graph_.palette(true, editor_.scheme_->selection_text.get_color()); + graph_.rectangle(::nana::rectangle{ text_pos, { text_px, line_px_ } }, true, + has_focused ? editor_.scheme_->selection.get_color() : editor_.scheme_->selection_unfocused.get_color()); graph_.string(text_pos, text, len); } - void rtl_string(point strpos, const wchar_t* str, std::size_t len, std::size_t str_px, unsigned glyph_front, unsigned glyph_selected) + void rtl_string(point strpos, const wchar_t* str, std::size_t len, std::size_t str_px, unsigned glyph_front, unsigned glyph_selected, bool has_focused) { - editor_._m_draw_parse_string(parser_, true, strpos, selection_fgcolor_.get_color(), str, len); + editor_._m_draw_parse_string(parser_, true, strpos, editor_.scheme_->selection_text.get_color(), str, len); //Draw selected part paint::graphics graph({ glyph_selected, line_px_ }); graph.typeface(this->graph_.typeface()); - graph.rectangle(true, selection_bgcolor_.get_color()); + graph.rectangle(true, (has_focused ? editor_.scheme_->selection.get_color() : editor_.scheme_->selection_unfocused.get_color())); int sel_xpos = static_cast(str_px - (glyph_front + glyph_selected)); - graph.palette(true, selection_fgcolor_.get_color()); + graph.palette(true, editor_.scheme_->selection_text.get_color()); graph.string({ -sel_xpos, 0 }, str, len); graph_.bitblt(nana::rectangle(strpos.x + sel_xpos, strpos.y, glyph_selected, line_px_), graph); }; @@ -3416,40 +3510,37 @@ namespace nana{ namespace widgets paint::graphics& graph_; const text_editor& editor_; keyword_parser & parser_; - const color_proxy & selection_fgcolor_; - const color_proxy & selection_bgcolor_; unsigned line_px_; }; - void text_editor::_m_draw_string(int top, const ::nana::color& clr, const nana::upoint& text_coord, const std::wstring& text, bool if_mask) const + void text_editor::_m_draw_string(int top, const ::nana::color& clr, const nana::upoint& text_coord, const text_section& sct, bool if_mask) const { - - point text_draw_pos{ text_area_.area.x - points_.offset.x, top }; - const int text_right = text_area_.area.right(); + point text_draw_pos{ _m_text_x(sct), top }; - auto text_ptr = &text; + const int text_right = text_area_.area.right(); + auto const text_len = static_cast(sct.end - sct.begin); + auto text_ptr = sct.begin; std::wstring mask_str; if (if_mask && mask_char_) { - mask_str.resize(text.size(), mask_char_); - text_ptr = &mask_str; + mask_str.resize(text_len, mask_char_); + text_ptr = mask_str.c_str(); } const auto focused = API::is_focus_ready(window_); std::vector reordered; - unicode_bidi{}.linestr(text_ptr->c_str(), text_ptr->size(), reordered); + unicode_bidi{}.linestr(text_ptr, text_len, reordered); //Parse highlight keywords keyword_parser parser; - parser.parse(*text_ptr, impl_->keywords); + parser.parse({ text_ptr, text_len }, impl_->keywords); const auto line_h_pixels = line_height(); - helper_pencil pencil(graph_, *this, scheme_->selection_text, scheme_->selection, parser); + helper_pencil pencil(graph_, *this, parser); - graph_.palette(true, clr); graph_.palette(false, scheme_->selection.get_color()); @@ -3458,41 +3549,41 @@ namespace nana{ namespace widgets const wchar_t *sbegin = nullptr, *send = nullptr; nana::upoint a, b; - if (get_select_points(a, b)) + if (get_selected_points(a, b)) { if (a.y < text_coord.y && text_coord.y < b.y) { - sbegin = text_ptr->c_str(); - send = sbegin + text_ptr->size(); + sbegin = sct.begin; + send = sct.end; } else if ((a.y == b.y) && a.y == text_coord.y) { auto sbegin_pos = (std::max)(a.x, text_coord.x); - auto send_pos = (std::min)(text_coord.x + static_cast(text_ptr->size()), b.x); + auto send_pos = (std::min)(text_coord.x + text_len, b.x); if (sbegin_pos < send_pos) { - sbegin = text_ptr->c_str() + (sbegin_pos - text_coord.x); - send = text_ptr->c_str() + (send_pos - text_coord.x); + sbegin = text_ptr + (sbegin_pos - text_coord.x); + send = text_ptr + (send_pos - text_coord.x); } } else if (a.y == text_coord.y) { - if (a.x < text_coord.x + text_ptr->size()) + if (a.x < text_coord.x + text_len) { - sbegin = text_ptr->c_str(); + sbegin = text_ptr; if (text_coord.x < a.x) sbegin += (a.x - text_coord.x); - send = text_ptr->c_str() + text_ptr->size(); + send = text_ptr + text_len; } } else if (b.y == text_coord.y) { if (text_coord.x < b.x) { - sbegin = text_ptr->c_str(); - send = text_ptr->c_str() + (std::min)(b.x - text_coord.x, static_cast(text_ptr->size())); + sbegin = text_ptr; + send = text_ptr + (std::min)(b.x - text_coord.x, text_len); } } } @@ -3500,7 +3591,7 @@ namespace nana{ namespace widgets //A text editor feature, it draws an extra block at end of line if the end of line is in range of selection. bool extra_space = false; - const bool text_selected = (sbegin == text_ptr->c_str() && send == text_ptr->c_str() + text_ptr->size()); + const bool text_selected = (sbegin == text_ptr && send == text_ptr+ text_len); //The text is not selected or the whole line text is selected if (!focused || (!sbegin || !send) || text_selected || !attributes_.enable_caret) { @@ -3511,8 +3602,8 @@ namespace nana{ namespace widgets if ((text_draw_pos.x + static_cast(str_w) > text_area_.area.x) && (text_draw_pos.x < text_right)) { - if (text_selected && focused) - pencil.write_selection(text_draw_pos, str_w, ent.begin, len); + if (text_selected) + pencil.write_selection(text_draw_pos, str_w, ent.begin, len, focused); else _m_draw_parse_string(parser, is_right_text(ent), text_draw_pos, clr, ent.begin, len); } @@ -3541,7 +3632,7 @@ namespace nana{ namespace widgets else if (sbegin <= ent.begin && ent.end <= send) { //this string is completed selected - pencil.write_selection(text_draw_pos, ent_px, ent.begin, len); + pencil.write_selection(text_draw_pos, ent_px, ent.begin, len, focused); extra_space = true; } else @@ -3564,7 +3655,7 @@ namespace nana{ namespace widgets graph_.palette(true, clr); if (is_right_text(ent)) { //RTL - pencil.rtl_string(text_draw_pos, ent.begin, len, ent_px, head_px, select_px); + pencil.rtl_string(text_draw_pos, ent.begin, len, ent_px, head_px, select_px, focused); } else { //LTR @@ -3573,7 +3664,7 @@ namespace nana{ namespace widgets auto part_pos = text_draw_pos; part_pos.x += static_cast(head_px); - pencil.write_selection(part_pos, select_px, ent.begin + select_pos, select_len); + pencil.write_selection(part_pos, select_px, ent.begin + select_pos, select_len, focused); if (ent_send < ent.end) { @@ -3582,7 +3673,7 @@ namespace nana{ namespace widgets } } - extra_space = (select_pos + select_len == text_ptr->size()); + extra_space = (select_pos + select_len == text_len); } } text_draw_pos.x += static_cast(ent_px); @@ -3593,7 +3684,7 @@ namespace nana{ namespace widgets if (extra_space) { //draw the extra space if end of line is not equal to the second selection position. - auto pos = text_coord.x + text_ptr->size(); + auto pos = text_coord.x + text_len; if (b.x != pos || text_coord.y != b.y) { auto whitespace_w = graph_.text_extent_size(L" ", 1).width; @@ -3606,21 +3697,17 @@ namespace nana{ namespace widgets { if (false == impl_->capacities.behavior->adjust_caret_into_screen()) { - if (impl_->capacities.behavior->caret_to_screen(points_.caret).x < _m_text_area().right()) + if (_m_caret_to_screen(points_.caret).x < impl_->cview->view_area().right()) { - impl_->capacities.behavior->update_line(points_.caret.y, secondary_before); + _m_update_line(points_.caret.y, secondary_before); return false; } } + impl_->try_refresh = sync_graph::refresh; return true; } - void text_editor::_m_offset_y(int y) - { - points_.offset.y = y; - } - - unsigned text_editor::_m_char_by_pixels(const unicode_bidi::entity& ent, unsigned pos) + unsigned text_editor::_m_char_by_pixels(const unicode_bidi::entity& ent, unsigned pos) const { auto len = static_cast(ent.end - ent.begin); diff --git a/source/gui/widgets/slider.cpp b/source/gui/widgets/slider.cpp index b66b141c..b6fdfc09 100644 --- a/source/gui/widgets/slider.cpp +++ b/source/gui/widgets/slider.cpp @@ -80,7 +80,7 @@ namespace nana } color rgb = schm.color_slider; - if (mouse_action::normal != mouse_act) + if (mouse_action::normal != mouse_act && mouse_action::normal_captured != mouse_act) rgb = schm.color_slider_highlighted; graph.frame_rectangle(area, rgb + static_cast(0x0d0d0d), 1); @@ -128,7 +128,7 @@ namespace nana arrow_pos = data.position - arrow_weight; } - graph_vern.blend(rectangle{ label_size }, graph, label_pos, 0.5); + graph.blend(rectangle{ label_pos, label_size }, graph_vern, {}, 0.5); unsigned arrow_color = 0x7F | schm.color_vernier.get_color().argb().value; @@ -210,8 +210,7 @@ namespace nana arrow_pos = data.position; } - graph_vern.blend(rectangle{ label_size }, graph, label_pos, 0.5); - + graph.blend(rectangle{ label_pos, label_size }, graph_vern, {}, 0.5); unsigned arrow_color = 0x7F | schm.color_vernier.get_color().argb().value; for (auto & color : arrow_pxbuf) @@ -300,7 +299,7 @@ namespace nana { if(!graph.size().empty()) { - proto_.renderer->background(other_.wd, graph, (bground_mode::basic == API::effects_bground_mode(other_.wd)), other_.widget->scheme()); + proto_.renderer->background(other_.wd, graph, API::dev::copy_transparent_background(other_.wd, graph), other_.widget->scheme()); _m_draw_elements(graph); } } @@ -483,7 +482,7 @@ namespace nana attr_.adorn_pos = xpos; attr_.is_draw_adorn = true; - if (::nana::mouse_action::normal == slider_state_.mouse_state) + if (mouse_action::normal == slider_state_.mouse_state || mouse_action::normal_captured == slider_state_.mouse_state) slider_state_.mouse_state = ::nana::mouse_action::hovered; return (adorn_pos != static_cast(xpos)); @@ -610,7 +609,7 @@ namespace nana if (attr_.slider.vert) attr_.slider.pos = range - attr_.slider.pos; - if(::nana::mouse_action::normal == slider_state_.mouse_state) + if(mouse_action::normal == slider_state_.mouse_state || mouse_action::normal_captured == slider_state_.mouse_state) attr_.adorn_pos = attr_.slider.pos; } @@ -897,7 +896,7 @@ namespace nana bool slider::transparent() const { - return (bground_mode::basic == API::effects_bground_mode(*this)); + return API::is_transparent_background(*this); } //end class slider }//end namespace nana diff --git a/source/gui/widgets/spinbox.cpp b/source/gui/widgets/spinbox.cpp index 9f63baa2..87cec724 100644 --- a/source/gui/widgets/spinbox.cpp +++ b/source/gui/widgets/spinbox.cpp @@ -1,7 +1,7 @@ /* * A Spin box widget * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -25,25 +25,6 @@ namespace nana { namespace spinbox { - - class event_agent - : public widgets::skeletons::textbase_event_agent_interface - { - public: - event_agent(::nana::spinbox& wdg) - : widget_(wdg) - {} - - void first_change() override{} //empty, because spinbox does not have this event. - - void text_changed() override - { - widget_.events().text_changed.emit(::nana::arg_spinbox{ widget_ }, widget_.handle()); - } - private: - ::nana::spinbox & widget_; - }; - enum class buttons { none, increase, decrease @@ -73,6 +54,11 @@ namespace nana : begin_{ vbegin }, last_{ vlast }, step_{ step }, value_{ vbegin } {} + std::pair range() const + { + return std::make_pair(begin_, last_); + } + std::string value() const override { return std::to_string(value_); @@ -85,7 +71,8 @@ namespace nana T v; ss >> v; - if (v < begin_ || last_ < v) + + if (ss.fail() || v < begin_ || last_ < v) return false; diff = (value_ != v); @@ -157,19 +144,14 @@ namespace nana : public range_interface { public: - range_text(std::initializer_list & initlist) - : texts_(initlist) + range_text(std::vector&& texts): + texts_(std::move(texts)) { - for (auto & s : initlist) - { - texts_.emplace_back(std::string{ s }); - } } - range_text(std::initializer_list& initlist) + const std::vector& range() const { - for (auto & s : initlist) - texts_.emplace_back(to_utf8(s)); + return texts_; } std::string value() const override @@ -229,6 +211,29 @@ namespace nana class implementation { + class event_agent + : public widgets::skeletons::textbase_event_agent_interface + { + public: + event_agent(implementation* impl) + : impl_(impl) + {} + + void first_change() override {} //empty, because spinbox does not have this event. + + void text_changed() override + { + auto wdg = static_cast(API::get_widget(impl_->editor_->window_handle())); + + if (!impl_->value(to_utf8(impl_->editor_->text()), false)) + API::refresh_window(wdg->handle()); + + wdg->events().text_changed.emit(*wdg, wdg->handle()); + } + private: + implementation* const impl_; + }; + public: implementation() { @@ -269,7 +274,7 @@ namespace nana return range_->check_value(to_utf8(str)); }); - evt_agent_.reset(new event_agent(static_cast(wdg))); + evt_agent_.reset(new event_agent{this}); editor_->textbase().set_event_agent(evt_agent_.get()); if (!range_) @@ -295,13 +300,13 @@ namespace nana return range_->value(); } - bool value(const ::std::string& value_str) + bool value(const ::std::string& value_str, bool reset_editor) { bool diff; if (!range_->value(value_str, diff)) return false; - if (diff) + if (diff && reset_editor) reset_text(); return true; } @@ -313,6 +318,11 @@ namespace nana reset_text(); } + const range_interface* range() const + { + return range_.get(); + } + void modifier(std::string&& prefix, std::string&& suffix) { modifier_.prefix = std::move(prefix); @@ -372,7 +382,8 @@ namespace nana return true; } - if (editor_->mouse_pressed(arg)) + editor_->mouse_pressed(arg); + if(editor_->try_refresh()) { _m_draw_spins(buttons::none); return true; @@ -383,10 +394,11 @@ namespace nana bool mouse_move(bool left_button, const ::nana::point& pos) { - if (editor_->mouse_move(left_button, pos)) + editor_->mouse_move(left_button, pos); + if(editor_->try_refresh()) { editor_->reset_caret(); - render(); + _m_draw_spins(spin_stated_); return true; } @@ -421,10 +433,15 @@ namespace nana if (!editor_) return; + std::wstring text; + if (API::is_focus_ready(editor_->window_handle())) - editor_->text(to_wstring(range_->value()), false); + text = to_wstring(range_->value()); else - editor_->text(to_wstring(modifier_.prefix + range_->value() + modifier_.suffix), false); + text = to_wstring(modifier_.prefix + range_->value() + modifier_.suffix); + + if (editor_->text() != text) + editor_->text(text, false); _m_draw_spins(spin_stated_); } @@ -514,6 +531,11 @@ namespace nana impl_->attach(wdg, graph); } + void drawer::detached() + { + impl_->detach(); + } + void drawer::refresh(graph_reference) { impl_->render(); @@ -570,13 +592,9 @@ namespace nana void drawer::key_char(graph_reference, const arg_keyboard& arg) { - if (impl_->editor()->respond_char(arg)) - { - if (!impl_->value(to_utf8(impl_->editor()->text()))) - impl_->draw_spins(); - + impl_->editor()->respond_char(arg); + if (impl_->editor()->try_refresh()) API::dev::lazy_refresh(); - } } void drawer::resized(graph_reference, const arg_resized&) @@ -630,18 +648,38 @@ namespace nana API::refresh_window(handle()); } - void spinbox::range(std::initializer_list steps_utf8) + void spinbox::range(std::vector values) { using namespace drawerbase::spinbox; - get_drawer_trigger().impl()->set_range(std::unique_ptr(new range_text(steps_utf8))); + get_drawer_trigger().impl()->set_range(std::unique_ptr(new range_text(std::move(values)))); API::refresh_window(handle()); } - void spinbox::range(std::initializer_list steps) + std::vector spinbox::range_string() const { - using namespace drawerbase::spinbox; - get_drawer_trigger().impl()->set_range(std::unique_ptr(new range_text(steps))); - API::refresh_window(handle()); + auto range = dynamic_cast(get_drawer_trigger().impl()->range()); + if (nullptr == range) + throw std::runtime_error("the type of spinbox range is not string"); + + return range->range(); + } + + std::pair spinbox::range_int() const + { + auto range = dynamic_cast*>(get_drawer_trigger().impl()->range()); + if (nullptr == range) + throw std::runtime_error("the type of spinbox range is not integer"); + + return range->range(); + } + + std::pair spinbox::range_double() const + { + auto range = dynamic_cast*>(get_drawer_trigger().impl()->range()); + if (nullptr == range) + throw std::runtime_error("the type of spinbox range is not double"); + + return range->range(); } ::std::string spinbox::value() const @@ -657,7 +695,7 @@ namespace nana internal_scope_guard lock; if (handle()) { - if (get_drawer_trigger().impl()->value(s)) + if (get_drawer_trigger().impl()->value(s, true)) API::refresh_window(handle()); } } diff --git a/source/gui/widgets/tabbar.cpp b/source/gui/widgets/tabbar.cpp index b8824f3b..ad614c09 100644 --- a/source/gui/widgets/tabbar.cpp +++ b/source/gui/widgets/tabbar.cpp @@ -1,6 +1,6 @@ /* * A Tabbar Implementation - * Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -49,9 +49,9 @@ namespace nana { bgcolor_ = bgcolor; - dark_bgcolor_ = bgcolor.blend(colors::black, 0.9); + dark_bgcolor_ = bgcolor.blend(colors::black, 0.1); blcolor_ = bgcolor.blend(colors::black, 0.5); - ilcolor_ = bgcolor.blend(colors::white, 0.9); + ilcolor_ = bgcolor.blend(colors::white, 0.1); } graph.rectangle(true, bgcolor); @@ -74,7 +74,7 @@ namespace nana { bgcolor = m.bgcolor; blcolor = m.bgcolor.blend(colors::black, 0.5); - dark_bgcolor = m.bgcolor.blend(colors::black, 0.9); + dark_bgcolor = m.bgcolor.blend(colors::black, 0.1); } auto round_r = r; @@ -141,9 +141,9 @@ namespace nana ::nana::color rect_clr{ static_cast(0x9da3ab) }; graph.round_rectangle(r, 1, 1, rect_clr, false, {}); nana::rectangle draw_r(r); - graph.rectangle(draw_r.pare_off(1), false, rect_clr.blend(bgcolor, 0.8)); - graph.rectangle(draw_r.pare_off(1), false, rect_clr.blend(bgcolor, 0.4)); graph.rectangle(draw_r.pare_off(1), false, rect_clr.blend(bgcolor, 0.2)); + graph.rectangle(draw_r.pare_off(1), false, rect_clr.blend(bgcolor, 0.6)); + graph.rectangle(draw_r.pare_off(1), false, rect_clr.blend(bgcolor, 0.8)); } else if (!active) clr = static_cast(0x9299a4); diff --git a/source/gui/widgets/textbox.cpp b/source/gui/widgets/textbox.cpp index 2501e541..909cde92 100644 --- a/source/gui/widgets/textbox.cpp +++ b/source/gui/widgets/textbox.cpp @@ -1,7 +1,7 @@ /* * A Textbox Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -69,10 +69,10 @@ namespace drawerbase { auto scheme = API::dev::get_scheme(wdg); editor_ = new text_editor(wd, graph, dynamic_cast<::nana::widgets::skeletons::text_editor_scheme*>(scheme)); - editor_->textbase().set_event_agent(evt_agent_.get()); - editor_->set_event(evt_agent_.get()); evt_agent_.reset(new event_agent(static_cast<::nana::textbox&>(wdg), editor_->text_position())); + editor_->textbase().set_event_agent(evt_agent_.get()); + editor_->set_event(evt_agent_.get()); _m_text_area(graph.width(), graph.height()); @@ -103,22 +103,22 @@ namespace drawerbase { void drawer::mouse_down(graph_reference, const arg_mouse& arg) { - if (editor_->mouse_pressed(arg)) - { - editor_->render(true); + editor_->mouse_pressed(arg); + if(editor_->try_refresh()) API::dev::lazy_refresh(); - } } void drawer::mouse_move(graph_reference, const arg_mouse& arg) { - if(editor_->mouse_move(arg.left_button, arg.pos)) + editor_->mouse_move(arg.left_button, arg.pos); + if(editor_->try_refresh()) API::dev::lazy_refresh(); } void drawer::mouse_up(graph_reference, const arg_mouse& arg) { - if(editor_->mouse_pressed(arg)) + editor_->mouse_pressed(arg); + if(editor_->try_refresh()) API::dev::lazy_refresh(); } @@ -145,16 +145,16 @@ namespace drawerbase { void drawer::key_press(graph_reference, const arg_keyboard& arg) { - if(editor_->respond_key(arg)) - { - editor_->reset_caret(); + editor_->respond_key(arg); + editor_->reset_caret(true); + if(editor_->try_refresh()) API::dev::lazy_refresh(); - } } void drawer::key_char(graph_reference, const arg_keyboard& arg) { - if (editor_->respond_char(arg)) + editor_->respond_char(arg); + if(editor_->try_refresh()) API::dev::lazy_refresh(); } @@ -163,6 +163,10 @@ namespace drawerbase { _m_text_area(arg.width, arg.height); refresh(graph); editor_->reset_caret(); + + if (!editor_->try_refresh()) + refresh(graph); + API::dev::lazy_refresh(); } @@ -225,7 +229,10 @@ namespace drawerbase { internal_scope_guard lock; auto editor = get_drawer_trigger().editor(); if (editor && editor->load(file.data())) - API::update_window(handle()); + { + if (editor->try_refresh()) + API::update_window(handle()); + } } void textbox::store(std::string file) @@ -244,6 +251,15 @@ namespace drawerbase { editor->textbase().store(std::move(file), true, encoding); } + textbox::colored_area_access_interface* textbox::colored_area_access() + { + auto editor = get_drawer_trigger().editor(); + if (editor) + return &editor->colored_area(); + + return nullptr; + } + /// Enables/disables the textbox to indent a line. Idents a new line when it is created by pressing enter. /// @param generator generates text for identing a line. If it is empty, textbox indents the line according to last line. textbox& textbox::indention(bool enb, std::function generator) @@ -260,11 +276,16 @@ namespace drawerbase { internal_scope_guard lock; auto editor = get_drawer_trigger().editor(); if (editor) - { - editor->text(to_wstring(str), end_caret); + { + editor->text(to_wstring(str), false); + + if (end_caret) + editor->move_caret_end(true); editor->textbase().reset(); - API::update_window(this->handle()); + + if (editor->try_refresh()) + API::update_window(this->handle()); } return *this; } @@ -319,6 +340,25 @@ namespace drawerbase { return false; } + bool textbox::getline(std::size_t line_index,std::size_t start_point,std::string& text) const + { + internal_scope_guard lock; + auto editor = get_drawer_trigger().editor(); + if(editor) + { + std::wstring line_text; + if(editor->getline(line_index,line_text)) + { + if(line_text.length() >= start_point) + { + text = to_utf8(line_text.substr(start_point)); + return true; + } + } + } + return false; + } + /// Gets the caret position bool textbox::caret_pos(point& pos, bool text_coordinate) const { @@ -358,9 +398,11 @@ namespace drawerbase { if(editor) { if(at_caret == false) - editor->move_caret_end(); + editor->move_caret_end(false); editor->put(to_wstring(text)); + + editor->try_refresh(); API::update_window(this->handle()); } return *this; @@ -370,15 +412,18 @@ namespace drawerbase { bool textbox::line_wrapped() const { internal_scope_guard lock; - return get_drawer_trigger().editor()->line_wrapped(); + return get_drawer_trigger().editor()->attr().line_wrapped; } textbox& textbox::line_wrapped(bool autl) { internal_scope_guard lock; auto editor = get_drawer_trigger().editor(); - if (editor->line_wrapped(autl)) + if (editor && editor->line_wrapped(autl)) + { + editor->try_refresh(); API::update_window(handle()); + } return *this; } @@ -398,6 +443,8 @@ namespace drawerbase { { auto wd = handle(); API::eat_tabstop(wd, ml); //textbox handles the Tab pressing when it is multi-line. + + editor->try_refresh(); API::update_window(wd); } return *this; @@ -461,12 +508,19 @@ namespace drawerbase { return (editor ? editor->selected() : false); } + bool textbox::get_selected_points(nana::upoint &a, nana::upoint &b) const + { + internal_scope_guard lock; + auto editor = get_drawer_trigger().editor(); + return (editor ? editor->get_selected_points(a, b) : false); + } + void textbox::select(bool yes) { internal_scope_guard lock; auto editor = get_drawer_trigger().editor(); if(editor && editor->select(yes)) - API::update_window(*this); + API::refresh_window(*this); } std::pair textbox::selection() const @@ -476,7 +530,7 @@ namespace drawerbase { internal_scope_guard lock; auto editor = get_drawer_trigger().editor(); if (editor) - editor->get_select_points(points.first, points.second); + editor->get_selected_points(points.first, points.second); return points; } @@ -496,7 +550,8 @@ namespace drawerbase { if(editor) { editor->paste(); - API::update_window(*this); + if (editor->try_refresh()) + API::update_window(*this); } } @@ -539,12 +594,20 @@ namespace drawerbase { return *this; } + void textbox::clear_undo() + { + get_drawer_trigger().editor()->clear_undo(); + } + void textbox::set_highlight(const std::string& name, const ::nana::color& fgcolor, const ::nana::color& bgcolor) { internal_scope_guard lock; auto editor = get_drawer_trigger().editor(); if (editor) + { editor->set_highlight(name, fgcolor, bgcolor); + API::refresh_window(handle()); + } } void textbox::erase_highlight(const std::string& name) @@ -552,7 +615,10 @@ namespace drawerbase { internal_scope_guard lock; auto editor = get_drawer_trigger().editor(); if (editor) + { editor->erase_highlight(name); + API::refresh_window(handle()); + } } void textbox::set_keywords(const std::string& name, bool case_sensitive, bool whole_word_match, std::initializer_list kw_list) @@ -563,6 +629,7 @@ namespace drawerbase { { for (auto & kw : kw_list) editor->set_keyword(kw, name, case_sensitive, whole_word_match); + API::refresh_window(handle()); } } @@ -574,6 +641,7 @@ namespace drawerbase { { for (auto & kw : kw_list_utf8) editor->set_keyword(::nana::charset(kw, ::nana::unicode::utf8), name, case_sensitive, whole_word_match); + API::refresh_window(handle()); } } @@ -582,7 +650,23 @@ namespace drawerbase { internal_scope_guard lock; auto editor = get_drawer_trigger().editor(); if (editor) + { editor->erase_keyword(to_wstring(kw)); + API::refresh_window(handle()); + } + } + + textbox& textbox::text_align(::nana::align alignment) + { + internal_scope_guard lock; + auto editor = get_drawer_trigger().editor(); + if (editor) + { + editor->text_align(alignment); + API::refresh_window(handle()); + } + + return *this; } std::vector textbox::text_position() const diff --git a/source/gui/widgets/toolbar.cpp b/source/gui/widgets/toolbar.cpp index 94b5f888..953e4796 100644 --- a/source/gui/widgets/toolbar.cpp +++ b/source/gui/widgets/toolbar.cpp @@ -1,7 +1,7 @@ /* * A Toolbar Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -166,8 +166,10 @@ namespace nana gh.rgb_to_wb(); gh.paste(graph, pos.x, pos.y); } - else if(state == state_t::normal) + else if (state == state_t::normal) + { graph.blend(nana::rectangle(pos, imgsize), ::nana::color(0xc0, 0xdd, 0xfc).blend(bgcolor, 0.5), 0.25); + } x += scale; width -= scale; @@ -230,7 +232,7 @@ namespace nana auto bgcolor = API::bgcolor(widget_->handle()); graph.palette(true, bgcolor); - graph.gradual_rectangle(rectangle{ graph.size() }, bgcolor.blend(colors::white, 0.9), bgcolor.blend(colors::black, 0.95), true); + graph.gradual_rectangle(rectangle{ graph.size() }, bgcolor.blend(colors::white, 0.1), bgcolor.blend(colors::black, 0.05), true); item_renderer ir(graph, impl_->textout, impl_->scale, bgcolor); size_type index = 0; diff --git a/source/gui/widgets/treebox.cpp b/source/gui/widgets/treebox.cpp index c75aea52..54c94f94 100644 --- a/source/gui/widgets/treebox.cpp +++ b/source/gui/widgets/treebox.cpp @@ -1,7 +1,7 @@ /* * A Treebox Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -14,7 +14,6 @@ #include #include #include -#include #include namespace nana @@ -106,7 +105,7 @@ namespace nana nana::paint::graphics item_graph({ item_r_.width, item_r_.height }); item_graph.typeface(graph_->typeface()); - renderer_->set_color(widget_->bgcolor(), widget_->fgcolor()); + renderer_->begin_paint(*widget_); renderer_->bground(item_graph, this); renderer_->expander(item_graph, this); renderer_->crook(item_graph, this); @@ -166,7 +165,7 @@ namespace nana class trigger::item_locator { public: - typedef tree_cont_type::node_type node_type; + using node_type = tree_cont_type::node_type; item_locator(implement * impl, int item_pos, int x, int y); int operator()(node_type &node, int affect); @@ -199,7 +198,7 @@ namespace nana template struct trigger::basic_implement { - typedef trigger::node_type node_type; + using node_type = trigger::node_type; struct rep_tag { @@ -216,11 +215,10 @@ namespace nana { nana::upoint border; nana::scroll scroll; - std::size_t prev_first_value; mutable std::map image_table; - tree_cont_type::node_type * first; + tree_cont_type::node_type * first; //The node at the top of screen int indent_pixels; int offset_x; }shape; @@ -235,9 +233,9 @@ namespace nana { tooltip_window * tooltip; component comp_pointed; - tree_cont_type::node_type * pointed; - tree_cont_type::node_type * selected; - tree_cont_type::node_type * event_node; + node_type * pointed; + node_type * selected; + node_type * pressed_node; }node_state; struct track_node_tag @@ -260,7 +258,6 @@ namespace nana data.widget_ptr = nullptr; data.stop_drawing = false; - shape.prev_first_value = 0; shape.first = nullptr; shape.indent_pixels = 10; shape.offset_x = 0; @@ -271,7 +268,7 @@ namespace nana node_state.comp_pointed = component::end; node_state.pointed = nullptr; node_state.selected = nullptr; - node_state.event_node = nullptr; + node_state.pressed_node = nullptr; track_node.key_time = 0; @@ -304,19 +301,60 @@ namespace nana } } - bool draw(bool scrollbar_react) + bool unlink(node_type* node, bool perf_clear) + { + if (!attr.tree_cont.verify(node)) + return false; + + if (node->is_ancestor_of(shape.first)) + { + shape.first = node->front(); + if (shape.first) + shape.first = node->owner; + } + + if (node->is_ancestor_of(node_state.pointed)) + node_state.pointed = nullptr; + + if (node->is_ancestor_of(node_state.selected)) + node_state.selected = nullptr; + + if (perf_clear) + { + if (node->child) + { + attr.tree_cont.clear(node); + return true; + } + return false; + } + + attr.tree_cont.remove(node); + return true; + } + + bool draw(bool reset_scroll, bool ignore_update = false, bool ignore_auto_draw = false) { if(data.graph && (false == data.stop_drawing)) { - if(scrollbar_react) + if (reset_scroll) show_scroll(); - //Draw background - data.graph->rectangle(true, data.widget_ptr->bgcolor()); + if (attr.auto_draw || ignore_auto_draw) + { + //Draw background + rectangle bground_r{ data.graph->size() }; + if (!API::dev::copy_transparent_background(data.widget_ptr->handle(), bground_r, *data.graph, {})) + data.graph->rectangle(true, data.widget_ptr->bgcolor()); - //Draw tree - attr.tree_cont.for_each(shape.first, Renderer(this, nana::point(static_cast(attr.tree_cont.indent_size(shape.first) * shape.indent_pixels) - shape.offset_x, 1))); - return true; + //Draw tree + attr.tree_cont.for_each(shape.first, Renderer(this, nana::point(static_cast(attr.tree_cont.indent_size(shape.first) * shape.indent_pixels) - shape.offset_x, 1))); + + if (!ignore_update) + API::update_window(data.widget_ptr->handle()); + + return true; + } } return false; } @@ -402,42 +440,45 @@ namespace nana return nullptr; } - static bool check_kinship(const node_type* parent, const node_type * child) - { - if((!parent) || (!child)) - return false; - - while(child && (child != parent)) - child = child->owner; - - return (nullptr != child); - } - bool make_adjust(node_type * node, int reason) { if(!node) return false; - auto & tree_container = attr.tree_cont; + auto & tree = attr.tree_cont; + auto const first_pos = tree.distance_if(shape.first, pred_allow_child{}); + auto const node_pos = tree.distance_if(node, pred_allow_child{}); + auto const max_allow = max_allowed(); switch(reason) { case 0: - //adjust if the node expanded and the number of its children are over the max number allowed - if(shape.first != node) + if (node->value.second.expanded) { - unsigned child_size = tree_container.child_size_if(*node, pred_allow_child()); - const std::size_t max_allow = max_allowed(); - - if(child_size < max_allow) + //adjust if the number of its children are over the max number allowed + if (shape.first != node) { - unsigned off1 = tree_container.distance_if(shape.first, pred_allow_child()); - unsigned off2 = tree_container.distance_if(node, pred_allow_child()); - const unsigned size = off2 - off1 + child_size + 1; - if(size > max_allow) - shape.first = tree_container.advance_if(shape.first, size - max_allow, pred_allow_child()); + auto child_size = tree.child_size_if(*node, pred_allow_child()); + if (child_size < max_allow) + { + auto const size = node_pos - first_pos + child_size + 1; + if (size > max_allow) + shape.first = tree.advance_if(shape.first, size - max_allow, pred_allow_child{}); + } + else + shape.first = node; + } + } + else + { + //The node is shrank + auto visual_size = visual_item_size(); + if (visual_size > max_allow) + { + if (first_pos + max_allow > visual_size) + shape.first = tree.advance_if(nullptr, visual_size - max_allow, pred_allow_child{}); } else - shape.first = node; + shape.first = nullptr; } break; case 1: @@ -445,7 +486,7 @@ namespace nana case 3: //param is the begin pos of an item in absolute. { - int beg = static_cast(tree_container.indent_size(node) * shape.indent_pixels) - shape.offset_x; + int beg = static_cast(tree.indent_size(node) * shape.indent_pixels) - shape.offset_x; int end = beg + static_cast(node_w_pixels(node)); bool take_adjust = false; @@ -468,16 +509,14 @@ namespace nana case 4: if(shape.first != node) { - unsigned off_first = tree_container.distance_if(shape.first, pred_allow_child()); - unsigned off_node = tree_container.distance_if(node, pred_allow_child()); - if(off_node < off_first) + if (node_pos < first_pos) { shape.first = node; return true; } - else if(off_node - off_first > max_allowed()) + else if (node_pos - first_pos > max_allow) { - shape.first = tree_container.advance_if(0, off_node - max_allowed() + 1, pred_allow_child()); + shape.first = tree.advance_if(nullptr, node_pos - max_allow + 1, pred_allow_child{}); return true; } } @@ -535,7 +574,7 @@ namespace nana if(value == false) { //if contracting a parent of the selected node, select the contracted node. - if(check_kinship(node, node_state.selected)) + if (node->is_ancestor_of(node_state.selected)) set_selected(node); } @@ -543,7 +582,6 @@ namespace nana if(node->child) { data.stop_drawing = true; - //attr.ext_event.expand(data.widget_ptr->handle(), item_proxy(data.trigger_ptr, node), value); item_proxy iprx(data.trigger_ptr, node); data.widget_ptr->events().expanded.emit(::nana::arg_treebox{ *data.widget_ptr, iprx, value }, data.widget_ptr->handle()); data.stop_drawing = false; @@ -573,44 +611,24 @@ namespace nana { if(scroll.empty()) { - shape.prev_first_value = 0; scroll.create(*data.widget_ptr, nana::rectangle(data.graph->width() - 16, 0, 16, data.graph->height())); - auto fn = [this](const arg_mouse& arg){ - this->event_scrollbar(arg); - }; - auto & events = scroll.events(); - events.mouse_down(fn); - events.mouse_move(fn); - events.mouse_wheel(fn); + scroll.events().value_changed.connect_unignorable([this](const arg_scroll&) + { + adjust.scroll_timestamp = nana::system::timestamp(); + adjust.timer.start(); + + shape.first = attr.tree_cont.advance_if(nullptr, shape.scroll.value(), pred_allow_child{}); + draw(false, false, true); + }); } scroll.amount(visual_items); scroll.range(max_allow); } - scroll.value(attr.tree_cont.distance_if(shape.first, pred_allow_child())); - } - - void event_scrollbar(const arg_mouse& arg) - { - if((event_code::mouse_wheel == arg.evt_code) || arg.is_left_button()) - { - if(shape.prev_first_value != shape.scroll.value()) - { - shape.prev_first_value = shape.scroll.value(); - adjust.scroll_timestamp = nana::system::timestamp(); - adjust.timer.start(); - - shape.first = attr.tree_cont.advance_if(nullptr, shape.prev_first_value, pred_allow_child()); - - if(arg.window_handle == shape.scroll.handle()) - { - draw(false); - API::update_window(data.widget_ptr->handle()); - } - } - } + auto pos = attr.tree_cont.distance_if(shape.first, pred_allow_child{}); + scroll.value(pos); } std::size_t visual_item_size() const @@ -672,11 +690,10 @@ namespace nana attr.tree_cont.template for_each(shape.first, nl); bool redraw = false; - node_state.event_node = nl.node(); - - if(nl.node() && (nl.what() != component::end)) + auto const node = nl.node(); + if (node && (nl.what() != component::end)) { - if((nl.what() != node_state.comp_pointed || nl.node() != node_state.pointed)) + if ((nl.what() != node_state.comp_pointed) || (node != node_state.pointed)) { node_state.comp_pointed = nl.what(); @@ -685,12 +702,11 @@ namespace nana item_proxy iprx(data.trigger_ptr, node_state.pointed); data.widget_ptr->events().hovered.emit(::nana::arg_treebox{ *data.widget_ptr, iprx, false }, data.widget_ptr->handle()); - if (nl.node() != node_state.pointed) + if (node != node_state.pointed) close_tooltip_window(); } - - node_state.pointed = nl.node(); + node_state.pointed = node; item_proxy iprx(data.trigger_ptr, node_state.pointed); data.widget_ptr->events().hovered.emit(::nana::arg_treebox{ *data.widget_ptr, iprx, true }, data.widget_ptr->handle()); @@ -751,11 +767,11 @@ namespace nana }; auto & events = node_state.tooltip->events(); - events.mouse_leave(fn); - events.mouse_move(fn); - events.mouse_down.connect(fn); - events.mouse_up.connect(fn); - events.dbl_click.connect(fn); + events.mouse_leave.connect_unignorable(fn); + events.mouse_move.connect_unignorable(fn); + events.mouse_down.connect_unignorable(fn); + events.mouse_up.connect_unignorable(fn); + events.dbl_click.connect_unignorable(fn); } } @@ -866,8 +882,18 @@ namespace nana item_proxy& item_proxy::check(bool ck) { trigger_->check(node_, ck ? checkstate::checked : checkstate::unchecked); - if(trigger_->draw()) - API::update_window(trigger_->impl()->data.widget_ptr->handle()); + trigger_->impl()->draw(false); + return *this; + } + + item_proxy& item_proxy::clear() + { + if (node_) + { + auto impl = trigger_->impl(); + if(impl->unlink(node_, true)) + impl->draw(true); + } return *this; } @@ -880,10 +906,8 @@ namespace nana { auto * impl = trigger_->impl(); if(impl->set_expanded(node_, exp)) - { impl->draw(true); - API::update_window(impl->data.widget_ptr->handle()); - } + return *this; } @@ -896,10 +920,8 @@ namespace nana { auto * impl = trigger_->impl(); if(impl->set_selected(s ? node_ : nullptr)) - { impl->draw(true); - API::update_window(*impl->data.widget_ptr); - } + return *this; } @@ -1134,7 +1156,7 @@ namespace nana { switch(comp) { - case component_t::expender: + case component_t::expander: if(attr.has_children) { r->width = item_offset; @@ -1181,13 +1203,11 @@ namespace nana class internal_renderer : public renderer_interface { - nana::color bgcolor_; - nana::color fgcolor_; + window window_handle_; - void set_color(const nana::color & bgcolor, const nana::color& fgcolor) override + void begin_paint(::nana::widget& wdg) override { - bgcolor_ = bgcolor; - fgcolor_ = fgcolor; + window_handle_ = wdg.handle(); } void bground(graph_reference graph, const compset_interface * compset) const override @@ -1214,8 +1234,20 @@ namespace nana if (clrptr) { - graph.rectangle(attr.area, false, clrptr[1]); - graph.rectangle(attr.area.pare_off(1), true, *clrptr); + if (API::is_transparent_background(window_handle_)) + { + paint::graphics item_graph{ attr.area.dimension() }; + item_graph.rectangle(false, clrptr[1]); + item_graph.rectangle(rectangle{attr.area.dimension()}.pare_off(1), true, *clrptr); + + + graph.blend(attr.area, item_graph, attr.area.position(), 0.5); + } + else + { + graph.rectangle(attr.area, false, clrptr[1]); + graph.rectangle(attr.area.pare_off(1), true, *clrptr); + } } } } @@ -1223,7 +1255,7 @@ namespace nana void expander(graph_reference graph, const compset_interface * compset) const override { comp_attribute_t attr; - if(compset->comp_attribute(component::expender, attr)) + if(compset->comp_attribute(component::expander, attr)) { facade arrow("solid_triangle"); arrow.direction(direction::southeast); @@ -1235,7 +1267,7 @@ namespace nana auto r = attr.area; r.y += (attr.area.height - 16) / 2; r.width = r.height = 16; - arrow.draw(graph, bgcolor_, (attr.mouse_pointed ? colors::deep_sky_blue : colors::black), r, element_state::normal); + arrow.draw(graph, API::bgcolor(window_handle_), (attr.mouse_pointed ? colors::deep_sky_blue : colors::black), r, element_state::normal); } } @@ -1246,7 +1278,7 @@ namespace nana { attr.area.y += (attr.area.height - 16) / 2; crook_.check(compset->item_attribute().checked); - crook_.draw(graph, bgcolor_, fgcolor_, attr.area, attr.mouse_pointed ? element_state::hovered : element_state::normal); + crook_.draw(graph, API::bgcolor(window_handle_), API::fgcolor(window_handle_), attr.area, attr.mouse_pointed ? element_state::hovered : element_state::normal); } } @@ -1288,7 +1320,7 @@ namespace nana { comp_attribute_t attr; if (compset->comp_attribute(component::text, attr)) - graph.string(point{ attr.area.x, attr.area.y + 3 }, compset->item_attribute().text, fgcolor_); + graph.string(point{ attr.area.x, attr.area.y + 3 }, compset->item_attribute().text, API::fgcolor(window_handle_)); } private: mutable facade crook_; @@ -1324,29 +1356,31 @@ namespace nana node_r.width = comp_placer->item_width(*impl_->data.graph, node_attr_); node_r.height = comp_placer->item_height(*impl_->data.graph); - if(pos_.y < item_pos_.y + static_cast(node_r.height)) + if ((pos_.y < item_pos_.y + static_cast(node_r.height)) && (pos_.y >= item_pos_.y)) { - auto logic_pos = pos_ - item_pos_; - node_ = &node; + auto const logic_pos = pos_ - item_pos_; - for(int comp = static_cast(component::begin); comp != static_cast(component::end); ++comp) + for (int comp = static_cast(component::begin); comp != static_cast(component::end); ++comp) { nana::rectangle r = node_r; - if(comp_placer->locate(static_cast(comp), node_attr_, &r)) + if (!comp_placer->locate(static_cast(comp), node_attr_, &r)) + continue; + + if (r.is_hit(logic_pos)) { - if(r.is_hit(logic_pos)) - { - what_ = static_cast(comp); - if(component::expender == what_ && (false == node_attr_.has_children)) - what_ = component::end; + node_ = &node; + what_ = static_cast(comp); + if (component::expander == what_ && (false == node_attr_.has_children)) + what_ = component::end; - if(component::text == what_) - node_text_r_ = r; + if (component::text == what_) + node_text_r_ = r; - return 0; - } + break; } } + + return 0; //Stop iterating } item_pos_.y += node_r.height; @@ -1386,8 +1420,6 @@ namespace nana item_renderer(implement * impl, const nana::point& pos) : impl_(impl), - bgcolor_(impl->data.widget_ptr->bgcolor()), - fgcolor_(impl->data.widget_ptr->fgcolor()), pos_(pos) { } @@ -1419,7 +1451,7 @@ namespace nana node_r_.height = comp_placer->item_height(*impl_->data.graph); auto renderer = draw_impl->data.renderer; - renderer->set_color(bgcolor_, fgcolor_); + renderer->begin_paint(*draw_impl->data.widget_ptr); renderer->bground(*draw_impl->data.graph, this); renderer->expander(*draw_impl->data.graph, this); renderer->crook(*draw_impl->data.graph, this); @@ -1454,8 +1486,6 @@ namespace nana } private: trigger::implement * impl_; - ::nana::color bgcolor_; - ::nana::color fgcolor_; ::nana::point pos_; const node_type * iterated_node_; item_attribute_t node_attr_; @@ -1531,7 +1561,6 @@ namespace nana } impl_->draw(false); - API::update_window(impl_->data.widget_ptr->handle()); if (impl_->node_state.tooltip) { @@ -1563,35 +1592,6 @@ namespace nana return impl_; } - void trigger::auto_draw(bool ad) - { - if(impl_->attr.auto_draw != ad) - { - impl_->attr.auto_draw = ad; - if(ad) - API::update_window(impl_->data.widget_ptr->handle()); - } - } - - void trigger::checkable(bool enable) - { - auto & comp_placer = impl_->data.comp_placer; - if(comp_placer->enabled(component::crook) != enable) - { - comp_placer->enable(component::crook, enable); - if(impl_->attr.auto_draw) - { - impl_->draw(false); - API::update_window(impl_->data.widget_ptr->handle()); - } - } - } - - bool trigger::checkable() const - { - return impl_->data.comp_placer->enabled(component::crook); - } - void trigger::check(node_type* node, checkstate cs) { if (!node->owner) return; @@ -1659,25 +1659,6 @@ namespace nana } } - bool trigger::draw() - { - if (!impl_->attr.auto_draw) - return false; - - impl_->draw(false); - return true; - } - - auto trigger::tree() -> tree_cont_type & - { - return impl_->attr.tree_cont; - } - - auto trigger::tree() const -> tree_cont_type const & - { - return impl_->attr.tree_cont; - } - void trigger::renderer(::nana::pat::cloneable&& r) { impl_->data.renderer = std::move(r); @@ -1698,14 +1679,6 @@ namespace nana return impl_->data.comp_placer; } - nana::any & trigger::value(node_type* node) const - { - if(impl_->attr.tree_cont.verify(node) == false) - throw std::invalid_argument("Nana.GUI.treebox.value() invalid node"); - - return node->value.second.value; - } - trigger::node_type* trigger::insert(node_type* node, const std::string& key, std::string&& title) { node_type * p = impl_->attr.tree_cont.node(node, key); @@ -1714,54 +1687,20 @@ namespace nana else p = impl_->attr.tree_cont.insert(node, key, treebox_node_type(std::move(title))); - if(p && impl_->attr.auto_draw && impl_->draw(true)) - API::update_window(impl_->data.widget_ptr->handle()); + if (p) + impl_->draw(true); + return p; } trigger::node_type* trigger::insert(const std::string& path, std::string&& title) { auto x = impl_->attr.tree_cont.insert(path, treebox_node_type(std::move(title))); - if(x && impl_->attr.auto_draw && impl_->draw(true)) - API::update_window(impl_->data.widget_ptr->handle()); + if (x) + impl_->draw(true); return x; } - bool trigger::verify(const void* node) const - { - return impl_->attr.tree_cont.verify(reinterpret_cast(node)); - } - - bool trigger::verify_kinship(node_type* parent, node_type* child) const - { - if(false == (parent && child)) return false; - - while(child && (child != parent)) - child = child->owner; - - return (nullptr != child); - } - - void trigger::remove(node_type* node) - { - if(!verify(node)) - return; - - auto & shape = impl_->shape; - auto & node_state = impl_->node_state; - - if(verify_kinship(node, node_state.event_node)) - node_state.event_node = nullptr; - - if(verify_kinship(node, shape.first)) - shape.first = nullptr; - - if(verify_kinship(node, node_state.selected)) - node_state.selected = nullptr; - - impl_->attr.tree_cont.remove(node); - } - trigger::node_type* trigger::selected() const { return impl_->node_state.selected; @@ -1770,28 +1709,7 @@ namespace nana void trigger::selected(node_type* node) { if(impl_->attr.tree_cont.verify(node) && impl_->set_selected(node)) - { impl_->draw(true); - API::update_window(impl_->data.widget_ptr->handle()); - } - } - - void trigger::set_expand(node_type* node, bool exp) - { - if((impl_->data.widget_ptr) && impl_->set_expanded(node, exp)) - { - impl_->draw(true); - API::update_window(impl_->data.widget_ptr->handle()); - } - } - - void trigger::set_expand(const std::string& path, bool exp) - { - if(impl_->set_expanded(impl_->attr.tree_cont.find(path), exp)) - { - impl_->draw(true); - API::update_window(impl_->data.widget_ptr->handle()); - } } node_image_tag& trigger::icon(const std::string& id) const @@ -1814,12 +1732,12 @@ namespace nana void trigger::node_icon(node_type* node, const std::string& id) { - if(tree().verify(node)) + if(impl_->attr.tree_cont.verify(node)) { node->value.second.img_idstr = id; auto i = impl_->shape.image_table.find(id); - if((i != impl_->shape.image_table.end()) && impl_->draw(true)) - API::update_window(impl_->data.widget_ptr->handle()); + if (i != impl_->shape.image_table.end()) + impl_->draw(true); } } @@ -1832,7 +1750,7 @@ namespace nana bool trigger::rename(node_type *node, const char* key, const char* name) { - if((key || name ) && tree().verify(node)) + if((key || name ) && impl_->attr.tree_cont.verify(node)) { if(key && (key != node->value.first)) { @@ -1865,7 +1783,8 @@ namespace nana void trigger::refresh(graph_reference) { - impl_->draw(false); + //Don't reset the scroll and update the window + impl_->draw(false, true); } void trigger::dbl_click(graph_reference, const arg_mouse& arg) @@ -1876,12 +1795,20 @@ namespace nana item_locator nl(impl_, xpos, arg.pos.x, arg.pos.y); impl_->attr.tree_cont.for_each(shape.first, nl); - if(nl.node() && (nl.what() == component::text || nl.what() == component::icon)) + auto const node = nl.node(); + if (!node) + return; + + switch (nl.what()) { - impl_->node_state.event_node = nl.node(); - impl_->set_expanded(impl_->node_state.event_node, !impl_->node_state.event_node->value.second.expanded); - impl_->draw(true); + case component::icon: + case component::text: + impl_->set_expanded(node, !node->value.second.expanded); + impl_->draw(true, true, false); API::dev::lazy_refresh(); + break; + default: + break; } } @@ -1896,34 +1823,20 @@ namespace nana bool has_redraw = false; auto & node_state = impl_->node_state; - node_state.event_node = nullptr; + node_state.pressed_node = nl.node(); - if(nl.node()) + if (node_state.pressed_node && (component::expander == nl.what())) { - node_state.event_node = nl.node(); - if(nl.what() != component::end) - { - if(nl.what() == component::expender) - { - if(impl_->set_expanded(node_state.event_node, !node_state.event_node->value.second.expanded)) - impl_->make_adjust(node_state.event_node, 0); + if(impl_->set_expanded(node_state.pressed_node, !node_state.pressed_node->value.second.expanded)) + impl_->make_adjust(node_state.pressed_node, 0); - has_redraw = true; - } - else if(nl.item_body()) - { - if(node_state.selected != node_state.event_node) - { - impl_->set_selected(node_state.event_node); - has_redraw = true; - } - } - } - else if(node_state.selected != node_state.event_node) - { - impl_->set_selected(node_state.event_node); - has_redraw = true; - } + has_redraw = true; //btw, don't select the node + } + + if ((!has_redraw) && (node_state.selected != node_state.pressed_node)) + { + impl_->set_selected(node_state.pressed_node); + has_redraw = true; } if(has_redraw) @@ -1941,25 +1854,33 @@ namespace nana item_locator nl(impl_, xpos, arg.pos.x, arg.pos.y); impl_->attr.tree_cont.for_each(shape.first, nl); + auto const pressed_node = impl_->node_state.pressed_node; + impl_->node_state.pressed_node = nullptr; + if(!nl.node()) return; - if((impl_->node_state.selected != nl.node()) && nl.item_body()) + if (pressed_node == nl.node()) { - impl_->set_selected(nl.node()); - if(impl_->make_adjust(impl_->node_state.selected, 1)) - impl_->adjust.scroll_timestamp = 1; - } - else if (nl.what() == component::crook) - { - checkstate cs = checkstate::unchecked; - if (checkstate::unchecked == nl.node()->value.second.checked) - cs = checkstate::checked; + if ((impl_->node_state.selected != nl.node()) && nl.item_body()) + { + impl_->set_selected(nl.node()); + if (impl_->make_adjust(impl_->node_state.selected, 1)) + impl_->adjust.scroll_timestamp = 1; + } + else if (nl.what() == component::crook) + { + checkstate cs = checkstate::unchecked; + if (checkstate::unchecked == nl.node()->value.second.checked) + cs = checkstate::checked; - check(nl.node(), cs); + check(nl.node(), cs); + } + else + return; //Do not refresh } else - return; //Do not refresh + return; //Don't refresh impl_->draw(true); API::dev::lazy_refresh(); @@ -1976,18 +1897,19 @@ namespace nana void trigger::mouse_wheel(graph_reference, const arg_wheel& arg) { - auto & shape = impl_->shape; - std::size_t prev = shape.prev_first_value; + auto & scroll = impl_->shape.scroll; + if (scroll.empty()) + return; - shape.scroll.make_step(!arg.upwards); + auto const value_before = scroll.value(); - impl_->event_scrollbar(arg); + scroll.make_step(!arg.upwards); - if(prev != shape.prev_first_value) + if (value_before != scroll.value()) { impl_->track_mouse(arg.pos.x, arg.pos.y); - impl_->draw(false); + impl_->draw(false, true, true); API::dev::lazy_refresh(); } } @@ -2160,6 +2082,8 @@ namespace nana }//end namespace drawerbase //class treebox + using component = drawerbase::treebox::component; + treebox::treebox(){} treebox::treebox(window wd, bool visible) @@ -2184,18 +2108,37 @@ namespace nana void treebox::auto_draw(bool ad) { - get_drawer_trigger().auto_draw(ad); + auto impl = get_drawer_trigger().impl(); + if (impl->attr.auto_draw != ad) + { + impl->attr.auto_draw = ad; + if (ad) + API::refresh_window(this->handle()); + } } treebox & treebox::checkable(bool enable) { - get_drawer_trigger().checkable(enable); + auto impl = get_drawer_trigger().impl(); + auto & comp_placer = impl->data.comp_placer; + if (comp_placer->enabled(component::crook) != enable) + { + comp_placer->enable(component::crook, enable); + impl->draw(false); + } return *this; } bool treebox::checkable() const { - return get_drawer_trigger().checkable(); + return get_drawer_trigger().impl()->data.comp_placer->enabled(component::crook); + } + + void treebox::clear() + { + auto impl = get_drawer_trigger().impl(); + if (impl->unlink(impl->attr.tree_cont.get_root(), true)) + impl->draw(true); } treebox::node_image_type& treebox::icon(const std::string& id) const @@ -2211,7 +2154,7 @@ namespace nana auto treebox::find(const std::string& keypath) -> item_proxy { auto * trg = &get_drawer_trigger(); - return item_proxy(trg, trg->tree().find(keypath)); + return item_proxy(trg, trg->impl()->attr.tree_cont.find(keypath)); } treebox::item_proxy treebox::insert(const std::string& path_key, std::string title) @@ -2227,20 +2170,21 @@ namespace nana treebox::item_proxy treebox::erase(item_proxy i) { auto next = i.sibling(); - get_drawer_trigger().remove(i._m_node()); + if (get_drawer_trigger().impl()->unlink(i._m_node(), false)) + get_drawer_trigger().impl()->draw(true); return next; } void treebox::erase(const std::string& keypath) { auto i = find(keypath); - if(!i.empty()) - get_drawer_trigger().remove(i._m_node()); + if (!i.empty()) + this->erase(i); } std::string treebox::make_key_path(item_proxy i, const std::string& splitter) const { - auto & tree = get_drawer_trigger().tree(); + auto & tree = get_drawer_trigger().impl()->attr.tree_cont; auto pnode = i._m_node(); if(tree.verify(pnode)) { diff --git a/source/gui/wvl.cpp b/source/gui/wvl.cpp index 33f027f3..820a8ba7 100644 --- a/source/gui/wvl.cpp +++ b/source/gui/wvl.cpp @@ -1,6 +1,6 @@ /* * Nana GUI Library Definition - * Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -28,6 +28,8 @@ namespace nana } } +#ifdef NANA_AUTOMATIC_GUI_TESTING + void click(widget& w) { std::cout << "Automatically clicking widget "<f // = {} ///< emit events to mimics user actions and may asert results ) { - #ifdef NANA_AUTOMATIC_GUI_TESTING std::cout << "Will wait " << wait << " sec...\n"; @@ -82,13 +88,12 @@ namespace nana pump(); if (t.joinable()) t.join(); - - #else - static_cast(wait); - static_cast(wait_end); - static_cast(f); //to eliminte unused parameter compiler warning. - - pump(); - #endif } +#else + void exec() + { + internal_scope_guard lock; + detail::bedrock::instance().pump_event(nullptr, false); + } +#endif }//end namespace nana diff --git a/source/internationalization.cpp b/source/internationalization.cpp index 345ae62c..87c92437 100644 --- a/source/internationalization.cpp +++ b/source/internationalization.cpp @@ -1,7 +1,7 @@ /* * An Implementation of i18n * Nana C++ Library(http://www.nanapro.org) -* Copyright(C) 2003-2014 Jinhao(cnjinhao@hotmail.com) +* Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -41,7 +41,7 @@ namespace nana class tokenizer { public: - tokenizer(const std::string& file) + tokenizer(const std::string& file, bool utf8) { std::ifstream ifs(file.data(), std::ios::binary); if (ifs) @@ -54,6 +54,11 @@ namespace nana data_.reset(new char[len]); ifs.read(data_.get(), len); read_ptr_ = data_.get(); + if (utf8 && len > 3) + { + if (static_cast(read_ptr_[0]) == 0xEF && static_cast(read_ptr_[1]) == 0xBB && static_cast(read_ptr_[2]) == 0xBF) + read_ptr_ += 3; + } end_ptr_ = read_ptr_ + len; } } @@ -78,6 +83,7 @@ namespace nana if (escape) { escape = false; + str_ += '\\'; str_ += *i; continue; } @@ -151,7 +157,44 @@ namespace nana struct data { + std::function on_missing; std::unordered_map table; + + data() + { + //initializes nana's translation + table["NANA_BUTTON_OK"] = "OK"; + table["NANA_BUTTON_OK_SHORTKEY"] = "&OK"; + table["NANA_BUTTON_YES"] = "Yes"; + table["NANA_BUTTON_NO"] = "No"; + table["NANA_BUTTON_BROWSE"] = "Browse"; + table["NANA_BUTTON_CANCEL"] = "Cancel"; + table["NANA_BUTTON_CANCEL_SHORTKEY"] = "&Cancel"; + table["NANA_BUTTON_CREATE"] = "Create"; + + table["NANA_FILEBOX_BYTES"] = "Bytes"; + table["NANA_FILEBOX_FILESYSTEM"] = "FILESYSTEM"; + table["NANA_FILEBOX_FILTER"] = "Filter"; + table["NANA_FILEBOX_NEW_FOLDER"] = "New Folder"; + table["NANA_FILEBOX_NEW_FOLDER_SHORTKEY"] = "&New Folder"; + table["NANA_FILEBOX_HEADER_NAME"] = "Name"; + table["NANA_FILEBOX_HEADER_MODIFIED"] = "Modified"; + table["NANA_FILEBOX_HEADER_TYPE"] = "Type"; + table["NANA_FILEBOX_HEADER_SIZE"] = "Size"; + table["NANA_FILEBOX_NEW_FOLDER_CAPTION"] = "Name the new folder"; + + table["NANA_FILEBOX_SAVE_AS"] = "Save As"; + table["NANA_FILEBOX_OPEN"] = "Open"; + table["NANA_FILEBOX_DIRECTORY"] = "Directory"; + table["NANA_FILEBOX_FILE"] = "File"; + table["NANA_FILEBOX_FILE_COLON"] = "File:"; + table["NANA_FILEBOX_ERROR_INVALID_FOLDER_NAME"] = "Please input a valid name for the new folder."; + table["NANA_FILEBOX_ERROR_RENAME_FOLDER_BECAUSE_OF_EXISTING"] = "The folder is existing, please rename it."; + table["NANA_FILEBOX_ERROR_RENAME_FOLDER_BECAUSE_OF_FAILED_CREATION"] = "Failed to create the folder, please rename it."; + table["NANA_FILEBOX_ERROR_INVALID_FILENAME"] = "The filename is invalid."; + table["NANA_FILEBOX_ERROR_NOT_EXISTING_AND_RETRY"] = "The file \"%arg0\"\n is not existing. Please check and retry."; + table["NANA_FILEBOX_ERROR_QUERY_REWRITE_BECAUSE_OF_EXISTING"] = "The input file is existing, do you want to overwrite it?"; + } }; static std::shared_ptr& get_data_ptr() @@ -167,7 +210,7 @@ namespace nana { auto impl = std::make_shared(); - tokenizer tknizer(file); + tokenizer tknizer(file, utf8); while (true) { if (token::msgid != tknizer.read()) @@ -188,9 +231,9 @@ namespace nana std::string str; if (utf8) - str = nana::charset(std::move(tknizer.get_str()), nana::unicode::utf8); + str = tknizer.get_str(); else - str = nana::charset(std::move(tknizer.get_str())); + str = nana::charset(std::move(tknizer.get_str())).to_bytes(nana::unicode::utf8); std::string::size_type pos = 0; while (true) @@ -293,6 +336,10 @@ namespace nana } }//end namespace internationalization_parts + void internationalization::set_missing(std::function handler) + { + internationalization_parts::get_data_ptr()->on_missing = std::move(handler); + } void internationalization::load(const std::string& file) { @@ -306,9 +353,8 @@ namespace nana std::string internationalization::get(std::string msgid) const { - std::string str; - if(_m_get(msgid, str)) - _m_replace_args(str, nullptr); + std::string str = _m_get(std::move(msgid)); + _m_replace_args(str, nullptr); return str; } @@ -318,18 +364,17 @@ namespace nana ptr->table[msgid].swap(msgstr); } - bool internationalization::_m_get(std::string& msgid, std::string& msgstr) const + std::string internationalization::_m_get(std::string&& msgid) const { auto & impl = internationalization_parts::get_data_ptr(); auto i = impl->table.find(msgid); if (i != impl->table.end()) - { - msgstr = i->second; - return true; - } + return i->second; - msgstr = nana::charset(std::move(msgid), nana::unicode::utf8); - return false; + if (impl->on_missing) + impl->on_missing(msgid); + + return std::move(msgid); } void internationalization::_m_replace_args(std::string& str, std::vector * arg_strs) const @@ -368,6 +413,50 @@ namespace nana offset += 4; } } + + void internationalization::_m_fetch_args(std::vector&) const + {} + + void internationalization::_m_fetch_args(std::vector& v, const char* arg) const + { + v.emplace_back(arg); + } + + void internationalization::_m_fetch_args(std::vector& v, const std::string& arg) const + { + v.emplace_back(arg); + } + + void internationalization::_m_fetch_args(std::vector& v, std::string& arg) const + { + v.emplace_back(arg); + } + + void internationalization::_m_fetch_args(std::vector& v, std::string&& arg) const + { + v.emplace_back(std::move(arg)); + } + + void internationalization::_m_fetch_args(std::vector& v, const wchar_t* arg) const + { + v.emplace_back(to_utf8(arg)); + } + + void internationalization::_m_fetch_args(std::vector& v, const std::wstring& arg) const + { + v.emplace_back(to_utf8(arg)); + } + + void internationalization::_m_fetch_args(std::vector& v, std::wstring& arg) const + { + v.emplace_back(to_utf8(arg)); + } + + void internationalization::_m_fetch_args(std::vector& v, std::wstring&& arg) const + { + v.emplace_back(to_utf8(arg)); + } + //end class internationalization @@ -462,10 +551,9 @@ namespace nana arg_strs.emplace_back(arg->eval()); internationalization i18n; - std::string msgid = msgid_; //msgid is required to be movable by i18n._m_get - std::string msgstr; - if (i18n._m_get(msgid, msgstr)) - i18n._m_replace_args(msgstr, &arg_strs); + + std::string msgstr = i18n._m_get(std::string{msgid_}); + i18n._m_replace_args(msgstr, &arg_strs); return msgstr; } diff --git a/source/paint/detail/image_bmp.hpp b/source/paint/detail/image_bmp.hpp index 23d7907e..e3893c61 100644 --- a/source/paint/detail/image_bmp.hpp +++ b/source/paint/detail/image_bmp.hpp @@ -1,7 +1,7 @@ /* * Bitmap Format Graphics Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -30,6 +30,15 @@ namespace nana{ namespace paint unsigned bfOffBits; } __attribute__((packed)); + struct bitmap_core_header + { + unsigned biSize; + unsigned short biWidth; + unsigned short biHeight; + unsigned short biPlanes; + unsigned short biBitCount; + } __attribute__((packed)); + struct bitmap_info_header { unsigned biSize; int biWidth; @@ -51,15 +60,10 @@ namespace nana{ namespace paint unsigned char rgbRed; unsigned char rgbReserved; }; - - struct bitmap_info - { - bitmap_info_header bmiHeader; - rgb_quad bmiColors[1]; - }__attribute__((packed)); #else - typedef BITMAPFILEHEADER bitmap_file_header; - typedef BITMAPINFO bitmap_info; + typedef BITMAPFILEHEADER bitmap_file_header; + typedef BITMAPCOREHEADER bitmap_core_header; + typedef BITMAPINFOHEADER bitmap_info_header; typedef RGBQUAD rgb_quad; #endif @@ -67,244 +71,42 @@ namespace nana{ namespace paint :public basic_image_pixbuf { public: - image_bmp(){} - ~image_bmp() { this->close(); } - bool open(const void* data, std::size_t bytes) override + bool open(const void* file_data, std::size_t bytes) override { - auto bmp_data = reinterpret_cast(data); - - auto header = reinterpret_cast(bmp_data); - if ((header->bfType != 0x4D42) || (header->bfSize != bytes)) + auto bmp_file = reinterpret_cast(file_data); + if ((bmp_file->bfType != 0x4D42) || (bmp_file->bfSize != bytes)) return false; - auto bits = reinterpret_cast(bmp_data + header->bfOffBits); - auto info = reinterpret_cast(header + 1); + auto const header_bytes = *reinterpret_cast(bmp_file + 1); + + //There are two kind of base headers. Determinate it by size of header(The first ulong of header). + //Only Windows Bitmap(BITMAPINFOHEADER) is supported. + if (sizeof(bitmap_core_header) == header_bytes) + { + //The OS/2 BITMAPCOREHEADER is not supported. + throw std::invalid_argument("BMP with OS/2 BITMAPCOREHEADER is not supported now."); + } + + auto header = reinterpret_cast(bmp_file + 1); + + const std::size_t bmp_height = std::abs(header->biHeight); //Bitmap file is 4byte-aligned for each line. - std::size_t bytes_per_line; - const std::size_t height_pixels = std::abs(info->bmiHeader.biHeight); - if (0 == info->bmiHeader.biSizeImage) - bytes_per_line = (((info->bmiHeader.biWidth * info->bmiHeader.biBitCount + 31) & ~31) >> 3); + auto bytes_per_line = (((header->biWidth * header->biBitCount + 31) & ~31) >> 3); + + pixbuf_.open(header->biWidth, bmp_height); + + auto bits = reinterpret_cast(reinterpret_cast(file_data) + bmp_file->bfOffBits); + + if (16 <= header->biBitCount) + pixbuf_.put(bits, header->biWidth, bmp_height, header->biBitCount, bytes_per_line, (header->biHeight < 0)); else - bytes_per_line = info->bmiHeader.biSizeImage / height_pixels; - - pixbuf_.open(info->bmiHeader.biWidth, height_pixels); - - auto d = pixbuf_.raw_ptr(0); - - if (16 <= info->bmiHeader.biBitCount) - { - pixbuf_.put(bits, info->bmiHeader.biWidth, height_pixels, info->bmiHeader.biBitCount, bytes_per_line, (info->bmiHeader.biHeight < 0)); - } - else if (8 == info->bmiHeader.biBitCount) - { - const auto lend = d + info->bmiHeader.biWidth * height_pixels; - - if (info->bmiHeader.biHeight < 0) - { - auto s = bits; - while (d < lend) - { - auto d_p = d; - auto dpend = d_p + info->bmiHeader.biWidth; - auto s_p = s; - while (d_p != dpend) - { - auto & rgb = info->bmiColors[*s_p++]; - d_p->element.red = rgb.rgbRed; - d_p->element.green = rgb.rgbGreen; - d_p->element.blue = rgb.rgbBlue; - d_p->element.alpha_channel = rgb.rgbReserved; - ++d_p; - } - d = dpend; - s += bytes_per_line; - } - } - else - { - const auto* s = bits + bytes_per_line * (height_pixels - 1); - while (d < lend) - { - auto d_p = d; - auto* const dpend = d_p + info->bmiHeader.biWidth; - const auto * s_p = s; - while (d_p != dpend) - { - auto & rgb = info->bmiColors[*s_p++]; - d_p->element.red = rgb.rgbRed; - d_p->element.green = rgb.rgbGreen; - d_p->element.blue = rgb.rgbBlue; - d_p->element.alpha_channel = rgb.rgbReserved; - ++d_p; - } - d = dpend; - s -= bytes_per_line; - } - } - } - else if (4 == info->bmiHeader.biBitCount) - { - const auto * const lend = d + info->bmiHeader.biWidth * height_pixels; - if (info->bmiHeader.biHeight < 0) - { - const unsigned char* s = bits; - while (d < lend) - { - auto d_p = d; - auto * const dpend = d_p + info->bmiHeader.biWidth; - unsigned index = 0; - while (d_p != dpend) - { - auto & rgb = info->bmiColors[(index & 1) ? (s[index >> 1] & 0xF) : (s[index >> 1] & 0xF0) >> 4]; - - d_p->element.red = rgb.rgbRed; - d_p->element.green = rgb.rgbGreen; - d_p->element.blue = rgb.rgbBlue; - d_p->element.alpha_channel = rgb.rgbReserved; - ++d_p; - ++index; - } - d = dpend; - s += bytes_per_line; - } - } - else - { - const auto* s = bits + bytes_per_line * (height_pixels - 1); - while (d < lend) - { - auto d_p = d; - auto * const dpend = d_p + info->bmiHeader.biWidth; - - unsigned index = 0; - while (d_p != dpend) - { - auto & rgb = info->bmiColors[(index & 1) ? (s[index >> 1] & 0xF) : (s[index >> 1] & 0xF0) >> 4]; - - d_p->element.red = rgb.rgbRed; - d_p->element.green = rgb.rgbGreen; - d_p->element.blue = rgb.rgbBlue; - d_p->element.alpha_channel = rgb.rgbReserved; - ++d_p; - ++index; - } - d = dpend; - s -= bytes_per_line; - } - } - } - else if (2 == info->bmiHeader.biBitCount) - { - const auto * const lend = d + info->bmiHeader.biWidth * height_pixels; - if (info->bmiHeader.biHeight < 0) - { - const unsigned char* s = bits; - while (d < lend) - { - auto d_p = d; - auto * const dpend = d_p + info->bmiHeader.biWidth; - unsigned index = 0; - while (d_p != dpend) - { - unsigned shift = (3 - (index & 0x3)) << 1; // (index % 4) * 2 - auto& rgb = info->bmiColors[(s[index >> 2] & (0x3 << shift)) >> shift]; - - d_p->element.red = rgb.rgbRed; - d_p->element.green = rgb.rgbGreen; - d_p->element.blue = rgb.rgbBlue; - d_p->element.alpha_channel = rgb.rgbReserved; - ++d_p; - ++index; - } - d = dpend; - s += bytes_per_line; - } - } - else - { - const auto* s = bits + bytes_per_line * (height_pixels - 1); - while (d < lend) - { - auto d_p = d; - auto * const dpend = d_p + info->bmiHeader.biWidth; - - unsigned index = 0; - while (d_p != dpend) - { - unsigned shift = (3 - (index & 0x3)) << 1; // (index % 4) * 2 - auto& rgb = info->bmiColors[(s[index >> 2] & (0x3 << shift)) >> shift]; - - d_p->element.red = rgb.rgbRed; - d_p->element.green = rgb.rgbGreen; - d_p->element.blue = rgb.rgbBlue; - d_p->element.alpha_channel = rgb.rgbReserved; - ++d_p; - ++index; - } - d = dpend; - s -= bytes_per_line; - } - } - } - else if (1 == info->bmiHeader.biBitCount) - { - const auto * const lend = d + info->bmiHeader.biWidth * height_pixels; - if (info->bmiHeader.biHeight < 0) - { - const auto* s = bits; - while (d < lend) - { - auto d_p = d; - auto * const dpend = d_p + info->bmiHeader.biWidth; - unsigned index = 0; - while (d_p != dpend) - { - unsigned bi = (7 - (index & 7)); //(index % 8) - auto & rgb = info->bmiColors[(s[index >> 3] & (1 << bi)) >> bi]; - - d_p->element.red = rgb.rgbRed; - d_p->element.green = rgb.rgbGreen; - d_p->element.blue = rgb.rgbBlue; - d_p->element.alpha_channel = rgb.rgbReserved; - ++d_p; - ++index; - } - d = dpend; - s += bytes_per_line; - } - } - else - { - const auto* s = bits + bytes_per_line * (height_pixels - 1); - while (d < lend) - { - auto d_p = d; - auto * const dpend = d_p + info->bmiHeader.biWidth; - - unsigned index = 0; - while (d_p != dpend) - { - unsigned bi = (7 - (index & 7)); - auto & rgb = info->bmiColors[(s[index >> 3] & (1 << bi)) >> bi]; - - d_p->element.red = rgb.rgbRed; - d_p->element.green = rgb.rgbGreen; - d_p->element.blue = rgb.rgbBlue; - d_p->element.alpha_channel = rgb.rgbReserved; - ++d_p; - ++index; - } - d = dpend; - s -= bytes_per_line; - } - } - } + _m_put_with_palette(header, bits, bytes_per_line); return true; } @@ -312,20 +114,15 @@ namespace nana{ namespace paint bool open(const std::experimental::filesystem::path& filename) override { std::ifstream ifs(filename.string(), std::ios::binary); - if(ifs) + + auto const bytes = static_cast(std::experimental::filesystem::file_size(filename)); + if (ifs && (bytes > static_cast(sizeof(bitmap_file_header)))) { - ifs.seekg(0, std::ios::end); - auto size = static_cast(ifs.tellg()); - ifs.seekg(0, std::ios::beg); + std::unique_ptr buffer{ new char[bytes] }; - if(size <= static_cast(sizeof(bitmap_file_header))) - return false; - - std::unique_ptr buffer(new char[static_cast(size)]); - - ifs.read(buffer.get(), size); - if (size == static_cast(ifs.gcount())) - return open(buffer.get(), size); + ifs.read(buffer.get(), bytes); + if (bytes == static_cast(ifs.gcount())) + return open(buffer.get(), bytes); } return false; } @@ -334,6 +131,96 @@ namespace nana{ namespace paint { return false; } + private: + void _m_put_with_palette(const bitmap_info_header* header, const unsigned char* pixel_indexes, unsigned line_bytes) + { + auto const image_height = std::abs(header->biHeight); + const std::size_t total_pixels = header->biWidth * static_cast(image_height); + + auto const color_table = reinterpret_cast(reinterpret_cast(header) + header->biSize); + + auto dst_px = pixbuf_.raw_ptr(0); + auto const end_dst_px = dst_px + total_pixels; + + int line_pos = image_height - 1; + int delta = -1; + if (header->biHeight < 0) + { + line_pos = 0; + delta = 1; + } + + if (8 == header->biBitCount) + { + while (dst_px < end_dst_px) + { + auto px_indexes = pixel_indexes + line_bytes * line_pos; + auto const line_end_dst_px = dst_px + header->biWidth; + while (dst_px != line_end_dst_px) + { + auto & rgb = color_table[*px_indexes++]; + dst_px->element.red = rgb.rgbRed; + dst_px->element.green = rgb.rgbGreen; + dst_px->element.blue = rgb.rgbBlue; + dst_px->element.alpha_channel = rgb.rgbReserved; + + ++dst_px; + } + line_pos += delta; + } + } + else + { + while (dst_px < end_dst_px) + { + auto px_indexes = pixel_indexes + line_bytes * line_pos; + auto const line_end_dst_px = dst_px + header->biWidth; + std::size_t pos = 0; + switch (header->biBitCount) + { + case 4: + while (dst_px != line_end_dst_px) + { + auto & rgb = color_table[((pos & 1) ? px_indexes[pos >> 1] : (px_indexes[pos >> 1] >> 4)) & 0xF]; + dst_px->element.red = rgb.rgbRed; + dst_px->element.green = rgb.rgbGreen; + dst_px->element.blue = rgb.rgbBlue; + dst_px->element.alpha_channel = rgb.rgbReserved; + ++dst_px; + ++pos; + } + break; + case 2: + while (dst_px != line_end_dst_px) + { + //auto const shift = ((3 - (pos & 0x3)) << 1); // (index % 4) * 2 + auto& rgb = color_table[(px_indexes[pos >> 2] >> ((3 - (pos & 0x3)) << 1)) & 0x3]; + dst_px->element.red = rgb.rgbRed; + dst_px->element.green = rgb.rgbGreen; + dst_px->element.blue = rgb.rgbBlue; + dst_px->element.alpha_channel = rgb.rgbReserved; + ++dst_px; + ++pos; + } + break; + case 1: + while (dst_px != line_end_dst_px) + { + auto & rgb = color_table[(px_indexes[pos >> 3] >> (7 - (pos & 7))) & 1]; + + dst_px->element.red = rgb.rgbRed; + dst_px->element.green = rgb.rgbGreen; + dst_px->element.blue = rgb.rgbBlue; + dst_px->element.alpha_channel = rgb.rgbReserved; + ++dst_px; + ++pos; + } + break; + } + line_pos += delta; + } + } + } };//end class bmpfile }//end namespace detail }//end namespace paint diff --git a/source/paint/detail/image_ico.hpp b/source/paint/detail/image_ico.hpp index 44b49191..4f78cad6 100644 --- a/source/paint/detail/image_ico.hpp +++ b/source/paint/detail/image_ico.hpp @@ -1,44 +1,301 @@ #ifndef NANA_PAINT_DETAIL_IMAGE_ICO_HPP #define NANA_PAINT_DETAIL_IMAGE_ICO_HPP -#include -#include +#include "image_pixbuf.hpp" +#include -namespace nana{ namespace paint +#if defined(NANA_WINDOWS) +# include +#endif + +namespace nana { +namespace paint { +namespace detail { +// These next two structs represent how the icon information is stored +// in an ICO file. +typedef struct { - namespace detail + std::uint8_t bWidth; // Width of the image + std::uint8_t bHeight; // Height of the image (times 2) + std::uint8_t bColorCount; // Number of colors in image (0 if >=8bpp) + std::uint8_t bReserved; // Reserved + std::uint16_t wPlanes; // Color Planes + std::uint16_t wBitCount; // Bits per pixel + std::uint32_t dwBytesInRes; // how many bytes in this resource? + std::uint32_t dwImageOffset; // where in the file is this image +} ICONDIRENTRY, *LPICONDIRENTRY; + +typedef struct +{ + std::uint16_t idReserved; // Reserved + std::uint16_t idType; // resource type (1 for icons) + std::uint16_t idCount; // how many images? + //ICONDIRENTRY idEntries[1]; // the entries for each image +} ICONDIR, *LPICONDIR; + +// size - 40 bytes +typedef struct +{ + std::uint32_t biSize; + std::uint32_t biWidth; + std::uint32_t biHeight; // Icon Height (added height of XOR-Bitmap and AND-Bitmap) + std::uint16_t biPlanes; + std::uint16_t biBitCount; + std::uint32_t biCompression; + std::int32_t biSizeImage; + std::uint32_t biXPelsPerMeter; + std::uint32_t biYPelsPerMeter; + std::uint32_t biClrUsed; + std::uint32_t biClrImportant; +} s_BITMAPINFOHEADER, *s_PBITMAPINFOHEADER; + +// 46 bytes +typedef struct +{ + s_BITMAPINFOHEADER icHeader; // DIB header + std::uint32_t icColors[1]; // Color table (short 4 bytes) //RGBQUAD + std::uint8_t icXOR[1]; // DIB bits for XOR mask + std::uint8_t icAND[1]; // DIB bits for AND mask +} ICONIMAGE, *LPICONIMAGE; + + +class image_ico + : public basic_image_pixbuf +{ + bool _m_read_ico(const void* data, std::size_t /*size*/) { - class image_ico - :public image::image_impl_interface + auto width = 0; + auto height = 0; + auto buffer = (std::uint8_t *)data; + auto icoDir = reinterpret_cast(buffer); + int iconsCount = icoDir->idCount; + if (icoDir->idReserved != 0 || icoDir->idType != 1 || iconsCount == 0 || iconsCount > 20) + return false; + + auto cursor = buffer; + cursor += 6; + auto dirEntry = reinterpret_cast(cursor); + auto maxSize = 0; + auto offset = 0; + auto maxBitCount = 0; + for (auto i = 0; i < iconsCount; i++, ++dirEntry) + { + int w = dirEntry->bWidth; + int h = dirEntry->bHeight; + int bitCount = dirEntry->wBitCount; + if (w * h > maxSize || bitCount > maxBitCount) // we choose icon with max resolution + { + width = w; + height = h; + offset = dirEntry->dwImageOffset; + maxSize = w * h; + maxBitCount = bitCount; + } + } + + if (offset == 0) return false; + + cursor = buffer; + cursor += offset; + auto icon = reinterpret_cast(cursor); + auto realBitsCount = static_cast(icon->icHeader.biBitCount); + auto hasAndMask = (realBitsCount < 32) && (height != static_cast(icon->icHeader.biHeight)); + cursor += 40; + pixbuf_.open(width, height); + + // rgba + vertical swap + if (realBitsCount >= 32) + { + for (auto x = 0; x < width; ++x) + for (auto y = 0; y < height; ++y) + { + pixbuf_.alpha_channel(true); + auto shift2 = 4 * (x + (height - y - 1) * width); + pixel_color_t image; + image.element.red = cursor[shift2 + 2]; + image.element.green = cursor[shift2 + 1]; + image.element.blue = cursor[shift2]; + image.element.alpha_channel = cursor[shift2 + 3]; + pixbuf_.pixel(x, y, image); + } + } + + else if (realBitsCount == 24) + { + for (auto x = 0; x < width; x++) + for (auto y = 0; y < height; y++) + { + pixbuf_.alpha_channel(true); + auto shift2 = 3 * (x + (height - y - 1) * width); + pixel_color_t image; + image.element.red = cursor[shift2 + 2]; + image.element.green = cursor[shift2 + 1]; + image.element.blue = cursor[shift2]; + image.element.alpha_channel = 255; + pixbuf_.pixel(x, y, image); + } + } + + else if (realBitsCount == 8) /// 256 colors + { + // 256 color table + auto colors = reinterpret_cast(cursor); + cursor += 256 * 4; + for (auto x = 0; x < width; x++) + for (auto y = 0; y < height; y++) + { + pixbuf_.alpha_channel(true); + + auto shift2 = (x + (height - y - 1) * width); + auto index = 4 * cursor[shift2]; + pixel_color_t image; + image.element.red = colors[index + 2]; + image.element.green = colors[index + 1]; + image.element.blue = colors[index]; + image.element.alpha_channel = 255; + pixbuf_.pixel(x, y, image); + } + } + + else if (realBitsCount == 4) /// 16 colors + { + // 16 color table + auto colors = reinterpret_cast(cursor); + cursor += 16 * 4; + for (auto x = 0; x < width; x++) + for (auto y = 0; y < height; y++) + { + auto shift2 = (x + (height - y - 1) * width); + auto index = cursor[shift2 / 2]; + if (shift2 % 2 == 0) + index = (index >> 4) & 0xF; + else + index = index & 0xF; + index *= 4; + pixbuf_.alpha_channel(true); + pixel_color_t image; + image.element.red = colors[index + 2]; + image.element.green = colors[index + 1]; + image.element.blue = colors[index]; + image.element.alpha_channel = 255; + pixbuf_.pixel(x, y, image); + } + } + + else if (realBitsCount == 1) /// 2 colors + { + // 2 color table + auto colors = reinterpret_cast(cursor); + cursor += 2 * 4; + auto boundary = width; //!!! 32 bit boundary (http://www.daubnet.com/en/file-format-ico) + while (boundary % 32 != 0) boundary++; + for (auto x = 0; x < width; x++) + for (auto y = 0; y < height; y++) + { + auto shift2 = (x + (height - y - 1) * boundary); + auto index = cursor[shift2 / 8]; + + // select 1 bit only + unsigned char bit = 7 - (x % 8); + index = (index >> bit) & 0x01; + index *= 4; + pixbuf_.alpha_channel(true); + pixel_color_t image; + image.element.red = colors[index + 2]; + image.element.green = colors[index + 1]; + image.element.blue = colors[index]; + image.element.alpha_channel = 255; + pixbuf_.pixel(x, y, image); + } + } + + // Read AND mask after base color data - 1 BIT MASK + if (hasAndMask) + { + auto boundary = width * realBitsCount; //!!! 32 bit boundary (http://www.daubnet.com/en/file-format-ico) + while (boundary % 32 != 0) boundary++; + cursor += boundary * height / 8; + boundary = width; + while (boundary % 32 != 0) boundary++; + for (auto y = 0; y < height; y++) + for (auto x = 0; x < width; x++) + { + unsigned char bit = 7 - (x % 8); + auto shift2 = (x + (height - y - 1) * boundary) / 8; + auto mask = (0x01 & (static_cast(cursor[shift2]) >> bit)); + auto pc = pixbuf_.pixel(x, y); + auto alpha = pc.element.alpha_channel; + alpha *= 1 - mask; + pc.element.alpha_channel = alpha; + pixbuf_.pixel(x, y, pc); + } + } + return true; + } +public: + ~image_ico() + { +#if defined(NANA_WINDOWS) + if (native_handle_) + ::DestroyIcon(reinterpret_cast(native_handle_)); +#endif + } + + bool open(const std::experimental::filesystem::path& ico_file) override + { + std::ifstream file(ico_file.string(), std::ios::binary); + if (!file.is_open()) return false; + + // allocates a buffer for the image + file.seekg(0, std::ios::end); + const auto bytes = static_cast(file.tellg()); + file.seekg(0, std::ios::beg); + auto buffer = new char[bytes]; + + // read data from the file and set them in the buffer + file.read(buffer, bytes); + auto okret = _m_read_ico(buffer, bytes); + + // delete buffer and return + delete[] buffer; + + if (okret) + path_ = ico_file; + + return okret; + } + + bool open(const void* data, std::size_t bytes) override + { + if (_m_read_ico(data, bytes)) { #if defined(NANA_WINDOWS) - struct handle_deleter - { - void operator()(HICON* handle) const; - };//end struct handle_deleter - typedef std::shared_ptr ptr_t; -#else - typedef std::shared_ptr ptr_t; + native_handle_ = ::CreateIconFromResourceEx(reinterpret_cast(const_cast(data)), static_cast(bytes), TRUE, 0x00030000, 0, 0, LR_DEFAULTCOLOR); #endif - public: - image_ico(bool is_ico); - - - bool open(const std::experimental::filesystem::path& filename) override; - bool open(const void* data, std::size_t bytes) override; - bool alpha_channel() const override; - bool empty() const override; - void close() override; - nana::size size() const override; - virtual void paste(const nana::rectangle& src_r, graph_reference graph, const point& p_dst) const override; - virtual void stretch(const nana::rectangle&, graph_reference graph, const nana::rectangle& r) const override; - const ptr_t & ptr() const; - private: - const bool is_ico_; - nana::size size_; - ptr_t ptr_; - };//end class image_ico + return true; + } + return false; } + + void* native_handle() + { +#if defined(NANA_WINDOWS) + if (native_handle_) + return native_handle_; + + native_handle_ = ::LoadImage(nullptr, path_.c_str(), IMAGE_ICON, 0, 0, LR_LOADFROMFILE); + return native_handle_; +#else + return nullptr; +#endif + } +private: + std::experimental::filesystem::path path_; +#if defined(NANA_WINDOWS) + void* native_handle_{nullptr}; +#endif +}; +}//end namespace detail }//end namespace paint }//end namespace nana diff --git a/source/paint/detail/image_ico_resource.hpp b/source/paint/detail/image_ico_resource.hpp new file mode 100644 index 00000000..dd451bf0 --- /dev/null +++ b/source/paint/detail/image_ico_resource.hpp @@ -0,0 +1,127 @@ +/* + * Icon Resource + * Nana C++ Library(http://www.nanapro.org) + * Copyright(C) 2017 Jinhao(cnjinhao@hotmail.com) + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * @file: nana/paint/detail/image_ico_resource.hpp + */ + +#ifndef NANA_PAINT_DETAIL_IMAGE_ICO_RESOURCE_INCLUDED +#define NANA_PAINT_DETAIL_IMAGE_ICO_RESOURCE_INCLUDED + +#include +#include +#include + +#if defined(NANA_WINDOWS) +# include +#endif + +namespace nana{ namespace paint +{ + namespace detail + { + class image_ico_resource + :public image::image_impl_interface + { + public: + bool open(const std::experimental::filesystem::path& filename) override + { +#if defined(NANA_WINDOWS) + SHFILEINFO sfi; + ::SHGetFileInfo(filename.c_str(), 0, &sfi, sizeof sfi, SHGFI_ICON); + + native_handle_ = sfi.hIcon; +#else + static_cast(filename); //eliminate unused parameter compiler warnings +#endif + return (nullptr != native_handle_); + } + + bool open(const void* /*data*/, std::size_t /*bytes*/) override + { + return false; + } + + bool alpha_channel() const override + { + return false; + } + + bool empty() const override + { + return !native_handle_; + } + + void close() override + { +#if defined(NANA_WINDOWS) + if (native_handle_) + ::DestroyIcon(reinterpret_cast(native_handle_)); +#endif + } + + nana::size size() const override + { +#if defined(NANA_WINDOWS) + ICONINFO info; + if ((!native_handle_) || !::GetIconInfo(reinterpret_cast(native_handle_), &info)) + return{}; + + ::nana::size sz{ + info.xHotspot << 1, + info.yHotspot << 1 + }; + + ::DeleteObject(info.hbmColor); + ::DeleteObject(info.hbmMask); + return sz; +#else + return{}; +#endif + } + + virtual void paste(const nana::rectangle& src_r, graph_reference graph, const point& p_dst) const override + { + if (!native_handle_) + return; + +#if defined(NANA_WINDOWS) + ::DrawIconEx(graph.handle()->context, p_dst.x, p_dst.y, reinterpret_cast(native_handle_), src_r.width, src_r.height, 0, 0, DI_NORMAL); +#else + static_cast(src_r); //eliminate unused parameter compiler warning. + static_cast(graph); + static_cast(p_dst); +#endif + + } + + virtual void stretch(const nana::rectangle&, graph_reference graph, const nana::rectangle& r) const override + { + if (!native_handle_) + return; + +#if defined(NANA_WINDOWS) + ::DrawIconEx(graph.handle()->context, r.x, r.y, reinterpret_cast(native_handle_), r.width, r.height, 0, 0, DI_NORMAL); +#else + static_cast(graph); //eliminate unused parameter compiler warning. + static_cast(r); +#endif + } + + void* native_handle() + { + return native_handle_; + } + private: + void* native_handle_; + };//end class image_ico + } +}//end namespace paint +}//end namespace nana + +#endif diff --git a/source/paint/detail/image_png.hpp b/source/paint/detail/image_png.hpp index 30ba18e5..f3cff150 100644 --- a/source/paint/detail/image_png.hpp +++ b/source/paint/detail/image_png.hpp @@ -2,6 +2,7 @@ #define NANA_PAINT_DETAIL_IMAGE_PNG_HPP #include "image_pixbuf.hpp" +#include //Separate the libpng from the package that system provides. #if defined(NANA_LIBPNG) @@ -12,6 +13,7 @@ #include + namespace nana { namespace paint{ namespace detail{ diff --git a/source/paint/detail/native_paint_interface.cpp b/source/paint/detail/native_paint_interface.cpp index 4d2caf0d..6efb8169 100644 --- a/source/paint/detail/native_paint_interface.cpp +++ b/source/paint/detail/native_paint_interface.cpp @@ -1,7 +1,7 @@ /* * Platform Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -11,7 +11,7 @@ * @contributors: dareg */ -#include +#include "../../detail/platform_spec_selector.hpp" #include #include #include @@ -100,18 +100,23 @@ namespace detail void blend(drawable_type dw, const rectangle& area, pixel_color_t color, double fade_rate) { - if (fade_rate <= 0) return; - if (fade_rate > 1) fade_rate = 1; + if (fade_rate <= 0) + return; + else if (fade_rate >= 1) + fade_rate = 1; rectangle r; if (false == ::nana::overlap(rectangle{ drawable_size(dw) }, area, r)) return; - unsigned red = static_cast((color.value & 0xFF0000) * fade_rate); - unsigned green = static_cast((color.value & 0xFF00) * fade_rate); - unsigned blue = static_cast((color.value & 0xFF) * fade_rate); + auto const color_fd_rate = (double(color.element.alpha_channel) / 255.0) * fade_rate; + + fade_rate = (1 - color_fd_rate); + + unsigned red = static_cast((color.value & 0xFF0000) * color_fd_rate); + unsigned green = static_cast((color.value & 0xFF00) * color_fd_rate); + unsigned blue = static_cast((color.value & 0xFF) * color_fd_rate); - double lrate = 1 - fade_rate; pixel_buffer pixbuf(dw, r.y, r.height); for (std::size_t row = 0; row < r.height; ++row) @@ -120,9 +125,9 @@ namespace detail const auto end = i + r.width; for (; i < end; ++i) { - unsigned px_r = ((static_cast((i->value & 0xFF0000) * lrate) + red) & 0xFF0000); - unsigned px_g = ((static_cast((i->value & 0xFF00) * lrate) + green) & 0xFF00); - unsigned px_b = ((static_cast((i->value & 0xFF) * lrate) + blue) & 0xFF); + unsigned px_r = ((static_cast((i->value & 0xFF0000) * fade_rate) + red) & 0xFF0000); + unsigned px_g = ((static_cast((i->value & 0xFF00) * fade_rate) + green) & 0xFF00); + unsigned px_b = ((static_cast((i->value & 0xFF) * fade_rate) + blue) & 0xFF); i->value = (px_r | px_g | px_b); } } @@ -140,14 +145,14 @@ namespace detail #if defined(NANA_USE_XFT) std::string utf8str = to_utf8(std::wstring(text, len)); XGlyphInfo ext; - XftFont * fs = reinterpret_cast(dw->font->handle); + XftFont * fs = reinterpret_cast(dw->font->native_handle()); ::XftTextExtentsUtf8(nana::detail::platform_spec::instance().open_display(), fs, reinterpret_cast(const_cast(utf8str.c_str())), utf8str.size(), &ext); return nana::size(ext.xOff, fs->ascent + fs->descent); #else XRectangle ink; XRectangle logic; - ::XmbTextExtents(reinterpret_cast(dw->font->handle), text, len, &ink, &logic); + ::XmbTextExtents(reinterpret_cast(dw->font->native_handle()), text, len, &ink, &logic); return nana::size(logic.width, logic.height); #endif #endif @@ -180,7 +185,7 @@ namespace detail #elif defined(NANA_X11) auto disp = ::nana::detail::platform_spec::instance().open_display(); #if defined(NANA_USE_XFT) - auto fs = reinterpret_cast(dw->font->handle); + auto fs = reinterpret_cast(dw->font->native_handle()); //Fixed missing array declaration by dareg std::unique_ptr glyphs_ptr(new FT_UInt[len]); @@ -192,7 +197,7 @@ namespace detail } XftDrawGlyphs(dw->xftdraw, &(dw->xft_fgcolor), fs, pos.x, pos.y + fs->ascent, glyphs_ptr.get(), len); #else - XFontSet fs = reinterpret_cast(dw->font->handle); + XFontSet fs = reinterpret_cast(dw->font->native_handle()); XFontSetExtents * ext = ::XExtentsOfFontSet(fs); XFontStruct ** fontstructs; char ** font_names; diff --git a/source/paint/graphics.cpp b/source/paint/graphics.cpp index aa606f4e..b3bedd40 100644 --- a/source/paint/graphics.cpp +++ b/source/paint/graphics.cpp @@ -1,7 +1,7 @@ /* * Paint Graphics Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -10,7 +10,7 @@ * @file: nana/paint/graphics.cpp */ -#include +#include "../detail/platform_spec_selector.hpp" #include #include #include @@ -24,8 +24,19 @@ #include #endif +#include "../detail/platform_abstraction.hpp" + namespace nana { + namespace detail + { + font_style::font_style(unsigned weight, bool italic, bool underline, bool strike_out) : + weight(weight), + italic(italic), + underline(underline), + strike_out(strike_out) + {} + } namespace paint { namespace detail @@ -56,33 +67,39 @@ namespace paint //class font struct font::impl_type { - typedef nana::detail::platform_spec::font_ptr_t ptr_t; - ptr_t font_ptr; + std::shared_ptr real_font; }; font::font() : impl_(new impl_type) { - impl_->font_ptr = nana::detail::platform_spec::instance().default_native_font(); + impl_->real_font = platform_abstraction::default_font(nullptr); } font::font(drawable_type dw) : impl_(new impl_type) { - impl_->font_ptr = dw->font; + impl_->real_font = dw->font; } font::font(const font& rhs) : impl_(new impl_type) { if(rhs.impl_) - impl_->font_ptr = rhs.impl_->font_ptr; + impl_->real_font = rhs.impl_->real_font; } - font::font(const std::string& name, unsigned size, bool bold, bool italic, bool underline, bool strike_out) - : impl_(new impl_type) + font::font(const std::string& font_family, double size_pt, const font_style& fs): + impl_(new impl_type) { - make(name, size, bold, italic, underline, strike_out); + impl_->real_font = platform_abstraction::make_font(font_family, size_pt, fs); + } + + + font::font(double size_pt, const path_type& truetype, const font_style& fs) : + impl_(new impl_type) + { + impl_->real_font = platform_abstraction::make_font_from_ttf(truetype, size_pt, fs); } font::~font() @@ -92,23 +109,7 @@ namespace paint bool font::empty() const { - return ((nullptr == impl_) || (nullptr == impl_->font_ptr)); - } - - void font::make(const std::string& name, unsigned size, bool bold, bool italic, bool underline, bool strike_out) - { - size = nana::detail::platform_spec::instance().font_size_to_height(size); - make_raw(name, size, bold ? 700 : 400, italic, underline, strike_out); - } - - void font::make_raw(const std::string& name, unsigned height, unsigned weight, bool italic, bool underline, bool strike_out) - { - if(impl_) - { - auto t = nana::detail::platform_spec::instance().make_native_font(name.c_str(), height, weight, italic, underline, strike_out); - if(t) - impl_->font_ptr = t; - } + return ((nullptr == impl_) || (nullptr == impl_->real_font)); } void font::set_default() const @@ -116,62 +117,69 @@ namespace paint if(empty()) return; - nana::detail::platform_spec::instance().default_native_font(impl_->font_ptr); + platform_abstraction::default_font(impl_->real_font); } std::string font::name() const { if(empty()) return std::string(); - return to_utf8(impl_->font_ptr->name); + return impl_->real_font->family(); } - unsigned font::size() const + double font::size() const { if(empty()) return 0; - return nana::detail::platform_spec::instance().font_height_to_size(impl_->font_ptr->height); + + return impl_->real_font->size(); } bool font::bold() const { if(empty()) return false; - return (impl_->font_ptr->weight >= 700); - } - - unsigned font::height() const - { - if(empty()) return 0; - return (impl_->font_ptr->height); + return (impl_->real_font->style().weight >= 700); } unsigned font::weight() const { if(empty()) return 0; - return (impl_->font_ptr->weight); + return impl_->real_font->style().weight; } bool font::italic() const { if(empty()) return false; - return (impl_->font_ptr->italic); + return impl_->real_font->style().italic; + } + + bool font::underline() const + { + if(empty()) return false; + return impl_->real_font->style().underline; + } + + bool font::strikeout() const + { + if(empty()) return false; + return impl_->real_font->style().strike_out; } native_font_type font::handle() const { if(empty()) return nullptr; - return reinterpret_cast(impl_->font_ptr->handle); + return impl_->real_font->native_handle(); } void font::release() { if(impl_) - impl_->font_ptr.reset(); + impl_->real_font.reset(); } font & font::operator=(const font& rhs) { if(impl_ && rhs.impl_ && (this != &rhs)) - impl_->font_ptr = rhs.impl_->font_ptr; + impl_->real_font = rhs.impl_->real_font; return *this; } @@ -179,7 +187,7 @@ namespace paint bool font::operator==(const font& rhs) const { if(empty() == rhs.empty()) - return (empty() || (impl_->font_ptr->handle == rhs.impl_->font_ptr->handle)); + return (empty() || (impl_->real_font == rhs.impl_->real_font)); return false; } @@ -187,7 +195,7 @@ namespace paint bool font::operator!=(const font& rhs) const { if(empty() == rhs.empty()) - return ((empty() == false) && (impl_->font_ptr->handle != rhs.impl_->font_ptr->handle)); + return ((empty() == false) && (impl_->real_font != rhs.impl_->real_font)); return true; } @@ -293,7 +301,7 @@ namespace paint dw->string.tab_length = reuse->string.tab_length; } else - dw->font = impl_->font_shadow.impl_->font_ptr; + dw->font = impl_->font_shadow.impl_->real_font; #if defined(NANA_WINDOWS) HDC hdc = ::GetDC(0); @@ -313,7 +321,7 @@ namespace paint if(bmp) { ::DeleteObject((HBITMAP)::SelectObject(cdc, bmp)); - ::DeleteObject(::SelectObject(cdc, dw->font->handle)); + ::DeleteObject(::SelectObject(cdc, dw->font->native_handle())); dw->context = cdc; dw->pixmap = bmp; @@ -376,9 +384,9 @@ namespace paint impl_->font_shadow = f; if(impl_->handle && (false == f.empty())) { - impl_->handle->font = f.impl_->font_ptr; + impl_->handle->font = f.impl_->real_font; #if defined(NANA_WINDOWS) - ::SelectObject(impl_->handle->context, reinterpret_cast(f.impl_->font_ptr->handle)); + ::SelectObject(impl_->handle->context, reinterpret_cast(f.impl_->real_font->native_handle())); #endif impl_->handle->string.tab_pixels = detail::raw_text_extent_size(impl_->handle, L"\t", 1).width; impl_->handle->string.whitespace_pixels = detail::raw_text_extent_size(impl_->handle, L" ", 1).width; @@ -477,8 +485,8 @@ namespace paint delete [] dx; #elif defined(NANA_X11) && defined(NANA_USE_XFT) - Display * disp = nana::detail::platform_spec::instance().open_display(); - XftFont * xft = impl_->handle->font->handle; + auto disp = nana::detail::platform_spec::instance().open_display(); + auto xft = reinterpret_cast(impl_->handle->font->native_handle()); XGlyphInfo extents; for(std::size_t i = 0; i < len; ++i) @@ -535,12 +543,12 @@ namespace paint if(impl_->handle->font) { #if defined(NANA_USE_XFT) - XftFont * fs = reinterpret_cast(impl_->handle->font->handle); + auto fs = reinterpret_cast(impl_->handle->font->native_handle()); ascent = fs->ascent; descent = fs->descent; internal_leading = 0; #else - XFontSet fs = reinterpret_cast(impl_->handle->font->handle); + auto fs = reinterpret_cast(impl_->handle->font->native_handle()); XFontSetExtents * ext = ::XExtentsOfFontSet(fs); XFontStruct ** fontstructs; char ** font_names; @@ -647,16 +655,27 @@ namespace paint } } - void graphics::blend(const nana::rectangle& s_r, graphics& dst, const nana::point& d_pos, double fade_rate) const + void graphics::blend(const nana::rectangle& r, const ::nana::color& clr, double fade_rate) { - if(dst.impl_->handle && impl_->handle && (dst.impl_->handle != impl_->handle)) + if (impl_->handle) { - pixel_buffer s_pixbuf; - s_pixbuf.attach(impl_->handle, ::nana::rectangle{ size() }); + nana::paint::detail::blend(impl_->handle, r, clr.px_color(), fade_rate); + if (impl_->changed == false) impl_->changed = true; + } + } - s_pixbuf.blend(s_r, dst.impl_->handle, d_pos, fade_rate); + void graphics::blend(const ::nana::rectangle& blend_r, const graphics& graph, const point& blend_graph_point, double fade_rate)///< blends with the blend_graph. + { + if (graph.impl_->handle && impl_->handle && (graph.impl_->handle != impl_->handle)) + { + pixel_buffer graph_px; - if(dst.impl_->changed == false) dst.impl_->changed = true; + nana::rectangle r{ blend_graph_point, blend_r.dimension() }; + + graph_px.attach(graph.impl_->handle, r); + graph_px.blend(r, impl_->handle, blend_r.position(), 1 - fade_rate); + + if (graph.impl_->changed == false) graph.impl_->changed = true; } } @@ -765,12 +784,22 @@ namespace paint ::ReleaseDC(reinterpret_cast(dst), dc); } #elif defined(NANA_X11) - Display * display = nana::detail::platform_spec::instance().open_display(); + auto & spec = nana::detail::platform_spec::instance(); + + Display * display = spec.open_display(); + + nana::detail::platform_scope_guard lock; + ::XCopyArea(display, impl_->handle->pixmap, reinterpret_cast(dst), impl_->handle->context, sx, sy, width, height, dx, dy); - ::XMapWindow(display, reinterpret_cast(dst)); + XWindowAttributes attr; + spec.set_error_handler(); + ::XGetWindowAttributes(display, reinterpret_cast(dst), &attr); + if(BadWindow != spec.rev_error_handler() && attr.map_state != IsUnmapped) + ::XMapWindow(display, reinterpret_cast(dst)); + ::XFlush(display); #endif } @@ -946,15 +975,6 @@ namespace paint return bidi_string(pos, wstr.data(), wstr.size()); } - void graphics::blend(const nana::rectangle& r, const ::nana::color& clr, double fade_rate) - { - if (impl_->handle) - { - nana::paint::detail::blend(impl_->handle, r, clr.px_color(), fade_rate); - if (impl_->changed == false) impl_->changed = true; - } - } - void graphics::set_pixel(int x, int y, const ::nana::color& clr) { if (impl_->handle) diff --git a/source/paint/image.cpp b/source/paint/image.cpp index 498dd944..1781a657 100644 --- a/source/paint/image.cpp +++ b/source/paint/image.cpp @@ -1,7 +1,7 @@ /* * Paint Image Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -11,8 +11,9 @@ * @contributors: * nabijaczleweli(pr#106) */ + #include -#include +#include "../detail/platform_spec_selector.hpp" #include #include #include @@ -32,9 +33,10 @@ #endif #include "detail/image_bmp.hpp" -#include "detail/image_ico.hpp" #include "image_accessor.hpp" +#include "detail/image_ico_resource.hpp" +#include "detail/image_ico.hpp" namespace fs = std::experimental::filesystem; @@ -45,9 +47,14 @@ namespace paint #if defined(NANA_WINDOWS) HICON image_accessor::icon(const nana::paint::image& img) { + auto ico_res = dynamic_cast(img.image_ptr_.get()); + if (ico_res) + return reinterpret_cast(ico_res->native_handle()); + auto ico = dynamic_cast(img.image_ptr_.get()); - if (ico && ico->ptr()) - return *(ico->ptr()); + if (ico) + return reinterpret_cast(ico->native_handle()); + return nullptr; } #else @@ -57,138 +64,6 @@ namespace paint } #endif - namespace detail - { - //class image_ico - image_ico::image_ico(bool is_ico): is_ico_(is_ico){} - - bool image_ico::open(const fs::path& file) - { - close(); -#if defined(NANA_WINDOWS) - HICON handle = 0; - if(is_ico_) - { - handle = (HICON)::LoadImage(0, file.c_str(), IMAGE_ICON, 0, 0, LR_LOADFROMFILE); - } - else - { - SHFILEINFO sfi; - ::SHGetFileInfo(file.c_str(), 0, &sfi, sizeof(sfi), SHGFI_ICON); - handle = sfi.hIcon; - } - - if(handle) - { - HICON * p = new HICON(handle); - ptr_ = std::shared_ptr(p, handle_deleter()); - ICONINFO info; - ::GetIconInfo(handle, &info); - size_.width = (info.xHotspot << 1); - size_.height = (info.yHotspot << 1); - - ::DeleteObject(info.hbmColor); - ::DeleteObject(info.hbmMask); - return true; - } -#else - static_cast(is_ico_); //eliminate the unused compiler warning in Linux. - static_cast(file); //to eliminate the unused parameter compiler warning. -#endif - return false; - } - - bool image_ico::open(const void* data, std::size_t bytes) - { - close(); -#if defined(NANA_WINDOWS) - // use actual resource size, stopped using CreateIconFromResource since it loads blurry image - HICON handle = ::CreateIconFromResourceEx((PBYTE)data, static_cast(bytes), TRUE, 0x00030000, 0, 0, LR_DEFAULTCOLOR); - if(handle) - { - ICONINFO info; - if (::GetIconInfo(handle, &info) != 0) - { - HICON * p = new HICON(handle); - ptr_ = std::shared_ptr(p, handle_deleter()); - size_.width = (info.xHotspot << 1); - size_.height = (info.yHotspot << 1); - ::DeleteObject(info.hbmColor); - ::DeleteObject(info.hbmMask); - return true; - } - } -#else - static_cast(is_ico_); //kill the unused compiler warning in Linux. - static_cast(data); //to eliminate unused parameter compiler warning. - static_cast(bytes); -#endif - return false; - } - - bool image_ico::alpha_channel() const - { - return false; - } - - bool image_ico::empty() const - { - return (nullptr == ptr_); - } - - void image_ico::close() - { - ptr_.reset(); - } - - nana::size image_ico::size() const - { - return size_; - } - - void image_ico::paste(const nana::rectangle& src_r, graph_reference graph, const point& p_dst) const - { - if(ptr_ && (graph.empty() == false)) - { -#if defined(NANA_WINDOWS) - ::DrawIconEx(graph.handle()->context, p_dst.x, p_dst.y, *ptr_, src_r.width, src_r.height, 0, 0, DI_NORMAL); -#else - static_cast(src_r); //eliminate unused parameter compiler warning. - static_cast(p_dst); -#endif - } - } - - void image_ico::stretch(const nana::rectangle&, graph_reference graph, const nana::rectangle& r) const - { - if(ptr_ && (graph.empty() == false)) - { -#if defined(NANA_WINDOWS) - ::DrawIconEx(graph.handle()->context, r.x, r.y, *ptr_, r.width, r.height, 0, 0, DI_NORMAL); -#else - static_cast(r); //eliminate unused parameter compiler warning. -#endif - } - } - - const image_ico::ptr_t& image_ico::ptr() const - { - return ptr_; - } - -#if defined(NANA_WINDOWS) - //struct handle_deleter - void image_ico::handle_deleter::operator()(HICON* p) const - { - if(p && *p) - ::DestroyIcon(*p); - delete p; - } - //end struct handle_deleter -#endif - //end class image_ico - } - image::image_impl_interface::~image_impl_interface() {} @@ -264,11 +139,7 @@ namespace paint { if (ext_ico == ext) { -#if defined(NANA_WINDOWS) - ptr = std::make_shared(true); -#else - return ptr; -#endif + ptr = std::make_shared(); break; } @@ -308,7 +179,7 @@ namespace paint if (*reinterpret_cast("BM") == meta) ptr = std::make_shared(); else if (*reinterpret_cast("MZ") == meta) - ptr = std::make_shared(false); + ptr = std::make_shared(); } } @@ -342,7 +213,7 @@ namespace paint if (*reinterpret_cast("BM") == meta) ptr = std::make_shared(); else if (*reinterpret_cast("MZ") == meta) - ptr = std::make_shared(false); + ptr = std::make_shared(); else { if (bytes > 8 && (0x474e5089 == *reinterpret_cast(data))) @@ -360,13 +231,16 @@ namespace paint else if (bytes > 9 && (0x66697845 == *reinterpret_cast(reinterpret_cast(data)+5))) //Exif ptr = std::make_shared(); #endif - -#if defined(NANA_WINDOWS) - // suppose icon data is bitmap data - if (!ptr && bytes > 40 /* sizeof(BITMAPINFOHEADER) */ && (40 == *reinterpret_cast(data))) { - ptr = std::make_shared(true); + if ((!ptr) && (bytes > 40)) + { + switch (*reinterpret_cast(data)) + { + case 40: + case 0x00010000: + if (!ptr && bytes > 40) + ptr = std::make_shared(); + } } -#endif } } diff --git a/source/paint/pixel_buffer.cpp b/source/paint/pixel_buffer.cpp index f29c4068..8502754f 100644 --- a/source/paint/pixel_buffer.cpp +++ b/source/paint/pixel_buffer.cpp @@ -11,7 +11,7 @@ * @note: The format of Xorg 16bits depth is 565 */ -#include +#include "../detail/platform_spec_selector.hpp" #include #include #include @@ -457,7 +457,8 @@ namespace nana{ namespace paint HDC context = drawable->context; HBITMAP pixmap = drawable->pixmap; - HBITMAP orig_bmp; + + HBITMAP orig_bmp = nullptr; if(need_dup) { context = ::CreateCompatibleDC(drawable->context); @@ -795,7 +796,8 @@ namespace nana{ namespace paint std::unique_ptr autoptr; auto rgb_color = clr.px_color().value; - nana::pixel_color_t rgb_imd; + + nana::pixel_color_t rgb_imd = {}; if(fade) { autoptr = detail::alloc_fade_table(1 - fade_rate); diff --git a/source/paint/text_renderer.cpp b/source/paint/text_renderer.cpp index 878b3ce0..af206d40 100644 --- a/source/paint/text_renderer.cpp +++ b/source/paint/text_renderer.cpp @@ -1,5 +1,5 @@ -#include +#include "../detail/platform_spec_selector.hpp" #include #include #include diff --git a/source/paint/truetype.hpp b/source/paint/truetype.hpp new file mode 100644 index 00000000..2caa3f0e --- /dev/null +++ b/source/paint/truetype.hpp @@ -0,0 +1,160 @@ +#ifndef TTF_HEADER_INCLUDED +#define TTF_HEADER_INCLUDED + +#include +#include +#include +#include + +namespace nana +{ + namespace spec + { + class truetype + { + struct tt_offset_table + { + std::uint16_t major_version; + std::uint16_t minor_version; + std::uint16_t num_of_tables; + std::uint16_t search_range; + std::uint16_t entry_selector; + std::uint16_t range_shift; + }; + + struct tt_table_directory + { + char name[4]; //table name + std::uint32_t checksum; //Check sum + std::uint32_t offset; //Offset from beginning of file + std::uint32_t length; //length of the table in bytes + }; + + struct tt_name_table_header + { + std::uint16_t format_selector; //format selector. Always 0 + std::uint16_t name_records_count; //Name Records count + std::uint16_t storage_offset; //Offset for strings storage, from start of the table + }; + + struct tt_name_record + { + std::uint16_t platform_id; + std::uint16_t encoding_id; + std::uint16_t language_id; + std::uint16_t name_id; + std::uint16_t string_length; + std::uint16_t string_offset; //from start of storage area + }; + public: + using path_type = ::std::experimental::filesystem::path; + + truetype(const path_type& filename) + { + std::ifstream ifs{ filename, std::ios::binary }; + if (!ifs.is_open()) + return; + + tt_offset_table offset_table; + if (ifs.read(reinterpret_cast(&offset_table), sizeof offset_table).gcount() != sizeof offset_table) + return; + + const std::size_t num_of_tables = _m_swap(offset_table.num_of_tables); + for (std::size_t i = 0; i < num_of_tables; ++i) + { + tt_table_directory table_directory; + if (ifs.read(reinterpret_cast(&table_directory), sizeof table_directory).gcount() != sizeof table_directory) + return; + + if (reinterpret_cast("name") == reinterpret_cast(table_directory.name)) + { + //const std::size_t length = _m_swap(table_directory.length); + const std::size_t directory_offset = _m_swap(table_directory.offset); + + ifs.seekg(directory_offset, std::ios::beg); + + tt_name_table_header name_table; + if (ifs.read(reinterpret_cast(&name_table), sizeof name_table).gcount() != sizeof name_table) + return; + + const std::size_t name_records_count = _m_swap(name_table.name_records_count); + const std::size_t storage_offset = _m_swap(name_table.storage_offset); + + for (std::size_t u = 0; u < name_records_count; ++u) + { + tt_name_record record; + if (ifs.read(reinterpret_cast(&record), sizeof record).gcount() != sizeof record) + return; + + if ((0 == record.string_length) || (0x100 != record.name_id)) + continue; + + std::size_t string_length = _m_swap(record.string_length); + + auto const filepos = ifs.tellg(); + ifs.seekg(directory_offset + _m_swap(record.string_offset) + storage_offset, std::ios::beg); + + std::string text; + + //Check if it is unicode + if ((0 == record.platform_id) || (record.platform_id == 0x300 && record.encoding_id == 0x100)) + { + if (0 == (string_length & 1)) //the string_length must be + { + //This is unicode + text.resize(string_length); + ifs.read(&text.front(), string_length); + + for (std::size_t i = 0; i < string_length; i += 2) + std::swap(text[i], text[i + 1]); + + text = ::nana::charset(text, nana::unicode::utf16).to_bytes(nana::unicode::utf8); + } + } + else + { + text.resize(string_length); + ifs.read(&text.front(), string_length); + } + + if (!text.empty()) + { + switch (record.name_id) + { + case 0x100: + font_family_.swap(text); + break; + case 0x400: + text.clear(); + } + } + ifs.seekg(filepos, std::ios::beg); + } + } + + + } + + } + + const std::string& font_family() const + { + return font_family_; + } + private: + static std::uint16_t _m_swap(std::uint16_t val) + { + return (val << 8) | (val >> 8); + } + + static std::uint32_t _m_swap(std::uint32_t val) + { + return (static_cast(_m_swap(std::uint16_t(val & 0xFFFF))) << 16) | _m_swap(std::uint16_t(val >> 16)); + } + private: + std::string font_family_; + }; + } +} + +#endif \ No newline at end of file diff --git a/source/stdc++.cpp b/source/stdc++.cpp new file mode 100644 index 00000000..3796cba8 --- /dev/null +++ b/source/stdc++.cpp @@ -0,0 +1,467 @@ +/** +* Standard Library for C++11/14/17 +* Nana C++ Library(http://www.nanapro.org) +* Copyright(C) 2017 Jinhao(cnjinhao@hotmail.com) +* +* Distributed under the Boost Software License, Version 1.0. +* (See accompanying file LICENSE_1_0.txt or copy at +* http://www.boost.org/LICENSE_1_0.txt) +* +* @file nana/stdc++.cpp +*/ + +#include + +//Implement workarounds for GCC/MinGW which version is below 4.8.2 +#if defined(STD_NUMERIC_CONVERSIONS_NOT_SUPPORTED) +#include +namespace std +{ + int stoi(const std::string& str, std::size_t * pos, int base) + { + auto sptr = str.c_str(); + char *end; + errno = 0; + auto result = std::strtol(sptr, &end, base); + + if (sptr == end) + throw std::invalid_argument("invalid stoi argument"); + if (errno == ERANGE) + throw std::out_of_range("stoi argument out of range"); + + if (pos) + *pos = (std::size_t)(end - sptr); + return ((int)result); + } + + int stoi(const std::wstring& str, std::size_t* pos, int base) + { + auto sptr = str.data(); + wchar_t *end; + errno = 0; + auto result = std::wcstol(sptr, &end, base); + + if (sptr == end) + throw std::invalid_argument("invalid stoi argument"); + if (errno == ERANGE) + throw std::out_of_range("stoi argument out of range"); + + if (pos) + *pos = (std::size_t)(end - sptr); + return ((int)result); + } + using ::strtof; + using ::strtold; + using ::wcstold; + using ::strtoll; + using ::wcstoll; + using ::strtoull; + using ::wcstoull; + + float stof(const std::string& str, std::size_t * pos) + { + auto *ptr = str.data(); + errno = 0; + char *end; + auto result = std::strtof(ptr, &end); + + if (ptr == end) + throw std::invalid_argument("invalid stod argument"); + if (errno == ERANGE) + throw std::out_of_range("stof argument out of range"); + if (pos) + *pos = (std::size_t)(end - ptr); + return result; + } + + float stof(const std::wstring& str, std::size_t* pos) + { + auto *ptr = str.data(); + errno = 0; + wchar_t *end; + auto result = std::wcstof(ptr, &end); + + if (ptr == end) + throw std::invalid_argument("invalid stod argument"); + if (errno == ERANGE) + throw std::out_of_range("stof argument out of range"); + if (pos) + *pos = (std::size_t)(end - ptr); + return result; + } + + double stod(const std::string& str, std::size_t * pos) + { + auto *ptr = str.data(); + errno = 0; + char *end; + auto result = std::strtod(ptr, &end); + + if (ptr == end) + throw std::invalid_argument("invalid stod argument"); + if (errno == ERANGE) + throw std::out_of_range("stod argument out of range"); + if (pos) + *pos = (std::size_t)(end - ptr); + return result; + } + + double stod(const std::wstring& str, std::size_t* pos) + { + auto *ptr = str.data(); + errno = 0; + wchar_t *end; + auto result = std::wcstod(ptr, &end); + + if (ptr == end) + throw std::invalid_argument("invalid stod argument"); + if (errno == ERANGE) + throw std::out_of_range("stod argument out of range"); + if (pos) + *pos = (std::size_t)(end - ptr); + return result; + } + + long double stold(const std::string& str, std::size_t * pos) + { + auto *ptr = str.data(); + errno = 0; + char *end; + auto result = std::strtold(ptr, &end); + + if (ptr == end) + throw std::invalid_argument("invalid stod argument"); + if (errno == ERANGE) + throw std::out_of_range("stold argument out of range"); + if (pos) + *pos = (std::size_t)(end - ptr); + return result; + } + + long double stold(const std::wstring& str, std::size_t* pos) + { + auto *ptr = str.data(); + errno = 0; + wchar_t *end; + auto result = std::wcstold(ptr, &end); + + if (ptr == end) + throw std::invalid_argument("invalid stod argument"); + if (errno == ERANGE) + throw std::out_of_range("stold argument out of range"); + if (pos) + *pos = (std::size_t)(end - ptr); + return result; + } + + long stol(const std::string& str, std::size_t* pos, int base) + { + auto *ptr = str.data(); + errno = 0; + char *end; + auto result = std::strtol(ptr, &end, base); + + if (ptr == end) + throw std::invalid_argument("invalid stod argument"); + if (errno == ERANGE) + throw std::out_of_range("stol argument out of range"); + if (pos) + *pos = (std::size_t)(end - ptr); + return result; + } + + long stol(const std::wstring& str, std::size_t* pos, int base) + { + auto *ptr = str.data(); + errno = 0; + wchar_t *end; + auto result = std::wcstol(ptr, &end, base); + + if (ptr == end) + throw std::invalid_argument("invalid stod argument"); + if (errno == ERANGE) + throw std::out_of_range("stol argument out of range"); + if (pos) + *pos = (std::size_t)(end - ptr); + return result; + } + + //Workaround for no implemenation of std::stoll in MinGW. + long long stoll(const std::string& str, std::size_t* pos, int base) + { + auto *ptr = str.data(); + errno = 0; + char* end; + auto result = std::strtoll(ptr, &end, base); + + if (ptr == end) + throw std::invalid_argument("invalid stod argument"); + if (errno == ERANGE) + throw std::out_of_range("stoll argument out of range"); + if (pos) + *pos = (std::size_t)(end - ptr); + return result; + } + + long long stoll(const std::wstring& str, std::size_t* pos, int base) + { + auto *ptr = str.data(); + errno = 0; + wchar_t* end; + auto result = std::wcstoll(ptr, &end, base); + + if (ptr == end) + throw std::invalid_argument("invalid stod argument"); + if (errno == ERANGE) + throw std::out_of_range("stoll argument out of range"); + if (pos) + *pos = (std::size_t)(end - ptr); + return result; + } + + unsigned long long stoull(const std::string& str, std::size_t* pos, int base) + { + auto *ptr = str.data(); + errno = 0; + char* end; + auto result = std::strtoull(ptr, &end, base); + + if (ptr == end) + throw std::invalid_argument("invalid stod argument"); + if (errno == ERANGE) + throw std::out_of_range("stoull argument out of range"); + if (pos) + *pos = (std::size_t)(end - ptr); + return result; + } + + unsigned long long stoull(const std::wstring& str, std::size_t* pos, int base) + { + auto *ptr = str.data(); + errno = 0; + wchar_t* end; + auto result = std::wcstoull(ptr, &end, base); + + if (ptr == end) + throw std::invalid_argument("invalid stod argument"); + if (errno == ERANGE) + throw std::out_of_range("stoull argument out of range"); + if (pos) + *pos = (std::size_t)(end - ptr); + return result; + } + + //Workaround for no implemenation of std::stoul in MinGW. + unsigned long stoul(const std::string& str, std::size_t* pos, int base) + { + auto *ptr = str.data(); + errno = 0; + char* end; + auto result = std::strtoul(ptr, &end, base); + + if (ptr == end) + throw std::invalid_argument("invalid stod argument"); + if (errno == ERANGE) + throw std::out_of_range("stoul argument out of range"); + if (pos) + *pos = (std::size_t)(end - ptr); + return result; + } + + unsigned long stoul(const std::wstring& str, std::size_t* pos, int base) + { + auto *ptr = str.data(); + errno = 0; + wchar_t* end; + auto result = std::wcstoul(ptr, &end, base); + + if (ptr == end) + throw std::invalid_argument("invalid stod argument"); + if (errno == ERANGE) + throw std::out_of_range("stoul argument out of range"); + if (pos) + *pos = (std::size_t)(end - ptr); + return result; + } +}//end namespace std +#endif //STD_NUMERIC_CONVERSIONS_NOT_SUPPORTED + +#ifdef STD_TO_STRING_NOT_SUPPORTED +#include +namespace std +{ + std::string to_string(double v) + { + std::stringstream ss; + ss << v; + return ss.str(); + } + + std::string to_string(long double v) + { + std::stringstream ss; + ss << v; + return ss.str(); + } + + std::string to_string(unsigned v) + { + std::stringstream ss; + ss << v; + return ss.str(); + } + + std::string to_string(int v) + { + std::stringstream ss; + ss << v; + return ss.str(); + } + + std::string to_string(long v) + { + std::stringstream ss; + ss << v; + return ss.str(); + } + + std::string to_string(unsigned long v) + { + std::stringstream ss; + ss << v; + return ss.str(); + } + + std::string to_string(long long v) + { + std::stringstream ss; + ss << v; + return ss.str(); + } + + std::string to_string(unsigned long long v) + { + std::stringstream ss; + ss << v; + return ss.str(); + } + + std::string to_string(float v) + { + std::stringstream ss; + ss << v; + return ss.str(); + } +} +#endif // STD_TO_STRING_NOT_SUPPORTED + +#ifdef STD_TO_WSTRING_NOT_SUPPORTED +#include +namespace std +{ + std::wstring to_wstring(double v) + { + std::wstringstream ss; + ss << v; + return ss.str(); + } + + std::wstring to_wstring(long double v) + { + std::wstringstream ss; + ss << v; + return ss.str(); + } + + std::wstring to_wstring(unsigned v) + { + std::wstringstream ss; + ss << v; + return ss.str(); + } + + std::wstring to_wstring(int v) + { + std::wstringstream ss; + ss << v; + return ss.str(); + } + + std::wstring to_wstring(long v) + { + std::wstringstream ss; + ss << v; + return ss.str(); + } + + std::wstring to_wstring(unsigned long v) + { + std::wstringstream ss; + ss << v; + return ss.str(); + } + + std::wstring to_wstring(long long v) + { + std::wstringstream ss; + ss << v; + return ss.str(); + } + + std::wstring to_wstring(unsigned long long v) + { + std::wstringstream ss; + ss << v; + return ss.str(); + } + + std::wstring to_wstring(float v) + { + std::wstringstream ss; + ss << v; + return ss.str(); + } +} +#endif + +#ifdef _enable_std_put_time +#include +namespace std +{ + //Workaround for no implemenation of std::put_time in gcc < 5. + /* std unspecified return type */ + //template< class CharT, class RTSTR >// let fail for CharT != char / wchar_t + //RTSTR put_time(const std::tm* tmb, const CharT* fmt); + + //template< > + std::string put_time/**/(const std::tm* tmb, const char* fmt) + { + std::size_t sz = 200; + std::string str(sz, '\0'); + sz = std::strftime(&str[0], str.size() - 1, fmt, tmb); + str.resize(sz); + return str; + } + //Defined in header + // std::size_t strftime(char* str, std::size_t count, const char* format, const std::tm* time); + //template<> + //std::wstring put_time(const std::tm* tmb, const wchar_t* fmt) + //{ + // unsigned sz = 200; + // std::wstring str(sz, L'\0'); + // sz = std::wcsftime(&str[0], str.size() - 1, fmt, tmb); + // str.resize(sz); + // return str; + //} + // http://en.cppreference.com/w/cpp/chrono/c/wcsftime + // Defined in header + // std::size_t wcsftime(wchar_t* str, std::size_t count, const wchar_t* format, const std::tm* time); + // Converts the date and time information from a given calendar time time to a null - terminated + // wide character string str according to format string format.Up to count bytes are written. + // Parameters + // str - pointer to the first element of the wchar_t array for output + // count - maximum number of wide characters to write + // format - pointer to a null - terminated wide character string specifying the format of conversion. + +} +#endif // _enable_std_put_time \ No newline at end of file diff --git a/source/system/dataexch.cpp b/source/system/dataexch.cpp index b647e1ec..28674822 100644 --- a/source/system/dataexch.cpp +++ b/source/system/dataexch.cpp @@ -1,6 +1,6 @@ /* * Data Exchanger Implementation - * Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -19,38 +19,38 @@ #include #if defined(NANA_WINDOWS) - #include +# include #elif defined(NANA_X11) - #include - #include - #include +# include "../detail/platform_spec_selector.hpp" +# include +# include #endif namespace nana{ namespace system{ //class dataexch - void dataexch::set(const std::string& text) + void dataexch::set(const std::string& text, native_window_type owner) { #ifdef NANA_WINDOWS std::wstring wstr = ::nana::charset(text, nana::unicode::utf8); - _m_set(format::text, wstr.c_str(), (wstr.length() + 1) * sizeof(wchar_t)); + _m_set(format::text, wstr.c_str(), (wstr.length() + 1) * sizeof(wchar_t), owner); #elif defined(NANA_X11) - _m_set(format::text, text.c_str(), text.length() + 1); + _m_set(format::text, text.c_str(), text.length() + 1, owner); #endif } - void dataexch::set(const std::wstring& text) + void dataexch::set(const std::wstring& text, native_window_type owner) { #ifdef NANA_WINDOWS - _m_set(format::text, text.c_str(), (text.length() + 1) * sizeof(wchar_t)); + _m_set(format::text, text.c_str(), (text.length() + 1) * sizeof(wchar_t), owner); #else std::string str = to_utf8(text); - _m_set(format::text, str.c_str(), str.size() + 1); + _m_set(format::text, str.c_str(), str.size() + 1, owner); #endif } - bool dataexch::set(const nana::paint::graphics& g) + bool dataexch::set(const nana::paint::graphics& g, native_window_type owner) { #if defined(NANA_WINDOWS) size sz = g.size(); @@ -100,11 +100,14 @@ namespace nana{ namespace system{ } if (::GlobalUnlock(h_gmem) || GetLastError() == NO_ERROR) - if (::OpenClipboard(::GetFocus())) + if (::OpenClipboard(reinterpret_cast(owner))) + { if (::EmptyClipboard()) if (::SetClipboardData(CF_DIB, h_gmem)) if (::CloseClipboard()) return true; + ::CloseClipboard(); + } } assert(false); ::GlobalFree(h_gmem); @@ -113,7 +116,9 @@ namespace nana{ namespace system{ //#elif defined(NANA_X11) #else static_cast(g); //eliminate unused parameter compiler warning. - throw "not implemented yet."; + static_cast(owner); + throw std::logic_error("dataexch::set(const paint::graphics&, native_window_type owner) not implemented yet."); + return false; #endif } @@ -179,13 +184,21 @@ namespace nana{ namespace system{ #endif } } + + std::wstring dataexch::wget() + { + std::wstring str; + this->get(str); + return str; + } + //private: - bool dataexch::_m_set(format fmt, const void* buf, std::size_t size) + bool dataexch::_m_set(format fmt, const void* buf, std::size_t size, native_window_type owner) { bool res = false; #if defined(NANA_WINDOWS) - if(::OpenClipboard(::GetFocus())) + if(::OpenClipboard(reinterpret_cast(owner))) { if(::EmptyClipboard()) { @@ -208,26 +221,18 @@ namespace nana{ namespace system{ } #elif defined(NANA_X11) auto & spec = ::nana::detail::platform_spec::instance(); - native_window_type owner = nullptr; + + Atom atom_type; + switch(fmt) { - internal_scope_guard lock; - auto wd = detail::bedrock::instance().focus(); - if(wd) owner = wd->root; + case format::text: atom_type = spec.atombase().utf8_string; break; + default: + return false; } - if(owner) - { - Atom atom_type; - switch(fmt) - { - case format::text: atom_type = spec.atombase().utf8_string; break; - default: - return false; - } - - spec.write_selection(owner, atom_type, buf, size); - return true; - } + spec.write_selection(owner, atom_type, buf, size); + return true; + #endif return res; } diff --git a/source/system/platform.cpp b/source/system/platform.cpp index 092c53e5..d6ee1462 100644 --- a/source/system/platform.cpp +++ b/source/system/platform.cpp @@ -16,7 +16,7 @@ #if defined(NANA_WINDOWS) #include - #include + #include "../detail/mswin/platform_spec.hpp" #elif defined(NANA_LINUX) || defined(NANA_MACOS) #include #include diff --git a/source/threads/pool.cpp b/source/threads/pool.cpp index f1237887..0e2c6c29 100644 --- a/source/threads/pool.cpp +++ b/source/threads/pool.cpp @@ -356,11 +356,28 @@ namespace threads { } + pool::pool(pool&& other) + : pool() + { + std::swap(impl_, other.impl_); + } + pool::pool(std::size_t thread_number) : impl_(new impl(thread_number)) { } + pool& pool::operator=(pool&& other) + { + if(this != &other) + { + delete impl_; + impl_ = other.impl_; + other.impl_ = new impl(4); + } + return *this; + } + pool::~pool() { delete impl_; diff --git a/source/unicode_bidi.cpp b/source/unicode_bidi.cpp index b98c6f29..3da7d2f0 100644 --- a/source/unicode_bidi.cpp +++ b/source/unicode_bidi.cpp @@ -515,7 +515,7 @@ namespace nana cur.level = _m_paragraph_level(str, end); //First character type - bidi_char begin_char_type; + bidi_char begin_char_type = {}; const char_type * begin_character = nullptr; for(const char_type * c = str; c < end; ++c) @@ -616,7 +616,7 @@ namespace nana e.bidi_char_type = bidi_char_type; } - + std::vector::iterator unicode_bidi::_m_search_first_character() { return levels_.begin(); @@ -811,7 +811,7 @@ namespace nana //N1. A sequence of neutrals takes the direction of the surrounding strong text if the text on both sides has the same direction. //European and Arabic numbers act as if they were R in terms of their influence on neutrals. //Start-of-level-run (sor) and end-of-level-run (eor) are used at level run boundaries. - bidi_char left; + bidi_char left = {}; for(auto i = begin_character; i != end; ++i) { if(level_of_run != i->level)