diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..83fd45e3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +*.sln text eol=crlf +*.vcxproj text eol=crlf +*.vcxproj.filters text eol=crlf +*.vcxproj.user text eol=crlf +*.bat text eol=crlf +Makefile text eolf=lf diff --git a/.gitignore b/.gitignore index 6bd33ab0..003f80b0 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ lib/ CMakeCache.txt CMakeFiles/ cmake-build-debug/ +cmake-build-*/ .idea/ cmake_install.cmake *.DS_Store diff --git a/.travis.yml b/.travis.yml index 42f63050..08c3d6f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,7 @@ matrix: - alsa-oss - libx11-dev - libxft-dev + - libxcursor-dev sources: - ubuntu-toolchain-r-test @@ -36,15 +37,18 @@ matrix: - alsa-oss - libx11-dev - libxft-dev + - libxcursor-dev sources: - ubuntu-toolchain-r-test - llvm-toolchain-precise before_install: - - git clone --depth=1 --branch=develop https://github.com/qPCR4vir/nana-demo.git ../nana-demo + - cd .. + - git clone --depth=1 --branch=cmake-dev https://github.com/qPCR4vir/nana-demo.git nana-demo - export PATH="$HOME/bin:$PATH" + #- mkdir ~/bin #it seemd that a bin already exists from 20170901 - - 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 + - wget --no-check-certificate --no-clobber -O /tmp/tools/cmake https://cmake.org/files/v3.12/cmake-3.12.0-rc3-Linux-x86_64.sh || true - chmod -R +x /tmp/tools install: @@ -57,31 +61,15 @@ before_script : - sleep 3 # give xvfb some time to start # we have: qPCR4vir/nana/../nana-demo and now we are in: qPCR4vir/nana/ our executable tests will access: ../nana-demo/Examples/*.bmp etc.(need to be in parallel with nana-demo/Examples) #- cd ../nana-demo - - mkdir ../nana_lib - - mkdir ../nana_demo_bin - - cd ../nana_lib - - mkdir bin - - cd bin + - mkdir demo-build + - cd demo-build script: - # Installing: the static "nana lib" will be in DESTDIR/CMAKE_INSTALL_PREFIX/lib/ - # and the includes files "nana" in DESTDIR/CMAKE_INSTALL_PREFIX/include/ - # we are in "... nana/../nana_lib/bin/" we need "../../nana" to get the CMakeList.txt of nana. - # Thus, make install will put the nana.lib in "... nana/../nana_lib/lib/" - # and the includes in "... nana/../nana_lib/include/" - - cmake -G"Unix Makefiles" ../../nana -DCMAKE_INSTALL_PREFIX=.. -DNANA_CMAKE_ENABLE_JPEG=ON -DNANA_CMAKE_ENABLE_PNG=OFF -DNANA_CMAKE_BUILD_DEMOS=ON -DNANA_CMAKE_ENABLE_AUDIO=OFF -DNANA_CMAKE_FIND_BOOST_FILESYSTEM=ON -DNANA_CMAKE_BOOST_FILESYSTEM_FORCE=OFF -DNANA_CMAKE_AUTOMATIC_GUI_TESTING=ON - - make install - - ls - - cd .. - - ls - - cd .. - - ls - - cd nana_demo_bin - - cmake -G"Unix Makefiles" ../nana-demo -DCMAKE_INSTALL_PREFIX=.. -DNANA_CMAKE_ENABLE_JPEG=ON -DNANA_CMAKE_ENABLE_PNG=OFF -DNANA_CMAKE_BUILD_DEMOS=ON -DNANA_CMAKE_ENABLE_AUDIO=OFF -DNANA_CMAKE_FIND_BOOST_FILESYSTEM=ON -DNANA_CMAKE_BOOST_FILESYSTEM_FORCE=OFF -DNANA_CMAKE_INCLUDE_EXPERIMENTAL_DEMOS=OFF -DNANA_CMAKE_AUTOMATIC_GUI_TESTING=ON + - cmake -G"Unix Makefiles" ../nana-demo -DCMAKE_INSTALL_PREFIX=.. -DNANA_CMAKE_ENABLE_JPEG=ON -DNANA_CMAKE_FIND_BOOST_FILESYSTEM=ON -DNANA_CMAKE_AUTOMATIC_GUI_TESTING=ON - make install # todo: separate resources from sources (a directory for images) - ls - - cd ../bin + - cd ../nana-test/bin - ls - ./a_group_impl - ./animate-bmp diff --git a/CMakeLists.txt b/CMakeLists.txt index 80f0e8c9..e9252dce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,381 +1,140 @@ # CMake configuration for Nana -# Author: Andrew Kornilov(https://github.com/ierofant) # Contributors: +# Andrew Kornilov (ierofant) - original version # Jinhao -# Robert Hauck - Enable support for PNG/Freetype -# Qiangqiang Wu - Add biicode support # Ariel Vina-Rodriguez (qPCR4vir) +# (King_DuckZ) +# Robert Hauck - Enable support for PNG/Freetype # Pavel O. - fix compilation with boost::filesystem (#281) # Frostbane - Add option for compiling a shared library (#263,#265) +# Qiangqiang Wu - Add biicode support: todo migrate to https://conan.io/ # # Nana uses some build systems: MS-VS solution, MAKE, bakefile, codeblock, etc. manually optimized. -# In the future CMake could be the prefered, and maybe will be used to generate the others and the central nana repo -# will distribute all of them. But by now CMake is just one of them and all the other distributed build system -# files/projects are manually write. This current CMakeList.txt reflect this fact and that is why we don't +# Maybe CMake will be used in the future to generate some of them in the central nana repository. +# But by now CMake is just one option and all the other build system +# files/projects distributed are manually writen. This current CMakeList.txt reflect this fact and that is why we don't # generate here configurated *.h files or explicitly enumerate the sources files: anyway this CM-list # will be "touched" to force a re-run of cmake. -#https://cmake.org/cmake-tutorial/ -#https://cmake.org/cmake/help/v3.3/module/CMakeDependentOption.html?highlight=cmakedependentoption -# use CACHE FORCE or set(ENABLE_MINGW_STD_THREADS_WITH_MEGANZ ON) or delete CMakecache.txt or the entirely build dir -# if your changes don't execute -# It seems that project() defines essential system variables like CMAKE_FIND_LIBRARY_PREFIXES. -# https://bbs.archlinux.org/viewtopic.php?id=84967 +# https://cliutils.gitlab.io/modern-cmake/ +# https://cmake.org/cmake-tutorial/ +# https://cmake.org/cmake/help/v3.12/module/CMakeDependentOption.html?highlight=cmakedependentoption +# cmake 3.12 have more better modern c++ support -project(nana) -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.12 FATAL_ERROR) +project(nana VERSION 1.6.2 + DESCRIPTION "C++ GUI library" + HOMEPAGE_URL http://nanapro.org + LANGUAGES CXX ) -option(NANA_CMAKE_INSTALL_INCLUDES "Install nana includes when compile the library" ON) -option(NANA_CMAKE_ENABLE_MINGW_STD_THREADS_WITH_MEGANZ "replaced boost.thread with meganz's mingw-std-threads." OFF) -option(NANA_CMAKE_ENABLE_PNG "Enable the use of PNG" OFF) -option(NANA_CMAKE_LIBPNG_FROM_OS "Use libpng from operating system." ON) -option(NANA_CMAKE_ENABLE_JPEG "Enable the use of JPEG" OFF) -option(NANA_CMAKE_LIBJPEG_FROM_OS "Use libjpeg from operating system." ON) -option(NANA_CMAKE_ENABLE_AUDIO "Enable class audio::play for PCM playback." OFF) -option(NANA_CMAKE_SHARED_LIB "Compile nana as a shared library." OFF) -option(NANA_CMAKE_VERBOSE_PREPROCESSOR "Show annoying debug messages during compilation." ON) -option(NANA_CMAKE_STOP_VERBOSE_PREPROCESSOR "Stop compilation after showing the annoying debug messages." OFF) -option(NANA_CMAKE_AUTOMATIC_GUI_TESTING "Activate automatic GUI testing?" OFF) -option(NANA_CLION "Activate some CLion specific workarounds" OFF) +####################### Main setting of Nana targets, sources and installs ##################### -# The ISO C++ File System Technical Specification (ISO-TS, or STD) is optional. -# http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4100.pdf -# This is not a workaround, but an user option. -# The library maybe available in the std library in use or from Boost (almost compatible) -# http://www.boost.org/doc/libs/1_60_0/libs/filesystem/doc/index.htm -# or you can choose to use the (partial, but functional) implementation provided by nana. -# If you include the file or -# the selected option will be set by nana into std::experimental::filesystem -# By default Nana will try to use the STD. If STD is not available and NANA_CMAKE_FIND_BOOST_FILESYSTEM -# is set to ON nana will try to use boost if available. Nana own implementation will be use if none of -# the previus were selected or available. -# You can change that default if you change one of the following -# (please don't define more than one of the _XX_FORCE options): -option(NANA_CMAKE_FIND_BOOST_FILESYSTEM "Search: Is Boost filesystem available?" OFF) -option(NANA_CMAKE_NANA_FILESYSTEM_FORCE "Force nana filesystem over ISO and boost?" OFF) -option(NANA_CMAKE_STD_FILESYSTEM_FORCE "Use of STD filesystem?(a compilation error will ocurre if not available)" OFF) -option(NANA_CMAKE_BOOST_FILESYSTEM_FORCE "Force use of Boost filesystem if available (over STD)?" OFF) +add_library(nana) +target_compile_features(nana PUBLIC cxx_std_17) -########### Compatibility with CMake 3.1 -if(POLICY CMP0054) - # http://www.cmake.org/cmake/help/v3.1/policy/CMP0054.html - cmake_policy(SET CMP0054 NEW) -endif() +# need after cxx_std_14 or cxx_std_17 ?? +target_compile_features(nana + PUBLIC cxx_nullptr + PUBLIC cxx_range_for + PUBLIC cxx_lambdas + PUBLIC cxx_decltype_auto + PUBLIC cxx_defaulted_functions + PUBLIC cxx_deleted_functions + PUBLIC cxx_auto_type + PUBLIC cxx_decltype_incomplete_return_types + PUBLIC cxx_defaulted_move_initializers + PUBLIC cxx_noexcept + PUBLIC cxx_rvalue_references + ) -########### OS +### collect all source sub-directories in a list to avoid duplication ### -if(WIN32) - add_definitions(-DWIN32) - # Global MSVC definitions. You may prefer the hand-tuned sln and projects from the nana repository. - if(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) +# By using CMAKE_CURRENT_LIST_DIR here you can compile and consume nana by just: +# add_subdirectory(../nana ../cmake-nana-build-${CONFIG} ) or simmilar +# in your own CMakeLists.txt, and them : +# target_link_libraries(yourApp PRIVATE nana ) - # Change the MSVC Compiler flags - if(MSVC_USE_MP) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") - endif() +set(NANA_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/source) - 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() - endif() - - if(MINGW) - if(NANA_CMAKE_ENABLE_MINGW_STD_THREADS_WITH_MEGANZ) - add_definitions(-DSTD_THREAD_NOT_SUPPORTED) - add_definitions(-DNANA_ENABLE_MINGW_STD_THREADS_WITH_MEGANZ) - endif() - endif() - - if(MSVC) - set(DLLTOOL OFF) - else() - # mingw: If dlltool is found the def and lib file will be created - find_program (DLLTOOL dlltool) - if(NOT DLLTOOL) - message(WARNING "dlltool not found. Skipping import library generation.") - endif() - endif() -endif() - -if(APPLE) - add_definitions(-DAPPLE) - include_directories(/opt/X11/include/) - list(APPEND NANA_LINKS -L/opt/X11/lib/ -liconv) - set(ENABLE_AUDIO OFF) -elseif(UNIX) - add_definitions(-Dlinux) -endif() - -if(UNIX) - list(APPEND NANA_LINKS -lX11) - include(FindFreetype) - if(FREETYPE_FOUND) - include_directories( ${FREETYPE_INCLUDE_DIRS}) - list(APPEND NANA_LINKS -lXft -lfontconfig) - endif() -endif() - - -########### Compilers -# -# Using gcc: gcc 4.8 don't support C++14 and make_unique. You may want to update at least to 4.9. -# gcc 5.3 and 5.4 include filesytem, but you need to add the link flag: -lstdc++fs -# -# In Windows, the gcc which come with CLion was 4.8 from MinGW. -# CLion was updated to MinGW with gcc 6.3 ? Allways check this in File/Settings.../toolchains -# You could install MinGW-w64 from the TDM-GCC Compiler Suite for Windows which will update you to gcc 5.1. -# It is posible to follow https://computingabdn.com/softech/mingw-howto-install-gcc-for-windows/ -# and install MinGW with gcc 7.1 with has STD_THREADS and fs, from: https://sourceforge.net/projects/mingw-w64/files/ -# -# -# see at end of: https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dynamic_or_shared.html -# -if(CMAKE_COMPILER_IS_GNUCXX OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") - if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") - if("${CMAKE_SYSTEM_NAME}" MATCHES "FreeBSD") - set(CMAKE_CXX_FLAGS "-std=gnu++14 -Wall -I/usr/local/include") - else() - set(CMAKE_CXX_FLAGS "-std=gnu++14 -Wall") - endif() - else() - set(CMAKE_CXX_FLAGS "-std=c++14 -Wall") - endif() -endif() - -if(CMAKE_COMPILER_IS_GNUCXX OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") - if(NANA_CMAKE_SHARED_LIB) - list(APPEND NANA_LINKS -lgcc -lstdc++ -pthread) - else() - if(MINGW) - set(CMAKE_EXE_LINKER_FLAGS "-static -pthread") - else() - set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++ -pthread") - endif() - endif(NANA_CMAKE_SHARED_LIB) - - if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) - # GCC 4.9 - list(APPEND NANA_LINKS "-lboost_system -lboost_thread") - elseif(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.3) - # IS_GNUCXX < 5.3 - else() - list(APPEND NANA_LINKS -lstdc++fs) - endif() -endif() - - -if (APPLE AND "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") # APPLE Clang - list(APPEND NANA_LINKS -stdlib=libstdc++) -endif () - - -############# Optional libraries - -# Find PNG -if(NANA_CMAKE_ENABLE_PNG) - if(NANA_CMAKE_LIBPNG_FROM_OS) - find_package(PNG) - if(PNG_FOUND) - include_directories(${PNG_INCLUDE_DIRS}) - list(APPEND NANA_LINKS ${PNG_LIBRARIES}) - add_definitions(-DNANA_ENABLE_PNG -DUSE_LIBPNG_FROM_OS) - endif() - else() - add_definitions(-DNANA_ENABLE_PNG) - endif() -endif() - -# Find JPEG -if(NANA_CMAKE_ENABLE_JPEG) - add_definitions(-DNANA_ENABLE_JPEG) - if(NANA_CMAKE_LIBJPEG_FROM_OS) - find_package(JPEG) - if(JPEG_FOUND) - include_directories( ${JPEG_INCLUDE_DIR}) - list(APPEND NANA_LINKS ${JPEG_LIBRARY}) - add_definitions(-DNANA_ENABLE_JPEG -DUSE_LIBJPEG_FROM_OS) - endif() - else() - add_definitions(-DNANA_ENABLE_JPEG) - endif() -endif() - -# Find ASOUND +set(NANA_SOURCE_SUBDIRS + /. + /detail + /detail/posix + /filesystem + /gui + /gui/detail + /gui/widgets + /gui/widgets/skeletons + /paint + /paint/detail + /system + /threads + ) if(NANA_CMAKE_ENABLE_AUDIO) - add_definitions(-DNANA_ENABLE_AUDIO) - if(UNIX) - find_package(ASOUND) - if(ASOUND_FOUND) - include_directories(${ASOUND_INCLUDE_DIRS}) - list(APPEND NANA_LINKS -lasound) - else() - message(FATAL_ERROR "libasound is not found") - endif() - endif() + list(APPEND NANA_SOURCE_SUBDIRS + /audio + /audio/detail + ) endif() -# Find/Select filesystem -if(NANA_CMAKE_NANA_FILESYSTEM_FORCE) - add_definitions(-DNANA_FILESYSTEM_FORCE) -elseif(NANA_CMAKE_STD_FILESYSTEM_FORCE) - add_definitions(-DSTD_FILESYSTEM_FORCE) -elseif(NANA_CMAKE_FIND_BOOST_FILESYSTEM OR NANA_CMAKE_BOOST_FILESYSTEM_FORCE) - if(NANA_CMAKE_BOOST_FILESYSTEM_FORCE) - add_definitions(-DBOOST_FILESYSTEM_FORCE) - endif() - # https://cmake.org/cmake/help/git-master/module/FindBoost.html - # Implicit dependencies such as Boost::filesystem requiring Boost::system will be automatically detected and satisfied, - # even if system is not specified when using find_package and if Boost::system is not added to target_link_libraries. - # If using Boost::thread, then Thread::Thread will also be added automatically. - find_package(Boost COMPONENTS filesystem) - if(Boost_FOUND) - add_definitions(-DBOOST_FILESYSTEM_AVAILABLE) - include_directories(SYSTEM "${Boost_INCLUDE_DIR}") - list(APPEND NANA_LINKS ${Boost_LIBRARIES}) - endif() - set(Boost_USE_STATIC_LIBS ON) - set(Boost_USE_STATIC_RUNTIME ON) +# collect all source files in the source-sub-dir +foreach(subdir ${NANA_SOURCE_SUBDIRS}) + aux_source_directory(${NANA_SOURCE_DIR}${subdir} SOURCES) # todo: use GLOB to add headers too ?? +endforeach() + +target_sources(nana PRIVATE ${SOURCES}) + +### collect all headers sub-directories in a list to avoid duplication ### +# To show .h files in Visual Studio, add them to the list of sources in add_executable / add_library / target_sources +# and Use SOURCE_GROUP if all your sources are in the same directory +set(NANA_INCLUDE_DIR ${CMAKE_CURRENT_LIST_DIR}/include) + +set(NANA_INCLUDE_SUBDIRS + /. + /filesystem + /gui + /gui/detail + /gui/widgets + /gui/widgets/skeletons + /paint + /paint/detail + /pat + /system + /threads + ) +if(NANA_CMAKE_ENABLE_AUDIO) + list(APPEND NANA_INCLUDE_SUBDIRS + /audio + /audio/detail + ) endif() +foreach(subdir ${NANA_INCLUDE_SUBDIRS}) + aux_source_directory(${NANA_INCLUDE_DIR}/nana${subdir} HEADERS) # todo: use GLOB to add headers too !!!!!!! +endforeach() + +### Some nana compilation options ### +option(NANA_CMAKE_AUTOMATIC_GUI_TESTING "Activate automatic GUI testing?" OFF) +option(NANA_CMAKE_ENABLE_MINGW_STD_THREADS_WITH_MEGANZ "replaced boost.thread with meganz's mingw-std-threads." OFF) # deprecate? ######## Nana options -add_definitions(-DNANA_IGNORE_CONF) -if(NANA_CMAKE_VERBOSE_PREPROCESSOR) - add_definitions(-DVERBOSE_PREPROCESSOR) -endif() +target_compile_definitions(nana PRIVATE NANA_IGNORE_CONF) # really ? if(NANA_CMAKE_AUTOMATIC_GUI_TESTING) - add_definitions(-DNANA_AUTOMATIC_GUI_TESTING) - enable_testing() + target_compile_definitions(nana PUBLIC NANA_AUTOMATIC_GUI_TESTING) + # todo: enable_testing() # ?? endif() +list (APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/build/cmake/Modules) +include(${CMAKE_CURRENT_LIST_DIR}/build/cmake/install_nana.cmake) # includes and libs, or just expose the nana target +include(${CMAKE_CURRENT_LIST_DIR}/build/cmake/OS.cmake) # windows, unix, linux, apple, ... +include(${CMAKE_CURRENT_LIST_DIR}/build/cmake/shared_libs.cmake) # static vs shared +include(${CMAKE_CURRENT_LIST_DIR}/build/cmake/compilers.cmake) # VC, gcc, clang -####################### Main setting of Nana sources, targets and install +############# Optional libraries ##################### +include(${CMAKE_CURRENT_LIST_DIR}/build/cmake/enable_png.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/build/cmake/enable_jpeg.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/build/cmake/enable_audio.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/build/cmake/select_filesystem.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/build/cmake/verbose.cmake) # Just for information -set(NANA_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/source) -set(NANA_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) -# collect all source sub-directories in a list to avoid duplication here -set(NANA_SOURCE_SUBDIRS /. - /detail - /filesystem - /gui - /gui/detail - /gui/widgets - /gui/widgets/skeletons - /paint - /paint/detail - /system - /threads - ) -if(NANA_CMAKE_ENABLE_AUDIO) - list(APPEND NANA_SOURCE_SUBDIRS /audio - /audio/detail - ) -endif() -# collect all source files in the source-sub-dir -# To show .h files in Visual Studio, add them to the list of sources in add_executable / add_library -# and Use SOURCE_GROUP if all your sources are in the same directory -foreach(subdir ${NANA_SOURCE_SUBDIRS}) - aux_source_directory(${NANA_SOURCE_DIR}${subdir} SOURCES) -endforeach() - -if("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") - add_definitions(-fmax-errors=3) -endif() - -set(CMAKE_DEBUG_POSTFIX "_d") - -if(NANA_CMAKE_SHARED_LIB) - add_library(${PROJECT_NAME} SHARED ${SOURCES}) -else() - add_library(${PROJECT_NAME} STATIC ${SOURCES}) -endif() - -target_include_directories(${PROJECT_NAME} PUBLIC ${NANA_INCLUDE_DIR}) -target_link_libraries(${PROJECT_NAME} ${NANA_LINKS}) - - # Headers: use INCLUDE_DIRECTORIES - # Libraries: use FIND_LIBRARY and link with the result of it (try to avoid LINK_DIRECTORIES) - -# Installing: the static "nana lib" will be in DESTDIR/CMAKE_INSTALL_PREFIX/lib/ -# and the includes files "include/nana/" in DESTDIR/CMAKE_INSTALL_PREFIX/include/nana/ -# unfortunatelly install() is still ignored by CLion: -# https://intellij-support.jetbrains.com/hc/en-us/community/posts/205822949-CMake-install-isn-t-supported- -install(TARGETS ${PROJECT_NAME} ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib - RUNTIME DESTINATION bin) - -# http://stackoverflow.com/questions/33788729/how-do-i-get-clion-to-run-an-install-target -if(NANA_CLION) # the Clion IDE don't reconize the install target - add_custom_target(install_${PROJECT_NAME} - $(MAKE) install - DEPENDS ${PROJECT_NAME} - COMMENT "Installing ${PROJECT_NAME}") -endif() - -if(NANA_CMAKE_SHARED_LIB) - if(WIN32) - set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) - if(DLLTOOL) - #generate the lib and def files needed by msvc - set_target_properties (${PROJECT_NAME} PROPERTIES OUTPUT_NAME "${PROJECT_NAME}" - ARCHIVE_OUTPUT_NAME "${PROJECT_NAME}" - LINK_FLAGS "${CMAKE_SHARED_LINKER_FLAGS_INIT} -Wl,--output-def=${CMAKE_CURRENT_BINARY_DIR}/lib${PROJECT_NAME}.def") - - add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD - WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" - COMMAND echo " Generating import library" - COMMAND "${DLLTOOL}" --dllname "lib${PROJECT_NAME}.dll" - --input-def "lib${PROJECT_NAME}.def" - --output-lib "lib${PROJECT_NAME}.lib") - - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/lib${PROJECT_NAME}.def" - "${CMAKE_CURRENT_BINARY_DIR}/lib${PROJECT_NAME}.lib" DESTINATION lib) - endif() - endif() -endif() - -message("") -message("The compiled Nana library will be installed in ${CMAKE_INSTALL_PREFIX}/lib") -# Install the include directories too. -if(NANA_CMAKE_INSTALL_INCLUDES) - install(DIRECTORY ${NANA_INCLUDE_DIR}/nana DESTINATION include) - message("The Nana include files will be installed in ${CMAKE_INSTALL_PREFIX}/include") -endif() - - -# Just for information: -message ("") -message ( "CMAKE_CXX_COMPILER_ID = " ${CMAKE_CXX_COMPILER_ID}) -message ( "COMPILER_IS_CLANG = " ${COMPILER_IS_CLANG}) -message ( "CMAKE_COMPILER_IS_GNUCXX = " ${CMAKE_COMPILER_IS_GNUCXX}) -message ( "CMAKE_CXX_FLAGS = " ${CMAKE_CXX_FLAGS}) -message ( "CMAKE_EXE_LINKER_FLAGS = " ${CMAKE_EXE_LINKER_FLAGS}) -message ( "CMAKE_STATIC_LINKER_FLAGS = " ${CMAKE_STATIC_LINKER_FLAGS}) -message ( "NANA_LINKS = " ${NANA_LINKS}) -message ( "DESTDIR = " ${DESTDIR}) -message ( "CMAKE_INSTALL_PREFIX = " ${CMAKE_INSTALL_PREFIX}) -message ( "NANA_INCLUDE_DIR = " ${NANA_INCLUDE_DIR}) -message ( "CMAKE_CURRENT_SOURCE_DIR = " ${CMAKE_CURRENT_SOURCE_DIR}) -message ( "NANA_CMAKE_ENABLE_AUDIO = " ${NANA_CMAKE_ENABLE_AUDIO}) -message ( "NANA_CMAKE_SHARED_LIB = " ${NANA_CMAKE_SHARED_LIB}) -message ( "NANA_CLION = " ${NANA_CLION}) -message ( "CMAKE_MAKE_PROGRAM = " ${CMAKE_MAKE_PROGRAM}) -message ( "CMAKE_CXX_COMPILER_VERSION = " ${CMAKE_CXX_COMPILER_VERSION}) - -message ( "NANA_CMAKE_FIND_BOOST_FILESYSTEM = " ${NANA_CMAKE_FIND_BOOST_FILESYSTEM}) -message ( "NANA_CMAKE_BOOST_FILESYSTEM_FORCE = " ${NANA_CMAKE_BOOST_FILESYSTEM_FORCE}) -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}) diff --git a/bii/layout.bii b/bii/layout.bii deleted file mode 100644 index 31e284d2..00000000 --- a/bii/layout.bii +++ /dev/null @@ -1,12 +0,0 @@ - # Minimal layout, with all auxiliary folders inside "bii" and -# The binary "bin" folder as is, and enabled code edition in the project root -cmake: bii/cmake -lib: bii/lib -build: bii/build - -deps: bii/deps -# Setting this to True enables directly editing in the project root -# instead of blocks/youruser/yourblock -# the block will be named as your project folder -auto-root-block: True -root-block: qiangwu/nana \ No newline at end of file diff --git a/bii/policies.bii b/bii/policies.bii deleted file mode 100644 index d6d5e86e..00000000 --- a/bii/policies.bii +++ /dev/null @@ -1,11 +0,0 @@ -# This file configures your finds of dependencies. -# -# It is an ordered list of rules, which will be evaluated in order, of the form: -# block_pattern: TAG -# -# For each possible block that could resolve your dependencies, -# only versions with tag >= TAG will be accepted - -qiangwu/* : DEV -* : STABLE - diff --git a/bii/settings.bii b/bii/settings.bii deleted file mode 100644 index fa648ede..00000000 --- a/bii/settings.bii +++ /dev/null @@ -1,2 +0,0 @@ -cmake: {generator: MinGW Makefiles} -os: {arch: 32bit, family: Windows, subfamily: '7', version: 6.1.7601} diff --git a/biicode.conf b/biicode.conf deleted file mode 100644 index 872c1375..00000000 --- a/biicode.conf +++ /dev/null @@ -1,49 +0,0 @@ -# Biicode configuration file - -[requirements] - glenn/png: 6 - -[parent] - # The parent version of this block. Must match folder name. E.g. - # user/block # No version number means not published yet - # You can change it to publish to a different track, and change version, e.g. - # user/block(track): 7 - qiangwu/nana: 0 - -[paths] - # Local directories to look for headers (within block) - # / - include - -[dependencies] - # Manual adjust file implicit dependencies, add (+), remove (-), or overwrite (=) - # hello.h + hello_imp.cpp hello_imp2.cpp - # *.h + *.cpp - include/nana/config.hpp + include/* - include/nana/config.hpp + source/* - -[mains] - # Manual adjust of files that define an executable - # !main.cpp # Do not build executable from this file - # main2.cpp # Build it (it doesnt have a main() function, but maybe it includes it) - -[tests] - # Manual adjust of files that define a CTest test - # test/* pattern to evaluate this test/ folder sources like tests - -[hooks] - # These are defined equal to [dependencies],files names matching bii*stage*hook.py - # will be launched as python scripts at stage = {post_process, clean} - # CMakeLists.txt + bii/my_post_process1_hook.py bii_clean_hook.py - -[includes] - # Mapping of include patterns to external blocks - # hello*.h: user3/depblock # includes will be processed as user3/depblock/hello*.h - png.h: glenn/png - -[data] - # Manually define data files dependencies, that will be copied to bin for execution - # By default they are copied to bin/user/block/... which should be taken into account - # when loading from disk such data - # image.cpp + image.jpg # code should write open("user/block/image.jpg") - diff --git a/build/bakefile/nana.bkl b/build/bakefile/nana.bkl index def827a4..93184ed7 100644 --- a/build/bakefile/nana.bkl +++ b/build/bakefile/nana.bkl @@ -30,6 +30,7 @@ library nana { detail/platform_spec_selector.cpp gui/animation.cpp gui/basis.cpp + gui/dragdrop.cpp gui/dragger.cpp gui/drawing.cpp gui/effects.cpp @@ -62,7 +63,6 @@ library nana { gui/widgets/date_chooser.cpp gui/widgets/float_listbox.cpp gui/widgets/form.cpp - gui/widgets/frame.cpp gui/widgets/label.cpp gui/widgets/listbox.cpp gui/widgets/menubar.cpp diff --git a/build/cmake/Modules/FindFontconfig.cmake b/build/cmake/Modules/FindFontconfig.cmake new file mode 100644 index 00000000..e6fa81d8 --- /dev/null +++ b/build/cmake/Modules/FindFontconfig.cmake @@ -0,0 +1,69 @@ +# - Try to find the Fontconfig +# Once done this will define +# +# FONTCONFIG_FOUND - system has Fontconfig +# FONTCONFIG_INCLUDE_DIR - The include directory to use for the fontconfig headers +# FONTCONFIG_LIBRARIES - Link these to use FONTCONFIG +# FONTCONFIG_DEFINITIONS - Compiler switches required for using FONTCONFIG + +# Copyright (c) 2006,2007 Laurent Montel, +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +if (FONTCONFIG_LIBRARIES AND FONTCONFIG_INCLUDE_DIR) + + # in cache already + set(FONTCONFIG_FOUND TRUE) + +else (FONTCONFIG_LIBRARIES AND FONTCONFIG_INCLUDE_DIR) + + if (NOT WIN32) + # use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + find_package(PkgConfig) + pkg_check_modules(PC_FONTCONFIG fontconfig) + + set(FONTCONFIG_DEFINITIONS ${PC_FONTCONFIG_CFLAGS_OTHER}) + endif (NOT WIN32) + + find_path(FONTCONFIG_INCLUDE_DIR fontconfig/fontconfig.h + PATHS + ${PC_FONTCONFIG_INCLUDEDIR} + ${PC_FONTCONFIG_INCLUDE_DIRS} + /usr/X11/include + ) + + find_library(FONTCONFIG_LIBRARIES NAMES fontconfig + PATHS + ${PC_FONTCONFIG_LIBDIR} + ${PC_FONTCONFIG_LIBRARY_DIRS} + ) + + include(FindPackageHandleStandardArgs) + FIND_PACKAGE_HANDLE_STANDARD_ARGS(Fontconfig DEFAULT_MSG FONTCONFIG_LIBRARIES FONTCONFIG_INCLUDE_DIR ) + + mark_as_advanced(FONTCONFIG_LIBRARIES FONTCONFIG_INCLUDE_DIR) + +endif (FONTCONFIG_LIBRARIES AND FONTCONFIG_INCLUDE_DIR) diff --git a/build/cmake/OS.cmake b/build/cmake/OS.cmake new file mode 100644 index 00000000..022e1e83 --- /dev/null +++ b/build/cmake/OS.cmake @@ -0,0 +1,70 @@ +########### OS +# https://blog.kowalczyk.info/article/j/guide-to-predefined-macros-in-c-compilers-gcc-clang-msvc-etc..html +# http://nadeausoftware.com/articles/2012/01/c_c_tip_how_use_compiler_predefined_macros_detect_operating_system + +if(WIN32) + target_compile_definitions(nana PUBLIC WIN32) # todo: why not simple test for _WIN32 in code?? + set(CMAKE_DEBUG_POSTFIX "_d") # ?? + # Global MSVC definitions. You may prefer the hand-tuned sln and projects from the nana repository. + if(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) + target_compile_options(nana PUBLIC "/MP" ) + endif() + + 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() + endif() + + if(MINGW) + if(NANA_CMAKE_ENABLE_MINGW_STD_THREADS_WITH_MEGANZ) # deprecated ????? + target_compile_definitions(nana PUBLIC STD_THREAD_NOT_SUPPORTED + PUBLIC NANA_ENABLE_MINGW_STD_THREADS_WITH_MEGANZ ) + endif() + endif() +endif() + +if(APPLE) + target_compile_definitions(nana PUBLIC APPLE) # ??? not added by compilers? use __APPLE__ ? + target_include_directories(nana PUBLIC /opt/X11/include/) + target_link_libraries(nana PRIVATE iconv) + set(ENABLE_AUDIO OFF) +endif() + +if(UNIX) + + find_package(X11 REQUIRED) # X11 - todo test PRIVATE + target_link_libraries(nana + PUBLIC ${X11_LIBRARIES} + PUBLIC ${X11_Xft_LIB} + ) + target_include_directories(nana SYSTEM + PUBLIC ${X11_Xft_INCLUDE_PATH} + PUBLIC ${X11_INCLUDE_DIR} + ) + + find_package(Freetype) # Freetype - todo test PRIVATE + if (FREETYPE_FOUND) + find_package(Fontconfig REQUIRED) + target_include_directories(nana SYSTEM + PUBLIC ${FREETYPE_INCLUDE_DIRS} + PUBLIC ${FONTCONFIG_INCLUDE_DIR} + ) + target_link_libraries(nana + PUBLIC ${FREETYPE_LIBRARIES} + PUBLIC ${FONTCONFIG_LIBRARIES} + ) + endif(FREETYPE_FOUND) +endif(UNIX) diff --git a/build/cmake/compilers.cmake b/build/cmake/compilers.cmake new file mode 100644 index 00000000..8cc53765 --- /dev/null +++ b/build/cmake/compilers.cmake @@ -0,0 +1,48 @@ + +########### Compilers +# +# Using gcc: gcc 4.8 don't support C++14 and make_unique. You may want to update at least to 4.9. +# gcc 5.3 and 5.4 include filesytem, but you need to add the link flag: -lstdc++fs +# +# In Windows, with CLion Allways check in File/Settings.../toolchains +# You could install MinGW-w64 from the TDM-GCC Compiler Suite for Windows which will update you to gcc 5.1. +# It is posible to follow https://computingabdn.com/softech/mingw-howto-install-gcc-for-windows/ +# and install MinGW with gcc 7.1 with has STD_THREADS and fs, from: https://sourceforge.net/projects/mingw-w64/files/ +# +# see at end of: https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dynamic_or_shared.html +# + +if(CMAKE_COMPILER_IS_GNUCXX OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") # AND NOT MINGW?? + + target_compile_options(nana PRIVATE -Wall + PUBLIC -g ) + + set(THREADS_PREFER_PTHREAD_FLAG ON) # todo - test this + find_package(Threads REQUIRED) + target_link_libraries(nana PRIVATE Threads::Threads) + # target_compile_options(nana PUBLIC -pthread) + + + if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + if("${CMAKE_SYSTEM_NAME}" MATCHES "FreeBSD") + target_compile_options(nana PUBLIC -I/usr/local/include) + endif() + endif() + + + # target_link_libraries(nana PRIVATE stdc++fs) # ?? + + +endif() + + +if (APPLE AND "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") # APPLE Clang + target_compile_options(nana PUBLIC -stdlib=libstdc++) +endif () + + + +if("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") + target_compile_options(nana PRIVATE -fmax-errors=3) +endif() + diff --git a/build/cmake/enable_audio.cmake b/build/cmake/enable_audio.cmake new file mode 100644 index 00000000..77ca7a4f --- /dev/null +++ b/build/cmake/enable_audio.cmake @@ -0,0 +1,15 @@ +option(NANA_CMAKE_ENABLE_AUDIO "Enable class audio::play for PCM playback." OFF) + +# todo: decide - PUBLIC vs PRIVATE +if(NANA_CMAKE_ENABLE_AUDIO) + target_compile_definitions(nana PUBLIC NANA_ENABLE_AUDIO) + if(UNIX) + find_package(ASOUND) # ? https://github.com/hintjens/demidi/blob/master/Findasound.cmake + if(ASOUND_FOUND) + target_include_directories(nana PUBLIC ${ASOUND_INCLUDE_DIRS}) + target_link_libraries(nana PUBLIC ${ASOUND_LIBRARIES}) + else() + message(FATAL_ERROR "libasound is not found") + endif() + endif() +endif() \ No newline at end of file diff --git a/build/cmake/enable_jpeg.cmake b/build/cmake/enable_jpeg.cmake new file mode 100644 index 00000000..008df7dc --- /dev/null +++ b/build/cmake/enable_jpeg.cmake @@ -0,0 +1,24 @@ +option(NANA_CMAKE_ENABLE_JPEG "Enable the use of JPEG" OFF) +option(NANA_CMAKE_LIBJPEG_FROM_OS "Use libjpeg from operating system." ON) +option(JPEG_HAVE_BOOLEAN "Defining HAVE_BOOLEAN before including jpeglib.h" OFF) + +# todo: decide - PUBLIC vs PRIVATE + +if(NANA_CMAKE_ENABLE_JPEG) + target_compile_definitions(nana PUBLIC NANA_ENABLE_JPEG) + if(NANA_CMAKE_LIBJPEG_FROM_OS) + find_package(JPEG) + if(JPEG_FOUND) + target_include_directories(nana PUBLIC ${JPEG_INCLUDE_DIRS}) + target_link_libraries (nana PUBLIC ${JPEG_LIBRARIES}) + target_compile_definitions(nana PUBLIC USE_LIBJPEG_FROM_OS) + endif() + else() + target_compile_definitions(nana PUBLIC -ljpeg) + endif() + if(JPEG_HAVE_BOOLEAN) + # ... Defining HAVE_BOOLEAN before including jpeglib.h should make it work... + target_compile_definitions(nana PUBLIC HAVE_BOOLEAN) + endif() + +endif() \ No newline at end of file diff --git a/build/cmake/enable_png.cmake b/build/cmake/enable_png.cmake new file mode 100644 index 00000000..7d3a5134 --- /dev/null +++ b/build/cmake/enable_png.cmake @@ -0,0 +1,20 @@ +option(NANA_CMAKE_ENABLE_PNG "Enable the use of PNG" OFF) +option(NANA_CMAKE_LIBPNG_FROM_OS "Use libpng from operating system." ON) + +# todo: decide - PUBLIC vs PRIVATE + +if(NANA_CMAKE_ENABLE_PNG) + target_compile_definitions(nana PUBLIC NANA_ENABLE_PNG) + if(NANA_CMAKE_LIBPNG_FROM_OS) + find_package(PNG) + if(PNG_FOUND) + target_include_directories(nana PUBLIC ${PNG_INCLUDE_DIRS}) + target_link_libraries (nana PUBLIC ${PNG_LIBRARIES}) + target_compile_definitions(nana PUBLIC USE_LIBPNG_FROM_OS ${PNG_DEFINITIONS}) + # target_include_directories (nana SYSTEM PUBLIC PNG::PNG) # ?? + # target_compile_definitions (nana PUBLIC USE_LIBPNG_FROM_OS) + endif() + else() + target_link_libraries(nana PUBLIC png) # provided by nana? + endif() +endif() \ No newline at end of file diff --git a/build/cmake/install_nana.cmake b/build/cmake/install_nana.cmake new file mode 100644 index 00000000..2b6851d6 --- /dev/null +++ b/build/cmake/install_nana.cmake @@ -0,0 +1,23 @@ +option(NANA_CMAKE_INSTALL "Install nana when compile the library (to be consumed without cmake)" OFF) + +# Install the include directories too. +if(NANA_CMAKE_INSTALL) + # this is the prefered method to consume nana directly with some specific bulid system + # Is your responsability to ensure all compiler options are compatible with the compilation + # of the project linking to the nana lib here generated + target_sources(nana PRIVATE ${HEADERS}) + target_include_directories(nana PRIVATE ${NANA_INCLUDE_DIR}) + message("The compiled Nana library will be installed in ${CMAKE_INSTALL_PREFIX}/lib") + # Actually in DESTDIR/CMAKE_INSTALL_PREFIX/lib but in windows there is no DESTDIR/ part. + install(TARGETS nana + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) + install(DIRECTORY ${NANA_INCLUDE_DIR}/nana DESTINATION include) # in ${CMAKE_INSTALL_PREFIX}/include/nana + message("The Nana include files will be installed in ${CMAKE_INSTALL_PREFIX}/include") +else() + # this is the prefered method to consume nana with cmake + target_sources(nana PUBLIC ${HEADERS}) + target_include_directories(nana PUBLIC ${NANA_INCLUDE_DIR}) +endif() + diff --git a/build/cmake/select_filesystem.cmake b/build/cmake/select_filesystem.cmake new file mode 100644 index 00000000..10c9836f --- /dev/null +++ b/build/cmake/select_filesystem.cmake @@ -0,0 +1,55 @@ +# The ISO C++ File System Technical Specification (ISO-TS, or STD) is optional. +# http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4100.pdf +# This is not a workaround, but an user option. +# The library maybe available in the std library in use or from Boost (almost compatible) +# http://www.boost.org/doc/libs/1_60_0/libs/filesystem/doc/index.htm +# or you can choose to use the (partial, but functional) implementation provided by nana. +# If you include the file or +# the selected option will be set by nana into std::experimental::filesystem +# By default Nana will try to use the STD. If STD is not available and NANA_CMAKE_FIND_BOOST_FILESYSTEM +# is set to ON nana will try to use boost if available. Nana own implementation will be use if none of +# the previus were selected or available. +# You can change that default if you change one of the following +# (please don't define more than one of the _XX_FORCE options): +option(NANA_CMAKE_NANA_FILESYSTEM_FORCE "Force nana filesystem over ISO and boost?" OFF) +option(NANA_CMAKE_STD_FILESYSTEM_FORCE "Use of STD filesystem?(a compilation error will ocurre if not available)" OFF) +option(NANA_CMAKE_BOOST_FILESYSTEM_FORCE "Force use of Boost filesystem if available (over STD)?" OFF) +option(NANA_CMAKE_FIND_BOOST_FILESYSTEM "Search: Is Boost filesystem available?" OFF) + +if(NANA_CMAKE_NANA_FILESYSTEM_FORCE) + target_compile_definitions(nana PUBLIC NANA_FILESYSTEM_FORCE) + +elseif(NANA_CMAKE_STD_FILESYSTEM_FORCE) + target_compile_definitions(nana PUBLIC STD_FILESYSTEM_FORCE) + target_link_libraries (nana PUBLIC stdc++fs) + +elseif(NANA_CMAKE_BOOST_FILESYSTEM_FORCE) + target_compile_definitions(nana PUBLIC BOOST_FILESYSTEM_FORCE) + # https://cmake.org/cmake/help/git-master/module/FindBoost.html + # Implicit dependencies such as Boost::filesystem requiring Boost::system will be automatically detected and satisfied, + # even if system is not specified when using find_package and if Boost::system is not added to target_link_libraries. + # If using Boost::thread, then Thread::Thread will also be added automatically. + find_package(Boost REQUIRED COMPONENTS filesystem) + if(Boost_FOUND) + target_compile_definitions(nana PUBLIC BOOST_FILESYSTEM_AVAILABLE) + # SYSTEM - ignore warnings from here + target_include_directories(nana SYSTEM PUBLIC "${Boost_INCLUDE_DIR}") # ?? SYSTEM + target_link_libraries (nana PUBLIC ${Boost_LIBRARIES}) + # target_include_directories (nana SYSTEM PUBLIC Boost::Boost) + # message("boost found true") + endif() + set(Boost_USE_STATIC_LIBS ON) + set(Boost_USE_STATIC_RUNTIME ON) + +else() + # todo test for std (for now just force nana or boost if there no std) + target_link_libraries (nana PUBLIC stdc++fs) + + # todo if not test for boost + # if not add nana filesystem +endif() + + + + + diff --git a/build/cmake/shared_libs.cmake b/build/cmake/shared_libs.cmake new file mode 100644 index 00000000..0734b47a --- /dev/null +++ b/build/cmake/shared_libs.cmake @@ -0,0 +1,51 @@ + +option(BUILD_SHARED_LIBS "Compile nana as a shared library." OFF) + +if(BUILD_SHARED_LIBS) # todo test + + if(WIN32) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + if(MSVC) + set(DLLTOOL OFF) + else() + # mingw: If dlltool is found the def and lib file will be created + find_program (DLLTOOL dlltool) + if(NOT DLLTOOL) + message(WARNING "dlltool not found. Skipping import library generation.") + endif() + endif() + if(DLLTOOL) + #generate the lib and def files needed by msvc + set_target_properties (nana PROPERTIES + OUTPUT_NAME nana + ARCHIVE_OUTPUT_NAME nana + LINK_FLAGS "${CMAKE_SHARED_LINKER_FLAGS_INIT} -Wl,--output-def=${CMAKE_CURRENT_BINARY_DIR}/libnana.def" + ) + + add_custom_command(TARGET nana POST_BUILD + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + COMMAND echo " Generating import library" + COMMAND "${DLLTOOL}" --dllname "libnana.dll" + --input-def "libnana.def" + --output-lib "libnana.lib") + + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libnana.def" + "${CMAKE_CURRENT_BINARY_DIR}/libnana.lib" DESTINATION lib) + endif() + endif() +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") # AND NOT MINGW?? + + if(BUILD_SHARED_LIBS) + target_compile_options(nana PUBLIC -lgcc -lstdc++) + else() + + if(MINGW) + target_compile_options(nana PUBLIC -static) # -static ?? cmake knows BUILD_SHARED_LIBS + else() + target_compile_options(nana PUBLIC -static-libgcc -static-libstdc++) + endif() + endif(BUILD_SHARED_LIBS) + +endif() \ No newline at end of file diff --git a/build/cmake/verbose.cmake b/build/cmake/verbose.cmake new file mode 100644 index 00000000..6d95eb9e --- /dev/null +++ b/build/cmake/verbose.cmake @@ -0,0 +1,57 @@ +option(NANA_CMAKE_VERBOSE_PREPROCESSOR "Show annoying debug messages during compilation." OFF) +option(NANA_CMAKE_STOP_VERBOSE_PREPROCESSOR "Stop compilation after showing the annoying debug messages." OFF) + + +if (NANA_CMAKE_VERBOSE_PREPROCESSOR) + + target_compile_definitions(nana PRIVATE VERBOSE_PREPROCESSOR) + + ### Just for information: ######################################## + include(CMakePrintHelpers) + # see: https://cmake.org/cmake/help/v3.12/manual/cmake-properties.7.html#properties-on-targets + cmake_print_properties(TARGETS nana PROPERTIES + COMPILE_DEFINITIONS COMPILE_OPTIONS COMPILE_FLAGS LINK_LIBRARIES + INCLUDE_DIRECTORIES INSTALL_NAME_DIR LINK_FLAGS VERSION + ) + + #message ("") + # cmake_print_variables(SOURCES) + cmake_print_variables(HEADERS) + cmake_print_variables(PUBLIC_HEADERS) + cmake_print_variables(NANA_CMAKE_INSTALL) + + cmake_print_variables(Boost_INCLUDE_DIR) + cmake_print_variables(Boost_LIBRARIES) + cmake_print_variables(Boost::filesystem) + + cmake_print_variables(PNG_INCLUDE_DIRS) + cmake_print_variables(PNG_LIBRARIES) + cmake_print_variables(PNG::PNG) + + cmake_print_variables(CMAKE_BUILD_TYPE) + cmake_print_variables(CMAKE_CONFIGURATION_TYPES) + message ( "CMAKE_CXX_COMPILER_ID = " ${CMAKE_CXX_COMPILER_ID}) + message ( "COMPILER_IS_CLANG = " ${COMPILER_IS_CLANG}) + message ( "CMAKE_COMPILER_IS_GNUCXX = " ${CMAKE_COMPILER_IS_GNUCXX}) + message ( "CMAKE_CXX_FLAGS = " ${CMAKE_CXX_FLAGS}) + message ( "CMAKE_EXE_LINKER_FLAGS = " ${CMAKE_EXE_LINKER_FLAGS}) + message ( "CMAKE_STATIC_LINKER_FLAGS = " ${CMAKE_STATIC_LINKER_FLAGS}) + + message ( "DESTDIR = " ${DESTDIR}) + message ( "CMAKE_INSTALL_PREFIX = " ${CMAKE_INSTALL_PREFIX}) + message ( "NANA_INCLUDE_DIR = " ${NANA_INCLUDE_DIR}) + message ( "CMAKE_CURRENT_SOURCE_DIR = " ${CMAKE_CURRENT_SOURCE_DIR}) + message ( "NANA_CMAKE_ENABLE_AUDIO = " ${NANA_CMAKE_ENABLE_AUDIO}) + message ( "NANA_CMAKE_SHARED_LIB = " ${NANA_CMAKE_SHARED_LIB}) + message ( "CMAKE_MAKE_PROGRAM = " ${CMAKE_MAKE_PROGRAM}) + message ( "CMAKE_CXX_COMPILER_VERSION = " ${CMAKE_CXX_COMPILER_VERSION}) + + message ( "NANA_CMAKE_NANA_FILESYSTEM_FORCE = " ${NANA_CMAKE_NANA_FILESYSTEM_FORCE}) + message ( "NANA_CMAKE_FIND_BOOST_FILESYSTEM = " ${NANA_CMAKE_FIND_BOOST_FILESYSTEM}) + message ( "NANA_CMAKE_BOOST_FILESYSTEM_FORCE = " ${NANA_CMAKE_BOOST_FILESYSTEM_FORCE}) + 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}) + +endif(NANA_CMAKE_VERBOSE_PREPROCESSOR) \ No newline at end of file diff --git a/build/codeblocks/nana.cbp b/build/codeblocks/nana.cbp index 18c78755..188c964d 100644 --- a/build/codeblocks/nana.cbp +++ b/build/codeblocks/nana.cbp @@ -66,6 +66,7 @@ + @@ -87,7 +88,6 @@ - diff --git a/build/vc2013/nana.vcxproj b/build/vc2013/nana.vcxproj index 8501f83f..7774a855 100644 --- a/build/vc2013/nana.vcxproj +++ b/build/vc2013/nana.vcxproj @@ -202,6 +202,7 @@ + @@ -223,7 +224,6 @@ - @@ -324,7 +324,6 @@ - diff --git a/build/vc2013/nana.vcxproj.filters b/build/vc2013/nana.vcxproj.filters index 6fe722eb..7337c804 100644 --- a/build/vc2013/nana.vcxproj.filters +++ b/build/vc2013/nana.vcxproj.filters @@ -147,9 +147,6 @@ Source Files\nana\gui\widgets - - Source Files\nana\gui\widgets - Source Files\nana\gui\widgets @@ -333,6 +330,9 @@ Source Files\nana\detail + + Source Files\nana\gui + @@ -361,9 +361,6 @@ Header Files\gui\widgets - - Header Files\gui\widgets - Header Files\gui\widgets diff --git a/build/vc2015/nana.vcxproj b/build/vc2015/nana.vcxproj index 3ec4cf7d..37189fc6 100644 --- a/build/vc2015/nana.vcxproj +++ b/build/vc2015/nana.vcxproj @@ -196,6 +196,7 @@ + @@ -217,7 +218,6 @@ - diff --git a/build/vc2015/nana.vcxproj.filters b/build/vc2015/nana.vcxproj.filters index d1d7ba78..8c2e213f 100644 --- a/build/vc2015/nana.vcxproj.filters +++ b/build/vc2015/nana.vcxproj.filters @@ -141,9 +141,6 @@ Source Files\gui\widgets - - Source Files\gui\widgets - Source Files\gui\widgets @@ -291,6 +288,9 @@ Source Files\detail + + Source Files\gui + diff --git a/build/vc2017/nana.vcxproj b/build/vc2017/nana.vcxproj index cc3c70dc..7ae3d890 100644 --- a/build/vc2017/nana.vcxproj +++ b/build/vc2017/nana.vcxproj @@ -23,7 +23,6 @@ {42D0520F-EFA5-4831-84FE-2B9085301C5D} Win32Proj nana - 8.1 @@ -102,6 +101,7 @@ Disabled WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) MultiThreadedDebug + true Windows @@ -115,6 +115,7 @@ Disabled _DEBUG;_LIB;%(PreprocessorDefinitions) MultiThreadedDebug + true Windows @@ -130,6 +131,7 @@ true WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) MultiThreaded + true Windows @@ -147,6 +149,7 @@ true NDEBUG;_LIB;%(PreprocessorDefinitions) MultiThreaded + true Windows @@ -179,6 +182,7 @@ + @@ -200,7 +204,6 @@ - @@ -270,7 +273,6 @@ - diff --git a/build/vc2017/nana.vcxproj.filters b/build/vc2017/nana.vcxproj.filters index 10e92180..e88f7ed3 100644 --- a/build/vc2017/nana.vcxproj.filters +++ b/build/vc2017/nana.vcxproj.filters @@ -151,9 +151,6 @@ Sources\gui\widgets - - Sources\gui\widgets - Sources\gui\widgets @@ -292,6 +289,9 @@ Sources\gui + + Sources\gui + @@ -363,9 +363,6 @@ Include\gui\widgets - - Include\gui\widgets - Include\gui diff --git a/how-to-install.txt b/how-to-install.txt index 5e18a1a2..c3455f7c 100644 --- a/how-to-install.txt +++ b/how-to-install.txt @@ -1,7 +1,14 @@ Please refer to the Library Installation Documentation for the detailed installation instructions. -http://nanapro.org/en-us/help/instl_lib_doc.htm +https://github.com/qPCR4vir/nana-docs/wiki/Installation NOTE: The method of library installation for VC2003/2005/2008/2010/2012 is only for the version older than 0.8. Since version 0.8, the library only works in VC2013 and later. If you have not VC2013 installed, please choose the latest Codeblocks instead. +Using Clang +https://github.com/cnjinhao/nana/wiki/Compiling-Nana-with-Clang-8.0 + 请参考库安装文档获取详细的安装步骤。 -http://nanapro.org/zh-cn/help/instl_lib_doc.htm -注意: 针对VC2003/2005/2008/2010/2012的安装方法只适用于低于0.8的版本。从0.8版起,该库对Visual C++的最低版本要求是2013。如果您没有安装VC2013,可以使用最新版的CodeBlocks代替。 \ No newline at end of file +https://github.com/qPCR4vir/nana-docs/wiki/Installation +注意: 针对VC2003/2005/2008/2010/2012的安装方法只适用于低于0.8的版本。从0.8版起,该库对Visual C++的最低版本要求是2013。如果您没有安装VC2013,可以使用最新版的CodeBlocks代替。 + + +使用 Clang +https://github.com/cnjinhao/nana/wiki/Compiling-Nana-with-Clang-8.0 \ No newline at end of file diff --git a/include/nana/basic_types.hpp b/include/nana/basic_types.hpp index a041279b..0821c91b 100644 --- a/include/nana/basic_types.hpp +++ b/include/nana/basic_types.hpp @@ -1,13 +1,13 @@ -/* +/** * Basic Types definition * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2018 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/basic_types.hpp + * @file nana/basic_types.hpp */ #ifndef NANA_BASIC_TYPES_HPP diff --git a/include/nana/c++defines.hpp b/include/nana/c++defines.hpp index ef5d8e9f..9456887f 100644 --- a/include/nana/c++defines.hpp +++ b/include/nana/c++defines.hpp @@ -211,10 +211,10 @@ #undef _nana_std_optional -#if ((defined(_MSC_VER) && (_MSC_VER >= 1912) && ((!defined(_MSVC_LANG)) || _MSVC_LANG < 201703))) || \ - ((__cplusplus < 201703L) || \ +#if ((defined(_MSC_VER) && ((!defined(_MSVC_LANG)) || _MSVC_LANG < 201703))) || \ + ((!defined(_MSC_VER)) && ((__cplusplus < 201703L) || \ (defined(__clang__) && (__clang_major__ * 100 + __clang_minor__ < 400)) || \ - (!defined(__clang__) && defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ < 701)) \ + (!defined(__clang__) && defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ < 701))) \ ) # define _nana_std_optional #endif diff --git a/include/nana/config.hpp b/include/nana/config.hpp index 17c5cdc2..1d88b379 100644 --- a/include/nana/config.hpp +++ b/include/nana/config.hpp @@ -1,7 +1,7 @@ /** * Nana Configuration * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -25,10 +25,6 @@ #include "c++defines.hpp" -//This marco is defined since 1.4 and until 1.5 for deprecating frame widget. -//This marco and class frame will be removed in version 1.5 -#define WIDGET_FRAME_DEPRECATED - //The following basic configurations are ignored when NANA_IGNORE_CONF is defined. //The NANA_IGNORE_CONF may be specified by CMake generated makefile. #ifndef NANA_IGNORE_CONF @@ -71,7 +67,7 @@ /////////////////// // Support of PCM playback // -#define NANA_ENABLE_AUDIO +//#define NANA_ENABLE_AUDIO /////////////////// // Support for PNG diff --git a/include/nana/datetime.hpp b/include/nana/datetime.hpp index b6678226..c87d58a3 100644 --- a/include/nana/datetime.hpp +++ b/include/nana/datetime.hpp @@ -1,6 +1,6 @@ /* * A Date Time Implementation - * Copyright(C) 2003-2013 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -27,7 +27,7 @@ namespace nana }; date(); ///< the initialized date is today. - date(const std::tm&); + explicit date(const std::tm&); date(int year, int month, int day); date operator - (int off) const; @@ -44,8 +44,8 @@ namespace nana void set(const std::tm&); static int day_of_week(int year, int month, int day); - static unsigned year_days(unsigned year); ///< the number of days in the specified year. - static unsigned month_days(unsigned year, unsigned month); ///< the number of days in the specified month. + static unsigned year_days(const unsigned year); ///< the number of days in the specified year. + static unsigned month_days(const unsigned year, const unsigned month); ///< the number of days in the specified month. static unsigned day_in_year(unsigned y, unsigned m, unsigned d); ///< Returns the index of the specified day in this year, at range[1, 365] or [1, 366] private: date _m_add(unsigned x) const; diff --git a/include/nana/filesystem/filesystem.hpp b/include/nana/filesystem/filesystem.hpp index b67a00ef..f14c043f 100644 --- a/include/nana/filesystem/filesystem.hpp +++ b/include/nana/filesystem/filesystem.hpp @@ -1,7 +1,7 @@ /** * A ISO C++ filesystem Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -13,7 +13,7 @@ * and need VC2015 or a C++11 compiler. With a few correction can be compiler by VC2013 */ -// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4100.pdf --- last pdf of std draft N4100 2014-07-04 +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4100.pdf --- pdf of std draft N4100 2014-07-04 // http://en.cppreference.com/w/cpp/experimental/fs // http://cpprocks.com/introduction-to-tr2-filesystem-library-in-vs2012/ --- TR2 filesystem in VS2012 // https://msdn.microsoft.com/en-us/library/hh874694%28v=vs.140%29.aspx --- C++ 14, the header VS2015 @@ -55,36 +55,6 @@ #define NANA_USING_BOOST_FILESYSTEM 1 # include # include -// dont include generic_u8string -// http://www.boost.org/doc/libs/1_66_0/boost/filesystem/path.hpp -// enable directory_iterator C++11 range-base for -// http://www.boost.org/doc/libs/1_66_0/boost/filesystem/operations.hpp -// but travis come with an oooold version of boost -// NOT enable directory_iterator C++11 range-base for -// http://www.boost.org/doc/libs/1_54_0/boost/filesystem/operations.hpp -namespace boost -{ - namespace filesystem - { - - // enable directory_iterator C++11 range-base for statement use --------------------// - - // begin() and end() are only used by a range-based for statement in the context of - // auto - thus the top-level const is stripped - so returning const is harmless and - // emphasizes begin() is just a pass through. - inline const directory_iterator& begin(const directory_iterator& iter) BOOST_NOEXCEPT - { - return iter; - } - - inline directory_iterator end(const directory_iterator&) BOOST_NOEXCEPT - { - return directory_iterator(); - } - - } // namespace filesystem -} - // add boost::filesystem into std::experimental::filesystem namespace std { @@ -105,24 +75,57 @@ namespace std { socket = boost::filesystem::file_type::socket_file, unknown = boost::filesystem::file_type::type_unknown, }; - /// enable directory_iterator range-based for statements - //inline directory_iterator begin(directory_iterator iter) noexcept - //{ - // return iter; - //} +// Boost dont include generic_u8string +// http://www.boost.org/doc/libs/1_66_0/boost/filesystem/path.hpp +// +// Boost versions: 1.67.0, 1.66.0, ... 1.56.0 enable directory_iterator C++11 range-base for +// http://www.boost.org/doc/libs/1_66_0/boost/filesystem/operations.hpp +// but travis come with an oooold version of boost +// 1.55.0 NOT enable directory_iterator C++11 range-base for +// http://www.boost.org/doc/libs/1_54_0/boost/filesystem/operations.hpp +#if BOOST_VERSION < 105600 + namespace boost + // enable directory_iterator C++11 range-base for statement use --------------------// + + // begin() and end() are only used by a range-based for statement in the context of + // auto - thus the top-level const is stripped - so returning const is harmless and + // emphasizes begin() is just a pass through. + inline const directory_iterator& begin(const directory_iterator& iter) BOOST_NOEXCEPT + { + return iter; + } + + inline directory_iterator end(const directory_iterator&) BOOST_NOEXCEPT + { + return directory_iterator(); + } +#endif - //inline directory_iterator end(const directory_iterator&) noexcept - //{ - // return {}; - //} } // filesystem } // experimental + + namespace filesystem + { + using namespace experimental::filesystem; + } } // std #else # undef NANA_USING_STD_FILESYSTEM # define NANA_USING_STD_FILESYSTEM 1 -# include +# if ((defined(_MSC_VER) && (_MSC_VER >= 1912) && defined(_MSVC_LANG) && _MSVC_LANG >= 201703)) || \ + ((__cplusplus >= 201703L) && \ + (defined(__clang__) && (__clang_major__ >= 7) || \ + (!defined(__clang__) && defined(__GNUC__) && (__GNUC__ >= 8))) ) +# include +# else +# include + namespace std{ + namespace filesystem{ + using namespace std::experimental::filesystem; + } + } +# endif #endif @@ -172,7 +175,13 @@ namespace nana { namespace experimental { namespace filesystem unknown = 0xFFFF ///< not known, such as when a file_status object is created without specifying the permissions }; //enum class copy_options; - //enum class directory_options; + + enum class directory_options + { + none, + follow_directory_symlink, + skip_permission_denied + }; struct space_info { @@ -231,7 +240,7 @@ namespace nana { namespace experimental { namespace filesystem } // modifiers - //void clear() noexcept; + void clear() noexcept; path& make_preferred(); path& remove_filename(); //path& replace_filename(const path& replacement); @@ -239,10 +248,10 @@ namespace nana { namespace experimental { namespace filesystem //void swap(path& rhs) noexcept; // decomposition - //path root_name() const; - //path root_directory() const; - //path root_path() const; - //path relative_path() const; + path root_name() const; + path root_directory() const; + path root_path() const; + path relative_path() const; path parent_path() const; path filename() const; //path stem() const; @@ -250,16 +259,16 @@ namespace nana { namespace experimental { namespace filesystem // query bool empty() const noexcept; - //bool has_root_name() const; - //bool has_root_directory() const; - //bool has_root_path() const; - //bool has_relative_path() const; - bool has_parent_path() const { return !parent_path().string().empty(); }; // temp;; - bool has_filename() const { return !filename().string().empty(); }; // temp; + bool has_root_name() const { return !root_name().empty(); } + bool has_root_directory() const { return !root_directory().empty(); } + bool has_root_path() const { return !root_path().empty(); } + bool has_relative_path() const { return !relative_path().empty(); } + bool has_parent_path() const { return !parent_path().empty(); }; // temp;; + bool has_filename() const { return !filename().empty(); }; // temp; //bool has_stem() const; - bool has_extension() const { return !extension().string().empty(); }; // temp - //bool is_absolute() const; - //bool is_relative() const; + bool has_extension() const { return !extension().empty(); }; // temp + bool is_absolute() const; + bool is_relative() const; int compare(const path& other) const; @@ -354,7 +363,8 @@ namespace nana { namespace experimental { namespace filesystem public: directory_iterator() noexcept; - explicit directory_iterator(const path& dir); + explicit directory_iterator(const path& p); + directory_iterator(const path& p, directory_options opt); const value_type& operator*() const; const value_type* operator->() const; @@ -378,6 +388,7 @@ namespace nana { namespace experimental { namespace filesystem private: bool end_{false}; path::string_type path_; + directory_options option_{ directory_options::none }; std::shared_ptr find_ptr_; find_handle handle_{nullptr}; @@ -417,14 +428,13 @@ namespace nana { namespace experimental { namespace filesystem file_status status(const path& p, std::error_code&); std::uintmax_t file_size(const path& p); - //uintmax_t file_size(const path& p, error_code& ec) noexcept; + std::uintmax_t file_size(const path& p, std::error_code& ec) noexcept; inline bool is_directory(file_status s) noexcept { return s.type() == file_type::directory ;} bool is_directory(const path& p); - - //bool is_directory(const path& p, error_code& ec) noexcept; + bool is_directory(const path& p, std::error_code& ec) noexcept; inline bool is_regular_file(file_status s) noexcept { @@ -520,10 +530,31 @@ namespace std { # else using namespace nana::experimental::filesystem::v1; # endif + } // filesystem } // experimental + + namespace filesystem { + using namespace std::experimental::filesystem; + +#if defined(NANA_FILESYSTEM_FORCE) || \ + (defined(_MSC_VER) && ((!defined(_MSVC_LANG)) || (_MSVC_LANG < 201703))) + path absolute(const path& p); + path absolute(const path& p, std::error_code& err); + + path canonical(const path& p); + path canonical(const path& p, std::error_code& err); +#endif + +#if defined(NANA_FILESYSTEM_FORCE) || defined(NANA_MINGW) + bool exists( std::filesystem::file_status s ) noexcept; + bool exists( const std::filesystem::path& p ); + bool exists( const std::filesystem::path& p, std::error_code& ec ) noexcept; +#endif + } } // std + #endif //NANA_USING_NANA_FILESYSTEM #include diff --git a/include/nana/filesystem/filesystem_ext.hpp b/include/nana/filesystem/filesystem_ext.hpp index ca579e8e..7bc69f0c 100644 --- a/include/nana/filesystem/filesystem_ext.hpp +++ b/include/nana/filesystem/filesystem_ext.hpp @@ -33,10 +33,10 @@ namespace filesystem_ext constexpr auto const def_rootname = "Root/"; #endif -std::experimental::filesystem::path path_user(); ///< extention ? +std::filesystem::path path_user(); ///< extention ? /// workaround Boost not having path.generic_u8string() - a good point for http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0251r0.pdf -inline std::string generic_u8string(const std::experimental::filesystem::path& p) +inline std::string generic_u8string(const std::filesystem::path& p) { #if NANA_USING_BOOST_FILESYSTEM return nana::to_utf8(p.generic_wstring()); @@ -45,15 +45,15 @@ inline std::string generic_u8string(const std::experimental::filesystem::path& p #endif } -inline bool is_directory(const std::experimental::filesystem::directory_entry& dir) noexcept +inline bool is_directory(const std::filesystem::directory_entry& dir) noexcept { return is_directory(dir.status()); } //template // DI = directory_iterator from std, boost, or nana : return directory_entry -class directory_only_iterator : public std::experimental::filesystem::directory_iterator +class directory_only_iterator : public std::filesystem::directory_iterator { - using directory_iterator = std::experimental::filesystem::directory_iterator; + using directory_iterator = std::filesystem::directory_iterator; directory_only_iterator& find_first() { @@ -93,9 +93,9 @@ inline directory_only_iterator end(const directory_only_iterator&) noexcept } //template // DI = directory_iterator from std, boost, or nana : value_type directory_entry -class regular_file_only_iterator : public std::experimental::filesystem::directory_iterator +class regular_file_only_iterator : public std::filesystem::directory_iterator { - using directory_iterator = std::experimental::filesystem::directory_iterator; + using directory_iterator = std::filesystem::directory_iterator; regular_file_only_iterator& find_first() { while (((*this) != directory_iterator{}) && !is_regular_file((**this).status())) @@ -128,11 +128,11 @@ inline regular_file_only_iterator end(const regular_file_only_iterator&) noexcep return{}; } -std::string pretty_file_size(const std::experimental::filesystem::path& path); +std::string pretty_file_size(const std::filesystem::path& path); -std::string pretty_file_date(const std::experimental::filesystem::path& path); +std::string pretty_file_date(const std::filesystem::path& path); -bool modified_file_time(const std::experimental::filesystem::path& p, struct tm&); ///< extention ? +bool modified_file_time(const std::filesystem::path& p, struct tm&); ///< extention ? } // filesystem_ext } // nana diff --git a/include/nana/gui/animation.hpp b/include/nana/gui/animation.hpp index 9f0df563..e8338399 100644 --- a/include/nana/gui/animation.hpp +++ b/include/nana/gui/animation.hpp @@ -1,7 +1,7 @@ /* * An Animation Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -49,24 +49,18 @@ namespace nana struct impl; class performance_manager; + + /// Non-copyable + animation(const animation&) = delete; + animation& operator=(const animation&) = delete; public: animation(std::size_t fps = 23); ~animation(); - void push_back(frameset frms); - /* - void branch(const std::string& name, const frameset& frms) - { - impl_->branches[name].frames = frms; - } + animation(animation&&); + animation& operator=(animation&&); - void branch(const std::string& name, const frameset& frms, std::function condition) - { - auto & br = impl_->branches[name]; - br.frames = frms; - br.condition = condition; - } - */ + void push_back(frameset frms); void looped(bool enable); ///< Enables or disables the animation repeating playback. diff --git a/include/nana/gui/basis.hpp b/include/nana/gui/basis.hpp index a582d7b9..1e043621 100644 --- a/include/nana/gui/basis.hpp +++ b/include/nana/gui/basis.hpp @@ -4,7 +4,7 @@ * * Basis Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -65,6 +65,13 @@ namespace nana blend }; + enum class dragdrop_status + { + not_ready, + ready, + in_progress + }; + namespace category { enum class flags @@ -73,17 +80,11 @@ namespace nana widget = 0x1, lite_widget = 0x3, root = 0x5 -#ifndef WIDGET_FRAME_DEPRECATED - ,frame = 0x9 -#endif }; //wait for constexpr struct widget_tag{ static const flags value = flags::widget; }; struct lite_widget_tag : public widget_tag{ static const flags value = flags::lite_widget; }; struct root_tag : public widget_tag{ static const flags value = flags::root; }; -#ifndef WIDGET_FRAME_DEPRECATED - struct frame_tag : public widget_tag{ static const flags value = flags::frame; }; -#endif }// end namespace category using native_window_type = detail::native_window_handle_impl*; diff --git a/include/nana/gui/detail/basic_window.hpp b/include/nana/gui/detail/basic_window.hpp index 6c5bc202..b79ec168 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-2017 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -116,10 +116,6 @@ namespace detail /// bind a native window and baisc_window void bind_native_window(native_window_type, unsigned width, unsigned height, unsigned extra_width, unsigned extra_height, paint::graphics&); -#ifndef WIDGET_FRAME_DEPRECATED - void frame_window(native_window_type); -#endif - bool is_ancestor_of(const basic_window* wd) const; bool visible_parents() const; bool displayed() const; @@ -179,7 +175,9 @@ namespace detail bool ignore_menubar_focus : 1; ///< A flag indicates whether the menubar sets the focus. bool ignore_mouse_focus : 1; ///< A flag indicates whether the widget accepts focus when clicking on it bool space_click_enabled : 1; ///< A flag indicates whether enable mouse_down/click/mouse_up when pressing and releasing whitespace key. - unsigned Reserved :18; + bool draggable : 1; + bool ignore_child_mapping : 1; + unsigned Reserved :16; unsigned char tab; ///< indicate a window that can receive the keyboard TAB mouse_action action; mouse_action action_before; @@ -205,19 +203,8 @@ namespace detail struct other_tag { -#ifndef WIDGET_FRAME_DEPRECATED - struct attr_frame_tag - { - native_window_type container{nullptr}; - std::vector attach; - }; -#endif - struct attr_root_tag { -#ifndef WIDGET_FRAME_DEPRECATED - container frames; ///< initialization is null, it will be created while creating a frame widget. Refer to WindowManager::create_frame -#endif container tabstop; std::vector effects_edge_nimbus; basic_window* focus{nullptr}; @@ -234,13 +221,13 @@ namespace detail ///< if the active_window is null, the parent of this window keeps focus. paint::graphics glass_buffer; ///< if effect.bground is avaiable. Refer to window_layout::make_bground. update_state upd_state; + dragdrop_status dnd_state{ dragdrop_status::not_ready }; + + container mapping_requester; ///< Children which are ignored to mapping union { attr_root_tag * root; -#ifndef WIDGET_FRAME_DEPRECATED - attr_frame_tag * frame; -#endif }attribute; other_tag(category::flags); diff --git a/include/nana/gui/detail/bedrock_pi_data.hpp b/include/nana/gui/detail/bedrock_pi_data.hpp deleted file mode 100644 index 7404777b..00000000 --- a/include/nana/gui/detail/bedrock_pi_data.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef NANA_DETAIL_BEDROCK_PI_DATA_HPP -#define NANA_DETAIL_BEDROCK_PI_DATA_HPP - -#include - -#include -#include "color_schemes.hpp" -#include "events_operation.hpp" -#include "window_manager.hpp" -#include - -namespace nana -{ - namespace detail - { - struct bedrock::pi_data - { - color_schemes scheme; - events_operation evt_operation; - window_manager wd_manager; - std::set auto_form_set; - bool shortkey_occurred{ false }; - - struct menu_rep - { - core_window_t* taken_window{ nullptr }; - bool delay_restore{ false }; - native_window_type window{ nullptr }; - native_window_type owner{ nullptr }; - bool has_keyboard{ false }; - }menu; - }; - } -} - -#include - -#endif diff --git a/include/nana/gui/detail/drawer.hpp b/include/nana/gui/detail/drawer.hpp index 63cea67f..7c20db17 100644 --- a/include/nana/gui/detail/drawer.hpp +++ b/include/nana/gui/detail/drawer.hpp @@ -91,7 +91,7 @@ namespace nana virtual void shortkey(graph_reference, const arg_keyboard&); void filter_event(const event_code evt_code, const bool bDisabled); - void filter_event(const std::vector evt_codes, const bool bDisabled); + void filter_event(const std::vector& evt_codes, const bool bDisabled); void filter_event(const event_filter_status& evt_all_states); bool filter_event(const event_code evt_code); event_filter_status filter_event(); diff --git a/include/nana/gui/detail/general_events.hpp b/include/nana/gui/detail/general_events.hpp index 9a9496e2..2171cb7a 100644 --- a/include/nana/gui/detail/general_events.hpp +++ b/include/nana/gui/detail/general_events.hpp @@ -1,7 +1,7 @@ /** * Definition of General Events * Nana C++ Library(http://www.nanapro.org) -* Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) +* Copyright(C) 2003-2019 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 #include "event_code.hpp" #include "internal_scope_guard.hpp" +#include "../../filesystem/filesystem.hpp" #include #include #include @@ -46,12 +47,11 @@ namespace nana struct docker_base : public docker_interface { - event_interface * event_ptr; - bool flag_deleted{ false }; + event_interface * const event_ptr; + bool flag_deleted; const bool unignorable; docker_base(event_interface*, bool unignorable_flag); - detail::event_interface * get_event() const override; }; @@ -117,7 +117,7 @@ namespace nana private: struct docker : public detail::docker_base - { + { /// the callback/response function taking the typed argument std::function invoke; @@ -220,33 +220,16 @@ namespace nana //The dockers may resize when a new event handler is created by a calling handler. //Traverses with position can avaid crash error which caused by a iterator which becomes invalid. - - auto i = dockers_->data(); - auto const end = i + dockers_->size(); - - for (; i != end; ++i) + for (std::size_t i = 0; i < dockers_->size(); ++i) { - if (static_cast(*i)->flag_deleted) + auto d = static_cast(dockers_->data()[i]); + if (d->flag_deleted || (arg.propagation_stopped() && !d->unignorable)) continue; - static_cast(*i)->invoke(arg); + d->invoke(arg); if (window_handle && (!detail::check_window(window_handle))) break; - - if (arg.propagation_stopped()) - { - for (++i; i != end; ++i) - { - if (!static_cast(*i)->unignorable || static_cast(*i)->flag_deleted) - continue; - - static_cast(*i)->invoke(arg); - if (window_handle && (!detail::check_window(window_handle))) - break; - } - break; - } } } private: @@ -297,34 +280,16 @@ namespace nana }; } - static std::function build_second(fn_type&& fn, void(fn_type::*)(arg_reference)) + template + static std::function build_second(Tfn&& fn, Ret(fn_type::*)(arg_reference)) { - return std::move(fn); + return std::forward(fn); } - static std::function build_second(fn_type&& fn, void(fn_type::*)(arg_reference) const) + template + static std::function build_second(Tfn&& fn, Ret(fn_type::*)(arg_reference)const) { - return std::move(fn); - } - - static std::function build_second(fn_type& fn, void(fn_type::*)(arg_reference)) - { - return fn; - } - - static std::function build_second(fn_type& fn, void(fn_type::*)(arg_reference) const) - { - return fn; - } - - static std::function build_second(const fn_type& fn, void(fn_type::*)(arg_reference)) - { - return fn; - } - - static std::function build_second(const fn_type& fn, void(fn_type::*)(arg_reference) const) - { - return fn; + return std::forward(fn); } template @@ -420,7 +385,7 @@ namespace nana typedef typename std::remove_reference::type arg_type; static_assert(std::is_convertible::value, "The parameter type is not allowed, please check the function parameter type where you connected the event function."); - static std::function build(Ret(*fn)(Arg)) + static std::function build(Ret(*fn)(Arg2)) { return[fn](arg_reference arg){ fn(arg); @@ -470,9 +435,9 @@ namespace nana struct arg_dropfiles : public event_arg { - ::nana::window window_handle; ///< A handle to the event window - ::nana::point pos; ///< cursor position in the event window - std::vector files; ///< external filenames + ::nana::window window_handle; ///< A handle to the event window + ::nana::point pos; ///< cursor position in the event window + std::vector files; ///< external filenames }; struct arg_expose : public event_arg diff --git a/include/nana/gui/detail/widget_geometrics.hpp b/include/nana/gui/detail/widget_geometrics.hpp index 1cb8cd3e..d172536e 100644 --- a/include/nana/gui/detail/widget_geometrics.hpp +++ b/include/nana/gui/detail/widget_geometrics.hpp @@ -1,7 +1,7 @@ /* * Widget Geometrics * Nana C++ Library(http://www.nanapro.org) -* Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) +* Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -22,12 +22,17 @@ namespace nana public: color_proxy(const color_proxy&); color_proxy(color_rgb); + color_proxy(color_argb); + color_proxy(color_rgba); color_proxy(colors); color_proxy& operator=(const color_proxy&); color_proxy& operator=(const ::nana::color&); color_proxy& operator=(color_rgb); + color_proxy& operator=(color_argb); + color_proxy& operator=(color_rgba); color_proxy& operator=(colors); color get_color() const; + color get(const color& default_color) const; operator color() const; private: std::shared_ptr color_; diff --git a/include/nana/gui/detail/window_manager.hpp b/include/nana/gui/detail/window_manager.hpp index 0807978d..82e3c57c 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-2017 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -83,12 +83,6 @@ namespace detail core_window_t* create_root(core_window_t*, bool nested, rectangle, const appearance&, widget*); core_window_t* create_widget(core_window_t*, const rectangle&, bool is_lite, widget*); -#ifndef WIDGET_FRAME_DEPRECATED - core_window_t* create_frame(core_window_t*, const rectangle&, widget*); - - bool insert_frame(core_window_t* frame, native_window); - bool insert_frame(core_window_t* frame, core_window_t*); -#endif void close(core_window_t*); //destroy @@ -122,11 +116,8 @@ namespace detail void refresh_tree(core_window_t*); void do_lazy_refresh(core_window_t*, bool force_copy_to_screen, bool refresh_tree = false); + void map_requester(core_window_t*); - bool get_graphics(core_window_t*, nana::paint::graphics&); - bool get_visual_rectangle(core_window_t*, nana::rectangle&); - - std::vector get_children(core_window_t*) const; bool set_parent(core_window_t* wd, core_window_t* new_parent); core_window_t* set_focus(core_window_t*, bool root_has_been_focused, arg_focus::reason); @@ -149,7 +140,6 @@ namespace detail bool register_shortkey(core_window_t*, unsigned long key); void unregister_shortkey(core_window_t*, bool with_children); - std::vector> shortkeys(core_window_t*, bool with_children); core_window_t* find_shortkey(native_window_type, unsigned long key); @@ -159,6 +149,7 @@ namespace detail void _m_disengage(core_window_t*, core_window_t* for_new); void _m_destroy(core_window_t*); void _m_move_core(core_window_t*, const point& delta); + void _m_shortkeys(core_window_t*, bool with_chlidren, std::vector>& keys) const; core_window_t* _m_find(core_window_t*, const point&); static bool _m_effective(core_window_t*, const point& root_pos); private: diff --git a/include/nana/gui/dragdrop.hpp b/include/nana/gui/dragdrop.hpp new file mode 100644 index 00000000..1eeb9541 --- /dev/null +++ b/include/nana/gui/dragdrop.hpp @@ -0,0 +1,129 @@ +/** +* Drag and Drop Implementation +* Nana C++ Library(http://www.nanapro.org) +* Copyright(C) 2018-2019 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/dragdrop.hpp +* @author: Jinhao(cnjinhao@hotmail.com) +*/ +#ifndef NANA_GUI_DRAGDROP_INCLUDED +#define NANA_GUI_DRAGDROP_INCLUDED + +#include +#include +#include "basis.hpp" + +#include +#include + +namespace nana +{ + /// Drag and drop actions + enum class dnd_action + { + copy, ///< Copy the data to target. + move, ///< Move the data to target. + link ///< Create a link from source data to target. + }; + + class simple_dragdrop + { + struct implementation; + + simple_dragdrop(const simple_dragdrop&) = delete; + simple_dragdrop& operator=(const simple_dragdrop&) = delete; + + simple_dragdrop(simple_dragdrop&&) = delete; + simple_dragdrop& operator=(simple_dragdrop&&) = delete; + public: + simple_dragdrop(window source); + ~simple_dragdrop(); + + /// Condition checker + /** + * Sets a condition checker that determines whether the drag-and-drop operation can start. If a condition checker is not set, it always start drag-and-drop operation. + * @param predicate_fn Unary predicate which returns #true# for starting drag-and-drop operation. + */ + void condition(std::function predicate_fn); + void make_drop(window target, std::function drop_fn); + private: + implementation* const impl_; + }; + + namespace detail + { + struct dragdrop_data; + } + + class dragdrop + { + struct implementation; + + /// Non-copyable + dragdrop(const dragdrop&) = delete; + dragdrop& operator=(const dragdrop&) = delete; + + /// Non-movable + dragdrop(dragdrop&&) = delete; + dragdrop& operator=(dragdrop&&) = delete; + public: + class data + { + friend struct dragdrop::implementation; + + /// Non-copyable + data(const data&) = delete; + data& operator=(const data&) = delete; + public: + /// Constructor + /** + * Constructs a data object used for drag and drop + * @param requested_action Indicates how the data to be transferred. + */ + data(dnd_action requested_action = dnd_action::copy); + data(data&&); + ~data(); + + data& operator=(data&& rhs); + + void insert(std::filesystem::path); + private: + detail::dragdrop_data* real_data_; + }; + + dragdrop(window source); + ~dragdrop(); + + /// Condition checker + /*** + * Sets a condition checker that determines whether the drag-and-drop operation can start. If a condition checker is not set, it always start drag-and-drop operation. + * @param predicate_fn A predicate function to be set. + */ + void condition(std::function predicate_fn); + + /// Transferred data + /** + * Set a data generator. When drag begins, it is called to generate a data object for transferring. + * @param generator It returns the data for transferring. + */ + void prepare_data(std::function generator); + + /// Drop handler + /** + * The drop handler is called when the drop operation is completed. There are 3 parameters for the handler + * dropped Indicates whether the data is accepted by a target window. + * executed_action Indicates the action returned by target window. Ignore if dropped is false. + * data_transferred The data object which is generated by the generator. + * @param finish_fn The drop handling function. + */ + void drop_finished(std::function finish_fn); + private: + implementation* const impl_; + }; +} + +#endif \ No newline at end of file diff --git a/include/nana/gui/drawing.hpp b/include/nana/gui/drawing.hpp index 06ecece6..d7f7c7be 100644 --- a/include/nana/gui/drawing.hpp +++ b/include/nana/gui/drawing.hpp @@ -23,9 +23,10 @@ namespace nana class drawing :private nana::noncopyable { + struct draw_fn_handle; public: - typedef struct{}* diehard_t; ///< A handle to a drawing method - typedef std::function draw_fn_t; ///< A function to draw + using diehard_t = draw_fn_handle * ; ///< A handle to a drawing method + using draw_fn_t = std::function; ///< A function to draw drawing(window w); ///< Create a drawing object for a widget w diff --git a/include/nana/gui/effects.hpp b/include/nana/gui/effects.hpp index 3825c07a..c405f8ef 100644 --- a/include/nana/gui/effects.hpp +++ b/include/nana/gui/effects.hpp @@ -1,6 +1,6 @@ /* * Background Effects Implementation - * Copyright(C) 2003-2013 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -45,7 +45,7 @@ namespace nana : public bground_factory_interface { public: - bground_transparent(std::size_t percent); + explicit bground_transparent(std::size_t percent); private: bground_interface* create() const override; private: diff --git a/include/nana/gui/filebox.hpp b/include/nana/gui/filebox.hpp index f6729318..a2bfb4ef 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-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -9,14 +9,13 @@ * * @file nana/gui/filebox.hpp * @author Jinhao - * @brief a dialog to chose file(s), implemented "native" in windows but using nana for X11 + * @brief dialogs to chose file(s) or a directory, implemented "native" in windows but using nana for X11 */ #ifndef NANA_GUI_FILEBOX_HPP #define NANA_GUI_FILEBOX_HPP #include #include -#include #include #include @@ -29,9 +28,8 @@ namespace nana filebox(filebox&&) = delete; filebox& operator=(filebox&&) = delete; public: - using filters = std::vector>; + using path_type = std::filesystem::path; - filebox(bool is_open_mode); filebox(window owner, bool is_open_mode); filebox(const filebox&); ~filebox(); @@ -39,43 +37,54 @@ namespace nana filebox& operator=(const filebox&); /// Change owner window - void owner(window); - - /// Set a new title for the dialog - /// @param string a text for title - /// @return the old title. - ::std::string title( ::std::string new_title); - - /** @brief Suggest initial path used to locate a directory when the filebox starts. - * @param string initial_directory a path of initial directory - * @note the behavior of init_path is different between Win7 and Win2K/XP/Vista, but its behavior under Linux is conformed with Win7. + /** + * Changes the owner window for the filebox. When #show()/operator()# are invoked, the dialog of filebox will be created with the specified owner. + * @param handle A handle to a window which will be used for the owner of filebox */ - filebox& init_path(const ::std::string& initial_directory); + void owner(window handle); - filebox& init_file(const ::std::string&); ///< Init file, if it contains a path, the init path is replaced by the path of init file. + /// Changes new title + /** + * Changes the title. When #show()/operator()# are invoked, the dialog of filebox will be created with the specified title. + * @param text Text of title + * @return the reference of *this. + */ + filebox& title( ::std::string text); + + /// Sets a initial path + /** + * Suggest initial path used to locate a directory when the filebox starts. + * @note the behavior of init_path is different between Win7 and Win2K/XP/Vista, but its behavior under Linux is conformed with Win7. + * @param path a path of initial directory + * @return reference of *this. + */ + filebox& init_path(const path_type& path); + + /// Sets a initial filename + /** + * Suggest a filename when filebox starts. If the filename contains a path, the initial path will be replaced with the path presents in initial filename. + * @param filename a filename used for a initial filename when filebox starts. + * @return reference of *this. + */ + filebox& init_file(const ::std::string& filename); ///< Init file, if it contains a path, the init path is replaced by the path of init file. /// \brief Add a filetype filter. /// To specify multiple filter in a single description, use a semicolon to separate the patterns(for example,"*.TXT;*.DOC;*.BAK"). - filebox& add_filter(const ::std::string& description, ///< for example. "Text File" - const ::std::string& filetype ///< filter pattern(for example, "*.TXT") + filebox& add_filter(const ::std::string& description, ///< for example: "Text File" + const ::std::string& filetype ///< filter pattern(for example: "*.TXT") ); - filebox& add_filter(const filters &ftres) - { - for (auto &f : ftres) - add_filter(f.first, f.second); - return *this; - }; + filebox& add_filter(const std::vector> &filters); - - ::std::string path() const; - ::std::string file() const; + const path_type& path() const; + + filebox& allow_multi_select(bool allow); /// Display the filebox dialog - bool show() const; - + std::vector show() const; + /// a function object method alternative to show() to display the filebox dialog, - bool operator()() const + std::vector operator()() const { return show(); } @@ -92,17 +101,26 @@ namespace nana folderbox(folderbox&&) = delete; folderbox& operator=(folderbox&&) = delete; public: - using path_type = std::experimental::filesystem::path; + using path_type = std::filesystem::path; - folderbox(window owner = nullptr, const path_type& init_path = {}); + explicit folderbox(window owner = nullptr, const path_type& init_path = {}, std::string title={}); ~folderbox(); - std::optional show() const; + /// Enables/disables multi select + folderbox& allow_multi_select(bool allow); - std::optional operator()() const + std::vector show() const; + + std::vector operator()() const { return show(); } + + /// Changes title + /** + * @param text Text of title + */ + folderbox& title(std::string text); private: implement* impl_; }; diff --git a/include/nana/gui/programming_interface.hpp b/include/nana/gui/programming_interface.hpp index 134b0082..48a773ce 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-2017 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -95,9 +95,7 @@ namespace API window create_window(window, bool nested, const rectangle&, const appearance&, widget* attached); window create_widget(window, const rectangle&, widget* attached); window create_lite_widget(window, const rectangle&, widget* attached); -#ifndef WIDGET_FRAME_DEPRECATED - window create_frame(window, const rectangle&, widget* attached); -#endif + paint::graphics* window_graphics(window); void delay_restore(bool); @@ -117,6 +115,9 @@ namespace API void lazy_refresh(); void draw_shortkey_underline(paint::graphics&, const std::string& text, wchar_t shortkey, std::size_t shortkey_position, const point& text_pos, const color&); + + void window_draggable(window, bool enabled); + bool window_draggable(window); }//end namespace dev @@ -229,13 +230,7 @@ namespace API window root(native_window_type); ///< Retrieves the native window of a Nana.GUI window. void fullscreen(window, bool); - bool enabled_double_click(window, bool); -#ifndef WIDGET_FRAME_DEPRECATED - bool insert_frame(window frame, native_window_type); - native_window_type frame_container(window frame); - native_window_type frame_element(window frame, unsigned index); -#endif void close_window(window); void show_window(window, bool show); ///< Sets a window visible state. void restore_window(window); @@ -243,6 +238,7 @@ namespace API bool visible(window); window get_parent_window(window); window get_owner_window(window); + bool set_parent_window(window, window new_parent); template @@ -439,6 +435,7 @@ namespace API /// Sets the window active state. If a window active state is false, the window will not obtain the focus when a mouse clicks on it wich will be obteined by take_if_has_active_false. void take_active(window, bool has_active, window take_if_has_active_false); + /// Copies the graphics of a specified to a new graphics object. bool window_graphics(window, nana::paint::graphics&); bool root_graphics(window, nana::paint::graphics&); bool get_visual_rectangle(window, nana::rectangle&); @@ -476,6 +473,8 @@ namespace API ::std::optional> content_extent(window wd, unsigned limited_px, bool limit_width); unsigned screen_dpi(bool x_requested); + + dragdrop_status window_dragdrop_status(::nana::window); }//end namespace API }//end namespace nana diff --git a/include/nana/gui/widgets/button.hpp b/include/nana/gui/widgets/button.hpp index 9be71fd6..5dc4b9b4 100644 --- a/include/nana/gui/widgets/button.hpp +++ b/include/nana/gui/widgets/button.hpp @@ -1,13 +1,15 @@ /** * A Button Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2018 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/button.hpp + * @contributor + * besh81(pr#361) */ #ifndef NANA_GUI_WIDGET_BUTTON_HPP @@ -90,7 +92,12 @@ namespace nana{ button(window, const char* caption, bool visible = true); button(window, const nana::rectangle& = rectangle(), bool visible = true); - button& icon(const nana::paint::image&); + /// Shows an icon in front of caption + /** + * @param image Icon to be shown. If image is empty, the current icon is erased from the button. + * @return the reference of *this. + */ + button& icon(const nana::paint::image& image); button& enable_pushed(bool); bool pushed() const; button& pushed(bool); diff --git a/include/nana/gui/widgets/checkbox.hpp b/include/nana/gui/widgets/checkbox.hpp index c6f3a064..1a75c91a 100644 --- a/include/nana/gui/widgets/checkbox.hpp +++ b/include/nana/gui/widgets/checkbox.hpp @@ -37,6 +37,13 @@ namespace drawerbase { namespace checkbox { + struct scheme + : public widget_geometrics + { + color_proxy square_bgcolor{ static_cast(0x0) }; + color_proxy square_border_color{ colors::black }; + }; + struct events_type : public general_events { @@ -67,7 +74,7 @@ namespace drawerbase class checkbox - : public widget_object + : public widget_object { public: checkbox(); @@ -97,6 +104,7 @@ namespace drawerbase struct element_tag { checkbox * uiobj; + event_handle eh_clicked; event_handle eh_checked; event_handle eh_destroy; event_handle eh_keyboard; diff --git a/include/nana/gui/widgets/detail/compset.hpp b/include/nana/gui/widgets/detail/compset.hpp index 7e77629d..363bb6a7 100644 --- a/include/nana/gui/widgets/detail/compset.hpp +++ b/include/nana/gui/widgets/detail/compset.hpp @@ -1,6 +1,6 @@ /* * Concept of Component Set - * Copyright(C) 2003-2013 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -46,7 +46,7 @@ namespace nana{ namespace widgets{ namespace detail }; /// A component set placer used for specifying component position and size. - template + template class compset_placer { public: @@ -56,8 +56,11 @@ namespace nana{ namespace widgets{ namespace detail /// A type of widget-defined item attribute. typedef ItemAttribute item_attribute_t; + + /// Widget scheme. + typedef WidgetScheme widget_scheme_t; public: - /// The destrcutor. + /// The destructor. virtual ~compset_placer(){} /// Enable/Disable the specified component. diff --git a/include/nana/gui/widgets/detail/tree_cont.hpp b/include/nana/gui/widgets/detail/tree_cont.hpp index 2490ab95..3aa3b4f9 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-2017 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -63,7 +63,7 @@ namespace detail { while (child) { - if (child->owner == this) + if (child == this) return true; child = child->owner; @@ -357,12 +357,12 @@ namespace detail } template - unsigned distance_if(const node_type * node, PredAllowChild pac) const + std::size_t distance_if(const node_type * node, PredAllowChild pac) const { if(nullptr == node) return 0; const node_type * iterator = root_.child; - unsigned off = 0; + std::size_t off = 0; std::stack stack; while(iterator && iterator != node) @@ -479,6 +479,8 @@ namespace detail return nullptr; } + // foreach elements of key. + // the root name "/" and "\\" are treated as a node. This is a feature that can easily support the UNIX-like path. template void _m_for_each(const ::std::string& key, Function function) const { @@ -491,18 +493,24 @@ namespace detail { if(beg != end) { - if(function(key.substr(beg, end - beg)) == false) + if(!function(key.substr(beg, end - beg))) return; } auto next = key.find_first_not_of("\\/", end); - if(next != ::std::string::npos) - { - beg = next; - end = key.find_first_of("\\/", beg); - } - else + + if ((next == ::std::string::npos) && end) return; + + if (0 == end) + { + if ((!function(key.substr(0, 1))) || (next == ::std::string::npos)) + return; + } + + beg = next; + end = key.find_first_of("\\/", beg); + } function(key.substr(beg, key.size() - beg)); diff --git a/include/nana/gui/widgets/frame.hpp b/include/nana/gui/widgets/frame.hpp deleted file mode 100644 index 82a53d95..00000000 --- a/include/nana/gui/widgets/frame.hpp +++ /dev/null @@ -1,56 +0,0 @@ -/** - * A Frame Implementation - * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2015 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/frame.hpp - * - * @brief A frame provides a way to contain the platform window in a stdex GUI Window - */ - -#ifndef NANA_GUI_WIDGET_FRAME_HPP -#define NANA_GUI_WIDGET_FRAME_HPP - -#include "widget.hpp" - -#ifndef WIDGET_FRAME_DEPRECATED -namespace nana -{ - /** - \brief Container for system native windows. Provides an approach to - display a control that is not written with Nana.GUI in a Nana.GUI window. - - Notes: - - 1. nana::native_window_type is a type of system handle of windows. - 2. all the children windows of a nana::frame is topmost to Nana.GUI windows. - 3. a simple example. Displaying a Windows Edit Control. - - nana::frame frame(parent, 0, 0 200, 100); - HWND frame_handle = reinterpret_cast(frame.container()); - HWND edit = ::CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"Test", - WS_CHILD | WS_VISIBLE | WS_BORDER, 0, 0, 200, 100, - frame_handle, 0, ::GetModuleHandle(0), 0); - if(edit) - frame.insert(edit); - - */ - class frame: public widget_object - { - typedef widget_object base_type; - public: - frame(); - frame(window, bool visible); - frame(window, const rectangle& = rectangle(), bool visible = true); - bool insert(native_window_type); ///< Inserts a platform native window. - native_window_type element(unsigned index); ///< Returns the child window through index. - - native_window_type container() const; ///< Returns the frame container native window handle. - }; -}//end namespace nana -#endif -#endif diff --git a/include/nana/gui/widgets/group.hpp b/include/nana/gui/widgets/group.hpp index ba48928b..3ca870d2 100644 --- a/include/nana/gui/widgets/group.hpp +++ b/include/nana/gui/widgets/group.hpp @@ -3,8 +3,8 @@ * Nana C++ Library(http://www.nanaro.org) * Copyright(C) 2015-2018 Jinhao(cnjinhao@hotmail.com) * - * Distributed under the Boost Software License, Version 1.0. - * (See accompanying file LICENSE_1_0.txt or copy at + * 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/group.hpp @@ -42,6 +42,13 @@ namespace nana{ using field_reference = place::field_reference; constexpr static const std::size_t npos = static_cast(-1); + enum class background_mode + { + none, + transparent, + blending + }; + /// The default construction group(); @@ -66,17 +73,28 @@ namespace nana{ checkbox& add_option(::std::string); /// Modifies the alignment of the title - void caption_align(align position); + group& caption_align(align position); + group& caption_background_mode(background_mode mode); /// Enables/disables the radio mode which is single selection group& radio_mode(bool); - /// Returns the index of option in radio_mode, it throws a logic_error if radio_mode is false. + /// Returns the index of selected option in radio_mode, it throws a logic_error if radio_mode is false. std::size_t option() const; + /** Set check/unchecked status of specified option + @param[in] pos zero-based index of option to set + @param[in] check status required, defaults to checked + throws an out_of_range if !(pos < number of options) + */ + void option_check( std::size_t pos, bool check = true ); + /// Determines whether a specified option is checked, it throws an out_of_range if !(pos < number of options) bool option_checked(std::size_t pos) const; + /// Change typeface of caption label ( does not effect child widgets ) + void typeface( const nana::paint::font& font ); + group& enable_format_caption(bool format); group& collocate() noexcept; @@ -86,7 +104,7 @@ namespace nana{ void field_display(const char* field_name, bool display); /// Widget* create_child(const char* field, Args && ... args) { diff --git a/include/nana/gui/widgets/listbox.hpp b/include/nana/gui/widgets/listbox.hpp index a2eee239..b6279630 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-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -1461,6 +1461,23 @@ the nana::detail::basic_window member pointer scheme /// Returns the number of columns size_type column_size() const; + /// Move column to view_position + void move_column(size_type abs_pos, size_type view_pos); + + /// Sort columns in range first_col to last_col inclusive using the values from a row + void reorder_columns(size_type first_col, + size_type last_col, + index_pair row, bool reverse, + std::function comp); + + void column_resizable(bool resizable); + bool column_resizable() const; + void column_movable(bool); + bool column_movable() const; + /// Returns a rectangle in where the content is drawn. rectangle content_area() const; @@ -1481,9 +1498,19 @@ the nana::detail::basic_window member pointer scheme */ void insert_item(const index_pair& abs_pos, const ::std::wstring& text); + + void insert_item(index_pair abs_pos, const listbox& rhs, const index_pairs& indexes); + /// Returns an index of item which contains the specified point. index_pair cast(const point & screen_pos) const; + /// Returns the item which is hovered + /** + * @param return_end Indicates whether to return an end position instead of empty position if an item is not hovered. + * @return The position of the hovered item. If return_end is true, it returns the position next to the last item of last category if an item is not hovered. + */ + index_pair hovered(bool return_end) const; + /// Returns the absolute position of column which contains the specified point. size_type column_from_pos(const point & pos) const; @@ -1502,7 +1529,8 @@ the nana::detail::basic_window member pointer scheme ///Sets a strict weak ordering comparer for a column void set_sort_compare( size_type col, - std::function strick_ordering); + std::function strick_ordering); /// sort() and ivalidate any existing reference from display position to absolute item, that is: after sort() display offset point to different items void sort_col(size_type col, bool reverse = false); @@ -1523,6 +1551,7 @@ the nana::detail::basic_window member pointer scheme void enable_single(bool for_selection, bool category_limited); void disable_single(bool for_selection); + bool is_single_enabled(bool for_selection) const noexcept; ///< Determines whether the single selection/check is enabled. export_options& def_export_options(); @@ -1540,6 +1569,27 @@ the nana::detail::basic_window member pointer scheme * @return the reference of *this. */ listbox& category_icon(const paint::image& img_expanded, const paint::image&& img_collapsed); + + /// Returns first visible element + /** + * It may return an item or a category item. + * @return the index of first visible element. + */ + index_pair first_visible() const; + + /// Returns last visible element + /** + * It may return an item or a category item. + * @return the index of last visible element. + */ + index_pair last_visible() const; + + /// Returns all visible items + /** + * It returns all visible items that are displayed in listbox window. + * @return index_pairs containing all visible items. + */ + index_pairs visibles() const; private: drawerbase::listbox::essence & _m_ess() const; nana::any* _m_anyobj(size_type cat, size_type index, bool allocate_if_empty) const override; diff --git a/include/nana/gui/widgets/menubar.hpp b/include/nana/gui/widgets/menubar.hpp index 75158284..c6bbd7c9 100644 --- a/include/nana/gui/widgets/menubar.hpp +++ b/include/nana/gui/widgets/menubar.hpp @@ -1,7 +1,7 @@ /* * A Menubar implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2009-2017 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2009-2018 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -34,26 +34,6 @@ namespace nana color_proxy border_highlight{ colors::highlight }; }; - class item_renderer - { - public: - enum class state - { - normal, highlighted, selected - }; - - 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: - graph_reference graph_; - scheme *scheme_ptr_; - }; - class trigger : public drawer_trigger { diff --git a/include/nana/gui/widgets/scroll.hpp b/include/nana/gui/widgets/scroll.hpp index 81bc0a60..62857c6f 100644 --- a/include/nana/gui/widgets/scroll.hpp +++ b/include/nana/gui/widgets/scroll.hpp @@ -67,30 +67,30 @@ namespace nana class drawer { public: - struct states + enum class states { - enum{ none, highlight, actived, selected }; + none, highlight, actived, selected }; using graph_reference = paint::graphics&; const static unsigned fixedsize = 16; // make it part of a new "metric" in the widget_scheme - drawer(metrics_type& m); - void set_vertical(bool); + drawer(bool vert); buttons what(graph_reference, const point&); void scroll_delta_pos(graph_reference, int); void auto_scroll(); - void draw(graph_reference, buttons); + void draw(graph_reference); + private: bool _m_check() const; void _m_adjust_scroll(graph_reference); void _m_background(graph_reference); - void _m_button_frame(graph_reference, ::nana::rectangle, int state); - void _m_draw_scroll(graph_reference, int state); - void _m_draw_button(graph_reference, ::nana::rectangle, buttons what, int state); - private: - metrics_type &metrics_; - bool vertical_; + void _m_button_frame(graph_reference, ::nana::rectangle, states state); + void _m_draw_scroll(graph_reference, states state); + void _m_draw_button(graph_reference, ::nana::rectangle, buttons what, states state); + public: + metrics_type metrics; + bool const vert; }; template @@ -101,35 +101,34 @@ namespace nana typedef metrics_type::size_type size_type; trigger() - : graph_(nullptr), drawer_(metrics_) + : graph_(nullptr), drawer_(Vertical) { - drawer_.set_vertical(Vertical); } const metrics_type& metrics() const { - return metrics_; + return drawer_.metrics; } void peak(size_type s) { - if (graph_ && (metrics_.peak != s)) + if (graph_ && (drawer_.metrics.peak != s)) { - metrics_.peak = s; + drawer_.metrics.peak = s; API::refresh_window(widget_->handle()); } } void value(size_type s) { - if (metrics_.range > metrics_.peak) + if (drawer_.metrics.range > drawer_.metrics.peak) s = 0; - else if (s + metrics_.range > metrics_.peak) - s = metrics_.peak - metrics_.range; + else if (s + drawer_.metrics.range > drawer_.metrics.peak) + s = drawer_.metrics.peak - drawer_.metrics.range; - if (graph_ && (metrics_.value != s)) + if (graph_ && (drawer_.metrics.value != s)) { - metrics_.value = s; + drawer_.metrics.value = s; _m_emit_value_changed(); API::refresh_window(*widget_); @@ -138,16 +137,16 @@ namespace nana void range(size_type s) { - if (graph_ && (metrics_.range != s)) + if (graph_ && (drawer_.metrics.range != s)) { - metrics_.range = s; + drawer_.metrics.range = s; API::refresh_window(widget_->handle()); } } void step(size_type s) { - metrics_.step = (s ? s : 1); + drawer_.metrics.step = (s ? s : 1); } bool make_step(bool forward, unsigned multiple) @@ -155,12 +154,12 @@ namespace nana if (!graph_) return false; - size_type step = (multiple > 1 ? metrics_.step * multiple : metrics_.step); - size_type value = metrics_.value; + size_type step = (multiple > 1 ? drawer_.metrics.step * multiple : drawer_.metrics.step); + size_type value = drawer_.metrics.value; if (forward) { - size_type maxv = metrics_.peak - metrics_.range; - if (metrics_.peak > metrics_.range && value < maxv) + size_type maxv = drawer_.metrics.peak - drawer_.metrics.range; + if (drawer_.metrics.peak > drawer_.metrics.range && value < maxv) { if (maxv - value >= step) value += step; @@ -175,8 +174,8 @@ namespace nana else value = 0; } - size_type cmpvalue = metrics_.value; - metrics_.value = value; + size_type cmpvalue = drawer_.metrics.value; + drawer_.metrics.value = value; if (value != cmpvalue) { _m_emit_value_changed(); @@ -191,6 +190,9 @@ namespace nana widget_ = static_cast< ::nana::scroll*>(&widget); widget.caption("nana scroll"); + //scroll doesn't want the keyboard focus. + API::take_active(widget, false, widget.parent()); + timer_.stop(); timer_.elapse(std::bind(&trigger::_m_tick, this)); } @@ -202,47 +204,42 @@ namespace nana void refresh(graph_reference graph) override { - drawer_.draw(graph, metrics_.what); + drawer_.draw(graph); } void resized(graph_reference graph, const ::nana::arg_resized&) override { - drawer_.draw(graph, metrics_.what); + drawer_.draw(graph); API::dev::lazy_refresh(); } void mouse_enter(graph_reference graph, const ::nana::arg_mouse& arg) override { - metrics_.what = drawer_.what(graph, arg.pos); - drawer_.draw(graph, metrics_.what); + drawer_.metrics.what = drawer_.what(graph, arg.pos); + drawer_.draw(graph); API::dev::lazy_refresh(); } void mouse_move(graph_reference graph, const ::nana::arg_mouse& arg) override { - bool redraw = false; - if (metrics_.pressed && (metrics_.what == buttons::scroll)) + if (drawer_.metrics.pressed && (drawer_.metrics.what == buttons::scroll)) { - size_type cmpvalue = metrics_.value; + size_type cmpvalue = drawer_.metrics.value; drawer_.scroll_delta_pos(graph, (Vertical ? arg.pos.y : arg.pos.x)); - if (cmpvalue != metrics_.value) + if (cmpvalue != drawer_.metrics.value) _m_emit_value_changed(); - redraw = true; } else { buttons what = drawer_.what(graph, arg.pos); - if (metrics_.what != what) - { - redraw = true; - metrics_.what = what; - } - } - if (redraw) - { - drawer_.draw(graph, metrics_.what); - API::dev::lazy_refresh(); + if (drawer_.metrics.what == what) + return; //no change, don't redraw + + drawer_.metrics.what = what; } + + drawer_.draw(graph); + API::dev::lazy_refresh(); } void dbl_click(graph_reference graph, const arg_mouse& arg) override @@ -254,33 +251,33 @@ namespace nana { if (arg.left_button) { - metrics_.pressed = true; - metrics_.what = drawer_.what(graph, arg.pos); - switch (metrics_.what) + drawer_.metrics.pressed = true; + drawer_.metrics.what = drawer_.what(graph, arg.pos); + switch (drawer_.metrics.what) { case buttons::first: case buttons::second: - make_step(metrics_.what == buttons::second, 1); + make_step(drawer_.metrics.what == buttons::second, 1); timer_.interval(1000); timer_.start(); break; case buttons::scroll: widget_->set_capture(true); - metrics_.scroll_mouse_offset = (Vertical ? arg.pos.y : arg.pos.x) - metrics_.scroll_pos; + drawer_.metrics.scroll_mouse_offset = (Vertical ? arg.pos.y : arg.pos.x) - drawer_.metrics.scroll_pos; break; case buttons::forward: case buttons::backward: { - size_type cmpvalue = metrics_.value; + size_type cmpvalue = drawer_.metrics.value; drawer_.auto_scroll(); - if (cmpvalue != metrics_.value) + if (cmpvalue != drawer_.metrics.value) _m_emit_value_changed(); } break; default: //Ignore buttons::none break; } - drawer_.draw(graph, metrics_.what); + drawer_.draw(graph); API::dev::lazy_refresh(); } } @@ -291,18 +288,18 @@ namespace nana widget_->release_capture(); - metrics_.pressed = false; - metrics_.what = drawer_.what(graph, arg.pos); - drawer_.draw(graph, metrics_.what); + drawer_.metrics.pressed = false; + drawer_.metrics.what = drawer_.what(graph, arg.pos); + drawer_.draw(graph); API::dev::lazy_refresh(); } void mouse_leave(graph_reference graph, const arg_mouse&) override { - if (metrics_.pressed) return; + if (drawer_.metrics.pressed) return; - metrics_.what = buttons::none; - drawer_.draw(graph, buttons::none); + drawer_.metrics.what = buttons::none; + drawer_.draw(graph); API::dev::lazy_refresh(); } @@ -310,7 +307,7 @@ namespace nana { if (make_step(arg.upwards == false, 3)) { - drawer_.draw(graph, metrics_.what); + drawer_.draw(graph); API::dev::lazy_refresh(); } } @@ -322,14 +319,13 @@ namespace nana void _m_tick() { - make_step(metrics_.what == buttons::second, 1); + make_step(drawer_.metrics.what == buttons::second, 1); API::refresh_window(widget_->handle()); timer_.interval(100); } private: ::nana::scroll * widget_; nana::paint::graphics * graph_; - metrics_type metrics_; drawer drawer_; timer timer_; }; diff --git a/include/nana/gui/widgets/skeletons/text_editor.hpp b/include/nana/gui/widgets/skeletons/text_editor.hpp index dd24d365..5f691543 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-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -66,6 +66,7 @@ namespace nana{ namespace widgets using char_type = wchar_t; using size_type = textbase::size_type; using string_type = textbase::string_type; + using path_type = std::filesystem::path; using event_interface = text_editor_event_interface; @@ -105,7 +106,7 @@ namespace nana{ namespace widgets void indent(bool, std::function generator); void set_event(event_interface*); - bool load(const char*); + bool load(const path_type& file); void text_align(::nana::align alignment); @@ -135,13 +136,11 @@ namespace nana{ namespace widgets void enable_background(bool); void enable_background_counterpart(bool); - void undo_enabled(bool); - bool undo_enabled() const; + void undo_clear(); void undo_max_steps(std::size_t); 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(bool completed_line = false) const; @@ -167,6 +166,8 @@ namespace nana{ namespace widgets bool select(bool); + bool select_points(nana::upoint arg_a, nana::upoint arg_b); + /// Sets the end position of a selected string. void set_end_caret(bool stay_in_view); @@ -200,12 +201,11 @@ namespace nana{ namespace widgets void del(); void backspace(bool record_undo, bool perform_event); void undo(bool reverse); - void set_undo_queue_length(std::size_t len); void move_ns(bool to_north); //Moves up and down void move_left(); void move_right(); const upoint& mouse_caret(const point& screen_pos, bool stay_in_view); - const upoint& caret() const; + const upoint& caret() const noexcept; point caret_screen_pos() const; bool scroll(bool upwards, bool vertical); @@ -215,8 +215,8 @@ namespace nana{ namespace widgets void mouse_pressed(const arg_mouse& arg); bool select_word(const arg_mouse& arg); - skeletons::textbase& textbase(); - const skeletons::textbase& textbase() const; + skeletons::textbase& textbase() noexcept; + const skeletons::textbase& textbase() const noexcept; bool try_refresh(); diff --git a/include/nana/gui/widgets/skeletons/text_token_stream.hpp b/include/nana/gui/widgets/skeletons/text_token_stream.hpp index f94dfea8..36560067 100644 --- a/include/nana/gui/widgets/skeletons/text_token_stream.hpp +++ b/include/nana/gui/widgets/skeletons/text_token_stream.hpp @@ -682,7 +682,6 @@ namespace nana{ namespace widgets{ namespace skeletons { return lines_.end(); } - private: void _m_parse_format(tokenizer & tknizer, std::stack & fbstack) { diff --git a/include/nana/gui/widgets/skeletons/textbase.hpp b/include/nana/gui/widgets/skeletons/textbase.hpp index d11bc872..130b1466 100644 --- a/include/nana/gui/widgets/skeletons/textbase.hpp +++ b/include/nana/gui/widgets/skeletons/textbase.hpp @@ -1,7 +1,7 @@ /* * A textbase class implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -18,10 +18,11 @@ #include #include #include +#include + #include "textbase_export_interface.hpp" #include -#include #include #include @@ -36,15 +37,16 @@ namespace skeletons : public ::nana::noncopyable { public: - typedef CharT char_type; - typedef std::basic_string string_type; - typedef typename string_type::size_type size_type; + using char_type = CharT; + using string_type = std::basic_string; + using size_type = typename string_type::size_type; + using path_type = std::filesystem::path; textbase() { attr_max_.reset(); //Insert an empty string for the first line of empty text. - text_cont_.emplace_back(new string_type); + text_cont_.emplace_back(); } void set_event_agent(textbase_event_agent_interface * evt) @@ -55,21 +57,19 @@ namespace skeletons bool empty() const { return (text_cont_.empty() || - ((text_cont_.size() == 1) && (text_cont_.front()->empty()))); + ((text_cont_.size() == 1) && text_cont_.front().empty())); } - bool load(const char* file_utf8) + bool load(const path_type& file) { - if (!file_utf8) - return false; - - std::ifstream ifs(to_osmbstr(file_utf8)); + std::ifstream ifs{ file.string() }; if (!ifs) return false; - ifs.seekg(0, std::ios::end); - std::size_t bytes = static_cast(ifs.tellg()); - ifs.seekg(0, std::ios::beg); + std::error_code err; + auto const bytes = file_size(file, err); + if (err) + return false; if(bytes >= 2) { @@ -81,7 +81,7 @@ namespace skeletons if(0xBB == ch && 0xBF == ifs.get()) { ifs.close(); - return load(file_utf8, nana::unicode::utf8); + return load(file, nana::unicode::utf8); } } else if(0xFF == ch) @@ -89,16 +89,13 @@ namespace skeletons if(0xFE == ifs.get()) { //UTF16,UTF32 - if(bytes >= 4) + if((bytes >= 4) && (ifs.get() == 0 && ifs.get() == 0)) { - if(ifs.get() == 0 && ifs.get() == 0) - { - ifs.close(); - return load(file_utf8, nana::unicode::utf32); - } + ifs.close(); + return load(file, nana::unicode::utf32); } ifs.close(); - return load(file_utf8, nana::unicode::utf16); + return load(file, nana::unicode::utf16); } } else if(0xFE == ch) @@ -107,7 +104,7 @@ namespace skeletons { //UTF16(big-endian) ifs.close(); - return load(file_utf8, nana::unicode::utf16); + return load(file, nana::unicode::utf16); } } else if(0 == ch) @@ -119,7 +116,7 @@ namespace skeletons { //UTF32(big_endian) ifs.close(); - return load(file_utf8, nana::unicode::utf32); + return load(file, nana::unicode::utf32); } } } @@ -135,15 +132,15 @@ namespace skeletons while(ifs.good()) { std::getline(ifs, str_mbs); - text_cont_.emplace_back(new string_type(static_cast(nana::charset{ str_mbs }))); - if(text_cont_.back()->size() > attr_max_.size) + text_cont_.emplace_back(static_cast(nana::charset{ str_mbs })); + if (text_cont_.back().size() > attr_max_.size) { - attr_max_.size = text_cont_.back()->size(); + attr_max_.size = text_cont_.back().size(); attr_max_.line = text_cont_.size() - 1; } } - _m_saved(file_utf8); + _m_saved(file); return true; } @@ -175,12 +172,9 @@ namespace skeletons } } - bool load(const char* file_utf8, nana::unicode encoding) + bool load(const path_type& file, nana::unicode encoding) { - if (!file_utf8) - return false; - - std::ifstream ifs(to_osmbstr(file_utf8)); + std::ifstream ifs{ file.string() }; if (!ifs) return false; @@ -218,9 +212,8 @@ namespace skeletons byte_order_translate_4bytes(str); } - text_cont_.emplace_back(new string_type(static_cast(nana::charset{ str, encoding }))); - - attr_max_.size = text_cont_.back()->size(); + text_cont_.emplace_back(static_cast(nana::charset{ str, encoding })); + attr_max_.size = text_cont_.back().size(); attr_max_.line = 0; } @@ -236,21 +229,21 @@ namespace skeletons byte_order_translate_4bytes(str); } - text_cont_.emplace_back(new string_type(static_cast(nana::charset{ str, encoding }))); - if(text_cont_.back()->size() > attr_max_.size) + text_cont_.emplace_back(static_cast(nana::charset{ str, encoding })); + if (text_cont_.back().size() > attr_max_.size) { - attr_max_.size = text_cont_.back()->size(); + attr_max_.size = text_cont_.back().size(); attr_max_.line = text_cont_.size() - 1; } } - _m_saved(file_utf8); + _m_saved(file); return true; } - void store(std::string fs, bool is_unicode, ::nana::unicode encoding) const + void store(const path_type& filename, bool is_unicode, ::nana::unicode encoding) const { - std::ofstream ofs(to_osmbstr(fs), std::ios::binary); + std::ofstream ofs(filename.string(), std::ios::binary); if(ofs && text_cont_.size()) { auto i = text_cont_.cbegin(); @@ -277,27 +270,26 @@ namespace skeletons for (std::size_t pos = 0; pos < count; ++pos) { - auto mbs = nana::charset(**(i++)).to_bytes(encoding); + auto mbs = nana::charset(*(i++)).to_bytes(encoding); ofs.write(mbs.c_str(), static_cast(mbs.size())); ofs.write("\r\n", 2); } - last_mbs = nana::charset(*text_cont_.back()).to_bytes(encoding); + last_mbs = nana::charset(text_cont_.back()).to_bytes(encoding); } else { for (std::size_t pos = 0; pos < count; ++pos) { - std::string mbs = nana::charset(**(i++)); + std::string mbs = nana::charset(*(i++)); ofs.write(mbs.c_str(), mbs.size()); ofs.write("\r\n", 2); } - - last_mbs = nana::charset(*text_cont_.back()); + last_mbs = nana::charset(text_cont_.back()); } ofs.write(last_mbs.c_str(), static_cast(last_mbs.size())); - _m_saved(std::move(fs)); + _m_saved(filename); } } @@ -311,7 +303,7 @@ namespace skeletons { if (!changed_) { - _m_first_change(); + _m_emit_first_change(); changed_ = true; } @@ -332,8 +324,7 @@ namespace skeletons const string_type& getline(size_type pos) const { if (pos < text_cont_.size()) - return *text_cont_[pos]; - + return text_cont_[pos]; return nullstr_; } @@ -346,7 +337,7 @@ namespace skeletons { if (text_cont_.size() <= pos) { - text_cont_.emplace_back(new string_type(std::move(text))); + text_cont_.emplace_back(std::move(text)); pos = text_cont_.size() - 1; } else @@ -369,7 +360,7 @@ namespace skeletons } else { - text_cont_.emplace_back(new string_type(std::move(str))); + text_cont_.emplace_back(std::move(str)); pos.y = static_cast(text_cont_.size() - 1); } @@ -380,9 +371,9 @@ namespace skeletons void insertln(size_type pos, string_type&& str) { if (pos < text_cont_.size()) - text_cont_.emplace(_m_iat(pos), new string_type(std::move(str))); + text_cont_.emplace(_m_iat(pos), std::move(str)); else - text_cont_.emplace_back(new string_type(std::move(str))); + text_cont_.emplace_back(std::move(str)); _m_make_max(pos); edited_ = true; @@ -429,9 +420,9 @@ namespace skeletons { text_cont_.clear(); attr_max_.reset(); - text_cont_.emplace_back(new string_type); //text_cont_ must not be empty + text_cont_.emplace_back(); //text_cont_ must not be empty - _m_saved(std::string()); + _m_saved({}); } void merge(size_type pos) @@ -439,7 +430,8 @@ namespace skeletons if(pos + 1 < text_cont_.size()) { auto i = _m_iat(pos + 1); - _m_at(pos) += **i; + _m_at(pos) += *i; + text_cont_.erase(i); _m_make_max(pos); @@ -453,7 +445,7 @@ namespace skeletons } } - const std::string& filename() const + const path_type& filename() const { return filename_; } @@ -463,33 +455,25 @@ namespace skeletons return changed_; } - void edited_reset() + void reset_status(bool remain_saved_filename) { - changed_ = false; - } + if(!remain_saved_filename) + filename_.clear(); - void reset() - { - filename_.clear(); changed_ = false; } bool saved() const { - return ! not_saved(); - } - - bool not_saved() const - { - return edited() || filename_.empty(); + return !(changed_ || filename_.empty()); } private: string_type& _m_at(size_type pos) { - return **_m_iat(pos); + return *_m_iat(pos); } - typename std::deque>::iterator _m_iat(size_type pos) + typename std::deque::iterator _m_iat(size_type pos) { return text_cont_.begin() + pos; } @@ -506,50 +490,40 @@ namespace skeletons void _m_scan_for_max() { - attr_max_.size = 0; - std::size_t n = 0; - for(auto & p : text_cont_) - { - if(p->size() > attr_max_.size) - { - attr_max_.size = p->size(); - attr_max_.line = n; - } - ++n; - } + attr_max_.reset(); + for (std::size_t i = 0; i < text_cont_.size(); ++i) + _m_make_max(i); } - void _m_first_change() const + void _m_emit_first_change() const { if (evt_agent_) evt_agent_->first_change(); } - void _m_saved(std::string && filename) const + void _m_saved(const path_type& filename) const { - if(filename_ != filename) + if((filename_ != filename) || changed_) { - filename_ = std::move(filename); - _m_first_change(); + filename_ = filename; + _m_emit_first_change(); } - else if(changed_) - _m_first_change(); changed_ = false; } private: - std::deque> text_cont_; + std::deque text_cont_; textbase_event_agent_interface* evt_agent_{ nullptr }; mutable bool changed_{ false }; mutable bool edited_{ false }; - mutable std::string filename_; //A string for the saved filename. + mutable path_type filename_; ///< The saved filename const string_type nullstr_; struct attr_max { - std::size_t line; - std::size_t size; + std::size_t line; ///< The line number of max line + std::size_t size; ///< The number of characters in max line void reset() { diff --git a/include/nana/gui/widgets/slider.hpp b/include/nana/gui/widgets/slider.hpp index 8ce9602b..2fc015ce 100644 --- a/include/nana/gui/widgets/slider.hpp +++ b/include/nana/gui/widgets/slider.hpp @@ -151,7 +151,13 @@ namespace nana bool vertical() const; void maximum(unsigned); unsigned maximum() const; - void value(unsigned); + + /** Set slider value + @param[in] v new value for slider. + v will be clipped to the range 0 to maximum + */ + void value(int ); + unsigned value() const; unsigned move_step(bool forward); ///< Increase or decrease the value of slider. unsigned adorn() const; diff --git a/include/nana/gui/widgets/tabbar.hpp b/include/nana/gui/widgets/tabbar.hpp index 17a57411..863b66ec 100644 --- a/include/nana/gui/widgets/tabbar.hpp +++ b/include/nana/gui/widgets/tabbar.hpp @@ -29,17 +29,56 @@ namespace nana { tabbar & widget; T & value; + std::size_t item_pos; ///< position of the item - arg_tabbar(tabbar& wdg, T& v) - : widget(wdg), value{ v } + arg_tabbar(tabbar& wdg, T& v, std::size_t p) + : widget(wdg), value{ v }, item_pos(p) {} }; + template + struct arg_tabbar_click : public arg_tabbar + { + arg_tabbar_click(tabbar& wdg, T& v, std::size_t p) + : arg_tabbar({wdg, v, p}) + {} + + bool left_button = false; ///< true if mouse left button is pressed + bool mid_button = false; ///< true if mouse middle button is pressed + bool right_button = false; ///< true if mouse right button is pressed + }; + + template + struct arg_tabbar_mouse + : public arg_mouse + { + arg_tabbar_mouse(const arg_mouse& arg, tabbar& wdg, T& v, std::size_t p) + : arg_mouse{ arg }, widget(wdg), value{ v }, item_pos(p) + {} + + tabbar & widget; + T & value; + std::size_t item_pos; ///< position of the item + }; + + template + struct arg_tabbar_adding + : public event_arg + { + arg_tabbar_adding(tabbar& wdg, std::size_t p) + : widget(wdg), where(p) + {} + + tabbar & widget; + mutable bool add = true; ///< determines whether to add the item + std::size_t where; ///< position where to add the item + }; + template struct arg_tabbar_removed : public arg_tabbar { - arg_tabbar_removed(tabbar& wdg, T& v) - : arg_tabbar({wdg, v}) + arg_tabbar_removed(tabbar& wdg, T& v, std::size_t p) + : arg_tabbar({wdg, v, p}) {} mutable bool remove = true; ///< determines whether to remove the item @@ -56,7 +95,9 @@ namespace nana { using value_type = T; + basic_event> adding; basic_event> added; + basic_event> tab_click; basic_event> activated; basic_event> removed; }; @@ -65,7 +106,9 @@ namespace nana { public: virtual ~event_agent_interface() = default; + virtual bool adding(std::size_t) = 0; virtual void added(std::size_t) = 0; + virtual bool click(const arg_mouse&, std::size_t) = 0; virtual void activated(std::size_t) = 0; virtual bool removed(std::size_t, bool & close_attached) = 0; }; @@ -107,26 +150,40 @@ namespace nana : tabbar_(tb), drawer_trigger_(dtr) {} + bool adding(std::size_t pos) override + { + ::nana::arg_tabbar_adding arg_ta(tabbar_, pos); + tabbar_.events().adding.emit(arg_ta, tabbar_); + return arg_ta.add; + } + void added(std::size_t pos) override { if(pos != npos) { drawer_trigger_.at_no_bound_check(pos) = T(); - tabbar_.events().added.emit(arg_tabbar({ tabbar_, tabbar_[pos] }), tabbar_); + tabbar_.events().added.emit(arg_tabbar({ tabbar_, tabbar_[pos], pos }), tabbar_); } } + bool click(const arg_mouse& arg, std::size_t pos) override + { + ::nana::arg_tabbar_mouse arg_tm(arg, tabbar_, tabbar_[pos], pos); + tabbar_.events().tab_click.emit(arg_tm, tabbar_); + return arg_tm.propagation_stopped(); + } + void activated(std::size_t pos) override { if(pos != npos) - tabbar_.events().activated.emit(arg_tabbar({ tabbar_, tabbar_[pos]}), tabbar_); + tabbar_.events().activated.emit(arg_tabbar({ tabbar_, tabbar_[pos], pos}), tabbar_); } bool removed(std::size_t pos, bool & close_attach) override { - if (pos != npos) + if(pos != npos) { - ::nana::arg_tabbar_removed arg(tabbar_, tabbar_[pos]); + ::nana::arg_tabbar_removed arg(tabbar_, tabbar_[pos], pos); tabbar_.events().removed.emit(arg, tabbar_); close_attach = arg.close_attach_window; return arg.remove; diff --git a/include/nana/gui/widgets/textbox.hpp b/include/nana/gui/widgets/textbox.hpp index 01c9a9d1..8bf34b38 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-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -109,6 +109,8 @@ namespace nana using text_focus_behavior = widgets::skeletons::text_focus_behavior; using text_positions = std::vector; + using path_type = std::filesystem::path; + /// The default constructor without creating the widget. textbox(); @@ -136,9 +138,9 @@ namespace nana textbox(window, const rectangle& = rectangle(), bool visible = true); /// \brief Loads a text file. When attempt to load a unicode encoded text file, be sure the file have a BOM header. - void load(std::string file); - void store(std::string file); - void store(std::string file, nana::unicode encoding); + void load(const path_type& file); + void store(const path_type& file); + void store(const path_type& file, nana::unicode encoding); colored_area_access_interface* colored_area_access(); @@ -158,7 +160,7 @@ namespace nana textbox& reset(const std::string& text = std::string(), bool end_caret = true); ///< discard the old text and set a new text /// The file of last store operation. - std::string filename() const; + path_type filename() const; /// Determine whether the text was edited. bool edited() const; @@ -224,6 +226,8 @@ namespace nana /// Selects/unselects all text. void select(bool); + void select_points(nana::upoint arg_a, nana::upoint arg_b); + /// Returns the bounds of a text selection /** * @return no selection if pair.first == pair.second. diff --git a/include/nana/gui/widgets/treebox.hpp b/include/nana/gui/widgets/treebox.hpp index 671851c2..ba40c4c1 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-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE or copy at @@ -60,8 +60,26 @@ namespace nana ::std::string text; }; + struct scheme + : public widget_geometrics + { + color_proxy item_bg_selected{ static_cast(0xD5EFFC) }; ///< item selected: background color + color_proxy item_fg_selected{ static_cast(0x99DEFD) }; ///< item selected: foreground color + color_proxy item_bg_highlighted{ static_cast(0xE8F5FD) }; ///< item highlighted: background color + color_proxy item_fg_highlighted{ static_cast(0xD8F0FA) }; ///< item highlighted: foreground color + color_proxy item_bg_selected_and_highlighted{ static_cast(0xC4E8FA) }; ///< item selected and highlighted: background color + color_proxy item_fg_selected_and_highlighted{ static_cast(0xB6E6FB) }; ///< item selected and highlighted: foreground color + + unsigned item_offset{ 16 }; ///< item position displacement in pixels + unsigned text_offset{ 4 }; ///< text position displacement in pixels + unsigned icon_size{ 16 }; ///< icon size in pixels + unsigned crook_size{ 16 }; ///< crook size in pixels (TODO: the function that draw the crook doesn't scale the shape) + + unsigned indent_displacement{ 18 }; ///< children position displacement in pixels (def=18 (before was 10)) + }; + typedef widgets::detail::compset compset_interface; - typedef widgets::detail::compset_placer compset_placer_interface; + typedef widgets::detail::compset_placer compset_placer_interface; class renderer_interface { @@ -87,13 +105,8 @@ namespace nana class trigger :public drawer_trigger { - template - struct basic_implement; - - class item_renderer; + class implementation; class item_locator; - - typedef basic_implement implement; public: struct treebox_node_type { @@ -116,27 +129,25 @@ namespace nana trigger(); ~trigger(); - implement * impl() const; + implementation * impl() const; void check(node_type*, checkstate); - void renderer(::nana::pat::cloneable&&); - const ::nana::pat::cloneable& renderer() const; + pat::cloneable& renderer() const; + void placer(::nana::pat::cloneable&&); const ::nana::pat::cloneable& placer() const; node_type* insert(node_type*, const std::string& key, std::string&&); node_type* insert(const std::string& path, std::string&&); - node_type * selected() const; - void selected(node_type*); - - node_image_tag& icon(const ::std::string&) const; + node_image_tag& icon(const ::std::string&); 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); + private: //Overrides drawer_trigger methods void attached(widget_reference, graph_reference) override; @@ -152,7 +163,7 @@ namespace nana void key_press(graph_reference, const arg_keyboard&) override; void key_char(graph_reference, const arg_keyboard&) override; private: - implement * const impl_; + implementation * const impl_; }; //end class trigger @@ -171,11 +182,11 @@ namespace nana /// Append a child with a specified value (user object.). template - item_proxy append(const ::std::string& key, ::std::string name, const T&t) + item_proxy append(const ::std::string& key, ::std::string name, T&& t) { item_proxy ip = append(key, std::move(name)); if(false == ip.empty()) - ip.value(t); + ip.value(std::forward(t)); return ip; } @@ -291,17 +302,10 @@ namespace nana return *p; } - template - item_proxy & value(const T& t) - { - _m_value() = t; - return *this; - } - template item_proxy & value(T&& t) { - _m_value() = std::move(t); + _m_value() = std::forward(t); return *this; } @@ -346,7 +350,7 @@ namespace nana /// \brief Displays a hierarchical list of items, such as the files and directories on a disk. /// See also in [documentation](http://nanapro.org/en-us/documentation/widgets/treebox.htm) class treebox - :public widget_object < category::widget_tag, drawerbase::treebox::trigger, drawerbase::treebox::treebox_events> + :public widget_object { public: /// A type refers to the item and is also used to iterate through the nodes. @@ -378,7 +382,7 @@ namespace nana template treebox & renderer(const ItemRenderer & rd) ///< set user-defined node renderer { - get_drawer_trigger().renderer(::nana::pat::cloneable(rd)); + get_drawer_trigger().renderer() = ::nana::pat::cloneable{rd}; return *this; } @@ -403,6 +407,23 @@ namespace nana /// @param enable bool whether to enable. void auto_draw(bool enable); + /// Prevents drawing during execution. + template + void avoid_drawing(Function fn) + { + this->auto_draw(false); + try + { + fn(); + } + catch (...) + { + this->auto_draw(true); + throw; + } + this->auto_draw(true); + } + /// \brief Enable the checkboxs for each item of the widget. /// @param enable bool indicates whether to show or hide the checkboxs. treebox & checkable(bool enable); @@ -419,8 +440,9 @@ namespace nana /// These states are 'normal', 'hovered' and 'expanded'. /// If 'hovered' or 'expanded' are not set, it uses 'normal' state image for these 2 states. /// See also in [documentation](http://nanapro.org/en-us/help/widgets/treebox.htm) - node_image_type& icon(const ::std::string& id ///< the name of an icon scheme. If the name is not existing, it creates a new scheme for the name. - ) const; + /// @param id The name of an icon scheme. If the name is not existing, it creates a new scheme for the name. + /// @return The reference of node image scheme correspending with the specified id. + node_image_type& icon(const ::std::string& id); void icon_erase(const ::std::string& id); @@ -445,6 +467,19 @@ namespace nana item_proxy selected() const; ///< returns the selected node + /// Scrolls a specified item into view. + /** + * @param item An item to be requested. + * @param bearing The position where the item to be positioned in the view. + */ + void scroll_into_view(item_proxy item, align_v bearing); + + /// Scrolls a specified item into view. + /** + * @param item An item to be requested. + */ + void scroll_into_view(item_proxy item); + private: std::shared_ptr _m_scroll_operation() override; diff --git a/include/nana/gui/widgets/widget.hpp b/include/nana/gui/widgets/widget.hpp index 7448eea9..fe8fe543 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-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -499,72 +499,6 @@ namespace nana std::unique_ptr scheme_; };//end class widget_object -#ifndef WIDGET_FRAME_DEPRECATED - /// Base class of all the classes defined as a frame window. \see nana::frame - template - class widget_object: public widget{}; - - /// Especialization. Base class of all the classes defined as a frame window. \see nana::frame - template - class widget_object: public detail::widget_base - { - protected: - typedef int drawer_trigger_t; - public: - using scheme_type = Scheme; - using event_type = Events; - - widget_object() - : events_{ std::make_shared() }, scheme_{ API::dev::make_scheme() } - {} - - ~widget_object() - { - API::close_window(handle()); - } - - event_type& events() const - { - return *events_; - } - - bool create(window parent_wd, bool visible) ///< Creates a no-size (zero-size) widget. in a widget/root window specified by parent_wd. - { - return create(parent_wd, rectangle(), visible); - } - /// Creates in a widget/root window specified by parent_wd. - bool create(window parent_wd, const rectangle& r = rectangle(), bool visible = true) - { - if(parent_wd && this->empty()) - { - handle_ = API::dev::create_frame(parent_wd, r, this); - API::dev::set_events(handle_, events_); - API::dev::set_scheme(handle_, scheme_.get()); - API::show_window(handle_, visible); - this->_m_complete_creation(); - } - return (this->empty() == false); - } - - scheme_type& scheme() const - { - return *scheme_; - } - private: - virtual drawer_trigger* get_drawer_trigger() - { - return nullptr; - } - - general_events& _m_get_general_events() const override - { - return *events_; - } - private: - std::shared_ptr events_; - std::unique_ptr scheme_; - };//end class widget_object -#endif }//end namespace nana #include diff --git a/include/nana/paint/detail/image_impl_interface.hpp b/include/nana/paint/detail/image_impl_interface.hpp index 4a99abaf..6a0e1141 100644 --- a/include/nana/paint/detail/image_impl_interface.hpp +++ b/include/nana/paint/detail/image_impl_interface.hpp @@ -16,7 +16,7 @@ namespace nana{ namespace paint{ public: 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 std::filesystem::path& file) = 0; virtual bool open(const void* data, std::size_t bytes) = 0; // reads image from memory virtual bool alpha_channel() const = 0; virtual bool empty() const = 0; diff --git a/include/nana/paint/graphics.hpp b/include/nana/paint/graphics.hpp index 4eed26a6..3cec589c 100644 --- a/include/nana/paint/graphics.hpp +++ b/include/nana/paint/graphics.hpp @@ -33,7 +33,7 @@ namespace nana { friend class graphics; public: - using path_type = ::std::experimental::filesystem::path; + using path_type = ::std::filesystem::path; using font_style = ::nana::detail::font_style; diff --git a/source/datetime.cpp b/source/datetime.cpp index d633090d..8b024735 100644 --- a/source/datetime.cpp +++ b/source/datetime.cpp @@ -15,6 +15,7 @@ #include #endif #include +#include namespace { std::tm localtime() @@ -239,18 +240,20 @@ namespace nana return days + d; } - unsigned date::month_days(unsigned year, unsigned month) + unsigned date::month_days(const unsigned year, const unsigned month) { - unsigned num[] = {31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - if(month != 2) - return num[month - 1]; + if (month != 2) + { + constexpr std::array days_in_month = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + return days_in_month[month - 1]; + } if(((year % 4 == 0) && (year % 100)) || (year % 400 == 0)) return 29; return 28; } - unsigned date::year_days(unsigned year) + unsigned date::year_days(const unsigned year) { if(((year % 4 == 0) && (year % 100)) || (year % 400 == 0)) return 366; diff --git a/source/detail/mswin/platform_spec.hpp b/source/detail/mswin/platform_spec.hpp index 3505f6e3..1a44ce8b 100644 --- a/source/detail/mswin/platform_spec.hpp +++ b/source/detail/mswin/platform_spec.hpp @@ -1,4 +1,4 @@ -/* +/** * Platform Specification Implementation * Nana C++ Library(http://www.nanapro.org) * Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) @@ -7,7 +7,7 @@ * (See accompanying file LICENSE_1_0.txt or copy at * http://www.boost.org/LICENSE_1_0.txt) * - * @file: nana/detail/platform_spec.hpp + * @file nana/detail/platform_spec.hpp * * This file provides basis class and data structrue that required by nana * This file should not be included by any header files. diff --git a/source/detail/platform_abstraction.cpp b/source/detail/platform_abstraction.cpp index c9178c2f..515f6ace 100644 --- a/source/detail/platform_abstraction.cpp +++ b/source/detail/platform_abstraction.cpp @@ -158,7 +158,7 @@ namespace nana : public font_interface { public: - using path_type = std::experimental::filesystem::path; + using path_type = std::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), diff --git a/source/detail/platform_abstraction.hpp b/source/detail/platform_abstraction.hpp index 290e0a5c..3e22112a 100644 --- a/source/detail/platform_abstraction.hpp +++ b/source/detail/platform_abstraction.hpp @@ -28,7 +28,7 @@ namespace nana public: using font = font_interface; - using path_type = ::std::experimental::filesystem::path; + using path_type = ::std::filesystem::path; static void initialize(); /// Shutdown before destruction of platform_spec diff --git a/source/detail/platform_spec_posix.cpp b/source/detail/platform_spec_posix.cpp index bf3ab2fb..f7cbc9d6 100644 --- a/source/detail/platform_spec_posix.cpp +++ b/source/detail/platform_spec_posix.cpp @@ -500,20 +500,23 @@ namespace detail atombase_.net_wm_window_type_dialog = ::XInternAtom(display_, "_NET_WM_WINDOW_TYPE_DIALOG", False); atombase_.motif_wm_hints = ::XInternAtom(display_, "_MOTIF_WM_HINTS", False); - atombase_.clipboard = ::XInternAtom(display_, "CLIPBOARD", True); - atombase_.text = ::XInternAtom(display_, "TEXT", True); - atombase_.text_uri_list = ::XInternAtom(display_, "text/uri-list", True); - atombase_.utf8_string = ::XInternAtom(display_, "UTF8_STRING", True); - atombase_.targets = ::XInternAtom(display_, "TARGETS", True); + atombase_.clipboard = ::XInternAtom(display_, "CLIPBOARD", False); + atombase_.text = ::XInternAtom(display_, "TEXT", False); + atombase_.text_uri_list = ::XInternAtom(display_, "text/uri-list", False); + atombase_.utf8_string = ::XInternAtom(display_, "UTF8_STRING", False); + atombase_.targets = ::XInternAtom(display_, "TARGETS", False); atombase_.xdnd_aware = ::XInternAtom(display_, "XdndAware", False); atombase_.xdnd_enter = ::XInternAtom(display_, "XdndEnter", False); atombase_.xdnd_position = ::XInternAtom(display_, "XdndPosition", False); atombase_.xdnd_status = ::XInternAtom(display_, "XdndStatus", False); atombase_.xdnd_action_copy = ::XInternAtom(display_, "XdndActionCopy", False); + atombase_.xdnd_action_move = ::XInternAtom(display_, "XdndActionMove", False); + atombase_.xdnd_action_link = ::XInternAtom(display_, "XdndActionLink", False); atombase_.xdnd_drop = ::XInternAtom(display_, "XdndDrop", False); atombase_.xdnd_selection = ::XInternAtom(display_, "XdndSelection", False); atombase_.xdnd_typelist = ::XInternAtom(display_, "XdndTypeList", False); + atombase_.xdnd_leave = ::XInternAtom(display_, "XdndLeave", False); atombase_.xdnd_finished = ::XInternAtom(display_, "XdndFinished", False); msg_dispatcher_ = new msg_dispatcher(display_); @@ -658,21 +661,31 @@ namespace detail platform_scope_guard lock; if(umake_owner(wd)) { + auto & wd_manager = detail::bedrock::instance().wd_manager(); + + std::vector owned_children; + auto i = wincontext_.find(wd); if(i != wincontext_.end()) { if(i->second.owned) { - set_error_handler(); - auto & wd_manager = detail::bedrock::instance().wd_manager(); - for(auto u = i->second.owned->rbegin(); u != i->second.owned->rend(); ++u) - wd_manager.close(wd_manager.root(*u)); - - rev_error_handler(); - - delete i->second.owned; + for(auto child : *i->second.owned) + owned_children.push_back(child); } + } + //Closing a child will erase the wd from the table wincontext_, so the + //iterator i can't be reused after children closed. + set_error_handler(); + for(auto u = owned_children.rbegin(); u != owned_children.rend(); ++u) + wd_manager.close(wd_manager.root(*u)); + rev_error_handler(); + + i = wincontext_.find(wd); + if(i != wincontext_.end()) + { + delete i->second.owned; wincontext_.erase(i); } } @@ -1031,6 +1044,12 @@ namespace detail msg_dispatcher_->dispatch(reinterpret_cast(modal)); } + void platform_spec::msg_dispatch(std::function msg_filter_fn) + { + msg_dispatcher_->dispatch(msg_filter_fn); + + } + void* platform_spec::request_selection(native_window_type requestor, Atom type, size_t& size) { if(requestor) @@ -1094,6 +1113,62 @@ namespace detail return graph; } + + bool platform_spec::register_dragdrop(native_window_type wd, x11_dragdrop_interface* ddrop) + { + platform_scope_guard lock; + if(0 != xdnd_.dragdrop.count(wd)) + return false; + + xdnd_.dragdrop[wd] = ddrop; + return true; + } + + std::size_t platform_spec::dragdrop_target(native_window_type wd, bool insert, std::size_t count) + { + std::size_t new_val = 0; + platform_scope_guard lock; + if(insert) + { + new_val = (xdnd_.targets[wd] += count); + if(1 == new_val) + { + int dndver = 5; + ::XChangeProperty(display_, reinterpret_cast(wd), atombase_.xdnd_aware, XA_ATOM, sizeof(int) * 8, + PropModeReplace, reinterpret_cast(&dndver), 1); + } + } + else + { + auto i = xdnd_.targets.find(wd); + if(i == xdnd_.targets.end()) + return 0; + + new_val = (i->second > count ? i->second - count : 0); + if(0 == new_val) + { + xdnd_.targets.erase(wd); + ::XDeleteProperty(display_, reinterpret_cast(wd), atombase_.xdnd_aware); + } + else + i->second = new_val; + } + return new_val; + } + + x11_dragdrop_interface* platform_spec::remove_dragdrop(native_window_type wd) + { + platform_scope_guard lock; + auto i = xdnd_.dragdrop.find(wd); + if(i == xdnd_.dragdrop.end()) + return nullptr; + + auto ddrop = i->second; + xdnd_.dragdrop.erase(i); + + return ddrop; + } + //_m_msg_filter //@return: _m_msg_filter returns three states // 0 = msg_dispatcher dispatches the XEvent @@ -1153,7 +1228,7 @@ namespace detail else if(evt.xselection.property == self.atombase_.xdnd_selection) { bool accepted = false; - msg.kind = msg.kind_mouse_drop; + msg.kind = msg_packet_tag::pkt_family::mouse_drop; msg.u.mouse_drop.window = 0; if(bytes_left > 0 && type == self.xdnd_.good_type) { @@ -1163,8 +1238,9 @@ namespace detail 0, AnyPropertyType, &type, &format, &len, &dummy_bytes_left, &data)) { - auto files = new std::vector; + auto files = new std::vector; std::stringstream ss(reinterpret_cast(data)); + while(true) { std::string file; @@ -1182,8 +1258,9 @@ namespace detail break; } - files->push_back(file); + files->emplace_back(file); } + if(files->size()) { msg.u.mouse_drop.window = evt.xselection.requestor; @@ -1198,8 +1275,9 @@ namespace detail ::XFree(data); } } - XEvent respond; + ::XEvent respond; ::memset(respond.xclient.data.l, 0, sizeof(respond.xclient.data.l)); + respond.xany.type = ClientMessage; respond.xclient.display = self.display_; respond.xclient.window = self.xdnd_.wd_src; respond.xclient.message_type = self.atombase_.xdnd_finished; @@ -1222,6 +1300,10 @@ namespace detail } else if(SelectionRequest == evt.type) { + //Skip if it is requested by XDND, it will be processed by dragdrop's xdnd_protocol + if(self.atombase_.xdnd_selection == evt.xselectionrequest.selection) + return 0; + auto disp = evt.xselectionrequest.display; XEvent respond; @@ -1286,24 +1368,56 @@ namespace detail ::XGetWindowProperty(self.display_, self.xdnd_.wd_src, self.atombase_.xdnd_typelist, 0, bytes_left, False, XA_ATOM, &type, &format, &len, &bytes_left, &data); + if(XA_ATOM == type && len > 0) atoms = reinterpret_cast(data); } } +#define DEBUG_XdndDirectSave +#ifdef DEBUG_XdndDirectSave + Atom XdndDirectSave = 0; +#endif self.xdnd_.good_type = None; for(unsigned long i = 0; i < len; ++i) { + auto name = XGetAtomName(self.display_, atoms[i]); //debug + if(name) + { +#ifdef DEBUG_XdndDirectSave + if(strstr(name, "XdndDirectSave")) + XdndDirectSave = atoms[i]; +#endif + ::XFree(name); + } + if(atoms[i] == self.atombase_.text_uri_list) { self.xdnd_.good_type = self.atombase_.text_uri_list; - break; + //break; } } if(data) ::XFree(data); +#ifdef DEBUG_XdndDirectSave //debug + if(XdndDirectSave) + { + Atom type; + int format; + unsigned long bytes_left; + + ::XGetWindowProperty(self.display_, self.xdnd_.wd_src, XdndDirectSave, 0, 0, False, XA_ATOM, &type, &format, &len, &bytes_left, &data); + + if(bytes_left > 0) + { + ::XGetWindowProperty(self.display_, self.xdnd_.wd_src, XdndDirectSave, + 0, bytes_left, False, type, + &type, &format, &len, &bytes_left, &data); + } + } +#endif return 2; } else if(self.atombase_.xdnd_position == evt.xclient.message_type) @@ -1312,7 +1426,7 @@ namespace detail int x = (evt.xclient.data.l[2] >> 16); int y = (evt.xclient.data.l[2] & 0xFFFF); - bool accepted = false; + int accepted = 0; //0 means refusing, 1 means accpeting //We have got the type what we want. if(self.xdnd_.good_type != None) { @@ -1322,9 +1436,10 @@ namespace detail auto wd = bedrock.wd_manager().find_window(reinterpret_cast(evt.xclient.window), self.xdnd_.pos); if(wd && wd->flags.dropable) { - accepted = true; + //Cache the time stamp in XdndPosition, and the time stamp must be passed to XConvertSelection for requesting selection self.xdnd_.timestamp = evt.xclient.data.l[3]; self.xdnd_.pos = wd->pos_root; + accepted = 1; } } @@ -1339,7 +1454,7 @@ namespace detail //Target window respond.xclient.data.l[0] = evt.xclient.window; //Accept set - respond.xclient.data.l[1] = (accepted ? 1 : 0); + respond.xclient.data.l[1] = accepted; respond.xclient.data.l[2] = 0; respond.xclient.data.l[3] = 0; respond.xclient.data.l[4] = self.atombase_.xdnd_action_copy; @@ -1347,10 +1462,15 @@ namespace detail ::XSendEvent(self.display_, wd_src, True, NoEventMask, &respond); return 2; } + else if(self.atombase_.xdnd_status == evt.xclient.message_type) + { + //Platform Recv XdndStatus + } else if(self.atombase_.xdnd_drop == evt.xclient.message_type) { ::XConvertSelection(self.display_, self.atombase_.xdnd_selection, self.xdnd_.good_type, self.atombase_.xdnd_selection, evt.xclient.window, self.xdnd_.timestamp); + //The XdndDrop should send a XdndFinished to source window. //This operation is implemented in SelectionNotify, because //XdndFinished should be sent after retrieving data. diff --git a/source/detail/posix/msg_dispatcher.hpp b/source/detail/posix/msg_dispatcher.hpp index 9ba12ad1..34f9dfcf 100644 --- a/source/detail/posix/msg_dispatcher.hpp +++ b/source/detail/posix/msg_dispatcher.hpp @@ -143,7 +143,7 @@ namespace detail { //Make a cleanup msg packet to infor the dispatcher the window is closed. msg_packet_tag msg; - msg.kind = msg.kind_cleanup; + msg.kind = msg_packet_tag::pkt_family::cleanup; msg.u.packet_window = wd; thr->msg_queue.push_back(msg); } @@ -171,6 +171,37 @@ namespace detail } } } + + template + void dispatch(MsgFilter msg_filter_fn) + { + auto tid = nana::system::this_thread_id(); + msg_packet_tag msg; + int qstate; + + //Test whether the thread is registered for window, and retrieve the queue state for event + while((qstate = _m_read_queue(tid, msg, 0))) + { + //the queue is empty + if(-1 == qstate) + { + if(false == _m_wait_for_queue(tid)) + proc_.timer_proc(tid); + } + else + { + switch(msg_filter_fn(msg)) + { + case propagation_chain::exit: + return; + case propagation_chain::stop: + break; + case propagation_chain::pass: + proc_.event_proc(display_, msg); + } + } + } + } private: void _m_msg_driver() { @@ -220,7 +251,7 @@ namespace detail switch(proc_.filter_proc(event, msg_pack)) { case 0: - msg_pack.kind = msg_pack.kind_xevent; + msg_pack.kind = msg_packet_tag::pkt_family::xevent; msg_pack.u.xevent = event; _m_msg_dispatch(msg_pack); break; @@ -246,9 +277,9 @@ namespace detail { switch(pack.kind) { - case msg_packet_tag::kind_xevent: + case msg_packet_tag::pkt_family::xevent: return _m_event_window(pack.u.xevent); - case msg_packet_tag::kind_mouse_drop: + case msg_packet_tag::pkt_family::mouse_drop: return pack.u.mouse_drop.window; default: break; @@ -294,7 +325,7 @@ namespace detail //Check whether the event dispatcher is used for the modal window //and when the modal window is closing, the event dispatcher would //stop event pumping. - if((modal == msg.u.packet_window) && (msg.kind == msg.kind_cleanup)) + if((modal == msg.u.packet_window) && (msg.kind == msg_packet_tag::pkt_family::cleanup)) return 0; return 1; diff --git a/source/detail/posix/msg_packet.hpp b/source/detail/posix/msg_packet.hpp index ea24091f..df7a4bcf 100644 --- a/source/detail/posix/msg_packet.hpp +++ b/source/detail/posix/msg_packet.hpp @@ -3,15 +3,23 @@ #include #include #include +#include namespace nana { namespace detail { + enum class propagation_chain + { + exit, //Exit the chain + stop, //Stop propagating + pass //propagate + }; + struct msg_packet_tag { - enum kind_t{kind_xevent, kind_mouse_drop, kind_cleanup}; - kind_t kind; + enum class pkt_family{xevent, mouse_drop, cleanup}; + pkt_family kind; union { XEvent xevent; @@ -22,7 +30,7 @@ namespace detail Window window; int x; int y; - std::vector * files; + std::vector * files; }mouse_drop; }u; }; diff --git a/source/detail/posix/platform_spec.hpp b/source/detail/posix/platform_spec.hpp index 3191ddb1..84c1c24e 100644 --- a/source/detail/posix/platform_spec.hpp +++ b/source/detail/posix/platform_spec.hpp @@ -36,6 +36,7 @@ #include #include +#include #include "msg_packet.hpp" #include "../platform_abstraction_types.hpp" @@ -158,9 +159,12 @@ namespace detail Atom xdnd_position; Atom xdnd_status; Atom xdnd_action_copy; + Atom xdnd_action_move; + Atom xdnd_action_link; Atom xdnd_drop; Atom xdnd_selection; Atom xdnd_typelist; + Atom xdnd_leave; Atom xdnd_finished; }; @@ -176,6 +180,15 @@ namespace detail ~platform_scope_guard(); }; + class x11_dragdrop_interface + { + public: + virtual ~x11_dragdrop_interface() = default; + + virtual void add_ref() = 0; + virtual std::size_t release() = 0; + }; + class platform_spec { typedef platform_spec self_type; @@ -246,6 +259,7 @@ namespace detail void msg_insert(native_window_type); void msg_set(timer_proc_type, event_proc_type); void msg_dispatch(native_window_type modal); + void msg_dispatch(std::function); //X Selections void* request_selection(native_window_type requester, Atom type, size_t & bufsize); @@ -255,6 +269,10 @@ namespace detail //@biref: The image object should be kept for a long time till the window is closed, // the image object is release in remove() method. const nana::paint::graphics& keep_window_icon(native_window_type, const nana::paint::image&); + + bool register_dragdrop(native_window_type, x11_dragdrop_interface*); + std::size_t dragdrop_target(native_window_type, bool insert, std::size_t count); + x11_dragdrop_interface* remove_dragdrop(native_window_type); private: static int _m_msg_filter(XEvent&, msg_packet_tag&); void _m_caret_routine(); @@ -311,6 +329,9 @@ namespace detail int timestamp; Window wd_src; nana::point pos; + + std::map dragdrop; + std::map targets; }xdnd_; msg_dispatcher * msg_dispatcher_; diff --git a/source/detail/posix/theme.cpp b/source/detail/posix/theme.cpp new file mode 100644 index 00000000..e0ca1c2b --- /dev/null +++ b/source/detail/posix/theme.cpp @@ -0,0 +1,326 @@ +#include +#if defined(NANA_POSIX) && defined(NANA_X11) +#include "theme.hpp" +#include +#include +#include + +namespace nana +{ + namespace detail + { + static int gschema_override_priority(const std::string& filename) + { + if(filename.size() < 3) + return -1; + + auto str = filename.substr(0, 2); + if('0' <= str[0] && str[0] <= '9' && '0' <= str[1] && str[1] <= '9') + return std::stoi(str); + + return 0; + } + + //Removes the wrap of ' and " character. + std::string remove_decoration(const std::string& primitive_value) + { + auto pos = primitive_value.find_first_of("'\""); + if(pos == primitive_value.npos) + return primitive_value; + + auto endpos = primitive_value.find(primitive_value[pos], pos + 1); + if(endpos == primitive_value.npos) + return primitive_value; + + return primitive_value.substr(pos + 1, endpos - pos - 1); + } + + std::string find_value(std::ifstream& ifs, const std::string& category, const std::string& key) + { + ifs.seekg(0, std::ios::beg); + + std::string dec_categ = "[" + category + "]"; + + bool found_cat = false; + while(ifs.good()) + { + std::string text; + std::getline(ifs, text); + + if((text.size() > 2) && ('[' == text[0])) + { + if(found_cat) + break; + + found_cat = (text == dec_categ); + } + else if(found_cat && (text.find(key + "=") == 0)) + { + return remove_decoration(text.substr(key.size() + 1)); + } + } + return {}; + } + + std::vector split_value(const std::string& value_string) + { + std::vector values; + + std::size_t start_pos = 0; + while(start_pos != value_string.npos) + { + auto pos = value_string.find(',', start_pos); + if(value_string.npos == pos) + { + if(start_pos < value_string.size()) + values.emplace_back(value_string.substr(start_pos)); + break; + } + + values.emplace_back(value_string.substr(start_pos, pos - start_pos)); + + start_pos = value_string.find_first_not_of(',', pos); + } + return values; + } + + namespace fs = std::filesystem; + std::string find_gnome_theme_name() + { + try + { + //Searches all the gschema override files + std::vector overrides; + for(fs::directory_iterator i{"/usr/share/glib-2.0/schemas"}, end; i != end; ++i) + { + auto filename = i->path().filename().string(); + if(filename.size() > 17 && filename.substr(filename.size() - 17) == ".gschema.override") + { + auto priority = gschema_override_priority(filename); + if(priority < 0) + continue; + + auto i = std::find_if(overrides.cbegin(), overrides.cend(), [priority](const std::string& ovrd){ + return (priority > gschema_override_priority(ovrd)); + }); + + overrides.emplace(i, std::move(filename)); + //overrides.insert(i, filename); + } + } + + //Searches the org.gnome.desktop.interface in override files. + for(auto & gschema_override : overrides) + { + std::ifstream ifs{"/usr/share/glib-2.0/schemas/" + gschema_override}; + auto value = find_value(ifs, "org.gnome.desktop.interface", "icon-theme"); + if(!value.empty()) + return value; + } + + //Return the value from org.gnome.desktop.interface.gschema.xml + fs::path xml = "/usr/share/glib-2.0/schemas/org.gnome.desktop.interface.gschema.xml"; + auto bytes = fs::file_size(xml); + if(0 == bytes) + return {}; + + std::ifstream xml_ifs{"/usr/share/glib-2.0/schemas/org.gnome.desktop.interface.gschema.xml", std::ios::binary}; + if(xml_ifs) + { + std::string data; + data.resize(bytes); + + xml_ifs.read(&data.front(), bytes); + + auto pos = data.find("\"icon-theme\""); + if(pos != data.npos) + { + + pos = data.find("", pos + 22); + if(pos != data.npos) + { + pos += 9; + auto endpos = data.find("", pos); + if(endpos != data.npos) + { + return remove_decoration(data.substr(pos, endpos - pos)); + } + } + } + } + } + catch(...){} + + return {}; + } + + + std::string find_kde_theme_name() + { + auto home = getenv("HOME"); + if(home) + { + fs::path kdeglobals{home}; + kdeglobals /= ".kde/share/config/kdeglobals"; + + std::error_code err; + if(fs::exists(kdeglobals, err)) + { + std::ifstream ifs{kdeglobals}; + return find_value(ifs, "Icons", "Theme"); + } + } + return {}; + } + + std::string find_theme_name() + { + auto name = find_kde_theme_name(); + + if(name.empty()) + return find_gnome_theme_name(); + + return name; + } + + + class icon_theme + { + public: + icon_theme(const std::string& name): + theme_name_(name), + ifs_("/usr/share/icons/" + name + "/index.theme") + { + //First of all, read the Inherits and Directories + inherits_ = split_value(find_value(ifs_, "Icon Theme", "Inherits")); + directories_ = split_value(find_value(ifs_, "Icon Theme", "Directories")); + + } + + std::string find(const std::string& name, std::size_t size_wanted) const + { + namespace fs = std::filesystem; + //candidates + std::vector> first, second, third; + + fs::path theme_path = "/usr/share/icons/" + theme_name_; + + std::string base_path = "/usr/share/icons/" + theme_name_ + "/"; + std::string filename = "/" + name + ".png"; + + std::error_code err; + for(auto & dir : directories_) + { + if(!fs::exists(theme_path / dir / (name + ".png"), err)) + continue; + + auto size = find_value(ifs_, dir, "Size"); + auto type = find_value(ifs_, dir, "Type"); + auto scaled = find_value(ifs_, dir, "Scale"); + + if(size.empty() || ("Fixed" != type)) + continue; + + int int_size = std::stoi(size); + + if(!scaled.empty()) + int_size *= std::stoi(scaled); + + auto distance = std::abs(static_cast(size_wanted) - int_size); + + if(0 == distance) + { + if(scaled.empty() || scaled == "1") + return base_path + dir + filename; + else + first.emplace_back(dir, 0); + } + else + { + if(scaled.empty() || scaled == "1") + second.emplace_back(dir, distance); + else + third.emplace_back(dir, distance); + } + } + + using pair_type = std::pair; + auto comp = [](const pair_type& a, const pair_type& b){ + return a.second < b.second; + }; + + std::sort(first.begin(), first.end(), comp); + std::sort(second.begin(), second.end(), comp); + std::sort(third.begin(), third.end(), comp); + + std::string closer_dir; + if(!first.empty()) + closer_dir = first.front().first; + else if(!second.empty()) + closer_dir = second.front().first; + else if(!third.empty()) + closer_dir = third.front().first; + + + if(closer_dir.empty()) + { + for(auto & inh : inherits_) + { + auto dir = icon_theme{inh}.find(name, size_wanted); + if(!dir.empty()) + return dir; + } + + //Avoid recursively traverse directory for hicolor if current theme name is hicolor + if("hicolor" == theme_name_) + return {}; + + return icon_theme{"hicolor"}.find(name, size_wanted); + } + + return base_path + closer_dir + filename; + } + private: + const std::string theme_name_; + mutable std::ifstream ifs_; + std::vector inherits_; + std::vector directories_; + }; + + theme::theme(): + path_("/usr/share/icons/"), + ifs_("/usr/share/icons/default/index.theme") + { + } + + std::string theme::cursor(const std::string& name) const + { + auto theme = find_value(ifs_, "Icon Theme", "Inherits"); + return path_ + theme + "/cursors/" + name; + } + + std::string theme::icon(const std::string& name, std::size_t size_wanted) const + { + //Lookup in cache + auto i = iconcache_.find(name); + if(i != iconcache_.end()) + { + for(auto & p : i->second) + { + if(p.first == size_wanted) + return p.second; + } + } + + //Cache is missed. + auto file = icon_theme{find_theme_name()}.find(name, size_wanted); + if(!file.empty()) + iconcache_[name].emplace_back(size_wanted, file); + + return file; + } + + } + +} +#endif \ No newline at end of file diff --git a/source/detail/posix/theme.hpp b/source/detail/posix/theme.hpp new file mode 100644 index 00000000..6b4f7a75 --- /dev/null +++ b/source/detail/posix/theme.hpp @@ -0,0 +1,31 @@ +#ifndef NANA_DETAIL_THEME_INCLUDED +#define NANA_DETAIL_THEME_INCLUDED + +#include +#include +#include +#include + +namespace nana +{ + namespace detail + { + class theme + { + public: + theme(); + + std::string cursor(const std::string& name) const; + std::string icon(const std::string& name, std::size_t size_wanted) const; + private: + std::string path_; + mutable std::ifstream ifs_; + + mutable std::map>> iconcache_; + }; + + }//end namespace detail + +}//end namespace nana + +#endif \ No newline at end of file diff --git a/source/detail/posix/xdnd_protocol.hpp b/source/detail/posix/xdnd_protocol.hpp new file mode 100644 index 00000000..aa1ae432 --- /dev/null +++ b/source/detail/posix/xdnd_protocol.hpp @@ -0,0 +1,303 @@ +/* + * X-Window XDND Protocol Implementation + * Nana C++ Library(http://www.nanapro.org) + * Copyright(C) 2018-2019 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/posix/xdnd_protocol.hpp + * + * The XDS is not supported. + */ +#ifndef NANA_DETAIL_POSIX_XDND_PROTOCOL_INCLUDED +#define NANA_DETAIL_POSIX_XDND_PROTOCOL_INCLUDED + +#include "platform_spec.hpp" +#include + +#include "theme.hpp" +#include + +#include + + +namespace nana{ + namespace detail + { + + struct xdnd_data + { + Atom requested_action; + std::vector files; + }; + + class xdnd_protocol + { + public: + enum class xdnd_status_state + { + normal, + position, + drop, //Use the 'accept' flag of XdndStatus when mouse has released(XdndDrop has been sent). + status_ignore + }; + + xdnd_protocol(Window source): + spec_(nana::detail::platform_spec::instance()), + source_(source) + { + auto disp = spec_.open_display(); + detail::platform_scope_guard lock; + ::XSetSelectionOwner(disp, spec_.atombase().xdnd_selection, source, CurrentTime); + + cursor_.dnd_copy = ::XcursorLibraryLoadCursor(disp, "dnd-copy"); + cursor_.dnd_move = ::XcursorLibraryLoadCursor(disp, "dnd-move"); + cursor_.dnd_none = ::XcursorLibraryLoadCursor(disp, "dnd-none"); + } + + ~xdnd_protocol() + { + auto disp = spec_.open_display(); + ::XFreeCursor(disp, cursor_.dnd_copy); + ::XFreeCursor(disp, cursor_.dnd_move); + ::XFreeCursor(disp, cursor_.dnd_none); + } + + void mouse_move(Window wd, const nana::point& pos, Atom requested_action) + { + if(wd != target_) + { + _m_xdnd_leave(); + + if(_m_xdnd_enter(wd)) + _m_xdnd_position(pos, requested_action); + } + else + _m_xdnd_position(pos, requested_action); + } + + void mouse_leave() + { + _m_xdnd_leave(); + } + + bool mouse_release() + { + return _m_xdnd_drop(); + } + + Atom executed_action() const + { + return executed_action_; + } + + //Return true to exit xdnd_protocol event handler + bool client_message(const ::XClientMessageEvent& xclient) + { + auto & atombase = spec_.atombase(); + + if(atombase.xdnd_status == xclient.message_type) + { + if(xdnd_status_state::position != xstate_ && xdnd_status_state::drop != xstate_) + return false; + + Window target_wd = static_cast(xclient.data.l[0]); + bool is_accepted_by_target = (xclient.data.l[1] & 1); + + if(xclient.data.l[1] & 0x2) + { + rectangle rct{ + static_cast(xclient.data.l[2] >> 16), + static_cast(xclient.data.l[2] & 0xFFFF), + static_cast(xclient.data.l[3] >> 16), + static_cast(xclient.data.l[3] & 0xFFFF) + }; + + if(!rct.empty()) + mvout_table_[target_wd] = rct; + } + _m_cursor(is_accepted_by_target); + + if((!is_accepted_by_target) && (xdnd_status_state::drop == xstate_)) + { + _m_xdnd_leave(); + return true; + } + + executed_action_ = xclient.data.l[4]; + xstate_ = xdnd_status_state::normal; + } + else if(atombase.xdnd_finished == xclient.message_type) + return true; + + return false; + } + + void selection_request(const ::XSelectionRequestEvent& xselectionrequest, const xdnd_data& data) + { + if(spec_.atombase().xdnd_selection == xselectionrequest.selection) + { + ::XEvent evt; + evt.xselection.type = SelectionNotify; + evt.xselection.display = xselectionrequest.display; + evt.xselection.requestor = xselectionrequest.requestor; + evt.xselection.selection = xselectionrequest.selection; + evt.xselection.target = 0; + evt.xselection.property = 0; + evt.xselection.time = xselectionrequest.time; + + if(xselectionrequest.target == spec_.atombase().text_uri_list) + { + if(data.files.size()) + { + std::string uri_list; + for(auto& file : data.files) + { + uri_list += "file://"; + uri_list += file.u8string(); + uri_list += "\r\n"; + } + + ::XChangeProperty (spec_.open_display(), + xselectionrequest.requestor, + xselectionrequest.property, + xselectionrequest.target, + 8, PropModeReplace, + reinterpret_cast(&uri_list.front()), uri_list.size() + 1); + + evt.xselection.property = xselectionrequest.property; + evt.xselection.target = xselectionrequest.target; + + } + } + + platform_scope_guard lock; + ::XSendEvent(spec_.open_display(), xselectionrequest.requestor, False, 0, &evt); + ::XFlush(spec_.open_display()); + + if(0 == evt.xselection.target) + _m_xdnd_leave(); + } + } + private: + bool _m_xdnd_enter(Window wd) + { + //xdnd version of the window + auto xdnd_ver = _m_xdnd_aware(wd); + if(0 == xdnd_ver) + return false; + + target_ = wd; + _m_client_msg(spec_.atombase().xdnd_enter, (xdnd_ver << 24), spec_.atombase().text_uri_list, XA_STRING); + + return true; + } + + void _m_xdnd_position(const nana::point& pos, Atom requested_action) + { + if(xdnd_status_state::normal != xstate_) + return; + + auto i = mvout_table_.find(target_); + if(i != mvout_table_.end() && i->second.is_hit(pos)) + return; + + xstate_ = xdnd_status_state::position; + //Send XdndPosition + long position = (pos.x << 16 | pos.y); + _m_client_msg(spec_.atombase().xdnd_position, 0, position, CurrentTime, requested_action); + } + + void _m_xdnd_leave() + { + ::XUndefineCursor(spec_.open_display(), source_); + + if(target_) + { + _m_client_msg(spec_.atombase().xdnd_leave, 0, 0, 0); + target_ = 0; + executed_action_ = 0; + } + } + + bool _m_xdnd_drop() + { + ::XUndefineCursor(spec_.open_display(), source_); + xstate_ = xdnd_status_state::drop; + + if(executed_action_) + _m_client_msg(spec_.atombase().xdnd_drop, 0, CurrentTime, 0); + + target_ = 0; + + return (executed_action_ != 0); + } + private: + //dndversion<<24, fl_XdndURIList, XA_STRING, 0 + void _m_client_msg(Atom xdnd_atom, long data1, long data2, long data3, long data4 = 0) + { + auto const display = spec_.open_display(); + XEvent evt; + ::memset(&evt, 0, sizeof evt); + evt.xany.type = ClientMessage; + evt.xany.display = display; + evt.xclient.window = target_; + evt.xclient.message_type = xdnd_atom; + evt.xclient.format = 32; + + //Target window + evt.xclient.data.l[0] = source_; + + evt.xclient.data.l[1] = data1; + evt.xclient.data.l[2] = data2; + evt.xclient.data.l[3] = data3; + evt.xclient.data.l[4] = data4; + + ::XSendEvent(display, target_, True, NoEventMask, &evt); + } + + // Returns the XDND version of specified window + //@return the XDND version. If the specified window does not have property XdndAware, it returns 0 + int _m_xdnd_aware(Window wd) + { + Atom actual; int format; unsigned long count, remaining; + unsigned char *data = 0; + ::XGetWindowProperty(spec_.open_display(), wd, spec_.atombase().xdnd_aware, + 0, 4, False, XA_ATOM, &actual, &format, &count, &remaining, &data); + + int version = 0; + if (data) + { + if ((actual == XA_ATOM) && (format==32) && count) + version = int(*(Atom*)data); + + ::XFree(data); + } + return version; + } + + void _m_cursor(bool accepted) + { + ::XDefineCursor(spec_.open_display(), source_, (accepted ? cursor_.dnd_move : cursor_.dnd_none)); + } + private: + nana::detail::platform_spec& spec_; + Window const source_; + Window target_{ 0 }; + Atom executed_action_{ 0 }; + xdnd_status_state xstate_{xdnd_status_state::normal}; + std::map mvout_table_; + + struct cursor_rep + { + Cursor dnd_copy{ 0 }; + Cursor dnd_move{ 0 }; + Cursor dnd_none{ 0 }; + }cursor_; + }; //end class xdnd_protocol + } +} + +#endif \ No newline at end of file diff --git a/source/filesystem/filesystem.cpp b/source/filesystem/filesystem.cpp index ee636f18..b13e8465 100644 --- a/source/filesystem/filesystem.cpp +++ b/source/filesystem/filesystem.cpp @@ -1,6 +1,6 @@ /* * A ISO C++ FileSystem Implementation - * Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -45,7 +45,7 @@ #include #endif -namespace fs = std::experimental::filesystem; +namespace fs = std::filesystem; namespace nana { @@ -102,6 +102,22 @@ namespace nana std::string pretty_file_date(const fs::path& path) // todo: move to .cpp { + struct tm t; + if (modified_file_time(path, t)) + { + std::stringstream tm; + tm << std::put_time(&t, "%Y-%m-%d, %H:%M:%S"); + return tm.str(); + } + return {}; + +/* + // Deprecated + //Windows stores file times using the FILETIME structure, which is a 64 bit value of 100ns intervals from January 1, 1601. + //What's worse is that this 1601 date is fairly common to see given that it's the all zeroes value, and it is far before the + //earliest date representable with time_t.std::filesystem can't change the reality of the underlying platform. + + try { #if NANA_USING_BOOST_FILESYSTEM // The return type of boost::filesystem::last_write_time isn't @@ -117,11 +133,8 @@ namespace nana if (ftime == ((fs::file_time_type::min)())) return{}; - //std::time_t cftime = decltype(ftime)::clock::to_time_t(ftime); - //A workaround for VC2013 using time_point = decltype(ftime); - auto cftime = time_point::clock::to_time_t(ftime); std::stringstream tm; @@ -131,6 +144,8 @@ namespace nana catch (...) { return{}; } +#endif +*/ } bool modified_file_time(const fs::path& p, struct tm& t) @@ -209,9 +224,9 @@ namespace nana { namespace experimental { namespace filesystem //Because of No wide character version of POSIX #if defined(NANA_POSIX) - const char* splstr = "/"; + const char* separators = "/"; #else - const wchar_t* splstr = L"/\\"; + const wchar_t* separators = L"/\\"; #endif //class file_status @@ -249,14 +264,23 @@ namespace nana { namespace experimental { namespace filesystem /// true if the path is empty, false otherwise. ?? bool path::empty() const noexcept { -#if defined(NANA_WINDOWS) - return (::GetFileAttributes(pathstr_.c_str()) == INVALID_FILE_ATTRIBUTES); -#elif defined(NANA_POSIX) - struct stat sta; - return (::stat(pathstr_.c_str(), &sta) == -1); + return pathstr_.empty(); + } + + bool path::is_absolute() const + { +#ifdef NANA_WINDOWS + return has_root_name() && has_root_directory(); +#else + return has_root_directory(); #endif } + bool path::is_relative() const + { + return !is_absolute(); + } + path path::extension() const { // todo: make more globlal @@ -277,6 +301,107 @@ namespace nana { namespace experimental { namespace filesystem return path(pathstr_.substr(pos)); } + bool is_directory_separator(path::value_type ch) + { + return (ch == '/') +#ifdef NANA_WINDOWS + || (ch == path::preferred_separator) +#endif + ; + } + + + + path path::root_name() const + { + auto pos = pathstr_.find_first_not_of(separators); + if (pathstr_.npos != pos) + { + pos = pathstr_.find_first_of(separators, pos + 1); + if (pathstr_.npos != pos) + { + if ((is_directory_separator(pathstr_[0]) && is_directory_separator(pathstr_[1])) +#ifdef NANA_WINDOWS + || (pathstr_[pos - 1] == ':') +#endif + ) + return pathstr_.substr(0, pos); + } + } + + return path{}; + } + + std::size_t root_directory_pos(const path::string_type& str) + { +#ifdef NANA_WINDOWS + // case "c:/" + if (str.size() > 2 + && str[1] == ':' + && is_directory_separator(str[2])) return 2; +#endif + + // case "//" + if (str.size() == 2 + && is_directory_separator(str[0]) + && is_directory_separator(str[1])) return path::string_type::npos; + +#ifdef NANA_WINDOWS + // case "\\?\" + if (str.size() > 4 + && is_directory_separator(str[0]) + && is_directory_separator(str[1]) + && str[2] == '?' + && is_directory_separator(str[3])) + { + auto pos = str.find_first_of(separators, 4); + return pos < str.size() ? pos : str.npos; + } +#endif + + // case "//net {/}" + if (str.size() > 3 + && is_directory_separator(str[0]) + && is_directory_separator(str[1]) + && !is_directory_separator(str[2])) + { + auto pos = str.find_first_of(separators, 2); + return pos < str.size() ? pos : str.npos; + } + + // case "/" + if (str.size() > 0 && is_directory_separator(str[0])) return 0; + + return str.npos; + } + + path path::root_directory() const + { + auto pos = root_directory_pos(pathstr_); + + return pos == string_type::npos + ? path() + : path(pathstr_.substr(pos, 1)); + } + + path path::root_path() const + { + return root_name().pathstr_ + root_directory().pathstr_; + } + + path path::relative_path() const + { + if (!empty()) + { + auto pos = root_directory_pos(pathstr_); + + pos = pathstr_.find_first_not_of(separators, pos); + if (pathstr_.npos != pos) + return path{ pathstr_.substr(pos) }; + } + return{}; + } + path path::parent_path() const { return{nana_fs::parent_path(pathstr_)}; @@ -310,14 +435,14 @@ namespace nana { namespace experimental { namespace filesystem path path::filename() const { - auto pos = pathstr_.find_last_of(splstr); + auto pos = pathstr_.find_last_of(separators); if (pos != pathstr_.npos) { if (pos + 1 == pathstr_.size()) { value_type tmp[2] = {preferred_separator, 0}; - if (pathstr_.npos != pathstr_.find_last_not_of(splstr, pos)) + if (pathstr_.npos != pathstr_.find_last_not_of(separators, pos)) tmp[0] = '.'; return{ tmp }; @@ -328,6 +453,21 @@ namespace nana { namespace experimental { namespace filesystem return{ pathstr_ }; } + void path::clear() noexcept + { + pathstr_.clear(); + } + + path& path::make_preferred() + { +#ifdef NANA_WINDOWS + std::replace(pathstr_.begin(), pathstr_.end(), L'/', L'\\'); +#else + std::replace(pathstr_.begin(), pathstr_.end(), '\\', '/'); +#endif + return *this; + } + path& path::remove_filename() { #ifdef NANA_WINDOWS @@ -418,10 +558,10 @@ namespace nana { namespace experimental { namespace filesystem if (this == &p) { auto other = p.pathstr_; - if ((other.front() != '/') && (other.front() == path::preferred_separator)) + if (!is_directory_separator(other.front())) { - if (!this->pathstr_.empty() && (pathstr_.back() != '/' && pathstr_.back() == path::preferred_separator)) - pathstr_.append(path::preferred_separator, 1); + if (!pathstr_.empty() && !is_directory_separator(pathstr_.back())) + pathstr_.append(1, path::preferred_separator); } pathstr_ += other; @@ -429,10 +569,10 @@ namespace nana { namespace experimental { namespace filesystem else { auto & other = p.pathstr_; - if ((other.front() != '/') && (other.front() == path::preferred_separator)) + if (!is_directory_separator(other.front())) { - if (!this->pathstr_.empty() && (pathstr_.back() != '/' && pathstr_.back() == path::preferred_separator)) - pathstr_.append(path::preferred_separator, 1); + if (!pathstr_.empty() && !is_directory_separator(pathstr_.back())) + pathstr_.append(1, path::preferred_separator); } pathstr_ += p.pathstr_; @@ -534,6 +674,12 @@ namespace nana { namespace experimental { namespace filesystem _m_prepare(file_path); } + directory_iterator::directory_iterator(const path& p, directory_options opt): + option_(opt) + { + _m_prepare(p); + } + const directory_iterator::value_type& directory_iterator::operator*() const { return value_; } const directory_iterator::value_type* @@ -790,7 +936,7 @@ namespace nana { namespace experimental { namespace filesystem auto stat = status(p, err); if (err != std::error_code()) - throw filesystem_error("nana::experimental::filesystem::status", p, err); + throw filesystem_error("nana::filesystem::status", p, err); return stat; } @@ -849,8 +995,23 @@ namespace nana { namespace experimental { namespace filesystem return (status(p).type() == file_type::directory); } + bool is_directory(const path& p, std::error_code& ec) noexcept + { + return (status(p, ec).type() == file_type::directory); + } + std::uintmax_t file_size(const path& p) { + std::error_code err; + auto bytes = file_size(p, err); + if (err) + throw filesystem_error("nana::filesystem::status", p, err); + + return bytes; + } + + std::uintmax_t file_size(const path& p, std::error_code& ec) noexcept + { #if defined(NANA_WINDOWS) //Some compilation environment may fail to link to GetFileSizeEx typedef BOOL(__stdcall *GetFileSizeEx_fptr_t)(HANDLE, PLARGE_INTEGER); @@ -868,23 +1029,25 @@ namespace nana { namespace experimental { namespace filesystem return li.QuadPart; } } - return 0; + ec.assign(static_cast(::GetLastError()), std::generic_category()); #elif defined(NANA_POSIX) FILE * stream = ::fopen(p.c_str(), "rb"); - long long size = 0; if (stream) { + long long bytes = 0; # if defined(NANA_LINUX) fseeko64(stream, 0, SEEK_END); - size = ftello64(stream); + bytes = ftello64(stream); # elif defined(NANA_POSIX) fseeko(stream, 0, SEEK_END); - size = ftello(stream); + bytes = ftello(stream); # endif ::fclose(stream); + return bytes; } - return size; + ec.assign(static_cast(::errno), std::generic_category()); #endif + return static_cast(-1); } @@ -976,5 +1139,141 @@ namespace nana { namespace experimental { namespace filesystem }//end namespace filesystem } //end namespace experimental }//end namespace nana + +namespace std +{ + namespace filesystem + { +#if defined(NANA_FILESYSTEM_FORCE) || \ + (defined(_MSC_VER) && ((!defined(_MSVC_LANG)) || (_MSVC_LANG < 201703))) + path absolute(const path& p) + { + if (p.empty()) + return p; + + auto abs_base = current_path(); + + // store expensive to compute values that are needed multiple times + path p_root_name(p.root_name()); + path base_root_name(abs_base.root_name()); + path p_root_directory(p.root_directory()); + + if (!p_root_name.empty()) // p.has_root_name() + { + if (p_root_directory.empty()) // !p.has_root_directory() + return p_root_name / abs_base.root_directory() + / abs_base.relative_path() / p.relative_path(); + // p is absolute, so fall through to return p at end of block + } + else if (!p_root_directory.empty()) // p.has_root_directory() + { +#ifdef NANA_POSIX + // POSIX can have root name it it is a network path + if (base_root_name.empty()) // !abs_base.has_root_name() + return p; +#endif + return base_root_name / p; + } + else + return abs_base / p; + + return p; // p.is_absolute() is true + } + + path absolute(const path& p, std::error_code& err) + { + return absolute(p); + } + + path canonical(const path& p, std::error_code* err) + { + path source(p.is_absolute() ? p : absolute(p)); + path root(source.root_path()); + path result; + + std::error_code local_ec; + file_status stat(status(source, local_ec)); + + if (stat.type() == file_type::not_found) + { + if (nullptr == err) + throw (filesystem_error( + "nana::filesystem::canonical", source, + error_code(static_cast(errc::no_such_file_or_directory), generic_category()))); + err->assign(static_cast(errc::no_such_file_or_directory), generic_category()); + return result; + } + else if (local_ec) + { + if (nullptr == err) + throw (filesystem_error( + "nana::filesystem::canonical", source, local_ec)); + *err = local_ec; + return result; + } + + + auto tmp_p = source; + + std::vector source_elements; + while (tmp_p != root) + { + source_elements.emplace(source_elements.begin(), tmp_p.filename()); + tmp_p.remove_filename(); + } + + result = root; + + for(auto & e : source_elements) + { + auto str = e.string(); + if("." == str) + continue; + else if(".." == str) + { + if(result != root) + result.remove_filename(); + continue; + } + + result /= e; + } + + if (err) + err->clear(); + + return result; + } + + path canonical(const path& p) + { + return canonical(p, nullptr); + } + + path canonical(const path& p, std::error_code& err) + { + return canonical(p, &err); + } +#endif + +#if defined(NANA_FILESYSTEM_FORCE) || defined(NANA_MINGW) + bool exists( std::filesystem::file_status s ) noexcept + { + return s.type() != file_type::not_found; + } + + bool exists( const std::filesystem::path& p ) + { + return exists(status(p)); + } + + bool exists( const std::filesystem::path& p, std::error_code& ec ) noexcept + { + return exists(status(p, ec)); + } +#endif + }//end namespace filesystem +}//end namespace std + #endif diff --git a/source/gui/animation.cpp b/source/gui/animation.cpp index c737d334..16e54729 100644 --- a/source/gui/animation.cpp +++ b/source/gui/animation.cpp @@ -1,7 +1,7 @@ /* * An Animation Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -571,6 +571,25 @@ namespace nana delete impl_; } + animation::animation(animation&& rhs) + : impl_(rhs.impl_) + { + rhs.impl_ = new impl(23); + } + + animation& animation::operator=(animation&& rhs) + { + if (this != &rhs) + { + auto imp = new impl{ 23 }; + + delete impl_; + impl_ = rhs.impl_; + rhs.impl_ = imp; + } + return *this; + } + void animation::push_back(frameset frms) { impl_->framesets.emplace_back(std::move(frms)); diff --git a/source/gui/detail/basic_window.cpp b/source/gui/detail/basic_window.cpp index dc072370..a52b2215 100644 --- a/source/gui/detail/basic_window.cpp +++ b/source/gui/detail/basic_window.cpp @@ -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-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -216,40 +216,14 @@ namespace nana basic_window::other_tag::other_tag(category::flags categ) : category(categ), active_window(nullptr), upd_state(update_state::none) { -#ifndef WIDGET_FRAME_DEPRECATED - switch(categ) - { - case category::flags::root: - attribute.root = new attr_root_tag; - break; - case category::flags::frame: - attribute.frame = new attr_frame_tag; - break; - default: - attribute.root = nullptr; - } -#else if (category::flags::root == categ) attribute.root = new attr_root_tag; else attribute.root = nullptr; -#endif } basic_window::other_tag::~other_tag() { -#ifndef WIDGET_FRAME_DEPRECATED - switch(category) - { - case category::flags::root: - delete attribute.root; - break; - case category::flags::frame: - delete attribute.frame; - break; - default: break; - } -#endif if (category::flags::root == category) delete attribute.root; } @@ -290,14 +264,6 @@ namespace nana } } -#ifndef WIDGET_FRAME_DEPRECATED - void basic_window::frame_window(native_window_type wd) - { - if(category::flags::frame == this->other.category) - other.attribute.frame->container = wd; - } -#endif - bool basic_window::is_ancestor_of(const basic_window* wd) const { while (wd) @@ -410,6 +376,7 @@ namespace nana flags.enabled = true; flags.modal = false; flags.take_active = true; + flags.draggable = false; flags.dropable = false; flags.fullscreen = false; flags.tab = nana::detail::tab_type::none; @@ -423,6 +390,7 @@ namespace nana flags.ignore_menubar_focus = false; flags.ignore_mouse_focus = false; flags.space_click_enabled = false; + flags.ignore_child_mapping = false; visible = false; diff --git a/source/gui/detail/bedrock_pi.cpp b/source/gui/detail/bedrock_pi.cpp index 221d04c8..65d644ec 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-2018 Jinhao(cnjinhao@hotmail.com) +* Copyright(C) 2003-2019 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 @@ */ #include "../../detail/platform_spec_selector.hpp" -#include +#include "bedrock_types.hpp" #include #include #include @@ -94,6 +94,12 @@ namespace nana }; + bedrock::core_window_t* bedrock::focus() + { + auto wd = wd_manager().root(native_interface::get_focus_window()); + return (wd ? wd->other.attribute.root->focus : nullptr); + } + events_operation& bedrock::evt_operation() { return pi_data_->evt_operation; @@ -164,17 +170,10 @@ namespace nana caret_wd->annex.caret_ptr->visible(false); } - if (!exposed) + if ((!exposed) && (category::flags::root != wd->other.category)) { - if (category::flags::root != wd->other.category) - { - //find an ancestor until it is not a lite_widget - wd = wd->seek_non_lite_widget_ancestor(); - } -#ifndef WIDGET_FRAME_DEPRECATED - else if (category::flags::frame == wd->other.category) - wd = wd_manager().find_window(wd->root, wd->pos_root.x, wd->pos_root.y); -#endif + //find an ancestor until it is not a lite_widget + wd = wd->seek_non_lite_widget_ancestor(); } wd_manager().refresh_tree(wd); @@ -609,5 +608,93 @@ namespace nana throw std::runtime_error("Invalid event code"); } } + + void bedrock::thread_context_destroy(core_window_t * wd) + { + auto ctx = get_thread_context(0); + if(ctx && ctx->event_window == wd) + ctx->event_window = nullptr; + } + + void bedrock::thread_context_lazy_refresh() + { + auto ctx = get_thread_context(0); + if(ctx && ctx->event_window) + ctx->event_window->other.upd_state = core_window_t::update_state::refreshed; + } + + bool bedrock::emit(event_code evt_code, core_window_t* wd, const ::nana::event_arg& arg, bool ask_update, thread_context* thrd, const bool bForce__EmitInternal) + { + if(wd_manager().available(wd) == false) + return false; + + core_window_t * prev_wd = nullptr; + if(thrd) + { + prev_wd = thrd->event_window; + thrd->event_window = wd; + _m_event_filter(evt_code, wd, thrd); + } + + using update_state = basic_window::update_state; + + if (update_state::none == wd->other.upd_state) + wd->other.upd_state = update_state::lazy; + + auto ignore_mapping_value = wd->flags.ignore_child_mapping; + wd->flags.ignore_child_mapping = true; + + _m_emit_core(evt_code, wd, false, arg, bForce__EmitInternal); + + wd->flags.ignore_child_mapping = ignore_mapping_value; + + bool good_wd = false; + if(wd_manager().available(wd)) + { + //A child of wd may not be drawn if it was out of wd's range before wd resized, + //so refresh all children of wd when a resized occurs. + if(ask_update || (event_code::resized == evt_code) || (update_state::refreshed == wd->other.upd_state)) + { + wd_manager().do_lazy_refresh(wd, false, (event_code::resized == evt_code)); + } + else + { + wd_manager().map_requester(wd); + wd->other.upd_state = update_state::none; + } + + good_wd = true; + } + + + if(thrd) thrd->event_window = prev_wd; + return good_wd; + } + + void bedrock::_m_event_filter(event_code event_id, core_window_t * wd, thread_context * thrd) + { + auto not_state_cur = (wd->root_widget->other.attribute.root->state_cursor == nana::cursor::arrow); + + switch(event_id) + { + case event_code::mouse_enter: + if (not_state_cur) + set_cursor(wd, wd->predef_cursor, thrd); + break; + case event_code::mouse_leave: + if (not_state_cur && (wd->predef_cursor != cursor::arrow)) + set_cursor(wd, nana::cursor::arrow, thrd); + break; + case event_code::destroy: + if (wd->root_widget->other.attribute.root->state_cursor_window == wd) + undefine_state_cursor(wd, thrd); + + if(wd == thrd->cursor.window) + set_cursor(wd, cursor::arrow, thrd); + break; + default: + break; + } + } }//end namespace detail }//end namespace nana diff --git a/source/gui/detail/bedrock_posix.cpp b/source/gui/detail/bedrock_posix.cpp index 62738e98..65e66701 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-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 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,6 @@ #include "../../detail/platform_spec_selector.hpp" #if defined(NANA_POSIX) && defined(NANA_X11) -#include #include #include #include @@ -22,6 +21,8 @@ #include #include +#include "bedrock_types.hpp" + namespace nana { namespace detail @@ -52,38 +53,6 @@ namespace detail }; #pragma pack() - struct bedrock::thread_context - { - unsigned event_pump_ref_count{0}; - - int window_count{0}; //The number of windows - core_window_t* event_window{nullptr}; - bool is_alt_pressed{false}; - bool is_ctrl_pressed{false}; - - struct platform_detail_tag - { - native_window_type motion_window; - nana::point motion_pointer_pos; - }platform; - - struct cursor_tag - { - core_window_t * window; - native_window_type native_handle; - nana::cursor predef_cursor; - Cursor handle; - }cursor; - - thread_context() - { - cursor.window = nullptr; - cursor.native_handle = nullptr; - cursor.predef_cursor = nana::cursor::arrow; - cursor.handle = 0; - } - }; - struct bedrock::private_impl { typedef std::map thr_context_container; @@ -246,13 +215,7 @@ namespace detail { return bedrock_object; } - - bedrock::core_window_t* bedrock::focus() - { - core_window_t* wd = wd_manager().root(native_interface::get_focus_window()); - return (wd ? wd->other.attribute.root->focus : 0); - } - + void bedrock::get_key_state(arg_keyboard& arg) { XKeyEvent xkey; @@ -289,46 +252,6 @@ namespace detail //No implementation for Linux } - bool bedrock::emit(event_code evt_code, core_window_t* wd, const ::nana::event_arg& arg, bool ask_update, thread_context* thrd, const bool bForce__EmitInternal) - { - if(wd_manager().available(wd) == false) - return false; - - core_window_t * prev_wd = nullptr; - if(thrd) - { - prev_wd = thrd->event_window; - thrd->event_window = wd; - _m_event_filter(evt_code, wd, thrd); - } - - using update_state = basic_window::update_state; - - if(wd->other.upd_state == update_state::none) - wd->other.upd_state = update_state::lazy; - - _m_emit_core(evt_code, wd, false, arg, bForce__EmitInternal); - - bool good_wd = false; - if(wd_manager().available(wd)) - { - //A child of wd may not be drawn if it was out of wd's range before wd resized, - //so refresh all children of wd when a resized occurs. - if(ask_update || (event_code::resized == evt_code) || (update_state::refreshed == wd->other.upd_state)) - { - wd_manager().do_lazy_refresh(wd, false, (event_code::resized == evt_code)); - } - else - wd->other.upd_state = update_state::none; - - good_wd = true; - } - - - if(thrd) thrd->event_window = prev_wd; - return good_wd; - } - void assign_arg(arg_mouse& arg, basic_window* wd, unsigned msg, const XEvent& evt) { arg.window_handle = reinterpret_cast(wd); @@ -418,10 +341,10 @@ namespace detail { switch(msg.kind) { - case nana::detail::msg_packet_tag::kind_xevent: + case nana::detail::msg_packet_tag::pkt_family::xevent: window_proc_for_xevent(display, msg.u.xevent); break; - case nana::detail::msg_packet_tag::kind_mouse_drop: + case nana::detail::msg_packet_tag::pkt_family::mouse_drop: window_proc_for_packet(display, msg); break; default: break; @@ -441,8 +364,9 @@ namespace detail switch(msg.kind) { - case nana::detail::msg_packet_tag::kind_mouse_drop: + case nana::detail::msg_packet_tag::pkt_family::mouse_drop: msgwd = brock.wd_manager().find_window(native_window, {msg.u.mouse_drop.x, msg.u.mouse_drop.y}); + if(msgwd) { arg_dropfiles arg; @@ -973,7 +897,12 @@ namespace detail case Expose: if(msgwnd->visible && (msgwnd->root_graph->empty() == false)) { - nana::detail::platform_scope_guard lock; + nana::internal_scope_guard lock; + //Don't lock this scope using platform-scope-guard. Because it would cause the platform-scope-lock to be locked + //before the internal-scope-guard, and the order of locking would cause dead-lock. + // + //Locks this scope using internal-scope-guard is correct and safe. In the scope, the Xlib functions aren't called + //directly. if(msgwnd->is_draw_through()) { msgwnd->other.attribute.root->draw_through(); @@ -1243,10 +1172,12 @@ namespace detail default: if(message == ClientMessage) { - auto & atoms = nana::detail::platform_spec::instance().atombase(); - if(atoms.wm_protocols == xevent.xclient.message_type) + auto & spec = ::nana::detail::platform_spec::instance(); + auto & xclient = xevent.xclient; + auto & atoms = spec.atombase(); + if(atoms.wm_protocols == xclient.message_type) { - if(msgwnd->flags.enabled && (atoms.wm_delete_window == static_cast(xevent.xclient.data.l[0]))) + if(msgwnd->flags.enabled && (atoms.wm_delete_window == static_cast(xclient.data.l[0]))) { arg_unload arg; arg.window_handle = reinterpret_cast(msgwnd); @@ -1338,20 +1269,6 @@ namespace detail }//end bedrock::event_loop - void bedrock::thread_context_destroy(core_window_t * wd) - { - bedrock::thread_context * thr = get_thread_context(0); - if(thr && thr->event_window == wd) - thr->event_window = nullptr; - } - - void bedrock::thread_context_lazy_refresh() - { - thread_context* thrd = get_thread_context(0); - if(thrd && thrd->event_window) - thrd->event_window->other.upd_state = core_window_t::update_state::refreshed; - } - //Dynamically set a cursor for a window void bedrock::set_cursor(core_window_t* wd, nana::cursor cur, thread_context* thrd) { @@ -1421,32 +1338,6 @@ namespace detail if (rev_wd) set_cursor(rev_wd, rev_wd->predef_cursor, thrd); } - - void bedrock::_m_event_filter(event_code event_id, core_window_t * wd, thread_context * thrd) - { - auto not_state_cur = (wd->root_widget->other.attribute.root->state_cursor == nana::cursor::arrow); - - switch(event_id) - { - case event_code::mouse_enter: - if (not_state_cur) - set_cursor(wd, wd->predef_cursor, thrd); - break; - case event_code::mouse_leave: - if (not_state_cur && (wd->predef_cursor != cursor::arrow)) - set_cursor(wd, nana::cursor::arrow, thrd); - break; - case event_code::destroy: - if (wd->root_widget->other.attribute.root->state_cursor_window == wd) - undefine_state_cursor(wd, thrd); - - if(wd == thrd->cursor.window) - set_cursor(wd, cursor::arrow, thrd); - break; - default: - break; - } - } }//end namespace detail }//end namespace nana #endif //NANA_POSIX && NANA_X11 diff --git a/source/gui/detail/bedrock_types.hpp b/source/gui/detail/bedrock_types.hpp new file mode 100644 index 00000000..f3fb55bc --- /dev/null +++ b/source/gui/detail/bedrock_types.hpp @@ -0,0 +1,101 @@ +#ifndef NANA_GUI_DETAIL_BEDROCK_TYPES_INCLUDED +#define NANA_GUI_DETAIL_BEDROCK_TYPES_INCLUDED + +#include + +#include +#include +#include +#include +#include + +namespace nana +{ + namespace detail + { + struct bedrock::pi_data + { + color_schemes scheme; + events_operation evt_operation; + window_manager wd_manager; + std::set auto_form_set; + bool shortkey_occurred{ false }; + + struct menu_rep + { + core_window_t* taken_window{ nullptr }; + bool delay_restore{ false }; + native_window_type window{ nullptr }; + native_window_type owner{ nullptr }; + bool has_keyboard{ false }; + }menu; + }; + + +#ifdef NANA_WINDOWS + struct bedrock::thread_context + { + unsigned event_pump_ref_count{0}; + int window_count{0}; //The number of windows + core_window_t* event_window{nullptr}; + + struct platform_detail_tag + { + wchar_t keychar; + }platform; + + struct cursor_tag + { + core_window_t * window; + native_window_type native_handle; + nana::cursor predef_cursor; + HCURSOR handle; + }cursor; + + thread_context() + { + cursor.window = nullptr; + cursor.native_handle = nullptr; + cursor.predef_cursor = nana::cursor::arrow; + cursor.handle = nullptr; + } + }; +#else + struct bedrock::thread_context + { + unsigned event_pump_ref_count{0}; + + int window_count{0}; //The number of windows + core_window_t* event_window{nullptr}; + bool is_alt_pressed{false}; + bool is_ctrl_pressed{false}; + + struct platform_detail_tag + { + native_window_type motion_window; + nana::point motion_pointer_pos; + }platform; + + struct cursor_tag + { + core_window_t * window; + native_window_type native_handle; + nana::cursor predef_cursor; + Cursor handle; + }cursor; + + thread_context() + { + cursor.window = nullptr; + cursor.native_handle = nullptr; + cursor.predef_cursor = nana::cursor::arrow; + cursor.handle = 0; + } + }; +#endif + } +} + +#include + +#endif \ No newline at end of file diff --git a/source/gui/detail/bedrock_windows.cpp b/source/gui/detail/bedrock_windows.cpp index 674fe475..bd02a00d 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-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -14,8 +14,7 @@ #include "../../detail/platform_spec_selector.hpp" #if defined(NANA_WINDOWS) -#include -#include +#include "bedrock_types.hpp" #include #include #include @@ -36,6 +35,8 @@ #define WM_MOUSEHWHEEL 0x020E #endif +#include "bedrock_types.hpp" + typedef void (CALLBACK *win_event_proc_t)(HWINEVENTHOOK hWinEventHook, DWORD event, HWND hwnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime); namespace nana @@ -135,34 +136,6 @@ namespace detail }; #pragma pack() - struct bedrock::thread_context - { - unsigned event_pump_ref_count{0}; - int window_count{0}; //The number of windows - core_window_t* event_window{nullptr}; - - struct platform_detail_tag - { - wchar_t keychar; - }platform; - - struct cursor_tag - { - core_window_t * window; - native_window_type native_handle; - nana::cursor predef_cursor; - HCURSOR handle; - }cursor; - - thread_context() - { - cursor.window = nullptr; - cursor.native_handle = nullptr; - cursor.predef_cursor = nana::cursor::arrow; - cursor.handle = nullptr; - } - }; - struct bedrock::private_impl { typedef std::map thr_context_container; @@ -1176,10 +1149,10 @@ namespace detail std::unique_ptr varbuf; std::size_t bufsize = 0; - unsigned size = ::DragQueryFile(drop, 0xFFFFFFFF, 0, 0); + unsigned size = ::DragQueryFile(drop, 0xFFFFFFFF, nullptr, 0); for(unsigned i = 0; i < size; ++i) { - unsigned reqlen = ::DragQueryFile(drop, i, 0, 0) + 1; + unsigned reqlen = ::DragQueryFile(drop, i, nullptr, 0) + 1; if(bufsize < reqlen) { varbuf.reset(new wchar_t[reqlen]); @@ -1187,8 +1160,7 @@ namespace detail } ::DragQueryFile(drop, i, varbuf.get(), reqlen); - - dropfiles.files.emplace_back(to_utf8(varbuf.get())); + dropfiles.files.emplace_back(varbuf.get()); } while(msgwnd && (msgwnd->flags.dropable == false)) @@ -1594,12 +1566,6 @@ namespace detail return ::DefWindowProc(root_window, message, wParam, lParam); } - auto bedrock::focus() ->core_window_t* - { - core_window_t* wd = wd_manager().root(native_interface::get_focus_window()); - return (wd ? wd->other.attribute.root->focus : nullptr); - } - void bedrock::get_key_state(arg_keyboard& kb) { kb.alt = (0 != (::GetKeyState(VK_MENU) & 0x80)); @@ -1677,42 +1643,6 @@ namespace detail } } - bool bedrock::emit(event_code evt_code, core_window_t* wd, const ::nana::event_arg& arg, bool ask_update, thread_context* thrd, const bool bForce__EmitInternal) - { - if (wd_manager().available(wd) == false) - return false; - - basic_window* prev_event_wd = nullptr; - if (thrd) - { - prev_event_wd = thrd->event_window; - thrd->event_window = wd; - _m_event_filter(evt_code, wd, thrd); - } - - using update_state = basic_window::update_state; - - if (update_state::none == wd->other.upd_state) - wd->other.upd_state = update_state::lazy; - - _m_emit_core(evt_code, wd, false, arg, bForce__EmitInternal); - - bool good_wd = false; - if (wd_manager().available(wd)) - { - //Ignore ask_update if update state is refreshed. - if (ask_update || (update_state::refreshed == wd->other.upd_state)) - wd_manager().do_lazy_refresh(wd, false); - else - wd->other.upd_state = update_state::none; - - good_wd = true; - } - - if (thrd) thrd->event_window = prev_event_wd; - return good_wd; - } - const wchar_t* translate(cursor id) { const wchar_t* name = IDC_ARROW; @@ -1741,20 +1671,6 @@ namespace detail return name; } - void bedrock::thread_context_destroy(core_window_t * wd) - { - auto * thr = get_thread_context(0); - if (thr && thr->event_window == wd) - thr->event_window = nullptr; - } - - void bedrock::thread_context_lazy_refresh() - { - auto* thrd = get_thread_context(0); - if (thrd && thrd->event_window) - thrd->event_window->other.upd_state = core_window_t::update_state::refreshed; - } - //Dynamically set a cursor for a window void bedrock::set_cursor(core_window_t* wd, nana::cursor cur, thread_context* thrd) { @@ -1847,32 +1763,6 @@ namespace detail ::ShowCursor(FALSE); ::SetCursor(rev_handle); } - - void bedrock::_m_event_filter(event_code event_id, core_window_t * wd, thread_context * thrd) - { - auto not_state_cur = (wd->root_widget->other.attribute.root->state_cursor == nana::cursor::arrow); - - switch(event_id) - { - case event_code::mouse_enter: - if (not_state_cur) - set_cursor(wd, wd->predef_cursor, thrd); - break; - case event_code::mouse_leave: - if (not_state_cur && (wd->predef_cursor != cursor::arrow)) - set_cursor(wd, cursor::arrow, thrd); - break; - case event_code::destroy: - if (wd->root_widget->other.attribute.root->state_cursor_window == wd) - undefine_state_cursor(wd, thrd); - - if(wd == thrd->cursor.window) - set_cursor(wd, cursor::arrow, thrd); - break; - default: - break; - } - } }//end namespace detail }//end namespace nana #endif //NANA_WINDOWS diff --git a/source/gui/detail/color_schemes.cpp b/source/gui/detail/color_schemes.cpp index d9fca610..3bfea8db 100644 --- a/source/gui/detail/color_schemes.cpp +++ b/source/gui/detail/color_schemes.cpp @@ -1,7 +1,7 @@ /* * Color Schemes * Nana C++ Library(http://www.nanapro.org) -* Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) +* Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -24,6 +24,14 @@ namespace nana : color_(std::make_shared(clr)) {} + color_proxy::color_proxy(color_argb clr) + : color_(std::make_shared(clr)) + {} + + color_proxy::color_proxy(color_rgba clr) + : color_(std::make_shared(clr)) + {} + color_proxy::color_proxy(colors clr) : color_(std::make_shared(clr)) {} @@ -47,6 +55,18 @@ namespace nana return *this; } + color_proxy& color_proxy::operator = (color_argb clr) + { + color_ = std::make_shared<::nana::color>(clr); + return *this; + } + + color_proxy& color_proxy::operator = (color_rgba clr) + { + color_ = std::make_shared<::nana::color>(clr); + return *this; + } + color_proxy& color_proxy::operator = (colors clr) { color_ = std::make_shared<::nana::color>(clr); @@ -58,9 +78,16 @@ namespace nana return *color_; } + color color_proxy::get(const color& default_color) const + { + if (color_ && !color_->invisible()) + return *color_; + return default_color; + } + color_proxy::operator color() const { - return *color_; + return (color_ ? *color_ : color{}); } //end class color_proxy diff --git a/source/gui/detail/drawer.cpp b/source/gui/detail/drawer.cpp index e6b9108a..3f5bc755 100644 --- a/source/gui/detail/drawer.cpp +++ b/source/gui/detail/drawer.cpp @@ -136,11 +136,12 @@ namespace nana evt_disabled_ &= ~(1 << static_cast(evt_code)); // clear } - void drawer_trigger::filter_event(const std::vector evt_codes, const bool bDisabled) + void drawer_trigger::filter_event(const std::vector& evt_codes, const bool bDisabled) { - const auto it_end = evt_codes.end(); - for (auto it = evt_codes.begin(); it != it_end; it++) - filter_event(*it, bDisabled); + for (auto evt_code : evt_codes) + { + filter_event(evt_code, bDisabled); + } } void drawer_trigger::filter_event(const event_filter_status& evt_all_states) diff --git a/source/gui/detail/events_operation.cpp b/source/gui/detail/events_operation.cpp index 40fe338e..a278bc96 100644 --- a/source/gui/detail/events_operation.cpp +++ b/source/gui/detail/events_operation.cpp @@ -34,8 +34,10 @@ namespace nana //class docker_base - docker_base::docker_base(event_interface* evt, bool unignorable_flag) - : event_ptr(evt), unignorable(unignorable_flag) + docker_base::docker_base(event_interface* evt, bool unignorable_flag): + event_ptr(evt), + flag_deleted(false), + unignorable(unignorable_flag) {} detail::event_interface * docker_base::get_event() const diff --git a/source/gui/detail/inner_fwd_implement.hpp b/source/gui/detail/inner_fwd_implement.hpp index ed8ef73d..5c66323c 100644 --- a/source/gui/detail/inner_fwd_implement.hpp +++ b/source/gui/detail/inner_fwd_implement.hpp @@ -46,7 +46,7 @@ namespace nana{ void umake(window wd); - std::vector keys(window wd) const; + const std::vector* keys(window wd) const; window find(unsigned long key) const; private: diff --git a/source/gui/detail/native_window_interface.cpp b/source/gui/detail/native_window_interface.cpp index ef0b1935..2d2d0e58 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-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -29,6 +29,7 @@ #include "../../paint/image_accessor.hpp" + namespace nana{ namespace detail{ @@ -199,7 +200,7 @@ namespace nana{ namespace x11_wait { static Bool configure(Display *disp, XEvent *evt, char *arg) - { + { return disp && evt && arg && (evt->type == ConfigureNotify) && (evt->xconfigure.window == *reinterpret_cast(arg)); } @@ -371,6 +372,8 @@ namespace nana{ } Window parent = (owner ? reinterpret_cast(owner) : restrict::spec.root_window()); + + //The position passed to XCreateWindow is a screen coordinate. nana::point pos(r.x, r.y); if((false == nested) && owner) { @@ -396,7 +399,9 @@ namespace nana{ { auto origin_owner = (owner ? owner : reinterpret_cast(restrict::spec.root_window())); restrict::spec.make_owner(origin_owner, reinterpret_cast(handle)); - exposed_positions[handle] = pos; + + //The exposed_position is a relative position to its owner/parent. + exposed_positions[handle] = r.position(); } XChangeWindowAttributes(disp, handle, attr_mask, &win_attr); @@ -844,12 +849,17 @@ namespace nana{ #endif } - void native_interface::refresh_window(native_window_type wd) + void native_interface::refresh_window(native_window_type native_wd) { #if defined(NANA_WINDOWS) - ::InvalidateRect(reinterpret_cast(wd), nullptr, true); + auto wd = reinterpret_cast(native_wd); + RECT r; + ::GetClientRect(wd, &r); + ::InvalidateRect(wd, &r, FALSE); #elif defined(NANA_X11) - static_cast(wd); //eliminate unused parameter compiler warning. + Display * disp = restrict::spec.open_display(); + ::XClearArea(disp, reinterpret_cast(native_wd), 0, 0, 1, 1, true); + ::XFlush(disp); #endif } @@ -945,13 +955,6 @@ namespace nana{ auto fm_extents = window_frame_extents(wd); origin.x = -fm_extents.left; origin.y = -fm_extents.top; - - if(reinterpret_cast(coord_wd) != restrict::spec.root_window()) - { - fm_extents = window_frame_extents(coord_wd); - origin.x += fm_extents.left; - origin.y += fm_extents.top; - } } else coord_wd = get_window(wd, window_relationship::parent); @@ -1009,9 +1012,11 @@ namespace nana{ auto const owner = restrict::spec.get_owner(wd); if(owner && (owner != reinterpret_cast(restrict::spec.root_window()))) { - auto origin = window_position(owner); - x += origin.x; - y += origin.y; + int origin_x, origin_y; + Window child_useless_for_API; + ::XTranslateCoordinates(disp, reinterpret_cast(owner), restrict::spec.root_window(), 0, 0, &origin_x, &origin_y, &child_useless_for_API); + x += origin_x; + y += origin_y; } ::XMoveWindow(disp, reinterpret_cast(wd), x, y); @@ -1058,7 +1063,6 @@ namespace nana{ XSizeHints hints; nana::detail::platform_scope_guard psg; - //Returns if the requested rectangle is same with the current rectangle. //In some X-Server versions/implementations, XMapWindow() doesn't generate //a ConfigureNotify if the requested rectangle is same with the current rectangle. @@ -1098,9 +1102,11 @@ namespace nana{ auto const owner = restrict::spec.get_owner(wd); if(owner && (owner != reinterpret_cast(restrict::spec.root_window()))) { - auto origin = window_position(owner); - x += origin.x; - y += origin.y; + int origin_x, origin_y; + Window child_useless_for_API; + ::XTranslateCoordinates(disp, reinterpret_cast(owner), restrict::spec.root_window(), 0, 0, &origin_x, &origin_y, &child_useless_for_API); + x += origin_x; + y += origin_y; } ::XMoveResizeWindow(disp, reinterpret_cast(wd), x, y, r.width, r.height); @@ -1438,6 +1444,8 @@ namespace nana{ { if(owner) return owner; + + return x11_parent_window(wd); } else if(window_relationship::owner == rsp) return owner; @@ -1580,14 +1588,17 @@ namespace nana{ pos.y = point.y; return true; } - return false; #elif defined(NANA_X11) nana::detail::platform_scope_guard psg; int x = pos.x, y = pos.y; Window child; - return (True == ::XTranslateCoordinates(restrict::spec.open_display(), - reinterpret_cast(wd), restrict::spec.root_window(), x, y, &pos.x, &pos.y, &child)); + if(True == ::XTranslateCoordinates(restrict::spec.open_display(), + reinterpret_cast(wd), restrict::spec.root_window(), x, y, &pos.x, &pos.y, &child)) + { + return true; + } #endif + return false; } bool native_interface::calc_window_point(native_window_type wd, nana::point& pos) @@ -1600,14 +1611,16 @@ namespace nana{ pos.y = point.y; return true; } - return false; #elif defined(NANA_X11) nana::detail::platform_scope_guard psg; int x = pos.x, y = pos.y; Window child; - return (True == ::XTranslateCoordinates(restrict::spec.open_display(), - restrict::spec.root_window(), reinterpret_cast(wd), x, y, &pos.x, &pos.y, &child)); + if(True == ::XTranslateCoordinates(restrict::spec.open_display(), restrict::spec.root_window(), reinterpret_cast(wd), x, y, &pos.x, &pos.y, &child)) + { + return true; + } #endif + return false; } native_window_type native_interface::find_window(int x, int y) diff --git a/source/gui/detail/window_layout.cpp b/source/gui/detail/window_layout.cpp index 19ec8133..12b5f86f 100644 --- a/source/gui/detail/window_layout.cpp +++ b/source/gui/detail/window_layout.cpp @@ -1,7 +1,7 @@ /* * Window Layout Implementation * Nana C++ Library(http://www.nanapro.org) -* Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) +* Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -66,20 +66,9 @@ namespace nana nana::point p_src; for (auto & el : blocks) { -#ifndef WIDGET_FRAME_DEPRECATED - if (category::flags::frame == el.window->other.category) - { - native_window_type container = el.window->other.attribute.frame->container; - native_interface::refresh_window(container); - graph.bitblt(el.r, container); - } - else -#endif - { - p_src.x = el.r.x - el.window->pos_root.x; - p_src.y = el.r.y - el.window->pos_root.y; - graph.bitblt(el.r, (el.window->drawer.graphics), p_src); - } + p_src.x = el.r.x - el.window->pos_root.x; + p_src.y = el.r.y - el.window->pos_root.y; + graph.bitblt(el.r, (el.window->drawer.graphics), p_src); _m_paste_children(el.window, false, req_refresh_children, el.r, graph, nana::point{}); } diff --git a/source/gui/detail/window_manager.cpp b/source/gui/detail/window_manager.cpp index 9d8ebd65..6349685d 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-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -111,17 +111,17 @@ namespace nana } } - std::vector shortkey_container::keys(window wd) const + const std::vector* shortkey_container::keys(window wd) const { if (wd) { for (auto & m : impl_->base) { if (m.handle == wd) - return m.keys; + return &m.keys; } } - return{}; + return nullptr; } window shortkey_container::find(unsigned long key) const @@ -513,12 +513,7 @@ namespace detail if (owner->flags.destroying) throw std::runtime_error("the specified owner is destoryed"); -#ifndef WIDGET_FRAME_DEPRECATED - native = (category::flags::frame == owner->other.category ? - owner->other.attribute.frame->container : owner->root_widget->root); -#else native = owner->root_widget->root; -#endif r.x += owner->pos_root.x; r.y += owner->pos_root.y; } @@ -550,11 +545,6 @@ namespace detail 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); -#ifndef WIDGET_FRAME_DEPRECATED - if (owner && (category::flags::frame == owner->other.category)) - insert_frame(owner, wd); -#endif - bedrock::inc_window(wd->thread_id); this->icon(wd, impl_->default_icon_small, impl_->default_icon_big); return wd; @@ -562,56 +552,6 @@ namespace detail return nullptr; } -#ifndef WIDGET_FRAME_DEPRECATED - window_manager::core_window_t* window_manager::create_frame(core_window_t* parent, const rectangle& r, widget* wdg) - { - //Thread-Safe Required! - std::lock_guard lock(mutex_); - - if (impl_->wd_register.available(parent) == false) return nullptr; - - core_window_t * wd = new core_window_t(parent, widget_notifier_interface::get_notifier(wdg), r, (category::frame_tag**)nullptr); - wd->frame_window(native_interface::create_child_window(parent->root, rectangle(wd->pos_root.x, wd->pos_root.y, r.width, r.height))); - impl_->wd_register.insert(wd, wd->thread_id); - - //Insert the frame_widget into its root frames container. - wd->root_widget->other.attribute.root->frames.push_back(wd); - return (wd); - } - - - bool window_manager::insert_frame(core_window_t* frame, native_window wd) - { - if(frame) - { - //Thread-Safe Required! - std::lock_guard lock(mutex_); - if(category::flags::frame == frame->other.category) - frame->other.attribute.frame->attach.push_back(wd); - return true; - } - return false; - } - - bool window_manager::insert_frame(core_window_t* frame, core_window_t* wd) - { - if(frame) - { - //Thread-Safe Required! - std::lock_guard lock(mutex_); - if(category::flags::frame == frame->other.category) - { - if (impl_->wd_register.available(wd) && (category::flags::root == wd->other.category) && wd->root != frame->root) - { - frame->other.attribute.frame->attach.push_back(wd->root); - return true; - } - } - } - return false; - } -#endif - window_manager::core_window_t* window_manager::create_widget(core_window_t* parent, const rectangle& r, bool is_lite, widget* wdg) { //Thread-Safe Required! @@ -706,11 +646,7 @@ namespace detail std::lock_guard lock(mutex_); if (impl_->wd_register.available(wd) == false) return; -#ifndef WIDGET_FRAME_DEPRECATED - if((category::flags::root == wd->other.category) || (category::flags::frame != wd->other.category)) -#else if (category::flags::root == wd->other.category) -#endif { impl_->misc_register.erase(wd->root); impl_->wd_register.remove(wd); @@ -749,20 +685,7 @@ namespace detail if(visible != wd->visible) { -#ifndef WIDGET_FRAME_DEPRECATED - native_window_type nv = nullptr; - switch(wd->other.category) - { - case category::flags::root: - nv = wd->root; break; - case category::flags::frame: - nv = wd->other.attribute.frame->container; break; - default: //category::widget_tag, category::lite_widget_tag - break; - } -#else auto nv = (category::flags::root == wd->other.category ? wd->root : nullptr); -#endif if(visible && wd->effect.bground) window_layer::make_bground(wd); @@ -1012,22 +935,11 @@ namespace detail return false; } } -#ifndef WIDGET_FRAME_DEPRECATED - else if(category::flags::frame == wd->other.category) - { - native_interface::window_size(wd->other.attribute.frame->container, sz); - for(auto natwd : wd->other.attribute.frame->attach) - native_interface::window_size(natwd, sz); - } -#endif - else + else if(wd->effect.bground && wd->parent) { //update the bground buffer of glass window. - if(wd->effect.bground && wd->parent) - { - wd->other.glass_buffer.make(sz); - window_layer::make_bground(wd); - } + wd->other.glass_buffer.make(sz); + window_layer::make_bground(wd); } } @@ -1072,8 +984,19 @@ namespace detail auto parent = wd->parent; while (parent) { - if (parent->flags.refreshing) + if(parent->flags.ignore_child_mapping || parent->flags.refreshing) + { + auto top = parent; + while(parent->parent) + { + parent = parent->parent; + if(parent->flags.ignore_child_mapping || parent->flags.refreshing) + top = parent; + } + + top->other.mapping_requester.push_back(wd); return; + } parent = parent->parent; } @@ -1091,6 +1014,12 @@ namespace detail std::lock_guard lock(mutex_); if (impl_->wd_register.available(wd) == false) return false; + if ((wd->other.category == category::flags::root) && wd->is_draw_through()) + { + native_interface::refresh_window(wd->root); + return true; + } + if (wd->displayed()) { using paint_operation = window_layer::paint_operation; @@ -1162,42 +1091,24 @@ namespace detail 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; + wd->other.mapping_requester.clear(); } - //get_graphics - //@brief: Get a copy of the graphics object of a window. - // the copy of the graphics object has a same buf handle with the graphics object's, they are count-refered - // here returns a reference that because the framework does not guarantee the wnd's - // graphics object available after a get_graphics call. - bool window_manager::get_graphics(core_window_t* wd, nana::paint::graphics& result) + void window_manager::map_requester(core_window_t* wd) { //Thread-Safe Required! std::lock_guard lock(mutex_); - if (!impl_->wd_register.available(wd)) - return false; - result.make(wd->drawer.graphics.size()); - result.bitblt(0, 0, wd->drawer.graphics); - window_layer::paste_children_to_graphics(wd, result); - return true; - } + if (false == impl_->wd_register.available(wd)) + return; - bool window_manager::get_visual_rectangle(core_window_t* wd, nana::rectangle& r) - { - //Thread-Safe Required! - std::lock_guard lock(mutex_); - return (impl_->wd_register.available(wd) ? - window_layer::read_visual_rectangle(wd, r) : - false); - } + if (wd->visible_parents()) + { + for(auto requestor : wd->other.mapping_requester) + this->map(requestor, true); + } - std::vector window_manager::get_children(core_window_t* wd) const - { - std::lock_guard lock(mutex_); - if (impl_->wd_register.available(wd)) - return wd->children; - return{}; + wd->other.mapping_requester.clear(); } bool window_manager::set_parent(core_window_t* wd, core_window_t* newpa) @@ -1405,7 +1316,6 @@ namespace detail } } - // preconditions of get_tabstop: tabstop is not empty and at least one window is visible window_manager::core_window_t* get_tabstop(window_manager::core_window_t* wd, bool forward) { @@ -1518,9 +1428,8 @@ namespace detail std::lock_guard lock(mutex_); if (impl_->wd_register.available(wd)) { - auto object = root_runtime(wd->root); - if(object) - return object->shortkeys.make(reinterpret_cast(wd), key); + //the root runtime must exist, because the wd is valid. Otherse, it's bug of library + return root_runtime(wd->root)->shortkeys.make(reinterpret_cast(wd), key); } return false; } @@ -1543,35 +1452,6 @@ namespace detail } } - auto window_manager::shortkeys(core_window_t* wd, bool with_children) -> std::vector> - { - std::vector> result; - - //Thread-Safe Required! - std::lock_guard lock(mutex_); - if (impl_->wd_register.available(wd)) - { - auto root_rt = root_runtime(wd->root); - if (root_rt) - { - auto keys = root_rt->shortkeys.keys(reinterpret_cast(wd)); - for (auto key : keys) - result.emplace_back(wd, key); - - if (with_children) - { - for (auto child : wd->children) - { - auto child_keys = shortkeys(child, true); - std::copy(child_keys.begin(), child_keys.end(), std::back_inserter(result)); - } - } - } - } - - return result; - } - window_manager::core_window_t* window_manager::find_shortkey(native_window_type native_window, unsigned long key) { if(native_window) @@ -1631,7 +1511,6 @@ namespace detail { auto * const wdpa = wd->parent; - bool established = (for_new && (wdpa != for_new)); decltype(for_new->root_widget->other.attribute.root) pa_root_attr = nullptr; @@ -1657,7 +1536,7 @@ namespace detail if (root_attr->menubar && check_tree(wd, root_attr->menubar)) root_attr->menubar = nullptr; - sk_holder = shortkeys(wd, true); + _m_shortkeys(wd, true, sk_holder); } else { @@ -1752,17 +1631,6 @@ namespace detail } } -#ifndef WIDGET_FRAME_DEPRECATED - if (category::flags::frame == wd->other.category) - { - //remove the frame handle from the WM frames manager. - utl::erase(root_attr->frames, wd); - - if (established) - pa_root_attr->frames.push_back(wd); - } -#endif - if (established) { wd->parent = for_new; @@ -1851,18 +1719,6 @@ namespace detail wd->drawer.detached(); wd->widget_notifier->destroy(); -#ifndef WIDGET_FRAME_DEPRECATED - if(category::flags::frame == wd->other.category) - { - //The frame widget does not have an owner, and close their element windows without activating owner. - //close the frame container window, it's a native window. - for(auto i : wd->other.attribute.frame->attach) - native_interface::close_window(i); - - native_interface::close_window(wd->other.attribute.frame->container); - } -#endif - if(wd->other.category != category::flags::root) //Not a root window impl_->wd_register.remove(wd); @@ -1875,18 +1731,9 @@ namespace detail if(category::flags::root != wd->other.category) //A root widget always starts at (0, 0) and its childs are not to be changed { wd->pos_root += delta; -#ifndef WIDGET_FRAME_DEPRECATED - if (category::flags::frame != wd->other.category) - { - if (wd->annex.caret_ptr && wd->annex.caret_ptr->visible()) - wd->annex.caret_ptr->update(); - } - else - native_interface::move_window(wd->other.attribute.frame->container, wd->pos_root.x, wd->pos_root.y); -#else + if (wd->annex.caret_ptr && wd->annex.caret_ptr->visible()) wd->annex.caret_ptr->update(); -#endif if (wd->displayed() && wd->effect.bground) window_layer::make_bground(wd); @@ -1901,6 +1748,28 @@ namespace detail } } + void window_manager::_m_shortkeys(core_window_t* wd, bool with_children, std::vector>& keys) const + { + if (impl_->wd_register.available(wd)) + { + //The root_rt must exist, because wd is valid. Otherwise, it's a bug of the library. + auto root_rt = root_runtime(wd->root); + + auto pkeys = root_rt->shortkeys.keys(reinterpret_cast(wd)); + if (pkeys) + { + for (auto key : *pkeys) + keys.emplace_back(wd, key); + } + + if (with_children) + { + for (auto child : wd->children) + _m_shortkeys(child, true, keys); + } + } + } + //_m_find //@brief: find a window on root window through a given root coordinate. // the given root coordinate must be in the rectangle of wnd. diff --git a/source/gui/dragdrop.cpp b/source/gui/dragdrop.cpp new file mode 100644 index 00000000..18f51e52 --- /dev/null +++ b/source/gui/dragdrop.cpp @@ -0,0 +1,1203 @@ +/** +* Drag and Drop Implementation +* Nana C++ Library(http://www.nanapro.org) +* Copyright(C) 2018 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/dragdrop.cpp +* @author: Jinhao(cnjinhao@hotmail.com) +*/ + +#include +#include + +#include +#include + +#include +#include +#include + +#ifdef NANA_WINDOWS +# include +# include +# include +# include +#elif defined(NANA_X11) +# include "../detail/posix/xdnd_protocol.hpp" +# include +# include +# include +#endif + +namespace nana +{ + namespace detail + { + struct dragdrop_data + { + dnd_action requested_action; + std::vector files; + +#ifdef NANA_X11 + xdnd_data to_xdnd_data() const noexcept + { + auto & atombase = nana::detail::platform_spec::instance().atombase(); + xdnd_data xdata; + xdata.requested_action = atombase.xdnd_action_copy; + + switch(requested_action) + { + case dnd_action::copy: + xdata.requested_action = atombase.xdnd_action_copy; break; + case dnd_action::move: + xdata.requested_action = atombase.xdnd_action_move; break; + case dnd_action::link: + xdata.requested_action = atombase.xdnd_action_link; break; + } + + xdata.files = files; + return xdata; + } +#endif + }; + } + + + class dragdrop_session + { + public: + struct target_rep + { + std::set target; + std::map native_target_count; + }; + + virtual ~dragdrop_session() = default; + + void insert(window source, window target) + { + auto &rep = table_[source]; + rep.target.insert(target); +#ifdef NANA_X11 + auto native_wd = API::root(target); + rep.native_target_count[native_wd] += 1; + nana::detail::platform_spec::instance().dragdrop_target(native_wd, true, 1); +#endif + } + + void erase(window source, window target) + { + auto i = table_.find(source); + if (table_.end() == i) + return; + + i->second.target.erase(target); + +#ifdef NANA_WINDOWS + if ((nullptr == target) || i->second.target.empty()) + table_.erase(i); +#else + if(nullptr == target) + { + //remove all targets of source + for(auto & native : i->second.native_target_count) + { + nana::detail::platform_spec::instance().dragdrop_target(native.first, false, native.second); + } + + table_.erase(i); + } + else + { + nana::detail::platform_spec::instance().dragdrop_target(API::root(target), false, 1); + if(i->second.target.empty()) + table_.erase(i); + } +#endif + } + + bool has(window source, window target) const + { + auto i = table_.find(source); + if (i != table_.end()) + return (0 != i->second.target.count(target)); + + return false; + } + + bool has_source(window source) const + { + return (table_.count(source) != 0); + } + + bool empty() const + { + return table_.empty(); + } + + void set_current_source(window source) + { + if (table_.count(source)) + current_source_ = source; + else + current_source_ = nullptr; + } + + window current_source() const + { + return current_source_; + } + private: + std::map table_; + window current_source_{ nullptr }; + }; + +#ifdef NANA_WINDOWS + template + class win32com_iunknown : public Interface + { + public: + //Implements IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void **ppv) + { + if (riid == IID_IUnknown || riid == iid) { + *ppv = static_cast(this); + AddRef(); + return S_OK; + } + *ppv = NULL; + return E_NOINTERFACE; + } + + STDMETHODIMP_(ULONG) AddRef() + { + return InterlockedIncrement(&ref_count_); + } + + STDMETHODIMP_(ULONG) Release() + { + LONG cRef = InterlockedDecrement(&ref_count_); + if (cRef == 0) delete this; + return cRef; + } + private: + LONG ref_count_{ 1 }; + }; + + class win32com_drop_target : public IDropTarget, public dragdrop_session + { + public: + win32com_drop_target(bool simple_mode): + simple_mode_(simple_mode) + { + + } + + //Implements IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void **ppv) + { + if (riid == IID_IUnknown || riid == IID_IDropTarget) { + *ppv = static_cast(this); + AddRef(); + return S_OK; + } + *ppv = NULL; + return E_NOINTERFACE; + } + + STDMETHODIMP_(ULONG) AddRef() + { + return InterlockedIncrement(&ref_count_); + } + + STDMETHODIMP_(ULONG) Release() + { + LONG cRef = InterlockedDecrement(&ref_count_); + if (cRef == 0) delete this; + return cRef; + } + + private: + // IDropTarget + STDMETHODIMP DragEnter(IDataObject* data, DWORD grfKeyState, POINTL pt, DWORD* req_effect) + { + *req_effect &= DROPEFFECT_COPY; + + FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + STGMEDIUM medium; + if (S_OK == data->GetData(&fmt, &medium)) + { + ::ReleaseStgMedium(&medium); + effect_ = DROPEFFECT_COPY; + } + + return S_OK; + } + + STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD* req_effect) + { + //bool found_data = false; + if (simple_mode_) + { + auto hovered_wd = API::find_window(point(pt.x, pt.y)); + + if ((hovered_wd && (hovered_wd == this->current_source())) || this->has(this->current_source(), hovered_wd)) + *req_effect &= DROPEFFECT_COPY; + else + *req_effect = DROPEFFECT_NONE; + } + else + { + *req_effect = effect_; + } + return S_OK; + } + + STDMETHODIMP DragLeave() + { + effect_ = DROPEFFECT_NONE; + return S_OK; + } + + STDMETHODIMP Drop(IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) + { + return E_NOTIMPL; + } + private: + LONG ref_count_{ 1 }; + bool const simple_mode_; //Simple mode behaves the simple_dragdrop. + DWORD effect_{ DROPEFFECT_NONE }; + }; + + class drop_source : public win32com_iunknown + { + public: + drop_source(window wd) : + window_handle_(wd) + {} + + window source() const + { + return window_handle_; + } + private: + // IDropSource + STDMETHODIMP QueryContinueDrag(BOOL esc_pressed, DWORD key_state) override + { + if (esc_pressed) + return DRAGDROP_S_CANCEL; + + //Drop the object if left button is released. + if (0 == (key_state & (MK_LBUTTON))) + return DRAGDROP_S_DROP; + + return S_OK; + } + + STDMETHODIMP GiveFeedback(DWORD effect) override + { + return DRAGDROP_S_USEDEFAULTCURSORS; + } + private: + window const window_handle_; + }; + + class win32_dropdata : public win32com_iunknown + { + public: + struct data_entry + { + FORMATETC format; + STGMEDIUM medium; + bool read_from; //Indicates the data which is used for reading. + + ~data_entry() + { + ::CoTaskMemFree(format.ptd); + ::ReleaseStgMedium(&medium); + } + + bool compare(const FORMATETC& fmt, bool rdfrom) const + { + return (format.cfFormat == fmt.cfFormat && + (format.tymed & fmt.tymed) != 0 && + (format.dwAspect == DVASPECT_THUMBNAIL || format.dwAspect == DVASPECT_ICON || medium.tymed == TYMED_NULL || format.lindex == fmt.lindex || (format.lindex == 0 && fmt.lindex == -1) || (format.lindex == -1 && fmt.lindex == 0)) && + format.dwAspect == fmt.dwAspect && read_from == rdfrom); + } + }; + + data_entry * find(const FORMATETC& fmt, bool read_from) const + { + data_entry * last_weak_match = nullptr; + + for (auto & entry : entries_) + { + if (entry->compare(fmt, read_from)) + { + auto entry_ptd = entry->format.ptd; + if (entry_ptd && fmt.ptd && entry_ptd->tdSize == fmt.ptd->tdSize && (0 == std::memcmp(entry_ptd, fmt.ptd, fmt.ptd->tdSize))) + return entry.get(); + else if (nullptr == entry_ptd && nullptr == fmt.ptd) + return entry.get(); + + last_weak_match = entry.get(); + } + } + return last_weak_match; + } + + void assign(const detail::dragdrop_data& data) + { + if (!data.files.empty()) + { + std::size_t bytes = sizeof(wchar_t); + for (auto & file : data.files) + { + auto file_s = file.wstring(); + bytes += (file_s.size() + 1) * sizeof(file_s.front()); + } + + auto hglobal = ::GlobalAlloc(GHND | GMEM_SHARE, sizeof(DROPFILES) + bytes); + + auto dropfiles = reinterpret_cast(::GlobalLock(hglobal)); + dropfiles->pFiles = sizeof(DROPFILES); + dropfiles->fWide = true; + + auto file_buf = reinterpret_cast(dropfiles) + sizeof(DROPFILES); + + for (auto & file : data.files) + { + auto file_s = file.wstring(); + std::memcpy(file_buf, file_s.data(), (file_s.size() + 1) * sizeof(file_s.front())); + file_buf += (file_s.size() + 1) * sizeof(file_s.front()); + } + *reinterpret_cast(file_buf) = 0; + + ::GlobalUnlock(hglobal); + + assign(hglobal); + } + } + + data_entry* assign(HGLOBAL hglobal) + { + FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + auto entry = find(fmt, true); + if (entry) + { + //Free the current entry for reuse + ::CoTaskMemFree(entry->format.ptd); + ::ReleaseStgMedium(&entry->medium); + } + else + { + //Create a new entry + entries_.emplace_back(new data_entry); + entry = entries_.back().get(); + } + + //Assign the format to the entry. + entry->read_from = true; + entry->format = fmt; + + //Assign the stgMedium + entry->medium.tymed = TYMED_HGLOBAL; + entry->medium.hGlobal = hglobal; + entry->medium.pUnkForRelease = nullptr; + + return entry; + } + public: + // Implement IDataObject + STDMETHODIMP GetData(FORMATETC *request_format, STGMEDIUM *pmedium) override + { + if (!(request_format && pmedium)) + return E_INVALIDARG; + + pmedium->hGlobal = nullptr; + + auto entry = find(*request_format, true); + if (entry) + return _m_copy_medium(pmedium, &entry->medium, &entry->format); + + return DV_E_FORMATETC; + } + + STDMETHODIMP GetDataHere(FORMATETC *pformatetc, STGMEDIUM *pmedium) override + { + return E_NOTIMPL; + } + + STDMETHODIMP QueryGetData(FORMATETC *pformatetc) override + { + if (NULL == pformatetc) + return E_INVALIDARG; + + if (!(DVASPECT_CONTENT & pformatetc->dwAspect)) + return DV_E_DVASPECT; + + HRESULT result = DV_E_TYMED; + + for (auto & entry : entries_) + { + if (entry->format.tymed & pformatetc->tymed) + { + if (entry->format.cfFormat == pformatetc->cfFormat) + return S_OK; + + result = DV_E_FORMATETC; + } + } + return result; + } + + STDMETHODIMP GetCanonicalFormatEtc(FORMATETC *pformatectIn, FORMATETC *pformatetcOut) override + { + return E_NOTIMPL; + } + + STDMETHODIMP SetData(FORMATETC *pformatetc, STGMEDIUM *pmedium, BOOL fRelease) override + { + if (!(pformatetc && pmedium)) + return E_INVALIDARG; + + if (pformatetc->tymed != pmedium->tymed) + return E_FAIL; + + entries_.emplace_back(new data_entry); + auto entry = entries_.back().get(); + + entry->format = *pformatetc; + + _m_copy_medium(&entry->medium, pmedium, pformatetc); + + if (TRUE == fRelease) + ::ReleaseStgMedium(pmedium); + + return S_OK; + } + + STDMETHODIMP EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppenumFormatEtc) override + { + if (NULL == ppenumFormatEtc) + return E_INVALIDARG; + + if (DATADIR_GET != dwDirection) + return E_NOTIMPL; + + *ppenumFormatEtc = nullptr; + + FORMATETC rgfmtetc[] = + { + //{ CF_UNICODETEXT, nullptr, DVASPECT_CONTENT, 0, TYMED_HGLOBAL } + { CF_HDROP, nullptr, DVASPECT_CONTENT, 0, TYMED_HGLOBAL } + }; + return ::SHCreateStdEnumFmtEtc(ARRAYSIZE(rgfmtetc), rgfmtetc, ppenumFormatEtc); + } + + STDMETHODIMP DAdvise(FORMATETC *pformatetc, DWORD advf, IAdviseSink *pAdvSink, DWORD *pdwConnection) override + { + return OLE_E_ADVISENOTSUPPORTED; + } + + STDMETHODIMP DUnadvise(DWORD dwConnection) override + { + return OLE_E_ADVISENOTSUPPORTED; + } + + STDMETHODIMP EnumDAdvise(IEnumSTATDATA **ppenumAdvise) override + { + return OLE_E_ADVISENOTSUPPORTED; + } + private: + static HRESULT _m_copy_medium(STGMEDIUM* stgmed_dst, STGMEDIUM* stgmed_src, FORMATETC* fmt_src) + { + if (!(stgmed_dst && stgmed_src && fmt_src)) + return E_INVALIDARG; + + switch (stgmed_src->tymed) + { + case TYMED_HGLOBAL: + stgmed_dst->hGlobal = (HGLOBAL)OleDuplicateData(stgmed_src->hGlobal, fmt_src->cfFormat, 0); + break; + case TYMED_GDI: + case TYMED_ENHMF: + //GDI object can't be copied to an existing HANDLE + if (stgmed_dst->hGlobal) + return E_INVALIDARG; + + stgmed_dst->hGlobal = (HBITMAP)OleDuplicateData(stgmed_src->hGlobal, fmt_src->cfFormat, 0); + break; + case TYMED_MFPICT: + stgmed_dst->hMetaFilePict = (HMETAFILEPICT)OleDuplicateData(stgmed_src->hMetaFilePict, fmt_src->cfFormat, 0); + break; + case TYMED_FILE: + stgmed_dst->lpszFileName = (LPOLESTR)OleDuplicateData(stgmed_src->lpszFileName, fmt_src->cfFormat, 0); + break; + case TYMED_ISTREAM: + stgmed_dst->pstm = stgmed_src->pstm; + stgmed_src->pstm->AddRef(); + break; + case TYMED_ISTORAGE: + stgmed_dst->pstg = stgmed_src->pstg; + stgmed_src->pstg->AddRef(); + break; + case TYMED_NULL: + default: + break; + } + stgmed_dst->tymed = stgmed_src->tymed; + stgmed_dst->pUnkForRelease = nullptr; + if (stgmed_src->pUnkForRelease) + { + stgmed_dst->pUnkForRelease = stgmed_src->pUnkForRelease; + stgmed_src->pUnkForRelease->AddRef(); + } + return S_OK; + } + private: + std::vector> entries_; + }; + + +#elif defined(NANA_X11) + class x11_dropdata + { + public: + void assign(const detail::dragdrop_data& data) + { + data_ = &data; + } + + const detail::dragdrop_data* data() const + { + return data_; + } + private: + const detail::dragdrop_data* data_{nullptr}; + }; + + class x11_dragdrop: public detail::x11_dragdrop_interface, public dragdrop_session + { + public: + x11_dragdrop(bool simple_mode): + simple_mode_(simple_mode) + { + } + + bool simple_mode() const noexcept + { + return simple_mode_; + } + public: + //Implement x11_dragdrop_interface + void add_ref() override + { + ++ref_count_; + } + + std::size_t release() override + { + std::size_t val = --ref_count_; + if(0 == val) + delete this; + + return val; + } + private: + bool const simple_mode_; + std::atomic ref_count_{ 1 }; + }; +#endif + + class dragdrop_service + { + dragdrop_service() = default; + public: +#ifdef NANA_WINDOWS + using dragdrop_target = win32com_drop_target; + using dropdata_type = win32_dropdata; +#else + using dragdrop_target = x11_dragdrop; + using dropdata_type = x11_dropdata; +#endif + + static dragdrop_service& instance() + { + static dragdrop_service serv; + return serv; + } + + dragdrop_session* create_dragdrop(window wd, bool simple_mode) + { + auto native_wd = API::root(wd); + if (nullptr == native_wd) + return nullptr; + + dragdrop_target * ddrop = nullptr; + + auto i = table_.find(native_wd); + if(table_.end() == i) + { + ddrop = new dragdrop_target{simple_mode}; +#ifdef NANA_WINDOWS + if (table_.empty()) + ::OleInitialize(nullptr); + + ::RegisterDragDrop(reinterpret_cast(native_wd), ddrop); +#else + if(!_m_spec().register_dragdrop(native_wd, ddrop)) + { + delete ddrop; + return nullptr; + } +#endif + table_[native_wd] = ddrop; + } + else + { + ddrop = dynamic_cast(i->second); + +#ifdef NANA_WINDOWS + ddrop->AddRef(); +#else + ddrop->add_ref(); +#endif + } + + return ddrop; + } + + void remove(window source) + { + auto native_src = API::root(source); + + auto i = table_.find(API::root(source)); + if (i == table_.end()) + return; + + i->second->erase(source, nullptr); + + if (i->second->empty()) + { + auto ddrop = dynamic_cast(i->second); + table_.erase(i); +#ifdef NANA_WINDOWS + ::RevokeDragDrop(reinterpret_cast(native_src)); + ddrop->Release(); +#elif defined(NANA_X11) + _m_spec().remove_dragdrop(native_src); + ddrop->release(); +#endif + } + } + + bool dragdrop(window drag_wd, dropdata_type* dropdata, dnd_action* executed_action) + { + auto i = table_.find(API::root(drag_wd)); + if ((!dropdata) && table_.end() == i) + return false; + + internal_revert_guard rvt_lock; + +#ifdef NANA_WINDOWS + auto drop_src = new drop_source{ drag_wd }; + + i->second->set_current_source(drag_wd); + + DWORD result_effect{ DROPEFFECT_NONE }; + auto status = ::DoDragDrop(dropdata, drop_src, DROPEFFECT_COPY, &result_effect); + + i->second->set_current_source(nullptr); + + delete drop_src; + + if (executed_action) + { + switch (result_effect) + { + case DROPEFFECT_COPY: + *executed_action = dnd_action::copy; break; + case DROPEFFECT_MOVE: + *executed_action = dnd_action::move; break; + case DROPEFFECT_LINK: + *executed_action = dnd_action::link; break; + } + } + + return (DROPEFFECT_NONE != result_effect); +#elif defined(NANA_X11) + auto& atombase = _m_spec().atombase(); + + auto ddrop = dynamic_cast(i->second); + + auto const native_source = reinterpret_cast(API::root(drag_wd)); + + { + detail::platform_scope_guard lock; + ::XSetSelectionOwner(_m_spec().open_display(), atombase.xdnd_selection, native_source, CurrentTime); + } + + + hovered_.window_handle = nullptr; + hovered_.native_wd = 0; + + if(executed_action) + *executed_action = dropdata->data()->requested_action; + + if(ddrop->simple_mode()) + { + + _m_spec().msg_dispatch([this, ddrop, drag_wd, native_source, &atombase](const detail::msg_packet_tag& msg_pkt) mutable{ + if(detail::msg_packet_tag::pkt_family::xevent == msg_pkt.kind) + { + auto const disp = _m_spec().open_display(); + if (MotionNotify == msg_pkt.u.xevent.type) + { + auto pos = API::cursor_position(); + auto native_cur_wd = reinterpret_cast(detail::native_interface::find_window(pos.x, pos.y)); + + const char* icon = nullptr; + if(hovered_.native_wd != native_cur_wd) + { + if(hovered_.native_wd) + { + _m_free_cursor(); + ::XUndefineCursor(disp, hovered_.native_wd); + } + + _m_client_msg(native_cur_wd, native_source, 1, atombase.xdnd_enter, atombase.text_uri_list, XA_STRING); + hovered_.native_wd = native_cur_wd; + } + + + auto cur_wd = API::find_window(API::cursor_position()); + + if(hovered_.window_handle != cur_wd) + { + hovered_.window_handle = cur_wd; + + icon = (((drag_wd == cur_wd) || ddrop->has(drag_wd, cur_wd)) ? "dnd-move" : "dnd-none"); + } + + if(icon) + { + _m_free_cursor(); + hovered_.cursor = ::XcursorFilenameLoadCursor(disp, icons_.cursor(icon).c_str()); + ::XDefineCursor(disp, native_cur_wd, hovered_.cursor); + } + } + else if(msg_pkt.u.xevent.type == ButtonRelease) + { + ::XUndefineCursor(disp, hovered_.native_wd); + _m_free_cursor(); + API::release_capture(drag_wd); + return detail::propagation_chain::exit; + } + + } + return detail::propagation_chain::stop; + }); + + //In simple mode, it always returns true. The drag and drop is determined by class simple_dragdrop + return true; + } + else + { + auto data = dropdata->data()->to_xdnd_data(); + + API::set_capture(drag_wd, true); + nana::detail::xdnd_protocol xdnd_proto{native_source}; + + //Not simple mode + _m_spec().msg_dispatch([this, &data, drag_wd, &xdnd_proto](const detail::msg_packet_tag& msg_pkt) mutable{ + if(detail::msg_packet_tag::pkt_family::xevent == msg_pkt.kind) + { + if (MotionNotify == msg_pkt.u.xevent.type) + { + auto pos = API::cursor_position(); + auto native_cur_wd = reinterpret_cast(detail::native_interface::find_window(pos.x, pos.y)); + + xdnd_proto.mouse_move(native_cur_wd, pos, data.requested_action); + } + else if(ClientMessage == msg_pkt.u.xevent.type) + { + auto & xclient = msg_pkt.u.xevent.xclient; + if(xdnd_proto.client_message(xclient)) + { + API::release_capture(drag_wd); + return detail::propagation_chain::exit; + } + } + else if(SelectionRequest == msg_pkt.u.xevent.type) + { + xdnd_proto.selection_request(msg_pkt.u.xevent.xselectionrequest, data); + } + else if(msg_pkt.u.xevent.type == ButtonRelease) + { + API::release_capture(drag_wd); + _m_free_cursor(); + + //Exits the msg loop if xdnd_proto doesn't send the XdndDrop because of refusal of the DND + if(!xdnd_proto.mouse_release()) + return detail::propagation_chain::exit; + } + + return detail::propagation_chain::stop; + } + return detail::propagation_chain::pass; + }); + + if(xdnd_proto.executed_action() != 0) + { + if(executed_action) + *executed_action = _m_from_xdnd_action(xdnd_proto.executed_action()); + + return true; + } + } +#endif + return false; + } + +#ifdef NANA_X11 + private: + static nana::detail::platform_spec & _m_spec() + { + return nana::detail::platform_spec::instance(); + } + + static dnd_action _m_from_xdnd_action(Atom action) noexcept + { + auto & atombase = _m_spec().atombase(); + if(action == atombase.xdnd_action_copy) + return dnd_action::copy; + else if(action == atombase.xdnd_action_move) + return dnd_action::move; + else if(action == atombase.xdnd_action_link) + return dnd_action::link; + + return dnd_action::copy; + } + + //dndversion<<24, fl_XdndURIList, XA_STRING, 0 + static void _m_client_msg(Window wd_target, Window wd_src, int flag, Atom xdnd_atom, Atom data, Atom data_type) + { + auto const display = _m_spec().open_display(); + ::XEvent evt; + ::memset(&evt, 0, sizeof evt); + evt.xany.type = ClientMessage; + evt.xany.display = display; + evt.xclient.window = wd_target; + evt.xclient.message_type = xdnd_atom; + evt.xclient.format = 32; + + //Target window + evt.xclient.data.l[0] = wd_src; + //Accept set + evt.xclient.data.l[1] = flag; + evt.xclient.data.l[2] = data; + evt.xclient.data.l[3] = data_type; + evt.xclient.data.l[4] = 0; + + ::XSendEvent(display, wd_target, True, NoEventMask, &evt); + + } + + void _m_free_cursor() + { + if(hovered_.cursor) + { + ::XFreeCursor(_m_spec().open_display(), hovered_.cursor); + hovered_.cursor = 0; + } + } +#endif + private: + std::map table_; + +#ifdef NANA_WINDOWS +#elif defined (NANA_X11) + nana::detail::theme icons_; + struct hovered_status + { + Window native_wd{0}; + window window_handle{nullptr}; + + unsigned shape{0}; + Cursor cursor{0}; + }hovered_; +#endif + }; + + + + struct simple_dragdrop::implementation + { + window window_handle{ nullptr }; + dragdrop_session * ddrop{ nullptr }; + std::function predicate; + std::map> targets; + + bool dragging{ false }; + + struct event_handlers + { + nana::event_handle destroy; + nana::event_handle mouse_move; + nana::event_handle mouse_down; + }events; + +#ifdef NANA_X11 + bool cancel() + { + if (!dragging) + return false; + + if (API::is_window(window_handle)) + { + dragging = true; + auto real_wd = reinterpret_cast(window_handle); + real_wd->other.dnd_state = dragdrop_status::not_ready; + } + + API::release_capture(window_handle); + dragging = false; + + return true; + } +#endif + }; + + + + simple_dragdrop::simple_dragdrop(window drag_wd) : + impl_(new implementation) + { + if (!API::is_window(drag_wd)) + { + delete impl_; + throw std::invalid_argument("simple_dragdrop: invalid window handle"); + } + + impl_->ddrop = dragdrop_service::instance().create_dragdrop(drag_wd, true); + + impl_->window_handle = drag_wd; + API::dev::window_draggable(drag_wd, true); + + auto & events = API::events<>(drag_wd); + + impl_->events.destroy = events.destroy.connect_unignorable([this](const arg_destroy&) { + dragdrop_service::instance().remove(impl_->window_handle); + API::dev::window_draggable(impl_->window_handle, false); + }); + + impl_->events.mouse_down = events.mouse_down.connect_unignorable([this](const arg_mouse& arg){ + if (arg.is_left_button() && API::is_window(impl_->window_handle)) + { + impl_->dragging = ((!impl_->predicate) || impl_->predicate()); + + auto real_wd = reinterpret_cast(impl_->window_handle); + real_wd->other.dnd_state = dragdrop_status::ready; + } + }); + + impl_->events.mouse_move = events.mouse_move.connect_unignorable([this](const arg_mouse& arg) { + if (!(arg.is_left_button() && impl_->dragging && API::is_window(arg.window_handle))) + return; + + auto real_wd = reinterpret_cast(arg.window_handle); + real_wd->other.dnd_state = dragdrop_status::in_progress; + + std::unique_ptr dropdata{new dragdrop_service::dropdata_type}; + + auto has_dropped = dragdrop_service::instance().dragdrop(arg.window_handle, dropdata.get(), nullptr); + + real_wd->other.dnd_state = dragdrop_status::not_ready; + impl_->dragging = false; + + if (has_dropped) + { + auto drop_wd = API::find_window(API::cursor_position()); + auto i = impl_->targets.find(drop_wd); + if ((impl_->targets.end() != i) && i->second) + i->second(); + } + }); + } + + simple_dragdrop::~simple_dragdrop() + { + if (impl_->window_handle) + { + dragdrop_service::instance().remove(impl_->window_handle); + API::dev::window_draggable(impl_->window_handle, false); + + API::umake_event(impl_->events.destroy); + API::umake_event(impl_->events.mouse_down); + API::umake_event(impl_->events.mouse_move); + } + + delete impl_; + } + + void simple_dragdrop::condition(std::function predicate_fn) + { + if (nullptr == impl_) + throw std::logic_error("simple_dragdrop is empty"); + + impl_->predicate.swap(predicate_fn); + } + + void simple_dragdrop::make_drop(window target, std::function drop_fn) + { + if (nullptr == impl_) + throw std::logic_error("simple_dragdrop is empty"); + + impl_->ddrop->insert(impl_->window_handle, target); + impl_->targets[target].swap(drop_fn); + } + + //This is class dragdrop + struct dragdrop::implementation + { + window const source_handle; + bool dragging{ false }; + dragdrop_session * ddrop{nullptr}; + std::function predicate; + std::function generator; + std::function drop_finished; + + struct event_handlers + { + nana::event_handle destroy; + nana::event_handle mouse_move; + nana::event_handle mouse_down; + }events; + + implementation(window source): + source_handle(source) + { + ddrop = dragdrop_service::instance().create_dragdrop(source, false); + API::dev::window_draggable(source, true); + } + + void make_drop() + { + if (!generator) + return; + + auto transf_data = generator(); + dragdrop_service::dropdata_type dropdata; + dropdata.assign(*transf_data.real_data_); + + dnd_action executed_action; + auto has_dropped = dragdrop_service::instance().dragdrop(source_handle, &dropdata, &executed_action); + + if(drop_finished) + drop_finished(has_dropped, executed_action, transf_data); + } + }; + + dragdrop::dragdrop(window source) : + impl_(new implementation(source)) + { + auto & events = API::events(source); + impl_->events.destroy = events.destroy.connect_unignorable([this](const arg_destroy&) { + dragdrop_service::instance().remove(impl_->source_handle); + API::dev::window_draggable(impl_->source_handle, false); + }); + + impl_->events.mouse_down = events.mouse_down.connect_unignorable([this](const arg_mouse& arg) { + if (arg.is_left_button() && API::is_window(impl_->source_handle)) + { + impl_->dragging = ((!impl_->predicate) || impl_->predicate()); + + auto real_wd = reinterpret_cast(impl_->source_handle); + real_wd->other.dnd_state = dragdrop_status::ready; + } + }); + + impl_->events.mouse_move = events.mouse_move.connect_unignorable([this](const arg_mouse& arg) { + if (!(arg.is_left_button() && impl_->dragging && API::is_window(arg.window_handle))) + return; + + auto real_wd = reinterpret_cast(arg.window_handle); + real_wd->other.dnd_state = dragdrop_status::in_progress; + + impl_->make_drop(); + + + real_wd->other.dnd_state = dragdrop_status::not_ready; + impl_->dragging = false; + }); + } + + dragdrop::~dragdrop() + { + if (impl_->source_handle) + { + dragdrop_service::instance().remove(impl_->source_handle); + API::dev::window_draggable(impl_->source_handle, false); + + API::umake_event(impl_->events.destroy); + API::umake_event(impl_->events.mouse_down); + API::umake_event(impl_->events.mouse_move); + } + delete impl_; + } + + void dragdrop::condition(std::function predicate_fn) + { + impl_->predicate = predicate_fn; + } + + void dragdrop::prepare_data(std::function generator) + { + impl_->generator = generator; + } + + void dragdrop::drop_finished(std::function finish_fn) + { + impl_->drop_finished = finish_fn; + } + + + dragdrop::data::data(dnd_action requested_action): + real_data_(new detail::dragdrop_data) + { + real_data_->requested_action = requested_action; + } + + dragdrop::data::~data() + { + delete real_data_; + } + + dragdrop::data::data(data&& rhs): + real_data_(new detail::dragdrop_data) + { + std::swap(real_data_, rhs.real_data_); + } + + dragdrop::data& dragdrop::data::operator=(data&& rhs) + { + if (this != &rhs) + { + auto moved_data = new detail::dragdrop_data; + delete real_data_; + real_data_ = rhs.real_data_; + rhs.real_data_ = moved_data; + } + return *this; + } + + void dragdrop::data::insert(std::filesystem::path path) + { + real_data_->files.emplace_back(std::move(path)); + } +}//end namespace nana diff --git a/source/gui/dragger.cpp b/source/gui/dragger.cpp index 44ea2254..946273c7 100644 --- a/source/gui/dragger.cpp +++ b/source/gui/dragger.cpp @@ -1,7 +1,7 @@ /* * A Dragger Implementation * Nana C++ Library(http://www.nanapro.org) -* Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) +* Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -93,9 +93,7 @@ namespace nana for (auto & t : targets_) { t.origin = API::window_position(t.wd); - window owner = API::get_owner_window(t.wd); - if (owner) - API::calc_screen_point(owner, t.origin); + API::calc_screen_point(API::get_owner_window(t.wd), t.origin); } break; case event_code::mouse_move: @@ -108,10 +106,8 @@ namespace nana { if (API::is_window_zoomed(t.wd, true) == false) { - auto owner = API::get_owner_window(t.wd); auto wdps = t.origin; - if (owner) - API::calc_window_point(owner, wdps); + API::calc_window_point(API::get_owner_window(t.wd), wdps); switch (t.move_direction) { diff --git a/source/gui/element.cpp b/source/gui/element.cpp index a67a7341..ea3b94a2 100644 --- a/source/gui/element.cpp +++ b/source/gui/element.cpp @@ -163,7 +163,8 @@ namespace nana bld_fgcolor = fgcolor.blend(highlighted, 0.6); break; case element_state::disabled: - bld_bgcolor = bld_fgcolor = static_cast(0xb2b7bc); + bld_bgcolor = static_cast(0xE0E0E0); + bld_fgcolor = static_cast(0x999A9E); break; default: //Leave things as they are diff --git a/source/gui/filebox.cpp b/source/gui/filebox.cpp index fcaba515..5b2db9e2 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-2018 Jinhao(cnjinhao@hotmail.com) +* Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -10,6 +10,8 @@ * @file: nana/gui/filebox.cpp */ +#include + #include #include #include @@ -33,11 +35,13 @@ # include # include # include +# include "../detail/posix/theme.hpp" #endif -namespace fs = std::experimental::filesystem; -namespace fs_ext = nana::filesystem_ext; +#include //debug +namespace fs = std::filesystem; +namespace fs_ext = nana::filesystem_ext; namespace nana { @@ -151,11 +155,20 @@ namespace nana typedef treebox::item_proxy item_proxy; public: - filebox_implement(window owner, mode dialog_mode, const std::string& title, bool pick_directory = false): + filebox_implement(window owner, mode dialog_mode, const std::string& title, bool pick_directory, bool allow_multi_select): form(owner, API::make_center(owner, 630, 440)), pick_directory_(pick_directory), mode_(dialog_mode) { + images_.folder.open(theme_.icon("folder", 16)); + images_.file.open(theme_.icon("empty", 16)); + images_.exec.open(theme_.icon("exec", 16)); + images_.package.open(theme_.icon("package", 16)); + images_.text.open(theme_.icon("text", 16)); + images_.xml.open(theme_.icon("text-xml", 16)); + images_.image.open(theme_.icon("image", 16)); + images_.pdf.open(theme_.icon("application-pdf", 16)); + internationalization i18n; path_.create(*this); path_.splitstr("/"); @@ -171,7 +184,7 @@ namespace nana throw std::runtime_error("Nana.GUI.Filebox: Wrong categorize path"); if(path.size() == 0) path = "/"; /// \todo : use nana::filesystem_ext::def_rootstr? - _m_load_cat_path(path); + _m_enter_folder(path); }); @@ -212,17 +225,53 @@ namespace nana tree_.create(*this); + //Configure treebox icons + auto & fs_icons = tree_.icon("icon-fs"); + fs_icons.normal.open(theme_.icon("drive-harddisk", 16)); + + auto & folder_icons = tree_.icon("icon-folder"); + folder_icons.normal.open(theme_.icon("folder", 16)); + folder_icons.expanded.open(theme_.icon("folder-open", 16)); + + tree_.icon("icon-home").normal.open(theme_.icon("folder_home", 16)); + ls_file_.create(*this); 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_select_file(arg); + + auto fn_list_handler = [this](const arg_mouse& arg){ + if(event_code::mouse_down == arg.evt_code) + { + selection_.is_deselect_delayed = true; + } + else if(event_code::mouse_up == arg.evt_code) + { + selection_.is_deselect_delayed = false; + + if(_m_sync_with_selection()) + _m_display_target_filenames(); + } + else if(event_code::mouse_move == arg.evt_code) + { + if(arg.left_button) + selection_.is_deselect_delayed = false; + } + else if(event_code::dbl_click == arg.evt_code) + _m_list_dbl_clicked(); }; - ls_file_.events().dbl_click.connect_unignorable(fn_sel_file); - ls_file_.events().mouse_down.connect_unignorable(fn_sel_file); + + ls_file_.events().dbl_click.connect_unignorable(fn_list_handler); + ls_file_.events().mouse_down.connect_unignorable(fn_list_handler); + ls_file_.events().mouse_up.connect_unignorable(fn_list_handler); + ls_file_.events().mouse_move.connect_unignorable(fn_list_handler); + + ls_file_.events().selected.connect_unignorable([this](const arg_listbox& arg){ + _m_select_file(arg.item); + }); + ls_file_.set_sort_compare(0, [](const std::string& a, nana::any* fs_a, const std::string& b, nana::any* fs_b, bool reverse) -> bool { int dira = any_cast(fs_a)->directory ? 1 : 0; @@ -305,8 +354,9 @@ namespace nana tb_file_.events().key_char.connect_unignorable([this](const arg_keyboard& arg) { + allow_fall_back_ = false; if(arg.key == nana::keyboard::enter) - _m_ok(); + _m_try_select(tb_file_.caption()); }); //Don't create the combox for choose a file extension if the dialog is used for picking a directory. @@ -320,11 +370,11 @@ namespace nana btn_ok_.create(*this); btn_ok_.i18n(i18n_eval("NANA_BUTTON_OK_SHORTKEY")); - btn_ok_.events().click.connect_unignorable([this](const arg_click&) { - _m_ok(); + _m_try_select(tb_file_.caption()); }); + btn_cancel_.create(*this); btn_cancel_.i18n(i18n_eval("NANA_BUTTON_CANCEL_SHORTKEY")); @@ -356,6 +406,10 @@ namespace nana } else caption(title); + + + if(!allow_multi_select) + ls_file_.enable_single(true, true); } void def_extension(const std::string& ext) @@ -371,7 +425,7 @@ namespace nana std::string dir; auto pos = init_file.find_last_of("\\/"); - auto file_with_path_removed = (pos != init_file.npos ? init_file.substr(pos + 1) : init_file); + auto filename = (pos != init_file.npos ? init_file.substr(pos + 1) : init_file); if(saved_init_path != init_path) { @@ -379,7 +433,7 @@ namespace nana saved_init_path = init_path; //Phase 2: Check whether init_file contains a path - if(file_with_path_removed == init_file) + if(filename == init_file) { //Phase 3: Check whether init_path is empty if(init_path.size()) @@ -391,9 +445,9 @@ namespace nana else dir = saved_selected_path; - _m_load_cat_path(dir.size() ? dir : fs_ext::path_user().native()); + _m_enter_folder(dir.size() ? dir : fs_ext::path_user().native()); - tb_file_.caption(file_with_path_removed); + tb_file_.caption(filename); } void add_filter(const std::string& desc, const std::string& type) @@ -429,26 +483,29 @@ namespace nana cb_types_.anyobj(i, v); } - bool file(std::string& fs) const + std::vector files() const { - if(selection_.type == kind::none) - return false; + if(kind::none == selection_.type) + return {}; - auto pos = selection_.target.find_last_of("\\/"); - if(pos != selection_.target.npos) - saved_selected_path = selection_.target.substr(0, pos); + auto pos = selection_.targets.front().find_last_of("\\/"); + if(pos != selection_.targets.front().npos) + saved_selected_path = selection_.targets.front().substr(0, pos); else saved_selected_path.clear(); - fs = selection_.target; - return true; + return selection_.targets; } private: void _m_layout() { + unsigned ascent, desent, ileading; + paint::graphics{nana::size{1, 1}}.text_metrics(ascent, desent, ileading); + + auto text_height = ascent + desent + 16; place_.bind(*this); place_.div( "vert" - "" + "" "" "" "" @@ -476,8 +533,10 @@ namespace nana //"FS.HOME", "FS.ROOT". Because a key of the tree widget should not be '/' nodes_.home = tree_.insert("FS.HOME", "Home"); nodes_.home.value(kind::filesystem); + nodes_.home.icon("icon-home"); nodes_.filesystem = tree_.insert("FS.ROOT", "Filesystem"); nodes_.filesystem.value(kind::filesystem); + nodes_.filesystem.icon("icon-fs"); std::vector> paths; paths.emplace_back(fs_ext::path_user().native(), nodes_.home); @@ -493,7 +552,7 @@ namespace nana continue; item_proxy node = tree_.insert(p.second, name, name); - if (false == node.empty()) + if (!node.empty()) { node.value(kind::filesystem); break; @@ -512,7 +571,7 @@ namespace nana { auto path = tree_.make_key_path(arg.item, "/") + "/"; _m_resolute_path(path); - _m_load_cat_path(path); + _m_enter_folder(path); } }); } @@ -572,7 +631,7 @@ namespace nana std::sort(file_container_.begin(), file_container_.end(), pred_sort_fs()); } - void _m_load_cat_path(std::string path) + void _m_enter_folder(std::string path) { if((path.size() == 0) || (path[path.size() - 1] != '/')) path += '/'; @@ -677,7 +736,7 @@ namespace nana if(pick_directory_) return is_dir; - if((is_dir || 0 == extension) || (0 == extension->size())) return true; + if(is_dir || (nullptr == extension) || extension->empty()) return true; for(auto & extstr : *extension) { @@ -703,15 +762,77 @@ namespace nana { if(_m_filter_allowed(fs.name, fs.directory, filter, ext_types)) { - cat.append(fs).value(fs); + auto m = cat.append(fs); + m.value(fs); + + if(fs.directory) + m.icon(images_.folder); + else + { + std::string filename = fs.name; + for(auto ch : fs.name) + { + if('A' <= ch && ch <= 'Z') + ch = ch - 'A' + 'a'; + + filename += ch; + } + + auto size = filename.size(); + paint::image use_image; + + if(size > 3) + { + auto ext3 = filename.substr(size - 3); + if((".7z" == ext3) || (".ar" == ext3) || (".gz" == ext3) || (".xz" == ext3)) + use_image = images_.package; + } + + if(use_image.empty() && (size > 4)) + { + auto ext4 = filename.substr(size - 4); + + if( (".exe" == ext4) || + (".dll" == ext4)) + use_image = images_.exec; + else if((".zip" == ext4) || (".rar" == ext4) || + (".bz2" == ext4) || (".tar" == ext4)) + use_image = images_.package; + else if(".txt" == ext4) + use_image = images_.text; + else if ((".xml" == ext4) || (".htm" == ext4)) + use_image = images_.xml; + else if((".jpg" == ext4) || + (".png" == ext4) || + (".gif" == ext4) || + (".bmp" == ext4)) + use_image = images_.image; + else if(".pdf" == ext4) + use_image = images_.pdf; + } + + if(use_image.empty() && (size > 5)) + { + auto ext5 = filename.substr(size - 5); + if(".lzma" == ext5) + use_image = images_.package; + else if(".html" == ext5) + use_image = images_.xml; + } + + if(use_image.empty()) + m.icon(images_.file); + else + m.icon(use_image); + + } } } ls_file_.auto_draw(true); } - void _m_finish(kind::t type, const std::string& tar) + void _m_finish(kind::t type) { - selection_.target = tar; selection_.type = type; close(); } @@ -755,7 +876,7 @@ namespace nana return; } - fb_._m_load_cat_path(fb_.addr_.filesystem); + fb_._m_enter_folder(fb_.addr_.filesystem); fm_.close(); } @@ -791,76 +912,273 @@ namespace nana return true; } private: - void _m_select_file(const arg_mouse& arg) + void _m_insert_filename(const std::string& name) { - auto sel = ls_file_.selected(); - if(sel.empty()) + if(selection_.targets.cend() == std::find(selection_.targets.cbegin(), selection_.targets.cend(), name)) + selection_.targets.push_back(name); + } + + bool _m_hovered_good() const + { + auto pos = ls_file_.hovered(false); + if(!pos.empty()) + { + item_fs mfs; + ls_file_.at(pos).resolve_to(mfs); + return !mfs.directory; + } + return false; + } + + bool _m_has_good_select() const + { + auto selected_items = ls_file_.selected(); + if(selected_items.size()) + { + for(auto & pos : selected_items) + { + item_fs mfs; + ls_file_.at(pos).resolve_to(mfs); + + if((mode::open_directory == mode_) || (false == mfs.directory)) + return true; + } + } + return false; + } + + bool _m_sync_with_selection() + { + if(_m_has_good_select()) + { + auto selected_items = ls_file_.selected(); + if(selected_items.size() && (selected_items.size() < selection_.targets.size())) + { + selection_.targets.clear(); + for(auto & pos : selected_items) + { + item_fs mfs; + ls_file_.at(pos).resolve_to(mfs); + + if((mode::open_directory == mode_) || (false == mfs.directory)) + selection_.targets.push_back(mfs.name); + } + return true; + } + } + return false; + } + + //pos must be valid + item_fs _m_item_fs(const listbox::index_pair& pos) const + { + item_fs m; + ls_file_.at(pos).resolve_to(m); + return m; + } + + void _m_list_dbl_clicked() + { + auto selected_item = ls_file_.hovered(false); + if(selected_item.empty()) return; - auto index = sel[0]; - item_fs m; - ls_file_.at(index).resolve_to(m); - - if(event_code::dbl_click == arg.evt_code) + item_fs m = _m_item_fs(selected_item); + if(m.directory) { - if(m.directory) - _m_load_cat_path(addr_.filesystem + m.name + "/"); - else - _m_finish(kind::filesystem, addr_.filesystem + m.name); + _m_enter_folder(addr_.filesystem + m.name + "/"); + allow_fall_back_ = true; + } + else + _m_try_select(m.name); + } + + void _m_select_file(const listbox::item_proxy& item) + { + item_fs mfs; + ls_file_.at(item.pos()).resolve_to(mfs); + + if(item.selected()) + { + if((mode::open_directory == mode_) || (false == mfs.directory)) + { + if(ls_file_.selected().size() < 2) + selection_.targets.clear(); + + _m_insert_filename(mfs.name); + } + } + else if(_m_hovered_good() || _m_has_good_select()) + { + for(auto i = selection_.targets.cbegin(); i != selection_.targets.cend();) + { + std::filesystem::path p{*i}; + if(p.filename().u8string() == mfs.name) + { + if(!selection_.is_deselect_delayed) + { + i = selection_.targets.erase(i); + continue; + } + } + ++i; + } + } + _m_display_target_filenames(); + } + + void _m_display_target_filenames() + { + std::string filename_string; + if(selection_.targets.size() == 1) + { + filename_string = selection_.targets.front(); } else { - if((mode::open_directory == mode_) || (false == m.directory)) + for(auto i = selection_.targets.crbegin(); i != selection_.targets.crend(); ++i) { - selection_.target = addr_.filesystem + m.name; - tb_file_.caption(m.name); + std::filesystem::path p{*i}; + if(!filename_string.empty()) + filename_string += ' '; + + filename_string += "\"" + p.filename().u8string() + "\""; } } + + tb_file_.caption(filename_string); } - void _m_ok() + std::vector _m_strip_files(const std::string& text) { - std::string tar = selection_.target; - - if(selection_.target.empty()) + std::vector files; + std::size_t start_pos = 0; + while(true) { - auto file = tb_file_.caption(); - if(file.size()) + while(true) { - internationalization i18n; - if(file[0] == '.') + auto pos = text.find_first_of(" \"", start_pos); + if(text.npos == pos) { - msgbox mb(*this, caption()); - mb.icon(msgbox::icon_warning); - mb<(fattr.type()); + if(text[pos] == '"') + break; + } - //Check if the selected name is a directory - auto is_dir = fs::is_directory(fattr); + auto pos = text.find('"', start_pos); + if(text.npos == pos) + return {}; - if(!is_dir && _m_append_def_extension(tar)) + if(pos - start_pos > 0) + files.push_back(text.substr(start_pos, pos - start_pos)); + + start_pos = pos + 1; + } + + return files; + } + + void _m_msgbox(const char* i18n_idstr, const std::string& arg) const + { + internationalization i18n; + + msgbox mb(*this, caption()); + mb.icon(msgbox::icon_warning); + mb<(fattr.type()); + + //Check if the selected name is a directory + auto is_dir = fs::is_directory(fattr); + + if(!is_dir && _m_append_def_extension(tar)) + { + //Add the extension, then check if it is a directory again. + fattr = fs::status(tar); + ftype = static_cast(fattr.type()); + is_dir = fs::is_directory(fattr); + } + + if(mode::open_directory == mode_) + { + //In open_directory mode, all the folders must be valid + if(!is_dir) { - //Add the extension, then check if it is a directory again. - fattr = fs::status(tar); - ftype = static_cast(fattr.type()); - is_dir = fs::is_directory(fattr); - } + fs::path p{tar}; + auto p1 = p.filename(); + auto p2 = p.parent_path().filename(); + if(allow_fall_back_ && (p.filename() == p.parent_path().filename())) + { + //fallback check, and redirects to its parent path. + tar = p.parent_path().string(); + } + else + { + const char* i18n_idstr = "NANA_FILEBOX_ERROR_DIRECTORY_INVALID"; + if(fs::file_type::not_found == ftype) + i18n_idstr = "NANA_FILEBOX_ERROR_DIRECTORY_NOT_EXISTING_AND_RETRY"; + + _m_msgbox(i18n_idstr, p.filename().string()); + return; + } + } + } + else + { + //Enter the directory if this is the first tar. if(is_dir) { - _m_load_cat_path(tar); - tb_file_.caption(std::string{}); - return; + if(0 == tar_idx) + { + _m_enter_folder(tar); + tb_file_.caption(std::string{}); + return; + } + //Other folders are ignored + continue; } if(mode::write_file != mode_) @@ -869,7 +1187,7 @@ namespace nana { msgbox mb(*this, caption()); mb.icon(msgbox::icon_information); - if(mode::open_file != mode_) + if(mode::open_file == mode_) mb << i18n("NANA_FILEBOX_ERROR_NOT_EXISTING_AND_RETRY", tar); else mb << i18n("NANA_FILEBOX_ERROR_DIRECTORY_NOT_EXISTING_AND_RETRY", tar); @@ -890,9 +1208,15 @@ namespace nana } } } + + auto pos = tar.find_last_not_of("\\/"); + if(pos != tar.npos) + tar.erase(pos + 1); + + selection_.targets.push_back(tar); } - _m_finish(kind::filesystem, tar); + _m_finish(kind::filesystem); } void _m_tr_expand(item_proxy node, bool exp) @@ -914,6 +1238,7 @@ namespace nana auto child = node.append(name, name, kind::filesystem); if(!child.empty()) { + child->icon("icon-folder"); //The try-catch can be eleminated by using //directory_iterator( const std::filesystem::path& p, std::error_code& ec ) noexcept; //in C++17 @@ -957,6 +1282,7 @@ namespace nana textbox tb_file_; combox cb_types_; button btn_ok_, btn_cancel_; + bool allow_fall_back_{false}; struct tree_node_tag { @@ -973,11 +1299,25 @@ namespace nana struct selection_rep { kind::t type; - std::string target; + std::vector targets; + bool is_deselect_delayed{ false }; }selection_; static std::string saved_init_path; static std::string saved_selected_path; + nana::detail::theme theme_; + + struct images + { + paint::image folder; + paint::image file; + paint::image exec; + paint::image package; + paint::image text; + paint::image xml; + paint::image image; + paint::image pdf; + }images_; };//end class filebox_implement std::string filebox_implement::saved_init_path; std::string filebox_implement::saved_selected_path; @@ -995,22 +1335,20 @@ namespace nana window owner; bool open_or_save; - std::string file; + bool allow_multi_select; + std::string init_file; + std::string title; - std::string path; + path_type path; std::vector filters; }; - filebox::filebox(bool is_openmode) - : filebox(nullptr, is_openmode) - { - } - filebox::filebox(window owner, bool open) : impl_(new implement) { impl_->owner = owner; impl_->open_or_save = open; + impl_->allow_multi_select = false; #if defined(NANA_WINDOWS) auto len = ::GetCurrentDirectory(0, nullptr); if(len) @@ -1046,29 +1384,24 @@ namespace nana impl_->owner = wd; } - std::string filebox::title(std::string s) + filebox& filebox::title(std::string s) { impl_->title.swap(s); - return s; + return *this; } - filebox& filebox::init_path(const std::string& ipstr) + filebox& filebox::init_path(const path_type& p) { - if(ipstr.empty()) - { - impl_->path.clear(); - } - else - { - if (fs::is_directory(ipstr)) - impl_->path = ipstr; - } + std::error_code err; + if (p.empty() || is_directory(p, err)) + impl_->path = p; + return *this; } filebox& filebox::init_file(const std::string& ifstr) { - impl_->file = ifstr; + impl_->init_file = ifstr; return *this; } @@ -1079,22 +1412,25 @@ namespace nana return *this; } - std::string filebox::path() const + filebox& filebox::add_filter(const std::vector> &filters) + { + for (auto &f : filters) + add_filter(f.first, f.second); + return *this; + } + + const filebox::path_type& filebox::path() const { return impl_->path; } - std::string filebox::file() const + std::vector filebox::show() const { - return impl_->file; - } + std::vector targets; - bool filebox::show() const - { #if defined(NANA_WINDOWS) - auto winitfile = to_wstring(impl_->file); - std::wstring wfile(winitfile); - wfile.resize(520); + std::wstring wfile = to_wstring(impl_->init_file); + wfile.resize(impl_->allow_multi_select ? (520 + 32*256) : 520); OPENFILENAME ofn; memset(&ofn, 0, sizeof ofn); @@ -1114,29 +1450,29 @@ namespace nana for(auto & f : impl_->filters) { filter_holder += to_wstring(f.des); - filter_holder += static_cast('\0'); - std::wstring fs = to_wstring(f.type); + filter_holder += static_cast('\0'); // separator + std::wstring ff = to_wstring(f.type); std::size_t pos = 0; - while(true) + while(true) // eliminate spaces { - pos = fs.find(L" ", pos); - if(pos == fs.npos) + pos = ff.find(L" ", pos); + if(pos == ff.npos) break; - fs.erase(pos); + ff.erase(pos); } - filter_holder += fs; - filter_holder += static_cast('\0'); + filter_holder += ff; + filter_holder += static_cast('\0'); // separator - //Get the default file extentsion + //Get the default file extension if (default_extension.empty()) { - pos = fs.find_last_of('.'); - if (pos != fs.npos) + pos = ff.find_last_of('.'); + if (pos != ff.npos) { - fs = fs.substr(pos + 1); - if (fs != L"*") + ff = ff.substr(pos + 1); + if (ff != L"*") { - default_extension = fs; + default_extension = ff; ofn.lpstrDefExt = default_extension.data(); } } @@ -1158,19 +1494,46 @@ namespace nana if (!impl_->open_or_save) ofn.Flags = OFN_OVERWRITEPROMPT; //Overwrite prompt if it is save mode + else ofn.Flags = OFN_FILEMUSTEXIST; //In open mode, user can't type name of nonexistent file ofn.Flags |= OFN_NOCHANGEDIR; + if(impl_->allow_multi_select) + { + ofn.Flags |= (OFN_ALLOWMULTISELECT | OFN_EXPLORER); + } { internal_revert_guard revert; if (FALSE == (impl_->open_or_save ? ::GetOpenFileName(&ofn) : ::GetSaveFileName(&ofn))) - return false; + return targets; + } + + if(impl_->allow_multi_select) + { + const wchar_t* str = ofn.lpstrFile; + auto len = ::wcslen(str); + + path_type parent_path{ str }; + str += (len + 1); + + while(*str) + { + len = ::wcslen(str); + targets.emplace_back(parent_path / path_type{str}); + str += (len + 1); + } + impl_->path = parent_path.u8string(); + } + else + { + wfile.resize(std::wcslen(wfile.data())); + + targets.emplace_back(wfile); + impl_->path = targets.front().parent_path().u8string(); } - wfile.resize(std::wcslen(wfile.data())); - impl_->file = to_utf8(wfile); #elif defined(NANA_POSIX) using mode = filebox_implement::mode; - filebox_implement fb(impl_->owner, (impl_->open_or_save ? mode::open_file : mode::write_file), impl_->title); + filebox_implement fb(impl_->owner, (impl_->open_or_save ? mode::open_file : mode::write_file), impl_->title, false, impl_->allow_multi_select); if(impl_->filters.size()) { @@ -1191,41 +1554,58 @@ namespace nana else fb.add_filter("All Files", "*.*"); - fb.load_fs(impl_->path, impl_->file); + fb.load_fs(impl_->path, impl_->init_file); API::modal_window(fb); - if(false == fb.file(impl_->file)) - return false; -#endif - auto tpos = impl_->file.find_last_of("\\/"); - if(tpos != impl_->file.npos) - impl_->path = impl_->file.substr(0, tpos); + + + for(auto & f : fb.files()) + targets.emplace_back(f); + + + if(!targets.empty()) + impl_->path = targets.front().parent_path().u8string(); else impl_->path.clear(); - - return true; - }//end class filebox +#endif + return targets; + } - //class directory_picker + filebox& filebox::allow_multi_select(bool allow) + { + impl_->allow_multi_select = allow; + return *this; + } + //end class filebox + + //class directory picker struct folderbox::implement { window owner; path_type init_path; + std::string title; + bool allow_multi_select; }; - folderbox::folderbox(window owner, const path_type& init_path) - : impl_(new implement) - { - impl_->owner = owner; - impl_->init_path = init_path; - } + folderbox::folderbox(window owner, const path_type& init_path, std::string title) + : impl_(new implement{ owner, fs::canonical(init_path).make_preferred(), title, false}) + {} + folderbox::~folderbox() { delete impl_; } + + folderbox& folderbox::title(std::string s) + { + impl_->title.swap(s); + return *this; + } + + #ifdef NANA_MINGW static int CALLBACK browse_folder_callback(HWND hwnd, UINT msg, LPARAM lparam, LPARAM data) { @@ -1235,7 +1615,9 @@ namespace nana { case BFFM_INITIALIZED: if (data) + { SendMessage(hwnd, BFFM_SETSELECTION, TRUE, data); + } break; } @@ -1243,76 +1625,95 @@ namespace nana } #endif - std::optional folderbox::show() const + folderbox& folderbox::allow_multi_select(bool allow) { + impl_->allow_multi_select = allow; + return *this; + } + + std::vector folderbox::show() const + { + std::vector targets; #ifdef NANA_WINDOWS - std::optional target; nana::detail::platform_spec::co_initializer co_init; #ifndef NANA_MINGW - IFileDialog *fd(nullptr); + IFileOpenDialog *fd(nullptr); HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&fd)); if (SUCCEEDED(hr)) { IShellItem *init_path{ nullptr }; hr = SHCreateItemFromParsingName(impl_->init_path.wstring().c_str(), nullptr, IID_PPV_ARGS(&init_path)); if (SUCCEEDED(hr)) - fd->SetDefaultFolder(init_path); + fd->SetFolder(init_path); - fd->SetOptions(FOS_PICKFOLDERS); + fd->SetOptions(FOS_PICKFOLDERS | (impl_->allow_multi_select ? FOS_ALLOWMULTISELECT : 0)); fd->Show(reinterpret_cast(API::root(impl_->owner))); // the native handle of the parent nana form goes here - IShellItem *si; - hr = fd->GetResult(&si); // fails if user cancelled - if (SUCCEEDED(hr)) + + ::IShellItemArray *sia; + if (SUCCEEDED(fd->GetResults(&sia))) // fails if user cancelled { - PWSTR pwstr(nullptr); - hr = si->GetDisplayName(SIGDN_FILESYSPATH, &pwstr); - if (SUCCEEDED(hr)) + DWORD num_items; + if (SUCCEEDED(sia->GetCount(&num_items))) { - target = path_type{ pwstr }; - // use the c-string pointed to by pwstr here - CoTaskMemFree(pwstr); + for (DWORD i = 0; i < num_items; ++i) + { + ::IShellItem* si; + if (SUCCEEDED(sia->GetItemAt(i, &si))) + { + PWSTR pwstr(nullptr); + if (SUCCEEDED(si->GetDisplayName(SIGDN_FILESYSPATH, &pwstr))) + { + targets.emplace_back(pwstr); + // use the c-string pointed to by pwstr here + ::CoTaskMemFree(pwstr); + } + si->Release(); + } + } } - si->Release(); + sia->Release(); } fd->Release(); } #else - BROWSEINFO brw = { 0 }; wchar_t display_text[MAX_PATH]; - brw.hwndOwner = reinterpret_cast(API::root(impl_->owner)); - brw.pszDisplayName = display_text; - brw.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE; - brw.lpfn = browse_folder_callback; - + auto title = to_wstring( impl_->title ) ; std::wstring init_path = impl_->init_path.wstring(); - brw.lParam = reinterpret_cast(init_path.c_str()); - auto pidl = ::SHBrowseForFolder(&brw); + // https://docs.microsoft.com/en-us/windows/desktop/api/shlobj_core/ns-shlobj_core-_browseinfoa + BROWSEINFO brw = { 0 }; + brw.hwndOwner = reinterpret_cast(API::root(impl_->owner)); + brw.pszDisplayName = display_text; // buffer to receive the display name of the folder selected by the user. + brw.lpszTitle = title.data(); + brw.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE; // | BIF_EDITBOX; + brw.lpfn = browse_folder_callback; + brw.lParam = reinterpret_cast(init_path.c_str()); + + auto pidl = ::SHBrowseForFolder(&brw); if (pidl) { wchar_t folder_path[MAX_PATH]; if (FALSE != SHGetPathFromIDList(pidl, folder_path)) - target = folder_path; + targets.emplace_back(folder_path); co_init.task_mem_free(pidl); } #endif - return target; #elif defined(NANA_POSIX) using mode = filebox_implement::mode; - filebox_implement fb(impl_->owner, mode::open_directory, {}, true); + filebox_implement fb(impl_->owner, mode::open_directory, {}, true, impl_->allow_multi_select); fb.load_fs(impl_->init_path, ""); API::modal_window(fb); - std::string path_directory; - if(false == fb.file(path_directory)) - return {}; + auto path_dirs = fb.files(); - return path_type{path_directory}; + for(auto & p: path_dirs) + targets.push_back(p); #endif + return targets; } }//end namespace nana diff --git a/source/gui/msgbox.cpp b/source/gui/msgbox.cpp index caf6380b..7263aa84 100644 --- a/source/gui/msgbox.cpp +++ b/source/gui/msgbox.cpp @@ -1228,9 +1228,10 @@ namespace nana impl->browse.events().click.connect_unignorable([wd, impl](const arg_click&) { impl->fbox.owner(wd); - if (impl->fbox.show()) + auto files = impl->fbox.show(); + if(!files.empty()) { - impl->value = impl->fbox.file(); + impl->value = files.front().u8string(); impl->path_edit.caption(impl->value); } }); diff --git a/source/gui/programming_interface.cpp b/source/gui/programming_interface.cpp index 698aa543..148667ff 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-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -90,16 +91,21 @@ namespace API void enum_widgets_function_base::enum_widgets(window wd, bool recursive) { - using basic_window = ::nana::detail::basic_window; + auto iwd = reinterpret_cast(wd); internal_scope_guard lock; - - auto children = restrict::wd_manager().get_children(reinterpret_cast(wd)); - for (auto child : children) + if (restrict::wd_manager().available(iwd)) { - auto widget_ptr = API::get_widget(reinterpret_cast(child)); - if (widget_ptr) + //Use a copy, because enum function may close a child window and the original children container would be changed, + //in the situation, the walking thorugh directly to the iwd->children would cause error. + auto children = iwd->children; + + for (auto child : children) { + auto widget_ptr = API::get_widget(reinterpret_cast(child)); + if (!widget_ptr) + continue; + _m_enum_fn(widget_ptr); if (recursive) enum_widgets(reinterpret_cast(child), recursive); @@ -303,13 +309,6 @@ namespace API return reinterpret_cast(restrict::wd_manager().create_widget(reinterpret_cast(parent), r, true, wdg)); } -#ifndef WIDGET_FRAME_DEPRECATED - window create_frame(window parent, const rectangle& r, widget* wdg) - { - return reinterpret_cast(restrict::wd_manager().create_frame(reinterpret_cast(parent), r, wdg)); - } -#endif - paint::graphics* window_graphics(window wd) { internal_scope_guard isg; @@ -411,6 +410,26 @@ namespace API } } + + void window_draggable(window wd, bool enabled) + { + auto real_wd = reinterpret_cast(wd); + internal_scope_guard lock; + if (restrict::wd_manager().available(real_wd)) + real_wd->flags.draggable = enabled; + } + + bool window_draggable(window wd) + { + auto real_wd = reinterpret_cast(wd); + internal_scope_guard lock; + if (restrict::wd_manager().available(real_wd)) + return real_wd->flags.draggable; + + return false; + } + + }//end namespace dev widget* get_widget(window wd) @@ -580,34 +599,6 @@ namespace API reinterpret_cast(wd)->flags.fullscreen = v; } -#ifndef WIDGET_FRAME_DEPRECATED - bool insert_frame(window frame, native_window_type native_window) - { - return restrict::wd_manager().insert_frame(reinterpret_cast(frame), native_window); - } - - native_window_type frame_container(window frame) - { - auto frm = reinterpret_cast(frame); - internal_scope_guard lock; - if (restrict::wd_manager().available(frm) && (frm->other.category == category::flags::frame)) - return frm->other.attribute.frame->container; - return nullptr; - } - - native_window_type frame_element(window frame, unsigned index) - { - auto frm = reinterpret_cast(frame); - internal_scope_guard lock; - if (restrict::wd_manager().available(frm) && (frm->other.category == category::flags::frame)) - { - if (index < frm->other.attribute.frame->attach.size()) - return frm->other.attribute.frame->attach.at(index); - } - return nullptr; - } -#endif - void close_window(window wd) { restrict::wd_manager().close(reinterpret_cast(wd)); @@ -658,7 +649,15 @@ namespace API auto iwd = reinterpret_cast(wd); internal_scope_guard lock; if (restrict::wd_manager().available(iwd)) + { + if (category::flags::root == iwd->other.category) + { + return reinterpret_cast(restrict::wd_manager().root( + interface_type::get_window(iwd->root, window_relationship::parent) + )); + } return reinterpret_cast(iwd->parent); + } return nullptr; } @@ -942,7 +941,6 @@ namespace API restrict::wd_manager().update(reinterpret_cast(wd), false, true); } - void window_caption(window wd, const std::string& title_utf8) { throw_not_utf8(title_utf8); @@ -1324,7 +1322,16 @@ namespace API bool window_graphics(window wd, nana::paint::graphics& graph) { - return restrict::wd_manager().get_graphics(reinterpret_cast(wd), graph); + auto iwd = reinterpret_cast(wd); + + internal_scope_guard lock; + if (!restrict::wd_manager().available(iwd)) + return false; + + graph.make(iwd->drawer.graphics.size()); + graph.bitblt(0, 0, iwd->drawer.graphics); + nana::detail::window_layout::paste_children_to_graphics(iwd, graph); + return true; } bool root_graphics(window wd, nana::paint::graphics& graph) @@ -1341,7 +1348,12 @@ namespace API bool get_visual_rectangle(window wd, nana::rectangle& r) { - return restrict::wd_manager().get_visual_rectangle(reinterpret_cast(wd), r); + auto iwd = reinterpret_cast(wd); + internal_scope_guard lock; + if (restrict::wd_manager().available(iwd)) + return nana::detail::window_layout::read_visual_rectangle(iwd, r); + + return false; } void typeface(window wd, const nana::paint::font& font) @@ -1518,5 +1530,16 @@ namespace API { return ::nana::platform_abstraction::screen_dpi(x_requested); } + + dragdrop_status window_dragdrop_status(::nana::window wd) + { + auto real_wd = reinterpret_cast(wd); + internal_scope_guard lock; + + if (restrict::wd_manager().available(real_wd)) + return real_wd->other.dnd_state; + + return dragdrop_status::not_ready; + } }//end namespace API }//end namespace nana diff --git a/source/gui/widgets/button.cpp b/source/gui/widgets/button.cpp index d407f0eb..96ff7c25 100644 --- a/source/gui/widgets/button.cpp +++ b/source/gui/widgets/button.cpp @@ -379,7 +379,12 @@ namespace nana{ namespace drawerbase void trigger::icon(const nana::paint::image& img) { - if(img.empty()) return; + if(img.empty()) + { + delete attr_.icon; + attr_.icon = nullptr; + return; + } if(nullptr == attr_.icon) attr_.icon = new paint::image; diff --git a/source/gui/widgets/checkbox.cpp b/source/gui/widgets/checkbox.cpp index 06a5f0ee..fc08fcac 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-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -24,6 +24,7 @@ namespace nana{ namespace drawerbase struct drawer::implement { widget * widget_ptr; + scheme * scheme_ptr; bool react; bool radio; facade crook; @@ -47,6 +48,7 @@ namespace nana{ namespace drawerbase void drawer::attached(widget_reference widget, graph_reference) { impl_->widget_ptr = &widget; + impl_->scheme_ptr =static_cast(API::dev::get_scheme(widget)); API::dev::enable_space_click(widget, true); } @@ -78,12 +80,18 @@ namespace nana{ namespace drawerbase } //draw crook -#ifdef _nana_std_has_string_view - auto txt_px = graph.text_extent_size(std::wstring_view( L"jN", 2 )).height + 2; -#else - auto txt_px = graph.text_extent_size(L"jN", 2).height + 2; -#endif - impl_->crook.draw(graph, wdg->bgcolor(), wdg->fgcolor(), rectangle(0, txt_px > 16 ? (txt_px - 16) / 2 : 0, 16, 16), API::element_state(*wdg)); + + unsigned txt_px = 0, descent = 0, ileading = 0; + graph.text_metrics(txt_px, descent, ileading); + txt_px += (descent + 2); + + auto e_state = API::element_state(*wdg); + if(!wdg->enabled()) + e_state = element_state::disabled; + + impl_->crook.draw(graph, + impl_->scheme_ptr->square_bgcolor.get(wdg->bgcolor()), impl_->scheme_ptr->square_border_color.get(wdg->fgcolor()), + rectangle(0, txt_px > 16 ? (txt_px - 16) / 2 : 0, 16, 16), e_state); } void drawer::mouse_down(graph_reference graph, const arg_mouse&) @@ -208,6 +216,7 @@ namespace nana{ namespace drawerbase { e.uiobj->radio(false); e.uiobj->react(true); + API::umake_event(e.eh_clicked); API::umake_event(e.eh_checked); API::umake_event(e.eh_destroy); API::umake_event(e.eh_keyboard); @@ -224,7 +233,7 @@ namespace nana{ namespace drawerbase el.uiobj = &uiobj; - uiobj.events().checked.connect_unignorable([this](const arg_checkbox& arg) + el.eh_checked = uiobj.events().checked.connect_unignorable([this](const arg_checkbox& arg) { if (arg.widget->checked()) { @@ -236,7 +245,7 @@ namespace nana{ namespace drawerbase } }, true); - el.eh_checked = uiobj.events().click.connect_unignorable([this](const arg_click& arg) + el.eh_clicked = uiobj.events().click.connect_unignorable([this](const arg_click& arg) { for (auto & i : ui_container_) i.uiobj->check(arg.window_handle == i.uiobj->handle()); diff --git a/source/gui/widgets/frame.cpp b/source/gui/widgets/frame.cpp deleted file mode 100644 index 7b9e7af2..00000000 --- a/source/gui/widgets/frame.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * A Frame 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/widgets/frame.cpp - * - * A frame provides a way to contain the platform window in a stdex GUI Window - */ - -#include - -#ifndef WIDGET_FRAME_DEPRECATED - -namespace nana -{ - //class frame:: public widget_object - frame::frame(){} - - frame::frame(window wd, bool visible) - { - create(wd, rectangle(), visible); - } - - frame::frame(window wd, const nana::rectangle& r, bool visible) - { - create(wd, r, visible); - } - - bool frame::insert(native_window_type wd) - { - return API::insert_frame(handle(), wd); - } - - native_window_type frame::element(unsigned index) - { - return API::frame_element(handle(), index); - } - - native_window_type frame::container() const - { - return API::frame_container(handle()); - } - //end class frame -}//end namespace nana - -#endif - diff --git a/source/gui/widgets/group.cpp b/source/gui/widgets/group.cpp index 6d697a62..4c68d5f5 100644 --- a/source/gui/widgets/group.cpp +++ b/source/gui/widgets/group.cpp @@ -3,8 +3,8 @@ * Nana C++ Library(http://www.nanaro.org) * Copyright(C) 2015-2018 Jinhao(cnjinhao@hotmail.com) * - * Distributed under the Boost Software License, Version 1.0. - * (See accompanying file LICENSE_1_0.txt or copy at + * 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/group.cpp @@ -14,7 +14,7 @@ * @brief group is a widget used to visually group and layout other widgets. * * @contributor: - * dankan1890(https://github.com/dankan1890) + * dankan1890(https://github.com/dankan1890) */ @@ -27,121 +27,124 @@ if(empty()) \ throw std::logic_error("the group is invalid"); -namespace nana{ +namespace nana +{ - static const char* field_title = "__nana_group_title__"; - static const char* field_options = "__nana_group_options__"; +static const char* field_title = "__nana_group_title__"; +static const char* field_options = "__nana_group_options__"; struct group::implement { label caption; align caption_align{ align::left }; + background_mode caption_mode{ background_mode::blending }; place place_content; unsigned gap{2}; std::string usr_div_str; - nana::size caption_dimension; + nana::size caption_dimension; - std::vector> options; - radio_group * radio_logic{nullptr}; + std::vector> options; + radio_group * radio_logic{nullptr}; - implement() = default; + implement() = default; - implement(window grp_panel, ::std::string titel, bool vsb, unsigned gap=2) - : caption (grp_panel, std::move(titel), vsb), - place_content{grp_panel}, - gap{gap} - { - } + implement(window grp_panel, ::std::string titel, bool vsb, unsigned gap=2) + : caption (grp_panel, std::move(titel), vsb), + place_content{grp_panel}, + gap{gap} + { + } - void create(window pnl) - { - caption.create(pnl); - caption.caption(""); - place_content.bind(pnl); + void create(window pnl) + { + caption.create(pnl); + caption.caption(""); + place_content.bind(pnl); - if (!radio_logic) - radio_logic = new radio_group; - } + if (!radio_logic) + radio_logic = new radio_group; + } - void update_div() - { - const std::size_t padding = 10; - caption_dimension = caption.measure(1000); - caption_dimension.width += 1; + void update_div() + { + const std::size_t padding = 10; + caption_dimension = caption.measure(1000); + caption_dimension.width += 1; - std::string div = "vert margin=[0," + std::to_string(gap) + "," + std::to_string(gap + 5) + "," + std::to_string(gap) + "]"; + std::string div = "vert margin=[0," + std::to_string(gap) + "," + std::to_string(gap + 5) + "," + std::to_string(gap) + "]"; - div += ""; - else - div += "<>"; //right or center + if (align::left == caption_align) + div += ""; + else + div += "<>"; //right or center - div += "<" + std::string{ field_title } + " weight=" + std::to_string(caption_dimension.width) + ">"; + div += "<" + std::string{ field_title } + " weight=" + std::to_string(caption_dimension.width) + ">"; - if (align::right == caption_align) - div += ""; - else if (align::center == caption_align) - div += "<>"; + if (align::right == caption_align) + div += ""; + else if (align::center == caption_align) + div += "<>"; - div += "><"; + div += "><"; - if (!usr_div_str.empty()) - div += "<" + usr_div_str + ">>"; - else - div += ">"; + if (!usr_div_str.empty()) + div += "<" + usr_div_str + ">>"; + else + div += ">"; - place_content.div(div.c_str()); + place_content.div(div.c_str()); - if (options.empty()) - place_content.field_display(field_options, false); + if (options.empty()) + place_content.field_display(field_options, false); - if (caption.caption().empty()) - place_content.field_display(field_title, false); - } - }; + if (caption.caption().empty()) + place_content.field_display(field_title, false); + } +}; - group::group() - : impl_(new implement) - { - } +group::group() + : impl_(new implement) +{ +} - group::group(window parent, const rectangle& r, bool vsb) - : group() - { - create(parent, r, vsb); - } +group::group(window parent, const rectangle& r, bool vsb) + : group() +{ + create(parent, r, vsb); +} - using groupbase_type = widget_object; +using groupbase_type = widget_object; - group::group(window parent, ::std::string titel, bool formatted, unsigned gap, const rectangle& r, bool vsb) - : group(parent, r, vsb) - { - this->bgcolor(API::bgcolor(parent)); +group::group(window parent, ::std::string titel, bool formatted, unsigned gap, const rectangle& r, bool vsb) + : group(parent, r, vsb) +{ + this->bgcolor(API::bgcolor(parent)); - impl_.reset(new implement(*this, std::move(titel), vsb, gap)); + impl_.reset(new implement(*this, std::move(titel), vsb, gap)); - impl_->caption.format(formatted); - _m_init(); - } + impl_->caption.format(formatted); + _m_init(); +} - group::~group() - { - delete impl_->radio_logic; - } +group::~group() +{ + delete impl_->radio_logic; +} - checkbox& group::add_option(std::string text) - { - _THROW_IF_EMPTY() +checkbox& group::add_option(std::string text) +{ + _THROW_IF_EMPTY() #ifdef _nana_std_has_emplace_return_type - auto & opt = impl_->options.emplace_back(new checkbox{ handle() }); + auto & opt = impl_->options.emplace_back(new checkbox { handle() }); #else - impl_->options.emplace_back(new checkbox(handle())); - auto & opt = impl_->options.back(); + impl_->options.emplace_back(new checkbox(handle())); + auto & opt = impl_->options.back(); #endif + opt->transparent(true); opt->caption(std::move(text)); impl_->place_content[field_options] << *opt; @@ -154,7 +157,7 @@ namespace nana{ return *impl_->options.back(); } - void group::caption_align(align position) + group& group::caption_align(align position) { if (position != impl_->caption_align) { @@ -163,6 +166,32 @@ namespace nana{ impl_->place_content.collocate(); API::refresh_window(*this); } + return *this; + } + + group& group::caption_background_mode(background_mode mode) + { + if (mode != impl_->caption_mode) + { + impl_->caption_mode = mode; + switch (mode) + { + case background_mode::none: + impl_->caption.bgcolor(this->bgcolor()); + impl_->caption.transparent(false); + break; + case background_mode::blending: + impl_->caption.transparent(true); + impl_->caption.bgcolor(API::bgcolor(this->parent()).blend(colors::black, 0.025)); + break; + case background_mode::transparent: + impl_->caption.transparent(true); + impl_->caption.bgcolor(API::bgcolor(this->parent()).blend(colors::black, 0.025)); + break; + } + API::refresh_window(*this); + } + return *this; } group& group::radio_mode(bool enable) @@ -197,6 +226,12 @@ namespace nana{ throw std::logic_error("the radio_mode of the group is disabled"); } + void group::option_check( std::size_t pos, bool check ) + { + _THROW_IF_EMPTY(); + return impl_->options.at(pos)->check( check ); + } + bool group::option_checked(std::size_t pos) const { _THROW_IF_EMPTY(); @@ -206,6 +241,14 @@ namespace nana{ group& group::enable_format_caption(bool format) { impl_->caption.format(format); + + // if the caption is already set, make sure the layout is updated + if(!caption().empty()) + { + impl_->update_div(); + impl_->place_content.collocate(); + API::refresh_window(*this); + } return *this; } @@ -289,17 +332,20 @@ namespace nana{ ), 3, 3, this->scheme().border, true, this->bgcolor()); - auto opt_r = API::window_rectangle(impl_->caption); - if (opt_r) + if (background_mode::blending == impl_->caption_mode) { - rectangle grad_r{ opt_r->position(), nana::size{ opt_r->width + 4, static_cast(top_round_line - opt_r->y) } }; + auto opt_r = API::window_rectangle(impl_->caption); + if (opt_r) + { + rectangle grad_r{ opt_r->position(), nana::size{ opt_r->width + 4, static_cast(top_round_line - opt_r->y) } }; - grad_r.y += top_round_line*2 / 3; - grad_r.x -= 2; + grad_r.y += top_round_line * 2 / 3; + grad_r.x -= 2; - graph.gradual_rectangle(grad_r, - API::bgcolor(this->parent()), this->bgcolor(), true - ); + graph.gradual_rectangle(grad_r, + API::bgcolor(this->parent()), this->bgcolor(), true + ); + } } }); } @@ -322,5 +368,6 @@ namespace nana{ impl_->update_div(); impl_->place_content.collocate(); } + }//end namespace nana diff --git a/source/gui/widgets/label.cpp b/source/gui/widgets/label.cpp index 26be5a95..075591cc 100644 --- a/source/gui/widgets/label.cpp +++ b/source/gui/widgets/label.cpp @@ -20,6 +20,7 @@ #include #include + namespace nana { namespace drawerbase @@ -82,7 +83,7 @@ namespace nana dstream_.parse(s, format_enabled_); } - bool format(bool fm) + bool format(bool fm) noexcept { if (fm == format_enabled_) return false; @@ -95,80 +96,56 @@ namespace nana { traceable_.clear(); - auto pre_font = graph.typeface(); //used for restoring the font - -#ifdef _nana_std_has_string_view - const unsigned def_line_pixels = graph.text_extent_size(std::wstring_view{ L" ", 1 }).height; -#else - const unsigned def_line_pixels = graph.text_extent_size(L" ", 1).height; -#endif - - font_ = pre_font; - fblock_ = nullptr; - - _m_set_default(pre_font, fgcolor); - - _m_measure(graph); - render_status rs; rs.allowed_width = graph.size().width; rs.text_align = th; rs.text_align_v = tv; + ::nana::size extent_size; + //All visual lines data of whole text. - std::deque> content_lines; + auto content_lines = _m_measure_extent_size(graph, th, tv, true, graph.size().width, extent_size); - std::size_t extent_v_pixels = 0; //the pixels, in height, that text will be painted. - - for (auto & line : dstream_) + if ((tv != align_v::top) && extent_size.height < graph.height()) { - _m_prepare_visual_lines(graph, line, def_line_pixels, rs); + rs.pos.y = static_cast(graph.height() - extent_size.height); - for (auto & vsline : rs.vslines) - extent_v_pixels += vsline.extent_height_px; - - content_lines.emplace_back(std::move(rs.vslines)); - - if(extent_v_pixels >= graph.height()) - break; - } - - 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) + if (align_v::center == tv) rs.pos.y >>= 1; } else rs.pos.y = 0; - auto vsline_iterator = content_lines.begin(); - for (auto & line : dstream_) - { - if (rs.pos.y >= static_cast(graph.height())) - break; + auto pre_font = graph.typeface(); //used for restoring the font + _m_set_default(pre_font, fgcolor); + + for (auto & line : content_lines) + { rs.index = 0; - rs.vslines.clear(); - rs.vslines.swap(*vsline_iterator++); + rs.vslines.swap(line); rs.pos.x = rs.vslines.front().x_base; if (!_m_foreach_visual_line(graph, rs)) break; - rs.pos.y += static_cast(rs.vslines.back().extent_height_px); + //Now the y-position of rs has been modified to next line. } - graph.typeface(pre_font); + if (transient_.current_font != pre_font) + { + graph.typeface(pre_font); + transient_.current_font.release(); + transient_.current_fblock = nullptr; + } } - bool find(int x, int y, std::wstring& target, std::wstring& url) const noexcept + bool find(const point& mouse_pos, std::wstring& target, std::wstring& url) const { for (auto & t : traceable_) { - if(t.r.is_hit(x, y)) + if(t.r.is_hit(mouse_pos)) { target = t.target; url = t.url; @@ -181,44 +158,10 @@ namespace nana ::nana::size measure(graph_reference graph, unsigned limited, align th, align_v tv) { - ::nana::size retsize; + ::nana::size extent_size; + _m_measure_extent_size(graph, th, tv, false, limited, extent_size); - auto ft = graph.typeface(); //used for restoring the font - -#ifdef _nana_std_has_string_view - const unsigned def_line_pixels = graph.text_extent_size(std::wstring_view(L" ", 1)).height; -#else - const unsigned def_line_pixels = graph.text_extent_size(L" ", 1).height; -#endif - - font_ = ft; - fblock_ = nullptr; - - _m_set_default(ft, colors::black); - _m_measure(graph); - - render_status rs; - - rs.allowed_width = limited; - rs.text_align = th; - rs.text_align_v = tv; - - for(auto & line: dstream_) - { - rs.vslines.clear(); - auto w = _m_prepare_visual_lines(graph, line, def_line_pixels, rs); - - if(limited && (w > limited)) - w = limited; - - if(retsize.width < w) - retsize.width = w; - - for (auto& vsline : rs.vslines) - retsize.height += static_cast(vsline.extent_height_px); - } - - return retsize; + return extent_size; } private: //Manage the fblock for a specified rectangle if it is a traceable fblock. @@ -246,6 +189,9 @@ namespace nana def_.font_size = ft.size(); def_.font_bold = ft.bold(); def_.fgcolor = fgcolor; + + transient_.current_font = ft; + transient_.current_fblock = nullptr; } const ::nana::color& _m_fgcolor(nana::widgets::skeletons::fblock* fp) noexcept @@ -294,39 +240,20 @@ namespace nana void _m_change_font(graph_reference graph, nana::widgets::skeletons::fblock* fp) { - if(fp != fblock_) + if (fp != transient_.current_fblock) { auto& name = _m_fontname(fp); auto fontsize = _m_font_size(fp); bool bold = _m_bold(fp); - if((fontsize != font_.size()) || bold != font_.bold() || name != font_.name()) + if((fontsize != transient_.current_font.size()) || bold != transient_.current_font.bold() || name != transient_.current_font.name()) { paint::font::font_style fs; fs.weight = (bold ? 800 : 400); - font_ = paint::font{ name, fontsize, fs }; - graph.typeface(font_); + transient_.current_font = paint::font{ name, fontsize, fs }; + graph.typeface(transient_.current_font); } - fblock_ = fp; - } - } - - void _m_measure(graph_reference graph) - { - nana::paint::font ft = font_; - for (auto & line : dstream_) - { - for (auto & value : line) - { - _m_change_font(graph, value.fblock_ptr); - value.data_ptr->measure(graph); - } - } - if(font_ != ft) - { - font_ = ft; - graph.typeface(ft); - fblock_ = nullptr; + transient_.current_fblock = fp; } } @@ -346,6 +273,58 @@ namespace nana } } + std::deque> _m_measure_extent_size(graph_reference graph, nana::align text_align, nana::align_v text_align_v, bool only_screen, unsigned allowed_width_px, nana::size & extent_size) + { + auto pre_font = graph.typeface(); //used for restoring the font + + unsigned text_ascent, text_descent, text_ileading; + graph.text_metrics(text_ascent, text_descent, text_ileading); + + auto const def_line_pixels = text_ascent + text_descent; + + _m_set_default(pre_font, colors::black); + + render_status rs; + + rs.allowed_width = allowed_width_px; + rs.text_align = text_align; + rs.text_align_v = text_align_v; + + //All visual lines data of whole text. + std::deque> content_lines; + + extent_size.width = extent_size.height = 0; + + for (auto & line : dstream_) + { + auto width_px = _m_prepare_visual_lines(graph, line, def_line_pixels, rs); + + if (width_px > extent_size.width) + extent_size.width = width_px; + + for (auto & vsline : rs.vslines) + extent_size.height += static_cast(vsline.extent_height_px); + + content_lines.emplace_back(std::move(rs.vslines)); + + if (only_screen && (extent_size.height >= graph.height())) + break; + } + + //The width is not restricted if the allowed_width_px is zero. + if (allowed_width_px && (allowed_width_px < extent_size.width)) + extent_size.width = allowed_width_px; + + if (transient_.current_font != pre_font) + { + graph.typeface(pre_font); + transient_.current_font.release(); + transient_.current_fblock = nullptr; + } + + return content_lines; + } + /** * prepare data for rendering a line of text. */ @@ -359,11 +338,11 @@ namespace nana #else rs.vslines.emplace_back(); auto & vsline = rs.vslines.back(); - +#endif vsline.baseline = 0; vsline.extent_height_px = def_line_px; vsline.x_base = 0; -#endif + return 0; } @@ -379,7 +358,10 @@ namespace nana for (auto i = line.cbegin(); i != line.cend(); ++i) { auto const data = i->data_ptr; - auto fblock = i->fblock_ptr; + auto const fblock = i->fblock_ptr; + + _m_change_font(graph, fblock); + data->measure(graph); abs_text_px += data->size().width; @@ -457,15 +439,17 @@ namespace nana if (data->is_text()) { - _m_change_font(graph, fblock); //Split a text into multiple lines - auto rest_extent_size = extent_size.width; std::size_t text_begin = 0; while (text_begin < data->text().size()) { unsigned sub_text_px = 0; auto sub_text_len = _m_fit_text(graph, data->text().substr(text_begin), rs.allowed_width, sub_text_px); + //At least one character must be displayed no matter whether the width is enough or not. + if (0 == sub_text_len) + sub_text_len = 1; + if (text_begin + sub_text_len < data->text().size()) { //make a new visual line @@ -547,10 +531,6 @@ namespace nana bool _m_foreach_visual_line(graph_reference graph, render_status& rs) { - std::wstring text; - - content_element_iterator block_start; - auto const bottom = static_cast(graph.height()) - 1; for (auto & vsline : rs.vslines) @@ -593,8 +573,7 @@ namespace nana if (data->is_text()) { - auto const text = data->text().c_str() + vsline_elm.range.first; - auto const reordered = unicode_reorder(text, vsline_elm.range.second); + auto const reordered = unicode_reorder(data->text().c_str() + vsline_elm.range.first, vsline_elm.range.second); _m_change_font(graph, fblock); for (auto & bidi : reordered) @@ -628,25 +607,18 @@ namespace nana rs.pos.x += static_cast(data->size().width); } } - - 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) - { - pos -= n; - n = (++i)->data_ptr->text().length(); - } - - return{ pos, n - pos }; - } private: dstream dstream_; bool format_enabled_ = false; - ::nana::widgets::skeletons::fblock * fblock_ = nullptr; + ::std::deque traceable_; - ::nana::paint::font font_; + struct transient + { + widgets::skeletons::fblock * current_fblock{ nullptr }; + paint::font current_font; + }transient_; + struct def_font_tag { ::std::string font_name; @@ -744,7 +716,7 @@ namespace nana { std::wstring target, url; - if(impl_->renderer.find(arg.pos.x, arg.pos.y, target, url)) + if(impl_->renderer.find(arg.pos, target, url)) { int cur_state = 0; if(target != impl_->target) @@ -915,7 +887,8 @@ namespace nana if(graph_ptr->empty()) { graph_ptr = &substitute; - graph_ptr->make({ 10, 10 }); + substitute.make({ 10, 10 }); + substitute.typeface(this->typeface()); } return impl->renderer.measure(*graph_ptr, limited, impl->text_align, impl->text_align_v); diff --git a/source/gui/widgets/listbox.cpp b/source/gui/widgets/listbox.cpp index e6a5c1c6..29105225 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-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 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,12 @@ * dankan1890(pr#158) * */ +#include +#include +#include +#include +#include +#include #include #include //for inline widget @@ -28,12 +34,6 @@ #include #include "skeletons/content_view.hpp" -#include -#include -#include -#include -#include - namespace nana { static void check_range(std::size_t pos, std::size_t size) @@ -119,24 +119,31 @@ namespace nana class es_header { public: + struct attributes + { + bool movable{true}; + bool resizable{true}; + bool sortable{true}; + bool visible{true}; + }; + struct column : public column_interface { - native_string_type caption; - unsigned width_px; - std::pair range_width_px; + native_string_type caption; //< header title + unsigned width_px; //< column width in pixels + std::pair range_width_px; //< allowed width bool visible_state{ true }; - /// Absolute position of column when it was creating - size_type index; + + size_type index; //< Absolute position of column when it was created nana::align alignment{ nana::align::left }; - std::function weak_ordering; + std::function weak_ordering; std::shared_ptr font; ///< The exclusive column font - - column() = default; column(const column&) = default; @@ -178,18 +185,18 @@ namespace nana { } private: - //The definition is provided after essence + /// The definition is provided after essence void _m_refresh() noexcept; private: essence* const ess_; public: - //Implementation of column_interface + /// Implementation of column_interface unsigned width() const noexcept override { return width_px; } - // Sets the width and overrides the ranged width + /// Sets the width and overrides the ranged width void width(unsigned pixels) noexcept override { width_px = pixels; @@ -215,7 +222,7 @@ namespace nana } } - size_type position(bool disp_order) const noexcept override; //The definition is provided after essence + size_type position(bool disp_order) const noexcept override; //< The definition is provided after essence std::string text() const noexcept override { @@ -286,28 +293,14 @@ namespace nana return head_str; } - bool visible() const noexcept + const attributes& attrib() const noexcept { - return visible_; + return attrib_; } - bool visible(bool v) noexcept + attributes& attrib() noexcept { - if (visible_ == v) - return false; - - visible_ = v; - return true; - } - - bool sortable() const noexcept - { - return sortable_; - } - - void sortable(bool enable) noexcept - { - sortable_ = enable; + return attrib_; } size_type create(essence* ess, native_string_type&& text, unsigned pixels) @@ -446,7 +439,7 @@ namespace nana throw std::invalid_argument("listbox: invalid header index"); } - /// find and return a ref to the column that originaly was at position "pos" previous to any list reorganization. + /// find and return a ref to the column that originally was at position "pos" previous to any list reorganization. column& at(size_type pos, bool disp_order = false) { check_range(pos, cont_.size()); @@ -513,7 +506,7 @@ namespace nana return{ left, 0 }; } - /// return the original index of the visible col currently before(in front of) or after the col originaly at index "index" + /// return the original index of the visible col currently before(in front of) or after the col originally at index "index" size_type next(size_type index) const noexcept { bool found_me = false; @@ -548,7 +541,25 @@ namespace nana 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 + + /// move col to view pos + void move_to_view_pos (size_type col, size_type view, bool front) noexcept + { + if (!front) view++; + if (view >= cont_.size() ) return; + + auto i = std::find_if( cont_.begin(), + cont_.end(), + [&](const column& c){return col==c.index;}); + + if (i==cont_.end()) return; + + auto col_from = *i; + cont_.erase(i); + cont_.insert(cont_.begin()+ view, col_from); + + } + /// move the col originally at "from" to the position currently in front (or after) the col originally 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())) @@ -584,8 +595,7 @@ namespace nana } } private: - bool visible_{true}; - bool sortable_{true}; + attributes attrib_; unsigned margin_{ 5 }; container cont_; }; @@ -1857,6 +1867,11 @@ namespace nana (for_selection ? single_selection_ : single_check_) = false; } + bool is_single_enabled(bool for_selection) const noexcept + { + return (for_selection ? single_selection_ : single_check_); + } + size_type size_item(size_type cat) const { return get(cat)->items.size(); @@ -1968,6 +1983,11 @@ namespace nana std::vector active_panes_; };//end class es_lister + enum class operation_states + { + none, + msup_deselect + }; /// created and live by the trigger, holds data for listbox: the state of the struct does not effect on member funcions, therefore all data members are public. struct essence @@ -1981,7 +2001,6 @@ namespace nana bool auto_draw{true}; bool checkable{false}; bool if_image{false}; - bool deselect_deferred{ false }; //deselects items when mouse button is released. unsigned text_height; ::nana::listbox::export_options def_exp_options; @@ -1999,10 +2018,18 @@ namespace nana std::function ctg_icon_renderer; ///< Renderer for the category icon + struct operation_rep + { + operation_states state{operation_states::none}; + index_pair item; + }operation; + struct mouse_selection_part { bool started{ false }; bool reverse_selection{ false }; + bool scroll_direction; + bool deselect_when_start_to_move; point screen_pos; point begin_position; ///< Logical position to the @@ -2010,9 +2037,18 @@ namespace nana index_pairs already_selected; index_pairs selections; - bool scroll_direction; unsigned scroll_step{ 1 }; unsigned mouse_move_timestamp{ 0 }; + + bool is_already_selected(const index_pair& abs_pos) const noexcept + { + return (already_selected.cend() != std::find(already_selected.cbegin(), already_selected.cend(), abs_pos)); + } + + bool is_selected(const index_pair& abs_pos) const noexcept + { + return (selections.cend() != std::find(selections.cbegin(), selections.cend(), abs_pos)); + } }mouse_selection; @@ -2110,6 +2146,7 @@ namespace nana mouse_selection.started = true; mouse_selection.begin_position = logic_pos; mouse_selection.end_position = logic_pos; + mouse_selection.deselect_when_start_to_move = true; if (arg.ctrl || arg.shift) { @@ -2121,6 +2158,21 @@ namespace nana void update_mouse_selection(const point& screen_pos) { + //Don't update if it is not started + if (!mouse_selection.started) + return; + + // When the button is pressed and start to move the mouse, the listbox should deselect all items. + // But when ctrl is clicked + if (mouse_selection.deselect_when_start_to_move) + { + mouse_selection.deselect_when_start_to_move = false; + if (mouse_selection.already_selected.empty()) + lister.select_for_all(false); + + mouse_selection.selections.clear(); + } + mouse_selection.screen_pos = screen_pos; auto logic_pos = coordinate_cast(screen_pos, true); @@ -2136,7 +2188,7 @@ namespace nana mouse_selection.end_position = logic_pos; - bool cancel_selections = true; + std::vector> selections; 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 && @@ -2147,18 +2199,18 @@ namespace nana auto begin = lister.advance(lister.first(), begin_off); if (!begin.empty()) { - std::vector> selections; - if ((mouse_selection.end_position.y < 0) || (lister.distance(lister.first(), begin) == begin_off)) { //The range [begin_off, last_off] is a range of box selection auto last_off = (std::max)(mouse_selection.begin_position.y, mouse_selection.end_position.y) / item_height(); auto last = lister.advance(lister.first(), last_off); + //Tries to select the items in the box, then returns the items with their previous selected states selections = lister.select_display_range_if(begin, last, false, [this](const index_pair& abs_pos) { - if (this->mouse_selection.reverse_selection) + if (mouse_selection.reverse_selection) { - if (mouse_selection.already_selected.cend() != std::find(mouse_selection.already_selected.cbegin(), mouse_selection.already_selected.cend(), abs_pos)) + //Deselects the items in the box which has been already selected + if(mouse_selection.is_already_selected(abs_pos)) { item_proxy{ this, abs_pos }.select(false); return false; @@ -2169,21 +2221,21 @@ namespace nana for (auto & pair : selections) { + //Continue if the previous state is selected. It indicates the item now is not selected. if (pair.second) continue; - if (mouse_selection.selections.cend() == - std::find(mouse_selection.selections.cbegin(), mouse_selection.selections.cend(), pair.first)) - { + //Add the item to selections container. + if(!mouse_selection.is_selected(pair.first)) mouse_selection.selections.push_back(pair.first); - } } + //Deselects the items which are in mouse_selection.selections but not in selections. + //Eq to mouse_selection.selections = selections #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 { auto & selpos = *i; @@ -2197,30 +2249,45 @@ namespace nana else ++i; } - - cancel_selections = false; } } } - if (cancel_selections) + //Restores an already selected item if it is not in selections. + for (auto& abs_pos : mouse_selection.already_selected) { - if (!mouse_selection.already_selected.empty()) + if (selections.cend() == std::find_if(selections.cbegin(), selections.cend(), [abs_pos](const std::pair& rhs) { + return (abs_pos == rhs.first); + })) { - for (auto & pos : mouse_selection.selections) - item_proxy(this, pos).select(false); - - //Don't restore the already selections if it is reverse selection(pressing shift). Behaves like Windows Explorer. - if (!mouse_selection.reverse_selection) + item_proxy m{ this, abs_pos }; + if (!m.selected()) { - for (auto & abs_pos : mouse_selection.already_selected) - item_proxy(this, abs_pos).select(true); + m.select(true); + //Add the item to selections container. + if(!mouse_selection.is_selected(abs_pos)) + mouse_selection.selections.push_back(abs_pos); } } - else - lister.select_for_all(false); + } - mouse_selection.selections.clear(); + //Deselects the item which is not in already_selected and selections but in mouse_selection.selections + for(auto i = mouse_selection.selections.cbegin(); i != mouse_selection.selections.cend();) + { + auto abs_pos = *i; + + bool is_box_selected = (selections.cend() != std::find_if(selections.cbegin(), selections.cend(), [abs_pos](const std::pair& rhs) { + return (abs_pos == rhs.first); + })); + + if (is_box_selected || mouse_selection.is_already_selected(abs_pos)) + { + ++i; + continue; + } + + item_proxy{ this, abs_pos }.select(false); + i = mouse_selection.selections.erase(i); } } @@ -2280,16 +2347,16 @@ namespace nana return header.range(seq.front()).first + r.x - this->content_view->origin().x; } - //Returns the absolute coordinate of the specified item in the window - point item_coordinate(const index_pair& pos) const + //Returns the top of the specified item in listbox window. + int item_window_top(const index_pair& pos) const { - auto top = static_cast(this->lister.distance(index_pair{}, pos) * item_height()) - content_view->origin().y; + int top = static_cast(this->lister.distance(index_pair{}, pos) * item_height()) - content_view->origin().y; rectangle r; if (rect_lister(r)) top += r.y; - return{ top, top + static_cast(item_height()) }; + return top; } std::pair where(const nana::point& pos) const noexcept @@ -2302,7 +2369,7 @@ namespace nana { /// we are inside auto const origin = content_view->origin(); - if (header.visible() && (pos.y < static_cast(header_visible_px()) + area.y)) + if (header.attrib().visible && (pos.y < static_cast(header_visible_px()) + area.y)) { /// we are in the header new_where.first = parts::header; new_where.second = this->column_from_pos(pos.x); @@ -2419,15 +2486,15 @@ namespace nana unsigned header_visible_px() const { - if (!header.visible()) - return 0; + if(header.attrib().visible) + return scheme_ptr->header_padding_top + scheme_ptr->header_padding_bottom + static_cast(header_font_px()); - return scheme_ptr->header_padding_top + scheme_ptr->header_padding_bottom + static_cast(header_font_px()); + return 0; } bool rect_header(nana::rectangle& r) const { - if(header.visible()) + if(header.attrib().visible) { r = this->content_area(); @@ -3220,13 +3287,16 @@ namespace nana } // 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) noexcept + bool detect_splitter(int x) noexcept { + nana::rectangle r; + if (!essence_->rect_header(r)) + return false; + if(essence_->ptr_state == item_state::highlighted) { x -= r.x - essence_->content_view->origin().x + static_cast(essence_->header.margin()); - for(auto & col : essence_->header.cont()) // in current order { if(col.visible_state) @@ -3439,12 +3509,9 @@ namespace nana //Set column font graph.typeface(column.typeface()); - unsigned ascent, descent, ileading; - graph.text_metrics(ascent, descent, ileading); - point text_pos{ column_r.x + static_cast(crp.margin), - column_r.bottom() - static_cast(crp.height + ascent + descent) / 2 + static_cast(essence_->scheme_ptr->header_padding_top) }; if (align::left == column.alignment) @@ -3526,7 +3593,7 @@ namespace nana :essence_(es) {} - void draw(const nana::rectangle& rect) + void draw(const nana::rectangle& visual_r) { internal_scope_guard lock; @@ -3550,10 +3617,10 @@ namespace nana auto const origin = essence_->content_view->origin(); auto const header_margin = essence_->header.margin(); - if (header_w + header_margin < origin.x + rect.width) + if (header_w + header_margin < origin.x + visual_r.width) { - rectangle r{ point{ rect.x + static_cast(header_w + header_margin) - origin.x, rect.y }, - size{ rect.width + origin.x - header_w, rect.height } }; + rectangle r{ point{ visual_r.x + static_cast(header_w + header_margin) - origin.x, visual_r.y }, + size{ visual_r.width + origin.x - header_w, visual_r.height } }; if (!API::dev::copy_transparent_background(essence_->listbox_ptr->handle(), r, *essence_->graph, r.position())) essence_->graph->rectangle(r, true); @@ -3561,7 +3628,7 @@ namespace nana if (header_margin > 0) { - rectangle r = rect; + rectangle r = visual_r; r.width = header_margin; if (!API::dev::copy_transparent_background(essence_->listbox_ptr->handle(), r, *essence_->graph, r.position())) @@ -3575,8 +3642,8 @@ namespace nana auto first_disp = essence_->first_display(); point item_coord{ - essence_->item_xpos(rect), - rect.y - static_cast(origin.y % item_height_px) + essence_->item_xpos(visual_r), + visual_r.y - static_cast(origin.y % item_height_px) }; essence_->inline_buffered_table.swap(essence_->inline_table); @@ -3592,17 +3659,13 @@ namespace nana hoverred_pos = lister.advance(first_disp, static_cast(ptr_where.second)); } - auto const columns = essence_->ordered_columns(rect.width); + auto const columns = essence_->ordered_columns(visual_r.width); if (columns.empty()) return; auto const txtoff = static_cast(essence_->scheme_ptr->item_height_ex) / 2; - auto i_categ = lister.get(first_disp.cat); - - auto idx = first_disp; - for (auto & cat : lister.cat_container()) for (auto & ind : cat.indicators) { @@ -3610,75 +3673,49 @@ namespace nana 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 idx = first_disp; + for (auto i_categ = lister.get(first_disp.cat); i_categ != lister.cat_container().end(); ++i_categ) { - if (idx.cat == 0 && idx.is_category()) // the 0 cat + if (item_coord.y > visual_r.bottom()) + break; + + if (idx.cat > 0 && idx.is_category()) { - first_disp.item = 0; + _m_draw_categ(*i_categ, visual_r.x - origin.x, item_coord.y, txtoff, header_w, bgcolor, + (hoverred_pos.is_category() && (idx.cat == hoverred_pos.cat) ? item_state::highlighted : item_state::normal) + ); + item_coord.y += static_cast(item_height_px); 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 (i_categ->expand) { - if (item_coord.y >= rect.bottom()) - break; + auto size = i_categ->items.size(); + for (; idx.item < size; ++idx.item) + { + if (item_coord.y > visual_r.bottom()) + break; - auto item_pos = lister.index_cast(index_pair{ idx.cat, offs }, true); //convert display position to absolute position + auto item_pos = lister.index_cast(index_pair{ idx.cat, idx.item }, true); //convert display position to absolute position - _m_draw_item(*i_categ, item_pos, item_coord, txtoff, header_w, rect, columns, bgcolor, fgcolor, - (hoverred_pos == idx ? item_state::highlighted : item_state::normal) - ); + _m_draw_item(*i_categ, item_pos, item_coord, txtoff, header_w, visual_r, columns, bgcolor, fgcolor, + (idx == hoverred_pos ? item_state::highlighted : item_state::normal) + ); - item_coord.y += static_cast(item_height_px); + item_coord.y += static_cast(item_height_px); + } } - ++i_categ; ++idx.cat; - } - - for (; i_categ != lister.cat_container().end(); ++i_categ, ++idx.cat) - { - if (item_coord.y > rect.bottom()) - break; - - idx.item = 0; - - _m_draw_categ(*i_categ, rect.x - origin.x, item_coord.y, txtoff, header_w, bgcolor, - (hoverred_pos.is_category() && (idx.cat == hoverred_pos.cat) ? item_state::highlighted : item_state::normal) - ); - item_coord.y += static_cast(item_height_px); - - if (false == i_categ->expand) - continue; - - auto size = i_categ->items.size(); - for (decltype(size) pos = 0; pos < size; ++pos) - { - if (item_coord.y > rect.bottom()) - break; - - auto item_pos = lister.index_cast(index_pair{ idx.cat, pos }, true); //convert display position to absolute position - - _m_draw_item(*i_categ, item_pos, item_coord, txtoff, header_w, rect, columns, bgcolor, fgcolor, - (idx == hoverred_pos ? item_state::highlighted : item_state::normal) - ); - - item_coord.y += static_cast(item_height_px); - if (item_coord.y >= rect.bottom()) - break; - - ++idx.item; - } + idx.item = nana::npos; } } essence_->inline_buffered_table.clear(); - if (item_coord.y < rect.bottom()) + if (item_coord.y < visual_r.bottom()) { - rectangle bground_r{ rect.x, item_coord.y, rect.width, static_cast(rect.bottom() - item_coord.y) }; + rectangle bground_r{ visual_r.x, item_coord.y, visual_r.width, static_cast(visual_r.bottom() - item_coord.y) }; if (!API::dev::copy_transparent_background(essence_->listbox_ptr->handle(), bground_r, *essence_->graph, bground_r.position())) essence_->graph->rectangle(bground_r, true, bgcolor); } @@ -3802,10 +3839,10 @@ namespace nana void _m_draw_item(const category_t& cat, const index_pair& item_pos, const point& coord, - const int txtoff, ///< below y to print the text - unsigned width, - const nana::rectangle& content_r, ///< the rectangle where the full list content have to be drawn - const std::vector& seqs, ///< columns to print + const int txtoff, ///< below y to print the text + unsigned header_width, ///< width of all visible columns, in pixel + const nana::rectangle& content_r, ///< the rectangle where the full list content have to be drawn + const std::vector& seqs, ///< columns to print nana::color bgcolor, nana::color fgcolor, item_state state @@ -3824,12 +3861,16 @@ namespace nana if(!item.fgcolor.invisible()) fgcolor = item.fgcolor; - const unsigned show_w = (std::min)(content_r.width, width - essence_->content_view->origin().x); + const unsigned columns_shown_width = (std::min)(content_r.width, header_width - essence_->content_view->origin().x); auto graph = essence_->graph; //draw the background for the whole item - rectangle bground_r{ content_r.x + static_cast(essence_->header.margin()), coord.y, show_w, essence_->item_height() }; + rectangle bground_r{ + content_r.x + static_cast(essence_->header.margin()) - essence_->content_view->origin().x, + coord.y, + columns_shown_width + essence_->content_view->origin().x, + essence_->item_height() }; auto const state_bgcolor = this->_m_draw_item_bground(bground_r, bgcolor, {}, state, item); //The position of column in x-axis. @@ -3878,7 +3919,7 @@ namespace nana { nana::rectangle imgt(item.img_show_size); img_r = imgt; - img_r.x = content_pos + coord.x + (16 - static_cast(item.img_show_size.width)) / 2; // center in 16 - geom scheme? + img_r.x = content_pos + coord.x + 2 + (16 - static_cast(item.img_show_size.width)) / 2; // center in 16 - geom scheme? img_r.y = coord.y + (static_cast(essence_->item_height()) - static_cast(item.img_show_size.height)) / 2; // center } content_pos += 18; // image width, geom scheme? @@ -4033,7 +4074,6 @@ namespace nana void _m_draw_item_border(int item_top) const { //Draw selecting inner rectangle - rectangle r{ essence_->content_area().x - essence_->content_view->origin().x + static_cast(essence_->header.margin()), item_top, @@ -4121,19 +4161,21 @@ namespace nana if (essence_->rect_lister(r)) drawer_lister_->draw(r); - if (essence_->header.visible() && essence_->rect_header(r)) + if (essence_->header.attrib().visible && essence_->rect_header(r)) drawer_header_->draw(graph, r); essence_->draw_peripheral(); } + // In mouse move event, it cancels the msup_deselect if the listbox is draggable or it started the mouse selection. void trigger::mouse_move(graph_reference graph, const arg_mouse& arg) { using item_state = essence::item_state; using parts = essence::parts; - //Cancel deferred deselection operation when mouse moves. - essence_->deselect_deferred = false; + //Don't deselect the items if the listbox is draggable + if ((operation_states::msup_deselect == essence_->operation.state) && API::dev::window_draggable(arg.window_handle)) + essence_->operation.state = operation_states::none; bool need_refresh = false; @@ -4142,7 +4184,7 @@ namespace nana if(essence_->ptr_state == item_state::pressed) { - if(essence_->pointer_where.first == parts::header) + if((essence_->pointer_where.first == parts::header) && essence_->header.attrib().movable) { // moving a pressed header : grab it essence_->ptr_state = item_state::grabbed; @@ -4165,21 +4207,14 @@ namespace nana need_refresh = true; } - bool set_splitter = false; - if(essence_->pointer_where.first == parts::header) + //Detects column splitter + if(essence_->header.attrib().resizable && + (essence_->pointer_where.first == parts::header) && + drawer_header_->detect_splitter(arg.pos.x)) { - nana::rectangle r; - if(essence_->rect_header(r)) - { - if(drawer_header_->detect_splitter(r, arg.pos.x)) - { - set_splitter = true; - essence_->lister.wd_ptr()->cursor(cursor::size_we); - } - } + essence_->lister.wd_ptr()->cursor(cursor::size_we); } - - if((!set_splitter) && (essence_->ptr_state != item_state::grabbed)) + else if(essence_->ptr_state != item_state::grabbed) { if((drawer_header_->splitter() != npos) || (essence_->lister.wd_ptr()->cursor() == cursor::size_we)) { @@ -4192,6 +4227,9 @@ namespace nana if (essence_->mouse_selection.started) { essence_->update_mouse_selection(arg.pos); + + //Don't deselect items if the mouse selection is started + essence_->operation.state = operation_states::none; need_refresh = true; } @@ -4259,13 +4297,21 @@ namespace nana //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() }); + auto const item_top = this->essence_->item_window_top(abs_item_pos); + auto const item_bottom = item_top + static_cast(essence_->item_height()); - essence_->content_view->sync(false); + int move_top = 0; + + if (item_top < list_r.y && list_r.y < item_bottom) + move_top = item_top - list_r.y; + else if (item_top < list_r.bottom() && list_r.bottom() < item_bottom) + move_top = item_bottom - list_r.bottom(); + + if (0 != move_top) + { + essence_->content_view->move_origin({ 0, move_top }); + essence_->content_view->sync(false); + } } bool new_selected_status = true; @@ -4295,18 +4341,23 @@ namespace nana } else { - if (nana::mouse::right_button == arg.button) + auto selected = lister.pick_items(true); + if (selected.cend() != std::find(selected.cbegin(), selected.cend(), item_pos)) { - //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, abs_item_pos); + //If the current selected one has been selected before selecting, remains the selection states for all + //selected items. But these items will be unselected when the mouse is released. + + //Other items will be unselected if multiple items are selected. + if (selected.size() > 1) + { + essence_->operation.item = abs_item_pos; + + //Don't deselect the selections, let it determine in mouse_move event depending on whether dnd is enabled. + essence_->operation.state = operation_states::msup_deselect; + } } else - { - //Unselects all selected items except current item if right button clicked. - lister.select_for_all(false, abs_item_pos); //cancel all selections - } + lister.select_for_all(false, abs_item_pos); } } else @@ -4375,9 +4426,13 @@ namespace nana if (arg.is_left_button() && (!lister.single_status(true))) essence_->start_mouse_selection(arg); - //Deselection of all items is deferred to the mouse up event when ctrl or shift is not pressed + //Deselecting all items is deferred to the mouse up event when ctrl or shift is not pressed //Pressing ctrl or shift is to selects other items without deselecting current selections. - essence_->deselect_deferred = !(arg.ctrl || arg.shift); + if (!(arg.ctrl || arg.shift)) + { + essence_->operation.state = operation_states::msup_deselect; + essence_->operation.item = index_pair{nana::npos, nana::npos}; + } } if(update) @@ -4398,7 +4453,7 @@ namespace nana bool need_refresh = false; //Don't sort the column when the mouse is due to released for stopping resizing column. - if ((drawer_header_->splitter() == npos) && essence_->header.sortable() && essence_->pointer_where.first == parts::header && prev_state == item_state::pressed) + if ((drawer_header_->splitter() == npos) && essence_->header.attrib().sortable && essence_->pointer_where.first == parts::header && prev_state == item_state::pressed) { //Try to sort the column if(essence_->pointer_where.second < essence_->header.cont().size()) @@ -4418,11 +4473,11 @@ namespace nana essence_->stop_mouse_selection(); need_refresh = true; } - - if (essence_->deselect_deferred) + + if (operation_states::msup_deselect == essence_->operation.state) { - essence_->deselect_deferred = false; - need_refresh |= (essence_->lister.select_for_all(false)); + essence_->operation.state = operation_states::none; + need_refresh |= essence_->lister.select_for_all(false, essence_->operation.item); } if (need_refresh) @@ -5562,6 +5617,35 @@ namespace nana insert_item(pos, to_utf8(text)); } + void listbox::insert_item(index_pair abs_pos, const listbox& rhs, const index_pairs& indexes) + { + auto const columns = (std::min)(this->column_size(), rhs.column_size()); + + if (0 == columns) + return; + + item_proxy it_new = this->at(abs_pos.cat).end(); + for (auto & idx : indexes) + { + auto it_src = rhs.at(idx.cat).at(idx.item); + + if (abs_pos.item < this->at(abs_pos.cat).size()) + { + this->insert_item(abs_pos, it_src.text(0)); + it_new = this->at(abs_pos); + } + else + { + it_new = this->at(abs_pos.cat).append(it_src.text(0)); + } + + for (std::size_t col = 1; col < columns; ++col) + it_new.text(col, it_src.text(col)); + + ++abs_pos.item; + } + } + listbox::cat_proxy listbox::at(size_type pos) { internal_scope_guard lock; @@ -5601,6 +5685,35 @@ namespace nana return index_pair{ npos, npos }; } + listbox::index_pair listbox::hovered(bool return_end) const + { + using parts = drawerbase::listbox::essence::parts; + + internal_scope_guard lock; + + auto cur_pos = API::cursor_position(); + API::calc_window_point(handle(), cur_pos); + + auto pt_where = _m_ess().where(cur_pos); + + if ((pt_where.first == parts::list || pt_where.first == parts::checker) && pt_where.second != npos) + { + auto pos = _m_ess().lister.advance(_m_ess().first_display(), static_cast(pt_where.second)); + if (return_end && pos.is_category()) + { + if (0 < pos.cat) + --pos.cat; + pos.item = this->size_item(pos.cat); + } + return pos; + + } + else if (return_end) + return index_pair{ this->size_categ() - 1, this->size_item(this->size_categ() - 1) }; + + return index_pair{ npos, npos }; + } + auto listbox::column_at(size_type pos, bool disp_order) -> column_interface& { internal_scope_guard lock; @@ -5619,6 +5732,31 @@ namespace nana return _m_ess().header.cont().size(); } + + void listbox::column_resizable(bool resizable) + { + internal_scope_guard lock; + _m_ess().header.attrib().resizable = resizable; + } + + bool listbox::column_resizable() const + { + internal_scope_guard lock; + return _m_ess().header.attrib().resizable; + } + + void listbox::column_movable(bool movable) + { + internal_scope_guard lock; + _m_ess().header.attrib().movable = movable; + } + + bool listbox::column_movable() const + { + internal_scope_guard lock; + return _m_ess().header.attrib().movable; + } + //Contributed by leobackes(pr#97) listbox::size_type listbox::column_from_pos ( const point& pos ) const { @@ -5793,13 +5931,13 @@ namespace nana bool listbox::sortable() const { internal_scope_guard lock; - return _m_ess().header.sortable(); + return _m_ess().header.attrib().sortable; } void listbox::sortable(bool enable) { internal_scope_guard lock; - _m_ess().header.sortable(enable); + _m_ess().header.attrib().sortable = enable; } void listbox::set_sort_compare(size_type col, std::function strick_ordering) @@ -5843,13 +5981,14 @@ namespace nana void listbox::show_header(bool sh) { internal_scope_guard lock; - _m_ess().header.visible(sh); + _m_ess().header.attrib().visible = sh; _m_ess().update(); } bool listbox::visible_header() const { - return _m_ess().header.visible(); + internal_scope_guard lock; + return _m_ess().header.attrib().visible; } void listbox::move_select(bool upwards) ///index_pair + { + return _m_ess().first_display(); + } + + auto listbox::last_visible() const -> index_pair + { + return _m_ess().lister.advance(_m_ess().first_display(), static_cast(_m_ess().count_of_exposed(true))); + } + + auto listbox::visibles() const -> index_pairs + { + index_pairs indexes; + + auto idx = _m_ess().first_display(); + + auto n = _m_ess().count_of_exposed(true); + while (n > 0) + { + if (idx.empty()) + break; + + if (idx.is_category()) + { + indexes.push_back(idx); + --n; + } + else + { + auto const count = (std::min)(_m_ess().lister.size_item(idx.cat) - idx.item, n); + for (std::size_t i = 0; i < count; ++i) + { + indexes.push_back(idx); + ++idx.item; + } + if (count) + { + n -= count; + --idx.item; + } + } + + idx = _m_ess().lister.advance(idx, 1); + } + + return indexes; + } + drawerbase::listbox::essence & listbox::_m_ess() const { return get_drawer_trigger().ess(); @@ -5972,5 +6166,48 @@ namespace nana internal_scope_guard lock; return _m_ess().content_view->scroll_operation(); } + + /// Move column to view_position + void listbox::move_column(size_type abs_pos, size_type view_pos) + { + internal_scope_guard lock; + return _m_ess().header.move_to_view_pos(abs_pos, view_pos, true); + _m_ess().update(); + } + + /// Sort columns in range first_col to last_col inclusive using a row + void listbox::reorder_columns(size_type first_col, + size_type last_col, + index_pair row, bool reverse, + std::function comp) + { + if (last_col <= first_col || last_col >= column_size()) + return; + + std::vector new_idx; + for(size_type i=first_col; i<=last_col; ++i) new_idx.push_back(i); + + internal_scope_guard lock; + auto ip_row = this->at(row); + auto pnany=_m_ess().lister.anyobj(row,false); + std::sort(new_idx.begin(), new_idx.end(), [&](size_type col1, + size_type col2) + { + return comp(ip_row.text(col1), col1, + ip_row.text(col2), col2, + pnany, reverse); + }); + + //Only change the view position of columns + for(size_t i=0; i(API::dev::get_scheme(wd))) - {} - - void item_renderer::background(const nana::point& pos, const nana::size& size, state item_state) + class item_renderer + { + public: + enum class state { + normal, highlighted, selected + }; + + 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: + graph_reference graph_; + scheme *scheme_ptr_; + }; + + item_renderer::item_renderer(window wd, graph_reference graph) + :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 = scheme_ptr_->text_fgcolor; ::nana::color border, body; @@ -245,13 +265,12 @@ namespace nana paint::draw{ graph_ }.corner(r, 1); graph_.rectangle(r.pare_off(1), true, body); - } + } - void item_renderer::caption(const point& pos, const native_string_type& text) - { - graph_.string(pos, text, scheme_ptr_->text_fgcolor); - - } + void item_renderer::caption(const point& pos, const native_string_type& text) + { + graph_.string(pos, text, scheme_ptr_->text_fgcolor); + } //end class item_renderer //class trigger diff --git a/source/gui/widgets/scroll.cpp b/source/gui/widgets/scroll.cpp index 10b2c6da..b699517a 100644 --- a/source/gui/widgets/scroll.cpp +++ b/source/gui/widgets/scroll.cpp @@ -33,21 +33,16 @@ namespace nana //end struct metrics_type //class drawer - drawer::drawer(metrics_type& m) - :metrics_(m) + drawer::drawer(bool vert) : + vert(vert) {} - void drawer::set_vertical(bool v) - { - vertical_ = v; - } - buttons drawer::what(graph_reference graph, const point& screen_pos) { unsigned scale; int pos; - if(vertical_) + if(vert) { scale = graph.height(); pos = screen_pos.y; @@ -64,15 +59,15 @@ namespace nana if (pos > static_cast(scale) - bound_pos) return buttons::second; - if(metrics_.scroll_length) + if(metrics.scroll_length) { - if(metrics_.scroll_pos + static_cast(fixedsize) <= pos && pos < metrics_.scroll_pos + static_cast(fixedsize + metrics_.scroll_length)) + if(metrics.scroll_pos + static_cast(fixedsize) <= pos && pos < metrics.scroll_pos + static_cast(fixedsize + metrics.scroll_length)) return buttons::scroll; } - if(static_cast(fixedsize) <= pos && pos < metrics_.scroll_pos) + if(static_cast(fixedsize) <= pos && pos < metrics.scroll_pos) return buttons::forward; - else if(metrics_.scroll_pos + static_cast(metrics_.scroll_length) <= pos && pos < static_cast(scale - fixedsize)) + else if(metrics.scroll_pos + static_cast(metrics.scroll_length) <= pos && pos < static_cast(scale - fixedsize)) return buttons::backward; return buttons::none; @@ -80,39 +75,39 @@ namespace nana void drawer::scroll_delta_pos(graph_reference graph, int mouse_pos) { - if(mouse_pos + metrics_.scroll_mouse_offset == metrics_.scroll_pos) return; + if(mouse_pos + metrics.scroll_mouse_offset == metrics.scroll_pos) return; - unsigned scale = vertical_ ? graph.height() : graph.width(); + unsigned scale = vert ? graph.height() : graph.width(); if(scale > fixedsize * 2) { - int pos = mouse_pos - metrics_.scroll_mouse_offset; - const unsigned scroll_area = static_cast(scale - fixedsize * 2 - metrics_.scroll_length); + int pos = mouse_pos - metrics.scroll_mouse_offset; + const unsigned scroll_area = static_cast(scale - fixedsize * 2 - metrics.scroll_length); if(pos < 0) pos = 0; else if(pos > static_cast(scroll_area)) pos = static_cast(scroll_area); - metrics_.scroll_pos = pos; - auto value_max = metrics_.peak - metrics_.range; + metrics.scroll_pos = pos; + auto value_max = metrics.peak - metrics.range; //Check scroll_area to avoiding division by zero. if (scroll_area) - metrics_.value = static_cast(pos * (static_cast(value_max) / scroll_area)); //converting to double to avoid overflow. + metrics.value = static_cast(pos * (static_cast(value_max) / scroll_area)); //converting to double to avoid overflow. - if (metrics_.value < value_max) + if (metrics.value < value_max) { //converting to double to avoid overflow. auto const px_per_value = static_cast(scroll_area) / value_max; - int selfpos = static_cast(metrics_.value * px_per_value); - int nextpos = static_cast((metrics_.value + 1) * px_per_value); + int selfpos = static_cast(metrics.value * px_per_value); + int nextpos = static_cast((metrics.value + 1) * px_per_value); if(selfpos != nextpos && (pos - selfpos > nextpos - pos)) - ++metrics_.value; + ++metrics.value; } else - metrics_.value = value_max; + metrics.value = value_max; } } @@ -121,46 +116,46 @@ namespace nana if (!_m_check()) return; - if(buttons::forward == metrics_.what) + if(buttons::forward == metrics.what) { //backward - if(metrics_.value <= metrics_.range) - metrics_.value = 0; + if(metrics.value <= metrics.range) + metrics.value = 0; else - metrics_.value -= (metrics_.range-1); + metrics.value -= (metrics.range-1); } - else if(buttons::backward == metrics_.what) + else if(buttons::backward == metrics.what) { - if(metrics_.peak - metrics_.range - metrics_.value <= metrics_.range) - metrics_.value = metrics_.peak - metrics_.range; + if(metrics.peak - metrics.range - metrics.value <= metrics.range) + metrics.value = metrics.peak - metrics.range; else - metrics_.value += (metrics_.range-1); + metrics.value += (metrics.range-1); } } - void drawer::draw(graph_reference graph, buttons what) + void drawer::draw(graph_reference graph) { - if(false == metrics_.pressed || metrics_.what != buttons::scroll) + if(false == metrics.pressed || metrics.what != buttons::scroll) _m_adjust_scroll(graph); _m_background(graph); - rectangle_rotator r(vertical_, ::nana::rectangle{ graph.size() }); + rectangle_rotator r(vert, ::nana::rectangle{ graph.size() }); r.x_ref() = static_cast(r.w() - fixedsize); r.w_ref() = fixedsize; - int state = ((_m_check() == false || what == buttons::none) ? states::none : states::highlight); - int moused_state = (_m_check() ? (metrics_.pressed ? states::selected : states::actived) : states::none); + auto state = ((_m_check() == false || metrics.what == buttons::none) ? states::none : states::highlight); + auto moused_state = (_m_check() ? (metrics.pressed ? states::selected : states::actived) : states::none); auto result = r.result(); //draw first - _m_draw_button(graph, { 0, 0, result.width, result.height }, buttons::first, (buttons::first == what ? moused_state : state)); + _m_draw_button(graph, { 0, 0, result.width, result.height }, buttons::first, (buttons::first == metrics.what ? moused_state : state)); //draw second - _m_draw_button(graph, result, buttons::second, (buttons::second == what ? moused_state : state)); + _m_draw_button(graph, result, buttons::second, (buttons::second == metrics.what ? moused_state : state)); //draw scroll - _m_draw_scroll(graph, (buttons::scroll == what ? moused_state : states::highlight)); + _m_draw_scroll(graph, (buttons::scroll == metrics.what ? moused_state : states::highlight)); } //private: @@ -168,19 +163,19 @@ namespace nana { graph.rectangle(true, {0xf0, 0xf0, 0xf0}); - if (!metrics_.pressed || !_m_check()) + if (!metrics.pressed || !_m_check()) return; - nana::rectangle_rotator r(vertical_, ::nana::rectangle{ graph.size() }); - if(metrics_.what == buttons::forward) + nana::rectangle_rotator r(vert, ::nana::rectangle{ graph.size() }); + if(metrics.what == buttons::forward) { r.x_ref() = static_cast(fixedsize); - r.w_ref() = metrics_.scroll_pos; + r.w_ref() = metrics.scroll_pos; } - else if(buttons::backward == metrics_.what) + else if(buttons::backward == metrics.what) { - r.x_ref() = static_cast(fixedsize + metrics_.scroll_pos + metrics_.scroll_length); - r.w_ref() = static_cast((vertical_ ? graph.height() : graph.width()) - (fixedsize * 2 + metrics_.scroll_pos + metrics_.scroll_length)); + r.x_ref() = static_cast(fixedsize + metrics.scroll_pos + metrics.scroll_length); + r.w_ref() = static_cast((vert ? graph.height() : graph.width()) - (fixedsize * 2 + metrics.scroll_pos + metrics.scroll_length)); } else return; @@ -190,9 +185,9 @@ namespace nana graph.rectangle(result, true, static_cast(0xDCDCDC)); } - void drawer::_m_button_frame(graph_reference graph, rectangle r, int state) + void drawer::_m_button_frame(graph_reference graph, rectangle r, states state) { - if (!state) + if (states::none == state) return; ::nana::color clr{0x97, 0x97, 0x97}; //highlight @@ -202,6 +197,7 @@ namespace nana clr.from_rgb(0x86, 0xD5, 0xFD); break; case states::selected: clr.from_rgb(0x3C, 0x7F, 0xB1); break; + default: break; } graph.rectangle(r, false, clr); @@ -210,7 +206,7 @@ namespace nana graph.palette(false, clr); r.pare_off(2); - if(vertical_) + if(vert) { unsigned half = r.width / 2; graph.rectangle({ r.x + static_cast(r.width - half), r.y, half, r.height }, true); @@ -222,19 +218,19 @@ namespace nana graph.rectangle({r.x, r.y + static_cast(r.height - half), r.width, half}, true); r.height -= half; } - graph.gradual_rectangle(r, colors::white, clr, !vertical_); + graph.gradual_rectangle(r, colors::white, clr, !vert); } bool drawer::_m_check() const { - return (metrics_.scroll_length && metrics_.range && (metrics_.peak > metrics_.range)); + return (metrics.scroll_length && metrics.range && (metrics.peak > metrics.range)); } void drawer::_m_adjust_scroll(graph_reference graph) { - if(metrics_.range == 0 || metrics_.peak <= metrics_.range) return; + if(metrics.range == 0 || metrics.peak <= metrics.range) return; - unsigned pixels = vertical_ ? graph.height() : graph.width(); + unsigned pixels = vert ? graph.height() : graph.width(); int pos = 0; unsigned len = 0; @@ -242,38 +238,38 @@ namespace nana if(pixels > fixedsize * 2) { pixels -= (fixedsize * 2); - len = static_cast(pixels * metrics_.range / metrics_.peak); + len = static_cast(pixels * metrics.range / metrics.peak); if(len < fixedsize) len = fixedsize; - if(metrics_.value) + if(metrics.value) { pos = static_cast(pixels - len); - if(metrics_.value + metrics_.range >= metrics_.peak) - metrics_.value = metrics_.peak - metrics_.range; + if(metrics.value + metrics.range >= metrics.peak) + metrics.value = metrics.peak - metrics.range; else - pos = static_cast((metrics_.value * pos) /(metrics_.peak - metrics_.range)); + pos = static_cast((metrics.value * pos) /(metrics.peak - metrics.range)); } } - metrics_.scroll_pos = pos; - metrics_.scroll_length = len; + metrics.scroll_pos = pos; + metrics.scroll_length = len; } - void drawer::_m_draw_scroll(graph_reference graph, int state) + void drawer::_m_draw_scroll(graph_reference graph, states state) { if(_m_check()) { - rectangle_rotator r(vertical_, rectangle{ graph.size() }); - r.x_ref() = static_cast(fixedsize + metrics_.scroll_pos); - r.w_ref() = static_cast(metrics_.scroll_length); + rectangle_rotator r(vert, rectangle{ graph.size() }); + r.x_ref() = static_cast(fixedsize + metrics.scroll_pos); + r.w_ref() = static_cast(metrics.scroll_length); _m_button_frame(graph, r.result(), state); } } - void drawer::_m_draw_button(graph_reference graph, rectangle r, buttons what, int state) + void drawer::_m_draw_button(graph_reference graph, rectangle r, buttons what, states state) { if(_m_check()) _m_button_frame(graph, r, state); @@ -287,7 +283,7 @@ namespace nana direction dir; if (buttons::second == what) { - if (vertical_) + if (vert) { r.y = top; dir = direction::south; @@ -299,9 +295,9 @@ namespace nana } } else - dir = vertical_ ? direction::north : direction::west; + dir = vert ? direction::north : direction::west; - if (vertical_) + if (vert) r.x = left / 2; else r.y = top / 2; diff --git a/source/gui/widgets/skeletons/content_view.cpp b/source/gui/widgets/skeletons/content_view.cpp index 0596b4c6..62c92ce9 100644 --- a/source/gui/widgets/skeletons/content_view.cpp +++ b/source/gui/widgets/skeletons/content_view.cpp @@ -65,7 +65,7 @@ namespace nana { bool passive{ true }; //The passive mode determines whether to update if scrollbar changes. It updates the client window if passive is true. - bool drag_started{ false }; + bool drag_view_move{ false }; //indicates the status of the content-view if it moves origin by dragging point origin; std::shared_ptr cv_scroll; @@ -120,11 +120,17 @@ namespace nana { if (!arg.is_left_button()) return; - this->drag_started = this->view.view_area().is_hit(arg.pos); + this->drag_view_move = this->view.view_area().is_hit(arg.pos); } else if (event_code::mouse_move == arg.evt_code) { - if (this->drag_started && this->drive(arg.pos)) + if (dragdrop_status::not_ready != API::window_dragdrop_status(this->window_handle)) + { + //When dnd is in progress, it cancels the move_view operation. + this->drag_view_move = false; + tmr.stop(); + } + else if (this->drag_view_move && this->drive(arg.pos)) { tmr.interval(16); tmr.start(); @@ -132,7 +138,7 @@ namespace nana { } else if (event_code::mouse_up == arg.evt_code) { - this->drag_started = false; + this->drag_view_move = false; tmr.stop(); } }; @@ -224,7 +230,6 @@ namespace nana { { cv_scroll->vert.create(window_handle); cv_scroll->vert.events().value_changed.connect_unignorable(event_fn); - API::take_active(cv_scroll->vert, false, window_handle); this->passive = false; } @@ -255,7 +260,6 @@ namespace nana { { cv_scroll->horz.create(window_handle); cv_scroll->horz.events().value_changed.connect_unignorable(event_fn); - API::take_active(cv_scroll->horz, false, window_handle); this->passive = false; } diff --git a/source/gui/widgets/skeletons/text_editor.cpp b/source/gui/widgets/skeletons/text_editor.cpp index ca070ace..d3878e75 100644 --- a/source/gui/widgets/skeletons/text_editor.cpp +++ b/source/gui/widgets/skeletons/text_editor.cpp @@ -1,15 +1,15 @@ /* - * A text editor implementation - * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2018 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/text_editor.cpp - * @contributors: Ariel Vina-Rodriguez, Oleg Smolsky - */ +* A text editor implementation +* Nana C++ Library(http://www.nanapro.org) +* Copyright(C) 2003-2019 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/text_editor.cpp +* @contributors: Ariel Vina-Rodriguez, Oleg Smolsky +*/ #include #include #include @@ -25,637 +25,597 @@ #include #include -namespace nana{ namespace widgets -{ - namespace skeletons +namespace nana { + namespace widgets { - - template - class undoable_command_interface + namespace skeletons { - public: - virtual ~undoable_command_interface() = default; - virtual EnumCommand get() const = 0; - virtual bool merge(const undoable_command_interface&) = 0; - virtual void execute(bool redo) = 0; - }; - - template - class undoable - { - public: - using command = EnumCommand; - using container = std::deque>>; - - void clear() + template + class undoable_command_interface { - commands_.clear(); - pos_ = 0; - } + public: + virtual ~undoable_command_interface() = default; - void max_steps(std::size_t maxs) + virtual EnumCommand get() const = 0; + virtual bool merge(const undoable_command_interface&) = 0; + virtual void execute(bool redo) = 0; + }; + + template + class undoable { - max_steps_ = maxs; - if (maxs && (commands_.size() >= maxs)) - commands_.erase(commands_.begin(), commands_.begin() + (commands_.size() - maxs + 1)); - } + public: + using command = EnumCommand; + using container = std::deque>>; - std::size_t max_steps() const - { - return max_steps_; - } - - void enable(bool enb) - { - enabled_ = enb; - if (!enb) - clear(); - } - - bool enabled() const - { - return enabled_; - } - - void push(std::unique_ptr> && ptr) - { - if (!ptr || !enabled_) - return; - - if (pos_ < commands_.size()) - commands_.erase(commands_.begin() + pos_, commands_.end()); - else if (max_steps_ && (commands_.size() >= max_steps_)) - commands_.erase(commands_.begin(), commands_.begin() + (commands_.size() - max_steps_ + 1)); - - pos_ = commands_.size(); - if (!commands_.empty()) + void clear() noexcept { - if (commands_.back().get()->merge(*ptr)) + commands_.clear(); + pos_ = 0; + } + + void max_steps(std::size_t maxs) + { + max_steps_ = maxs; + if (maxs && (commands_.size() >= maxs)) + commands_.erase(commands_.begin(), commands_.begin() + (commands_.size() - maxs + 1)); + } + + std::size_t max_steps() const + { + return max_steps_; + } + + void push(std::unique_ptr> && ptr) + { + if (!ptr) return; + + if (pos_ < commands_.size()) + commands_.erase(commands_.begin() + pos_, commands_.end()); + else if (max_steps_ && (commands_.size() >= max_steps_)) + commands_.erase(commands_.begin(), commands_.begin() + (commands_.size() - max_steps_ + 1)); + + pos_ = commands_.size(); + if (!commands_.empty()) + { + if (commands_.back().get()->merge(*ptr)) + return; + } + + commands_.emplace_back(std::move(ptr)); + ++pos_; } - commands_.emplace_back(std::move(ptr)); - ++pos_; - } - - std::size_t count(bool is_undo) const - { - return (is_undo ? pos_ : commands_.size() - pos_); - } - - void undo() - { - if (pos_ > 0) + std::size_t count(bool is_undo) const noexcept { - --pos_; - commands_[pos_].get()->execute(false); + return (is_undo ? pos_ : commands_.size() - pos_); } - } - void redo() - { - if (pos_ != commands_.size()) - commands_[pos_++].get()->execute(true); - } - - private: - container commands_; - bool enabled_{ true }; - std::size_t max_steps_{ 30 }; - std::size_t pos_{ 0 }; - }; - - template - using undo_command_ptr = std::unique_ptr > ; - - template - class text_editor::basic_undoable - : public undoable_command_interface - { - public: - basic_undoable(text_editor& te, EnumCommand cmd) - : editor_(te), cmd_(cmd) - {} - - void set_selected_text() - { - //sel_a_ and sel_b_ are not sorted, sel_b_ keeps the caret position. - sel_a_ = editor_.select_.a; - sel_b_ = editor_.select_.b; - - if (sel_a_ != sel_b_) + void undo() { - selected_text_ = editor_._m_make_select_string(); + if (pos_ > 0) + commands_[--pos_].get()->execute(false); } - } - void set_caret_pos() - { - pos_ = editor_.caret(); - } - protected: - EnumCommand get() const override - { - return cmd_; - } - - virtual bool merge(const undoable_command_interface&) override - { - return false; - } - protected: - text_editor & editor_; - upoint pos_; - upoint sel_a_, sel_b_; - std::wstring selected_text_; - private: - const EnumCommand cmd_; - }; - - class text_editor::undo_backspace - : public basic_undoable < command > - { - public: - undo_backspace(text_editor& editor) - : basic_undoable(editor, command::backspace) - { - } - - void set_removed(std::wstring str) - { - selected_text_ = std::move(str); - } - - void execute(bool redo) override - { - editor_._m_cancel_select(0); - editor_.points_.caret = pos_; - - bool is_enter = ((selected_text_.size() == 1) && ('\n' == selected_text_[0])); - if (redo) + void redo() { + if (pos_ != commands_.size()) + commands_[pos_++].get()->execute(true); + } + + private: + container commands_; + std::size_t max_steps_{ 30 }; + std::size_t pos_{ 0 }; + }; + + template + using undo_command_ptr = std::unique_ptr >; + + template + class text_editor::basic_undoable + : public undoable_command_interface + { + public: + basic_undoable(text_editor& te, EnumCommand cmd) + : editor_(te), cmd_(cmd) + {} + + void set_selected_text() + { + //sel_a_ and sel_b_ are not sorted, sel_b_ keeps the caret position. + sel_a_ = editor_.select_.a; + sel_b_ = editor_.select_.b; + if (sel_a_ != sel_b_) { - editor_.select_.a = sel_a_; - editor_.select_.b = sel_b_; - editor_._m_erase_select(false); - editor_.select_.a = editor_.select_.b; - editor_.points_.caret = sel_a_; + selected_text_ = editor_._m_make_select_string(); + } + } + + void set_caret_pos() + { + pos_ = editor_.caret(); + } + protected: + EnumCommand get() const override + { + return cmd_; + } + + virtual bool merge(const undoable_command_interface&) override + { + return false; + } + protected: + text_editor & editor_; + upoint pos_; + upoint sel_a_, sel_b_; + std::wstring selected_text_; + private: + const EnumCommand cmd_; + }; + + class text_editor::undo_backspace + : public basic_undoable < command > + { + public: + undo_backspace(text_editor& editor) + : basic_undoable(editor, command::backspace) + { + } + + void set_removed(std::wstring str) + { + selected_text_ = std::move(str); + } + + void execute(bool redo) override + { + editor_._m_cancel_select(0); + editor_.points_.caret = pos_; + + bool is_enter = ((selected_text_.size() == 1) && ('\n' == selected_text_[0])); + if (redo) + { + if (sel_a_ != sel_b_) + { + editor_.select_.a = sel_a_; + editor_.select_.b = sel_b_; + editor_._m_erase_select(false); + editor_.select_.a = editor_.select_.b; + editor_.points_.caret = sel_a_; + } + else + { + if (is_enter) + { + editor_.points_.caret = nana::upoint(0, pos_.y + 1); + editor_.backspace(false, false); + } + else + editor_.textbase().erase(pos_.y, pos_.x, selected_text_.size()); + } } else { if (is_enter) { - editor_.points_.caret = nana::upoint(0, pos_.y + 1); - editor_.backspace(false, false); + editor_.enter(false, false); } else - editor_.textbase().erase(pos_.y, pos_.x, selected_text_.size()); - } - } - else - { - if (is_enter) - { - editor_.enter(false, false); - } - else - { - editor_._m_put(selected_text_, false); - if (sel_a_ != sel_b_) { - editor_.select_.a = sel_a_; - editor_.select_.b = sel_b_; - editor_.points_.caret = sel_b_; + editor_._m_put(selected_text_, false); + if (sel_a_ != sel_b_) + { + editor_.select_.a = sel_a_; + editor_.select_.b = sel_b_; + editor_.points_.caret = sel_b_; + } + else + ++editor_.points_.caret.x; } - else - ++editor_.points_.caret.x; - } - } - - editor_.textbase().text_changed(); - - editor_.reset_caret(); - } - }; - - class text_editor::undo_input_text - : public basic_undoable - { - public: - undo_input_text(text_editor & editor, const std::wstring& text) - : basic_undoable(editor, command::input_text), - text_(text) - { - } - - void execute(bool redo) override - { - bool is_enter = (text_.size() == 1 && '\n' == text_[0]); - editor_._m_cancel_select(0); - editor_.points_.caret = pos_; //The pos_ specifies the caret position before input - - if (redo) - { - if (is_enter) - { - editor_.enter(false, false); - } - else - { - if (!selected_text_.empty()) - { - editor_.select_.a = sel_a_; - editor_.select_.b = sel_b_; - editor_._m_erase_select(false); - } - editor_.points_.caret = editor_._m_put(text_, false); //redo - } - } - else - { - if (is_enter) - { - editor_.points_.caret.x = 0; - ++editor_.points_.caret.y; - editor_.backspace(false, false); - } - else - { - std::vector> lines; - if (editor_._m_resolve_text(text_, lines)) - { - editor_.select_.a = pos_; - editor_.select_.b = upoint(static_cast(lines.back().second - lines.back().first), static_cast(pos_.y + lines.size() - 1)); - editor_.backspace(false, false); - editor_.select_.a = editor_.select_.b; - } - else - editor_.textbase().erase(pos_.y, pos_.x, text_.size()); //undo } - if (!selected_text_.empty()) - { - editor_.points_.caret = (std::min)(sel_a_, sel_b_); - editor_._m_put(selected_text_, false); - editor_.points_.caret = sel_b_; - editor_.select_.a = sel_a_; //Reset the selected text - editor_.select_.b = sel_b_; - } - } + editor_.textbase().text_changed(); - editor_.textbase().text_changed(); - editor_.reset_caret(); - } - private: - std::wstring text_; - }; - - class text_editor::undo_move_text - : public basic_undoable - { - public: - undo_move_text(text_editor& editor) - : basic_undoable(editor, command::move_text) - {} - - void execute(bool redo) override - { - if (redo) - { - editor_.select_.a = sel_a_; - editor_.select_.b = sel_b_; - editor_.points_.caret = pos_; - editor_._m_move_select(false); - } - else - { - editor_.select_.a = dest_a_; - editor_.select_.b = dest_b_; - editor_.points_.caret = (sel_a_ < sel_b_ ? sel_a_ : sel_b_); - - const auto text = editor_._m_make_select_string(); - - editor_._m_erase_select(false); - editor_._m_put(text, false); - - editor_.select_.a = sel_a_; - editor_.select_.b = sel_b_; - - editor_.points_.caret = sel_b_; editor_.reset_caret(); } - editor_.textbase().text_changed(); - } + }; - void set_destination(const nana::upoint& dest_a, const nana::upoint& dest_b) + class text_editor::undo_input_text + : public basic_undoable { - dest_a_ = dest_a; - dest_b_ = dest_b; - } - private: - 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 & sp : colored_areas_) + public: + undo_input_text(text_editor & editor, const std::wstring& text) + : basic_undoable(editor, command::input_text), + text_(text) { - if (sp->begin <= pos && pos < sp->begin + sp->count) - return sp; - else if (sp->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() override - { - 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 + void execute(bool redo) override { - if (i->get()->begin <= pos && pos < i->get()->begin + i->get()->count) + bool is_enter = (text_.size() == 1 && '\n' == text_[0]); + editor_._m_cancel_select(0); + editor_.points_.caret = pos_; //The pos_ specifies the caret position before input + + if (redo) { - i = colored_areas_.erase(i); - changed = true; + if (is_enter) + { + editor_.enter(false, false); + } + else + { + if (!selected_text_.empty()) + { + editor_.select_.a = sel_a_; + editor_.select_.b = sel_b_; + editor_._m_erase_select(false); + } + editor_.points_.caret = editor_._m_put(text_, false); //redo + } } - else if (i->get()->begin > pos) - break; + else + { + if (is_enter) + { + editor_.points_.caret.x = 0; + ++editor_.points_.caret.y; + editor_.backspace(false, false); + } + else + { + std::vector> lines; + if (editor_._m_resolve_text(text_, lines)) + { + editor_.select_.a = pos_; + editor_.select_.b = upoint(static_cast(lines.back().second - lines.back().first), static_cast(pos_.y + lines.size() - 1)); + editor_.backspace(false, false); + editor_.select_.a = editor_.select_.b; + } + else + editor_.textbase().erase(pos_.y, pos_.x, text_.size()); //undo + } + + if (!selected_text_.empty()) + { + editor_.points_.caret = (std::min)(sel_a_, sel_b_); + editor_._m_put(selected_text_, false); + editor_.points_.caret = sel_b_; + editor_.select_.a = sel_a_; //Reset the selected text + editor_.select_.b = sel_b_; + } + } + + editor_.textbase().text_changed(); + editor_.reset_caret(); } - if (changed) - API::refresh_window(window_handle_); + private: + std::wstring text_; + }; - return changed; - } - - std::size_t size() const override + class text_editor::undo_move_text + : public basic_undoable { - return colored_areas_.size(); - } + public: + undo_move_text(text_editor& editor) + : basic_undoable(editor, command::move_text) + {} - std::shared_ptr at(std::size_t index) override + void execute(bool redo) override + { + if (redo) + { + editor_.select_.a = sel_a_; + editor_.select_.b = sel_b_; + editor_.points_.caret = pos_; + editor_._m_move_select(false); + } + else + { + editor_.select_.a = dest_a_; + editor_.select_.b = dest_b_; + editor_.points_.caret = (sel_a_ < sel_b_ ? sel_a_ : sel_b_); + + const auto text = editor_._m_make_select_string(); + + editor_._m_erase_select(false); + editor_._m_put(text, false); + + editor_.select_.a = sel_a_; + editor_.select_.b = sel_b_; + + editor_.points_.caret = sel_b_; + editor_.reset_caret(); + } + editor_.textbase().text_changed(); + } + + void set_destination(const nana::upoint& dest_a, const nana::upoint& dest_b) + { + dest_a_ = dest_a; + dest_b_ = dest_b; + } + private: + nana::upoint dest_a_, dest_b_; + }; + + struct text_editor::text_section { - return colored_areas_.at(index); - } - private: - window window_handle_; - std::vector> colored_areas_; - }; + const wchar_t* begin{ nullptr }; + const wchar_t* end{ nullptr }; + unsigned pixels{ 0 }; - struct text_editor::implementation - { - undoable undo; //undo command - renderers customized_renderers; - std::vector text_position; //positions of text since last rendering. - int text_position_origin{ -1 }; //origin when last text_exposed + text_section() = default; + text_section(const wchar_t* ptr, const wchar_t* endptr, unsigned px) + : begin(ptr), end(endptr), pixels(px) + {} + }; - skeletons::textbase textbase; - sync_graph try_refresh{ sync_graph::none }; - - colored_area_access colored_area; - - struct inner_capacities + struct keyword_scheme { - editor_behavior_interface * behavior; + ::nana::color fgcolor; + ::nana::color bgcolor; + }; - accepts acceptive{ accepts::no_restrict }; - std::function pred_acceptive; - }capacities; - - struct inner_counterpart + struct keyword_desc { - bool enabled{ false }; - paint::graphics buffer; //A offscreen buffer which keeps the background that painted by external part. - }counterpart; + std::wstring text; + std::string scheme; + bool case_sensitive; + bool whole_word_matched; - struct indent_rep + 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 { - bool enabled{ false }; - std::function generator; - }indent; + const wchar_t* begin; + const wchar_t* end; + const keyword_scheme * scheme; + }; - struct inner_keywords + enum class sync_graph { - std::map> schemes; - std::deque base; - }keywords; + none, + refresh, + lazy_refresh + }; - std::unique_ptr cview; - }; + colored_area_access_interface::~colored_area_access_interface() {} - - 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. - virtual void add_lines(std::size_t pos, std::size_t lines) = 0; - virtual void prepare() = 0; - virtual void pre_calc_line(std::size_t line, unsigned pixels) = 0; - virtual void pre_calc_lines(unsigned pixels) = 0; - 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; - }; - - inline bool is_right_text(const unicode_bidi::entity& e) - { - return ((e.bidi_char_type != unicode_bidi::bidi_char::L) && (e.level & 1)); - } - - - class text_editor::behavior_normal - : public editor_behavior_interface - { - public: - behavior_normal(text_editor& editor) - : editor_(editor) - {} - - std::vector line(std::size_t pos) const override + class colored_area_access + : public colored_area_access_interface { - //Every line of normal behavior only has one text_section - std::vector sections; - sections.emplace_back(this->sections_[pos]); - return sections; - } + public: + void set_window(window handle) + { + window_handle_ = handle; + } - 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)) + std::shared_ptr find(std::size_t pos) const + { + for (auto & sp : colored_areas_) + { + if (sp->begin <= pos && pos < sp->begin + sp->count) + return sp; + else if (sp->begin > pos) + break; + } 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; - - auto const & const_sections = sections_; - for (auto & sct : const_sections) + public: + //Overrides methods of colored_area_access_interface + std::shared_ptr get(std::size_t line_pos) override { - 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); +#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; - ++line; + if (area.begin > line_pos) + break; + } + + return *colored_areas_.emplace(i, + std::make_shared(colored_area_type{ line_pos, 1, color{}, color{} }) + ); } + + bool clear() override + { + 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. + int text_position_origin{ -1 }; //origin when last text_exposed + + 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. + virtual void add_lines(std::size_t pos, std::size_t lines) = 0; + virtual void prepare() = 0; + virtual void pre_calc_line(std::size_t line, unsigned pixels) = 0; + virtual void pre_calc_lines(unsigned pixels) = 0; + 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; + }; + + inline bool is_right_text(const unicode_bidi::entity& e) + { + return ((e.bidi_char_type != unicode_bidi::bidi_char::L) && (e.level & 1)); } - void add_lines(std::size_t pos, std::size_t line_size) override + + class text_editor::behavior_normal + : public editor_behavior_interface { - if (pos < this->sections_.size()) + public: + behavior_normal(text_editor& editor) + : editor_(editor) + {} + + std::vector line(std::size_t pos) const override { - for (std::size_t i = 0; i < line_size; ++i) + //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_.emplace(this->sections_.cbegin() + (pos + i)); + this->sections_.erase(this->sections_.cbegin() + (first + 1), this->sections_.cbegin() + second); #else - this->sections_.emplace(this->sections_.begin() + (pos + i)); + 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; @@ -663,3133 +623,3157 @@ namespace nana{ namespace widgets auto const & const_sections = sections_; for (auto & sct : const_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); - } + 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 prepare() override + 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; + + auto const & const_sections = sections_; + for (auto & sct : const_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 prepare() override + { + auto const line_count = editor_.textbase().lines(); + this->sections_.resize(line_count); + } + + 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 = txt_section.begin + text.size(); + txt_section.pixels = editor_._m_text_extent_size(txt_section.begin, 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 + { + return editor_.textbase().lines(); + } + + std::size_t take_lines(std::size_t) const override + { + return 1; + } + private: + text_editor& editor_; + std::vector sections_; + }; //end class behavior_normal + + + class text_editor::behavior_linewrapped + : public text_editor::editor_behavior_interface { - auto const line_count = editor_.textbase().lines(); - this->sections_.resize(line_count); - } + struct line_metrics + { + std::size_t take_lines; //The number of lines that text of this line takes. + std::vector line_sections; + }; + public: + behavior_linewrapped(text_editor& editor) + : editor_(editor) + {} - 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 = txt_section.begin + text.size(); - txt_section.pixels = editor_._m_text_extent_size(txt_section.begin, text.size()).width; - } + std::vector line(std::size_t pos) const override + { + return linemtr_[pos].line_sections; + } - 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); - } + row_coordinate text_position_from_screen(int top) const override + { + row_coordinate coord; + const auto line_px = static_cast(editor_.line_height()); - std::size_t take_lines() const override - { - return editor_.textbase().lines(); - } + if ((0 == editor_.textbase().lines()) || (0 == line_px)) + return coord; - std::size_t take_lines(std::size_t) const override - { - return 1; - } - private: - text_editor& editor_; - std::vector sections_; - }; //end class behavior_normal + auto text_row = (std::max)(0, (top - editor_.text_area_.area.y + editor_.impl_->cview->origin().y) / line_px); - - class text_editor::behavior_linewrapped - : public text_editor::editor_behavior_interface - { - struct line_metrics - { - std::size_t take_lines; //The number of lines that text of this line takes. - std::vector line_sections; - }; - public: - 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)) + coord = _m_textline(static_cast(text_row)); + if (linemtr_.size() <= coord.first) + { + coord.first = linemtr_.size() - 1; + coord.second = linemtr_.back().line_sections.size() - 1; + } return coord; - - auto text_row = (std::max)(0, (top - editor_.text_area_.area.y + editor_.impl_->cview->origin().y) / line_px); - - coord = _m_textline(static_cast(text_row)); - 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) - std::swap(first, second); - - if (second < linemtr_.size()) - linemtr_.erase(linemtr_.begin() + first + 1, linemtr_.begin() + second + 1); - - auto const width_px = editor_.width_pixels(); - - pre_calc_line(first, width_px); - } - - void add_lines(std::size_t pos, std::size_t lines) override - { - if (pos < linemtr_.size()) + unsigned max_pixels() const override { - for (std::size_t i = 0; i < lines; ++i) - linemtr_.emplace(linemtr_.begin() + pos + i); + return editor_.width_pixels(); + } - //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; + void merge_lines(std::size_t first, std::size_t second) override + { + if (first > second) + std::swap(first, second); - auto const & const_linemtr = linemtr_; - for (auto & mtr : const_linemtr) + if (second < linemtr_.size()) + linemtr_.erase(linemtr_.begin() + first + 1, linemtr_.begin() + second + 1); + + pre_calc_line(first, editor_.width_pixels()); + } + + void add_lines(std::size_t pos, std::size_t lines) override + { + if (pos < linemtr_.size()) { - if (line < pos || (pos + lines) <= line) + for (std::size_t i = 0; i < lines; ++i) + linemtr_.emplace(linemtr_.begin() + pos + i); + + //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; + + auto const & const_linemtr = linemtr_; + for (auto & mtr : const_linemtr) { - auto & linestr = editor_.textbase().getline(line); - auto p = mtr.line_sections.front().begin; - if (p < linestr.c_str() || (linestr.c_str() + linestr.size() < p)) - pre_calc_line(line, editor_.width_pixels()); + if (line < pos || (pos + lines) <= line) + { + auto & linestr = editor_.textbase().getline(line); + auto p = mtr.line_sections.front().begin; + if (p < linestr.c_str() || (linestr.c_str() + linestr.size() < p)) + pre_calc_line(line, editor_.width_pixels()); + } + ++line; } - ++line; } } - } - void prepare() override - { - auto const lines = editor_.textbase().lines(); - linemtr_.resize(lines); - } - - void pre_calc_line(std::size_t line, unsigned pixels) override - { - const string_type& lnstr = editor_.textbase().getline(line); - if (lnstr.empty()) + void prepare() override { - auto & mtr = linemtr_[line]; - mtr.line_sections.clear(); - - mtr.line_sections.emplace_back(lnstr.c_str(), lnstr.c_str(), unsigned{}); - mtr.take_lines = 1; - return; + auto const lines = editor_.textbase().lines(); + linemtr_.resize(lines); } - std::vector sections; - _m_text_section(lnstr, sections); - - std::vector line_sections; - - unsigned text_px = 0; - const wchar_t * secondary_begin = nullptr; - for (auto & ts : sections) + void pre_calc_line(std::size_t line, unsigned pixels) override { - if (!secondary_begin) - secondary_begin = ts.begin; - - const unsigned str_w = editor_._m_text_extent_size(ts.begin, ts.end - ts.begin).width; - - text_px += str_w; - if (text_px >= pixels) + const string_type& lnstr = editor_.textbase().getline(line); + if (lnstr.empty()) { - if (text_px != str_w) + auto & mtr = linemtr_[line]; + mtr.line_sections.clear(); + + mtr.line_sections.emplace_back(lnstr.c_str(), lnstr.c_str(), unsigned{}); + mtr.take_lines = 1; + return; + } + + std::vector sections; + _m_text_section(lnstr, sections); + + std::vector line_sections; + + unsigned text_px = 0; + const wchar_t * secondary_begin = nullptr; + for (auto & ts : sections) + { + if (!secondary_begin) + secondary_begin = ts.begin; + + 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 != str_w) + { + line_sections.emplace_back(secondary_begin, ts.begin, unsigned{ text_px - str_w }); + text_px = str_w; + secondary_begin = ts.begin; + } + + if (str_w > pixels) //Indicates the splitting of ts string + { + std::size_t len = ts.end - ts.begin; +#ifdef _nana_std_has_string_view + auto pxbuf = editor_.graph_.glyph_pixels({ ts.begin, len }); +#else + std::unique_ptr pxbuf(new unsigned[len]); + editor_.graph_.glyph_pixels(ts.begin, len, pxbuf.get()); +#endif + + auto pxptr = pxbuf.get(); + auto pxend = pxptr + len; + + secondary_begin = ts.begin; + text_px = 0; + for (auto pxi = pxptr; pxi != pxend; ++pxi) + { + text_px += *pxi; + if (text_px < pixels) + continue; + + const wchar_t * endptr = ts.begin + (pxi - pxptr) + (text_px == pixels ? 1 : 0); + line_sections.emplace_back(secondary_begin, endptr, unsigned{ text_px - (text_px == pixels ? 0 : *pxi) }); + secondary_begin = endptr; + + text_px = (text_px == pixels ? 0 : *pxi); + } + } + continue; + } + else if (text_px == pixels) { line_sections.emplace_back(secondary_begin, ts.begin, unsigned{ text_px - str_w }); - text_px = str_w; secondary_begin = ts.begin; + text_px = str_w; + } + } + + auto & mtr = linemtr_[line]; + + mtr.take_lines = line_sections.size(); + mtr.line_sections.swap(line_sections); + + if (secondary_begin) + { + mtr.line_sections.emplace_back(secondary_begin, sections.back().end, unsigned{ text_px }); + ++mtr.take_lines; + } + } + + void pre_calc_lines(unsigned pixels) override + { + auto const lines = editor_.textbase().lines(); + linemtr_.resize(lines); + + for (std::size_t i = 0; i < lines; ++i) + pre_calc_line(i, pixels); + } + + std::size_t take_lines() const override + { + std::size_t lines = 0; + for (auto & mtr : linemtr_) + lines += mtr.take_lines; + + return lines; + } + + std::size_t take_lines(std::size_t pos) const override + { + return (pos < linemtr_.size() ? linemtr_[pos].take_lines : 0); + } + private: + /// Split a text into multiple sections, a section indicates an english word or a CKJ character + void _m_text_section(const std::wstring& str, std::vector& tsec) + { + if (str.empty()) + { + tsec.emplace_back(str.c_str(), str.c_str(), unsigned{}); + return; + } + const auto end = str.c_str() + str.size(); + + const wchar_t * word = nullptr; + for (auto i = str.c_str(); i != end; ++i) + { + wchar_t const ch = *i; + + //CKJ characters and whitespace + if (' ' == ch || '\t' == ch || (0x4E00 <= ch && ch <= 0x9FCF)) + { + if (word) //Record the word. + { + tsec.emplace_back(word, i, unsigned{}); + word = nullptr; + } + + tsec.emplace_back(i, i + 1, unsigned{}); + continue; } - if (str_w > pixels) //Indicates the splitting of ts string + if (nullptr == word) + word = i; + } + + if (word) + tsec.emplace_back(word, end, unsigned{}); + } + + row_coordinate _m_textline(std::size_t scrline) const + { + row_coordinate coord; + for (auto & mtr : linemtr_) + { + if (mtr.take_lines > scrline) { - std::size_t len = ts.end - ts.begin; -#ifdef _nana_std_has_string_view - auto pxbuf = editor_.graph_.glyph_pixels({ts.begin, len}); -#else - std::unique_ptr pxbuf(new unsigned[len]); - editor_.graph_.glyph_pixels(ts.begin, len, pxbuf.get()); -#endif + coord.second = scrline; + return coord; + } + else + scrline -= mtr.take_lines; - auto pxptr = pxbuf.get(); - auto pxend = pxptr + len; + ++coord.first; + } + return coord; + } + private: + text_editor& editor_; + std::vector linemtr_; + }; //end class behavior_linewrapped - secondary_begin = ts.begin; - text_px = 0; - for (auto pxi = pxptr; pxi != pxend; ++pxi) + class text_editor::keyword_parser + { + public: + void parse(const wchar_t* c_str, std::size_t len, implementation::inner_keywords& keywords) //need string_view + { + if (keywords.base.empty() || (0 == len) || (*c_str == 0)) + return; + + std::wstring text{ c_str, len }; + + using index = std::wstring::size_type; + + std::vector entities; + + ::nana::ciwstring cistr; + for (auto & ds : keywords.base) + { + index pos{ 0 }; + for (index rest{ text.size() }; rest >= ds.text.size(); ++pos, rest = text.size() - pos) + { + if (ds.case_sensitive) { - text_px += *pxi; - if (text_px < pixels) - continue; + pos = text.find(ds.text, pos); + if (pos == text.npos) + break; + } + else + { + if (cistr.empty()) + cistr.append(text.c_str(), text.size()); - const wchar_t * endptr = ts.begin + (pxi - pxptr) + (text_px == pixels ? 1 : 0); - line_sections.emplace_back(secondary_begin, endptr, unsigned{ text_px - (text_px == pixels ? 0 : *pxi) }); - secondary_begin = endptr; + pos = cistr.find(ds.text.c_str(), pos); + if (pos == cistr.npos) + break; + } - text_px = (text_px == pixels ? 0 : *pxi); + 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) + { +#ifdef _nana_std_has_emplace_return_type + auto & last = entities.emplace_back(); +#else + entities.emplace_back(); + auto & last = entities.back(); +#endif + last.begin = c_str + pos; + last.end = last.begin + ds.text.size(); + last.scheme = ki->second.get(); } } - continue; } - else if (text_px == pixels) + + if (!entities.empty()) { - line_sections.emplace_back(secondary_begin, ts.begin, unsigned{ text_px - str_w }); - secondary_begin = ts.begin; - text_px = str_w; - } - } - - auto & mtr = linemtr_[line]; - - mtr.take_lines = line_sections.size(); - mtr.line_sections.swap(line_sections); - - if (secondary_begin) - { - mtr.line_sections.emplace_back(secondary_begin, sections.back().end, unsigned{ text_px }); - ++mtr.take_lines; - } - } - - void pre_calc_lines(unsigned pixels) override - { - auto const lines = editor_.textbase().lines(); - linemtr_.resize(lines); - - for (std::size_t i = 0; i < lines; ++i) - pre_calc_line(i, pixels); - } - - std::size_t take_lines() const override - { - std::size_t lines = 0; - for (auto & mtr : linemtr_) - lines += mtr.take_lines; - - return lines; - } - - std::size_t take_lines(std::size_t pos) const override - { - return (pos < linemtr_.size() ? linemtr_[pos].take_lines : 0); - } - private: - /// Split a text into multiple sections, a section indicates an english word or a CKJ character - void _m_text_section(const std::wstring& str, std::vector& tsec) - { - if (str.empty()) - { - tsec.emplace_back(str.c_str(), str.c_str(), unsigned{}); - return; - } - const auto end = str.c_str() + str.size(); - - const wchar_t * word = nullptr; - for (auto i = str.c_str(); i != end; ++i) - { - wchar_t const ch = *i; - - //CKJ characters and whitespace - if (' ' == ch || '\t' == ch || (0x4E00 <= ch && ch <= 0x9FCF)) - { - if (word) //Record the word. + std::sort(entities.begin(), entities.end(), [](const entity& a, const entity& b) { - tsec.emplace_back(word, i, unsigned{}); - word = nullptr; + return (a.begin < b.begin); + }); + + auto i = entities.begin(); + auto bound = i->end; + + for (++i; i != entities.end(); ) + { + if (bound > i->begin) + i = entities.erase(i); // erase overlaping. Left only the first. + else + ++i; } - - tsec.emplace_back(i, i + 1, unsigned{}); - continue; } - if (nullptr == word) - word = i; + entities_.swap(entities); } - if(word) - tsec.emplace_back(word, end, unsigned{}); - } - - row_coordinate _m_textline(std::size_t scrline) const - { - row_coordinate coord; - for (auto & mtr : linemtr_) + const std::vector& entities() const { - if (mtr.take_lines > scrline) + return entities_; + } + private: + static bool _m_whole_word(const std::wstring& text, std::wstring::size_type pos, std::size_t len) + { + if (pos) { - coord.second = scrline; - return coord; + auto chr = text[pos - 1]; + if ((std::iswalpha(chr) && !std::iswspace(chr)) || chr == '_') + return false; } - else - scrline -= mtr.take_lines; - ++coord.first; - } - return coord; - } - private: - text_editor& editor_; - std::vector linemtr_; - }; //end class behavior_linewrapped - - class text_editor::keyword_parser - { - public: - void parse(const wchar_t* c_str, std::size_t len, implementation::inner_keywords& keywords) //need string_view - { - if ( keywords.base.empty() || (0 == len) || (*c_str == 0) ) - return; - - std::wstring text{ c_str, len }; - - using index = std::wstring::size_type; - - std::vector entities; - - ::nana::ciwstring cistr; - for (auto & ds : keywords.base) - { - index pos{0} ; - for (index rest{text.size()}; rest >= ds.text.size() ; ++pos, rest = text.size() - pos) - { - if (ds.case_sensitive) - { - pos = text.find(ds.text, pos); - if (pos == text.npos) - break; - } - else - { - if (cistr.empty()) - cistr.append(text.c_str(), text.size()); - - pos = cistr.find(ds.text.c_str(), pos); - if (pos == cistr.npos) - break; - } - - 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) - { -#ifdef _nana_std_has_emplace_return_type - auto & last = entities.emplace_back(); -#else - entities.emplace_back(); - auto & last = entities.back(); -#endif - last.begin = c_str + pos; - last.end = last.begin + ds.text.size(); - last.scheme = ki->second.get(); - } - } - } - - if (!entities.empty()) - { - std::sort(entities.begin(), entities.end(), [](const entity& a, const entity& b) + if (pos + len < text.size()) { - return (a.begin < b.begin); - }); - - auto i = entities.begin(); - auto bound = i->end; - - for (++i; i != entities.end(); ) - { - if (bound > i->begin) - i = entities.erase(i); // erase overlaping. Left only the first. - else - ++i; + auto chr = text[pos + len]; + if ((std::iswalpha(chr) && !std::iswspace(chr)) || chr == '_') + return false; } + + return true; } + private: + std::vector entities_; + }; - entities_.swap(entities); - } + //class text_editor - const std::vector& entities() const - { - return entities_; - } - private: - static bool _m_whole_word(const std::wstring& text, std::wstring::size_type pos, std::size_t len) - { - if (pos) - { - auto chr = text[pos - 1]; - if ((std::iswalpha(chr) && !std::iswspace(chr)) || chr == '_') - return false; - } - - if (pos + len < text.size()) - { - auto chr = text[pos + len]; - if ((std::iswalpha(chr) && !std::iswspace(chr)) || chr == '_') - return false; - } - - return true; - } - private: - std::vector entities_; - }; - - //class text_editor - - text_editor::text_editor(window wd, graph_reference graph, const text_editor_scheme* schm) - : impl_(new implementation), + text_editor::text_editor(window wd, graph_reference graph, const text_editor_scheme* schm): + impl_(new implementation), window_(wd), graph_(graph), scheme_(schm) - { - impl_->capacities.behavior = new behavior_normal(*this); - - text_area_.area.dimension(graph.size()); - - impl_->cview.reset(new content_view{ wd }); - impl_->cview->disp_area(text_area_.area, {}, {}, {}); - impl_->cview->events().scrolled = [this] { - this->reset_caret(); - }; - - impl_->cview->events().hover_outside = [this](const point& pos) { - mouse_caret(pos, false); - if (selection::mode::mouse_selected == select_.mode_selection || selection::mode::method_selected == select_.mode_selection) - set_end_caret(false); - }; - - API::create_caret(wd, { 1, line_height() }); - API::bgcolor(wd, colors::white); - API::fgcolor(wd, colors::black); - } - - text_editor::~text_editor() - { - //For instance of unique_ptr pimpl idiom. - - delete impl_->capacities.behavior; - delete impl_; - } - - size text_editor::caret_size() const - { - return { 1, line_height() }; - } - - const point& text_editor::content_origin() const - { - return impl_->cview->origin(); - } - - void text_editor::set_highlight(const std::string& name, const ::nana::color& fgcolor, const ::nana::color& bgcolor) - { - if (fgcolor.invisible() && bgcolor.invisible()) { - impl_->keywords.schemes.erase(name); - return; + impl_->capacities.behavior = new behavior_normal(*this); + + text_area_.area.dimension(graph.size()); + + impl_->cview.reset(new content_view{ wd }); + impl_->cview->disp_area(text_area_.area, {}, {}, {}); + impl_->cview->events().scrolled = [this] { + this->reset_caret(); + }; + + impl_->cview->events().hover_outside = [this](const point& pos) { + mouse_caret(pos, false); + if (selection::mode::mouse_selected == select_.mode_selection || selection::mode::method_selected == select_.mode_selection) + set_end_caret(false); + }; + + API::create_caret(wd, { 1, line_height() }); + API::bgcolor(wd, colors::white); + API::fgcolor(wd, colors::black); } - auto sp = std::make_shared(); - sp->fgcolor = fgcolor; - sp->bgcolor = bgcolor; - impl_->keywords.schemes[name].swap(sp); - } - - void text_editor::erase_highlight(const std::string& name) - { - impl_->keywords.schemes.erase(name); - } - - void text_editor::set_keyword(const ::std::wstring& kw, const std::string& name, bool case_sensitive, bool whole_word_matched) - { - for(auto & ds : impl_->keywords.base) + text_editor::~text_editor() { - if (ds.text == kw) - { - ds.scheme = name; - ds.case_sensitive = case_sensitive; - ds.whole_word_matched = whole_word_matched; - return; - } - } - - impl_->keywords.base.emplace_back(kw, name, case_sensitive, whole_word_matched); - } - - void text_editor::erase_keyword(const ::std::wstring& kw) - { - for (auto i = impl_->keywords.base.begin(); i != impl_->keywords.base.end(); ++i) - { - if (kw == i->text) - { - impl_->keywords.base.erase(i); - return; - } - } - } - - colored_area_access_interface& text_editor::colored_area() - { - return impl_->colored_area; - } - - void text_editor::set_accept(std::function pred) - { - impl_->capacities.pred_acceptive = std::move(pred); - } - - void text_editor::set_accept(accepts acceptive) - { - impl_->capacities.acceptive = acceptive; - } - - bool text_editor::respond_char(const arg_keyboard& arg) //key is a character of ASCII code - { - if (!API::window_enabled(window_)) - return false; - - char_type key = arg.key; - switch (key) - { - case keyboard::end_of_text: - copy(); - return false; - case keyboard::select_all: - select(true); - return true; - } - - if (attributes_.editable && (!impl_->capacities.pred_acceptive || impl_->capacities.pred_acceptive(key))) - { - switch (key) - { - case '\b': - backspace(true, true); break; - case '\n': case '\r': - enter(true, true); break; - case keyboard::sync_idel: - paste(); break; - case keyboard::tab: - put(static_cast(keyboard::tab)); break; - case keyboard::cancel: - cut(); - break; - case keyboard::end_of_medium: - undo(true); - break; - case keyboard::substitute: - undo(false); - break; - default: - if (!_m_accepts(key)) - return false; - - if (key > 0x7F || (32 <= key && key <= 126)) - put(key); - } - reset_caret(); - impl_->try_refresh = sync_graph::refresh; - return true; - } - return false; - } - - bool text_editor::respond_key(const arg_keyboard& arg) - { - char_type key = arg.key; - switch (key) - { - case keyboard::os_arrow_left: - case keyboard::os_arrow_right: - case keyboard::os_arrow_up: - case keyboard::os_arrow_down: - case keyboard::os_home: - case keyboard::os_end: - case keyboard::os_pageup: - case keyboard::os_pagedown: - _m_handle_move_key(arg); - break; - case keyboard::del: - // 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; - } - - void text_editor::typeface_changed() - { - _m_reset_content_size(true); - } - - void text_editor::indent(bool enb, std::function generator) - { - impl_->indent.enabled = enb; - impl_->indent.generator = std::move(generator); - } - - void text_editor::set_event(event_interface* ptr) - { - event_handler_ = ptr; - } - - bool text_editor::load(const char* fs) - { - if (!impl_->textbase.load(fs)) - return false; - - _m_reset(); - - impl_->try_refresh = sync_graph::refresh; - _m_reset_content_size(true); - 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) - return false; - - text_area_.area = r; - - if (impl_->counterpart.enabled) - impl_->counterpart.buffer.make(r.dimension()); - - 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); - - reset_caret(); - return true; - } - - rectangle text_editor::text_area(bool including_scroll) const - { - return (including_scroll ? impl_->cview->view_area() : text_area_.area); - } - - bool text_editor::tip_string(::std::string&& str) - { - if(attributes_.tip_string == str) - return false; - - attributes_.tip_string = std::move(str); - return true; - } - - 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; + //For instance of unique_ptr pimpl idiom. 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) - { - //retain the first line and remove the extra lines - if (impl_->textbase.erase(1, impl_->textbase.lines() - 1)) - _m_reset(); + delete impl_; } - if (attributes_.multi_lines == ml) - return false; - - attributes_.multi_lines = ml; - - if (!ml) - line_wrapped(false); - - _m_reset_content_size(); - impl_->cview->enable_scrolls(ml ? content_view::scrolls::both : content_view::scrolls::none); - impl_->cview->move_origin(point{} -impl_->cview->origin()); - - impl_->try_refresh = sync_graph::refresh; - return true; - } - - void text_editor::editable(bool enable, bool enable_caret) - { - attributes_.editable = enable; - attributes_.enable_caret = (enable || enable_caret); - } - - void text_editor::enable_background(bool enb) - { - attributes_.enable_background = enb; - } - - void text_editor::enable_background_counterpart(bool enb) - { - impl_->counterpart.enabled = enb; - if (enb) - impl_->counterpart.buffer.make(text_area_.area.dimension()); - else - impl_->counterpart.buffer.release(); - } - - void text_editor::undo_enabled(bool enb) - { - impl_->undo.enable(enb); - } - - bool text_editor::undo_enabled() const - { - return impl_->undo.enabled(); - } - - void text_editor::undo_max_steps(std::size_t maxs) - { - impl_->undo.max_steps(maxs); - } - - std::size_t text_editor::undo_max_steps() const - { - 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; - } - - unsigned text_editor::line_height() const - { - unsigned ascent, descent, internal_leading; - unsigned px = 0; - if (graph_.text_metrics(ascent, descent, internal_leading)) - px = ascent + descent; - - impl_->cview->step(px, false); - return px; - } - - unsigned text_editor::screen_lines(bool completed_line) const - { - auto const line_px = line_height(); - if (line_px) + size text_editor::caret_size() const { - auto h = impl_->cview->view_area().height; - if (graph_ && h) + return { 1, line_height() }; + } + + const point& text_editor::content_origin() const + { + return impl_->cview->origin(); + } + + void text_editor::set_highlight(const std::string& name, const ::nana::color& fgcolor, const ::nana::color& bgcolor) + { + if (fgcolor.invisible() && bgcolor.invisible()) { - if (completed_line) - return (h / line_px); - - return (h / line_px + (h % line_px ? 1 : 0)); - } - } - return 0; - } - - bool text_editor::focus_changed(const arg_focus& arg) - { - bool renderred = false; - - if (arg.getting && (select_.a == select_.b)) //Do not change the selected text - { - bool select_all = false; - switch (select_.behavior) - { - case text_focus_behavior::select: - select_all = true; - break; - case text_focus_behavior::select_if_click: - select_all = (arg_focus::reason::mouse_press == arg.focus_reason); - break; - case text_focus_behavior::select_if_tabstop: - select_all = (arg_focus::reason::tabstop == arg.focus_reason); - break; - case text_focus_behavior::select_if_tabstop_or_click: - select_all = (arg_focus::reason::tabstop == arg.focus_reason || arg_focus::reason::mouse_press == arg.focus_reason); - default: - break; - } - - if (select_all) - { - select(true); - move_caret_end(false); - renderred = true; - - //If the text widget is focused by clicking mouse button, the selected text will be cancelled - //by the subsequent mouse down event. In this situation, the subsequent mouse down event should - //be ignored. - select_.ignore_press = (arg_focus::reason::mouse_press == arg.focus_reason); - } - } - show_caret(arg.getting); - reset_caret(); - return renderred; - } - - bool text_editor::mouse_enter(bool entering) - { - if ((false == entering) && (false == text_area_.captured)) - API::window_cursor(window_, nana::cursor::arrow); - - return false; - } - - bool text_editor::mouse_move(bool left_button, const point& scrpos) - { - cursor cur = cursor::iterm; - if(((!hit_text_area(scrpos)) && (!text_area_.captured)) || !attributes_.enable_caret || !API::window_enabled(window_)) - cur = cursor::arrow; - - API::window_cursor(window_, cur); - - if(!attributes_.enable_caret) - return false; - - if(left_button) - { - mouse_caret(scrpos, false); - - if (selection::mode::mouse_selected == select_.mode_selection || selection::mode::method_selected == select_.mode_selection) - set_end_caret(false); - else if (selection::mode::move_selected == select_.mode_selection) - select_.mode_selection = selection::mode::move_selected_take_effect; - - impl_->try_refresh = sync_graph::refresh; - return true; - } - return false; - } - - void text_editor::mouse_pressed(const arg_mouse& arg) - { - if(!attributes_.enable_caret) - return; - - if (event_code::mouse_down == arg.evt_code) - { - if (select_.ignore_press || (!hit_text_area(arg.pos))) - { - select_.ignore_press = false; + impl_->keywords.schemes.erase(name); return; } - if (::nana::mouse::left_button == arg.button) - { - API::set_capture(window_, true); - text_area_.captured = true; + auto sp = std::make_shared(); + sp->fgcolor = fgcolor; + sp->bgcolor = bgcolor; + impl_->keywords.schemes[name].swap(sp); + } - if (this->hit_select_area(_m_coordinate_to_caret(arg.pos), true)) + void text_editor::erase_highlight(const std::string& name) + { + impl_->keywords.schemes.erase(name); + } + + void text_editor::set_keyword(const ::std::wstring& kw, const std::string& name, bool case_sensitive, bool whole_word_matched) + { + for (auto & ds : impl_->keywords.base) + { + if (ds.text == kw) { - //The selected of text can be moved only if it is editable - if (attributes_.editable) - select_.mode_selection = selection::mode::move_selected; + ds.scheme = name; + ds.case_sensitive = case_sensitive; + ds.whole_word_matched = whole_word_matched; + return; } - else + } + + impl_->keywords.base.emplace_back(kw, name, case_sensitive, whole_word_matched); + } + + void text_editor::erase_keyword(const ::std::wstring& kw) + { + for (auto i = impl_->keywords.base.begin(); i != impl_->keywords.base.end(); ++i) + { + if (kw == i->text) { - //Set caret pos by screen point and get the caret pos. - mouse_caret(arg.pos, true); - if (arg.shift) + impl_->keywords.base.erase(i); + return; + } + } + } + + colored_area_access_interface& text_editor::colored_area() + { + return impl_->colored_area; + } + + void text_editor::set_accept(std::function pred) + { + impl_->capacities.pred_acceptive = std::move(pred); + } + + void text_editor::set_accept(accepts acceptive) + { + impl_->capacities.acceptive = acceptive; + } + + bool text_editor::respond_char(const arg_keyboard& arg) //key is a character of ASCII code + { + if (!API::window_enabled(window_)) + return false; + + char_type key = arg.key; + switch (key) + { + case keyboard::end_of_text: + copy(); + return false; + case keyboard::select_all: + select(true); + return true; + } + + if (attributes_.editable && (!impl_->capacities.pred_acceptive || impl_->capacities.pred_acceptive(key))) + { + switch (key) + { + case '\b': + backspace(true, true); break; + case '\n': case '\r': + enter(true, true); break; + case keyboard::sync_idel: + paste(); break; + case keyboard::tab: + put(static_cast(keyboard::tab)); break; + case keyboard::cancel: + cut(); + break; + case keyboard::end_of_medium: + undo(true); + break; + case keyboard::substitute: + undo(false); + break; + default: + if (!_m_accepts(key)) + return false; + + if (key > 0x7F || (32 <= key && key <= 126)) + put(key); + } + reset_caret(); + impl_->try_refresh = sync_graph::refresh; + return true; + } + return false; + } + + bool text_editor::respond_key(const arg_keyboard& arg) + { + char_type key = arg.key; + switch (key) + { + case keyboard::os_arrow_left: + case keyboard::os_arrow_right: + case keyboard::os_arrow_up: + case keyboard::os_arrow_down: + case keyboard::os_home: + case keyboard::os_end: + case keyboard::os_pageup: + case keyboard::os_pagedown: + _m_handle_move_key(arg); + break; + case keyboard::del: + // 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; + } + + void text_editor::typeface_changed() + { + _m_reset_content_size(true); + } + + void text_editor::indent(bool enb, std::function generator) + { + impl_->indent.enabled = enb; + impl_->indent.generator = std::move(generator); + } + + void text_editor::set_event(event_interface* ptr) + { + event_handler_ = ptr; + } + + bool text_editor::load(const path_type& fs) + { + if (!impl_->textbase.load(fs)) + return false; + + _m_reset(); + + impl_->try_refresh = sync_graph::refresh; + _m_reset_content_size(true); + 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) + return false; + + text_area_.area = r; + + if (impl_->counterpart.enabled) + impl_->counterpart.buffer.make(r.dimension()); + + 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); + + reset_caret(); + return true; + } + + rectangle text_editor::text_area(bool including_scroll) const + { + return (including_scroll ? impl_->cview->view_area() : text_area_.area); + } + + bool text_editor::tip_string(::std::string&& str) + { + if (attributes_.tip_string == str) + return false; + + attributes_.tip_string = std::move(str); + return true; + } + + 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) + { + //retain the first line and remove the extra lines + if (impl_->textbase.erase(1, impl_->textbase.lines() - 1)) + _m_reset(); + } + + if (attributes_.multi_lines == ml) + return false; + + attributes_.multi_lines = ml; + + if (!ml) + line_wrapped(false); + + _m_reset_content_size(); + impl_->cview->enable_scrolls(ml ? content_view::scrolls::both : content_view::scrolls::none); + impl_->cview->move_origin(point{} -impl_->cview->origin()); + + impl_->try_refresh = sync_graph::refresh; + return true; + } + + void text_editor::editable(bool enable, bool enable_caret) + { + attributes_.editable = enable; + attributes_.enable_caret = (enable || enable_caret); + } + + void text_editor::enable_background(bool enb) + { + attributes_.enable_background = enb; + } + + void text_editor::enable_background_counterpart(bool enb) + { + impl_->counterpart.enabled = enb; + if (enb) + impl_->counterpart.buffer.make(text_area_.area.dimension()); + else + impl_->counterpart.buffer.release(); + } + + void text_editor::undo_clear() + { + auto size = this->undo_max_steps(); + impl_->undo.max_steps(0); + impl_->undo.max_steps(size); + } + + void text_editor::undo_max_steps(std::size_t maxs) + { + impl_->undo.max_steps(maxs); + } + + std::size_t text_editor::undo_max_steps() const + { + return impl_->undo.max_steps(); + } + + auto text_editor::customized_renderers() -> renderers& + { + return impl_->customized_renderers; + } + + unsigned text_editor::line_height() const + { + unsigned ascent, descent, internal_leading; + if (!graph_.text_metrics(ascent, descent, internal_leading)) + return 0; + + impl_->cview->step(ascent + descent, false); + return ascent + descent; + } + + unsigned text_editor::screen_lines(bool completed_line) const + { + auto const line_px = line_height(); + if (line_px) + { + auto h = impl_->cview->view_area().height; + return (h / line_px) + ((completed_line || !(h % line_px)) ? 0 : 1); + } + return 0; + } + + bool text_editor::focus_changed(const arg_focus& arg) + { + bool renderred = false; + + if (arg.getting && (select_.a == select_.b)) //Do not change the selected text + { + bool select_all = false; + switch (select_.behavior) + { + case text_focus_behavior::select: + select_all = true; + break; + case text_focus_behavior::select_if_click: + select_all = (arg_focus::reason::mouse_press == arg.focus_reason); + break; + case text_focus_behavior::select_if_tabstop: + select_all = (arg_focus::reason::tabstop == arg.focus_reason); + break; + case text_focus_behavior::select_if_tabstop_or_click: + select_all = (arg_focus::reason::tabstop == arg.focus_reason || arg_focus::reason::mouse_press == arg.focus_reason); + default: + break; + } + + if (select_all) + { + select(true); + move_caret_end(false); + renderred = true; + + //If the text widget is focused by clicking mouse button, the selected text will be cancelled + //by the subsequent mouse down event. In this situation, the subsequent mouse down event should + //be ignored. + select_.ignore_press = (arg_focus::reason::mouse_press == arg.focus_reason); + } + } + show_caret(arg.getting); + reset_caret(); + return renderred; + } + + bool text_editor::mouse_enter(bool entering) + { + if ((false == entering) && (false == text_area_.captured)) + API::window_cursor(window_, nana::cursor::arrow); + + return false; + } + + bool text_editor::mouse_move(bool left_button, const point& scrpos) + { + cursor cur = cursor::iterm; + if (((!hit_text_area(scrpos)) && (!text_area_.captured)) || !attributes_.enable_caret || !API::window_enabled(window_)) + cur = cursor::arrow; + + API::window_cursor(window_, cur); + + if (!attributes_.enable_caret) + return false; + + if (left_button) + { + mouse_caret(scrpos, false); + + if (selection::mode::mouse_selected == select_.mode_selection || selection::mode::method_selected == select_.mode_selection) + set_end_caret(false); + else if (selection::mode::move_selected == select_.mode_selection) + select_.mode_selection = selection::mode::move_selected_take_effect; + + impl_->try_refresh = sync_graph::refresh; + return true; + } + return false; + } + + void text_editor::mouse_pressed(const arg_mouse& arg) + { + if (!attributes_.enable_caret) + return; + + if (event_code::mouse_down == arg.evt_code) + { + if (select_.ignore_press || (!hit_text_area(arg.pos))) + { + select_.ignore_press = false; + return; + } + + if (::nana::mouse::left_button == arg.button) + { + API::set_capture(window_, true); + text_area_.captured = true; + + if (this->hit_select_area(_m_coordinate_to_caret(arg.pos), true)) { - if (points_.shift_begin_caret != points_.caret) - { - select_.a = points_.shift_begin_caret; - select_.b = points_.caret; - } + //The selected of text can be moved only if it is editable + if (attributes_.editable) + select_.mode_selection = selection::mode::move_selected; } else { - if (!select(false)) + //Set caret pos by screen point and get the caret pos. + mouse_caret(arg.pos, true); + if (arg.shift) { - select_.a = points_.caret; //Set begin caret - set_end_caret(true); + if (points_.shift_begin_caret != points_.caret) + { + select_.a = points_.shift_begin_caret; + select_.b = points_.caret; + } } - points_.shift_begin_caret = points_.caret; + else + { + if (!select(false)) + { + select_.a = points_.caret; //Set begin caret + set_end_caret(true); + } + points_.shift_begin_caret = points_.caret; + } + select_.mode_selection = selection::mode::mouse_selected; } - select_.mode_selection = selection::mode::mouse_selected; } + + impl_->try_refresh = sync_graph::refresh; } - - impl_->try_refresh = sync_graph::refresh; - } - else if (event_code::mouse_up == arg.evt_code) - { - select_.ignore_press = false; - - if (select_.mode_selection == selection::mode::mouse_selected) + else if (event_code::mouse_up == arg.evt_code) { - select_.mode_selection = selection::mode::no_selected; - set_end_caret(true); - } - else if (selection::mode::move_selected == select_.mode_selection || selection::mode::move_selected_take_effect == select_.mode_selection) - { - //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. + select_.ignore_press = false; - if ((selection::mode::move_selected == select_.mode_selection) || !move_select()) + if (select_.mode_selection == selection::mode::mouse_selected) { - //no move occurs - select(false); - move_caret(_m_coordinate_to_caret(arg.pos)); + select_.mode_selection = selection::mode::no_selected; + set_end_caret(true); + } + else if (selection::mode::move_selected == select_.mode_selection || selection::mode::move_selected_take_effect == select_.mode_selection) + { + //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); + move_caret(_m_coordinate_to_caret(arg.pos)); + } + + select_.mode_selection = selection::mode::no_selected; + impl_->try_refresh = sync_graph::refresh; } - select_.mode_selection = selection::mode::no_selected; - impl_->try_refresh = sync_graph::refresh; - } + API::release_capture(window_); - API::release_capture(window_); - - text_area_.captured = false; - if (hit_text_area(arg.pos) == false) - API::window_cursor(window_, nana::cursor::arrow); - } - } - - //Added Windows-style mouse double-click to the textbox(https://github.com/cnjinhao/nana/pull/229) - //Oleg Smolsky - bool text_editor::select_word(const arg_mouse& arg) - { - if(!attributes_.enable_caret) - return false; - - // Set caret pos by screen point and get the caret pos. - mouse_caret(arg.pos, true); - - // Set the initial selection: it is an empty range. - select_.a = select_.b = points_.caret; - const auto& line = impl_->textbase.getline(select_.b.y); - - // Expand the selection forward to the word's end. - while (select_.b.x < line.size() && !std::iswspace(line[select_.b.x])) - ++select_.b.x; - - // Expand the selection backward to the word's start. - while (select_.a.x > 0 && !std::iswspace(line[select_.a.x - 1])) - --select_.a.x; - - select_.mode_selection = selection::mode::method_selected; - impl_->try_refresh = sync_graph::refresh; - return true; - } - - textbase & text_editor::textbase() - { - return impl_->textbase; - } - - const textbase & text_editor::textbase() const - { - 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) - return false; - - text = impl_->textbase.getline(pos); - return true; - } - - void text_editor::text(std::wstring str, bool end_caret) - { - impl_->undo.clear(); - - impl_->textbase.erase_all(); - _m_reset(); - _m_reset_content_size(true); - - if (!end_caret) - { - auto undo_ptr = std::unique_ptr{ new undo_input_text(*this, str) }; - undo_ptr->set_caret_pos(); - - _m_put(std::move(str), false); - - impl_->undo.push(std::move(undo_ptr)); - - if (graph_) - { - this->_m_adjust_view(); - - reset_caret(); - impl_->try_refresh = sync_graph::refresh; - - //_m_put calcs the lines - _m_reset_content_size(true); - impl_->cview->sync(false); + text_area_.captured = false; + if (hit_text_area(arg.pos) == false) + API::window_cursor(window_, nana::cursor::arrow); } } - else - put(std::move(str), false); - textbase().text_changed(); - } - - std::wstring text_editor::text() const - { - std::wstring str; - std::size_t lines = impl_->textbase.lines(); - if(lines > 0) + //Added Windows-style mouse double-click to the textbox(https://github.com/cnjinhao/nana/pull/229) + //Oleg Smolsky + bool text_editor::select_word(const arg_mouse& arg) { - str = impl_->textbase.getline(0); - for(std::size_t i = 1; i < lines; ++i) - { - str += L"\n\r"; - str += impl_->textbase.getline(i); + if (!attributes_.enable_caret) + return false; + + // Set caret pos by screen point and get the caret pos. + mouse_caret(arg.pos, true); + + // Set the initial selection: it is an empty range. + select_.a = select_.b = points_.caret; + const auto& line = impl_->textbase.getline(select_.b.y); + + + + if (select_.a.x < line.size() && !std::isalnum(line[select_.a.x]) && line[select_.a.x] != '_') { + ++select_.b.x; } - } - return str; - } + else { + // Expand the selection forward to the word's end. + while (select_.b.x < line.size() && !std::iswspace(line[select_.b.x]) && (std::isalnum(line[select_.b.x]) || line[select_.b.x] == '_')) + ++select_.b.x; - bool text_editor::move_caret(upoint crtpos, bool stay_in_view) - { - const unsigned line_pixels = line_height(); - - if (crtpos != points_.caret) - { - //Check and make the crtpos available - if (crtpos.y < impl_->textbase.lines()) - { - crtpos.x = (std::min)(static_cast(impl_->textbase.getline(crtpos.y).size()), crtpos.x); + // Expand the selection backward to the word's start. + while (select_.a.x > 0 && !std::iswspace(line[select_.a.x - 1]) && (std::isalnum(line[select_.a.x - 1]) || line[select_.a.x - 1] == '_')) + --select_.a.x; } - else - { - crtpos.y = static_cast(impl_->textbase.lines()); - if (crtpos.y > 0) - --crtpos.y; - - crtpos.x = static_cast(impl_->textbase.getline(crtpos.y).size()); - } - - points_.caret = crtpos; - } - - //The coordinate of caret - auto coord = _m_caret_to_coordinate(crtpos); - - const int line_bottom = coord.y + static_cast(line_pixels); - - if (!API::is_focus_ready(window_)) - return false; - - auto caret = API::open_caret(window_, true); - - bool visible = false; - auto text_area = impl_->cview->view_area(); - - if (text_area.is_hit(coord) && (line_bottom > text_area.y)) - { - visible = true; - if (line_bottom > text_area.bottom()) - caret->dimension(nana::size(1, line_pixels - (line_bottom - text_area.bottom()))); - else if (caret->dimension().height != line_pixels) - reset_caret_pixels(); - } - - if(!attributes_.enable_caret) - visible = false; - - caret->visible(visible); - if(visible) - caret->position(coord); - - //Adjust the caret into screen when the caret position is modified by this function - if (stay_in_view && (!hit_text_area(coord))) - { - if (_m_adjust_view()) - impl_->cview->sync(false); - - impl_->try_refresh = sync_graph::refresh; - caret->visible(true); - return true; - } - return false; - } - - 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->reset_caret(); - } - - void text_editor::reset_caret_pixels() const - { - API::open_caret(window_, true).get()->dimension({ 1, line_height() }); - } - - void text_editor::reset_caret(bool stay_in_view) - { - 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).get()->visible(isshow); - } - - bool text_editor::selected() const - { - return (select_.a != select_.b); - } - - bool text_editor::get_selected_points(nana::upoint &a, nana::upoint &b) const - { - if (select_.a == select_.b) - return false; - - if (select_.a < select_.b) - { - a = select_.a; - b = select_.b; - } - else - { - a = select_.b; - b = select_.a; - } - - 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)) + textbase & text_editor::textbase() noexcept { + return impl_->textbase; + } + + const textbase & text_editor::textbase() const noexcept + { + 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) + return false; + + text = impl_->textbase.getline(pos); + return true; + } + + void text_editor::text(std::wstring str, bool end_caret) + { + impl_->undo.clear(); + + impl_->textbase.erase_all(); + _m_reset(); + _m_reset_content_size(true); + + if (!end_caret) + { + auto undo_ptr = std::unique_ptr{ new undo_input_text(*this, str) }; + undo_ptr->set_caret_pos(); + + _m_put(std::move(str), false); + + impl_->undo.push(std::move(undo_ptr)); + + if (graph_) + { + this->_m_adjust_view(); + + reset_caret(); + impl_->try_refresh = sync_graph::refresh; + + //_m_put calcs the lines + _m_reset_content_size(true); + impl_->cview->sync(false); + } + } + else + put(std::move(str), false); + + textbase().text_changed(); + } + + std::wstring text_editor::text() const + { + std::wstring str; + std::size_t lines = impl_->textbase.lines(); + if (lines > 0) + { + str = impl_->textbase.getline(0); + for (std::size_t i = 1; i < lines; ++i) + { + str += L"\r\n"; + str += impl_->textbase.getline(i); + } + } + return str; + } + + bool text_editor::move_caret(upoint crtpos, bool stay_in_view) + { + const unsigned line_pixels = line_height(); + + if (crtpos != points_.caret) + { + //Check and make the crtpos available + if (crtpos.y < impl_->textbase.lines()) + { + crtpos.x = (std::min)(static_cast(impl_->textbase.getline(crtpos.y).size()), crtpos.x); + } + else + { + crtpos.y = static_cast(impl_->textbase.lines()); + if (crtpos.y > 0) + --crtpos.y; + + crtpos.x = static_cast(impl_->textbase.getline(crtpos.y).size()); + } + + points_.caret = crtpos; + } + + //The coordinate of caret + auto coord = _m_caret_to_coordinate(crtpos); + + const int line_bottom = coord.y + static_cast(line_pixels); + + if (!API::is_focus_ready(window_)) + return false; + + auto caret = API::open_caret(window_, true); + + bool visible = false; + auto text_area = impl_->cview->view_area(); + + if (text_area.is_hit(coord) && (line_bottom > text_area.y)) + { + visible = true; + if (line_bottom > text_area.bottom()) + caret->dimension(nana::size(1, line_pixels - (line_bottom - text_area.bottom()))); + else if (caret->dimension().height != line_pixels) + reset_caret_pixels(); + } + + if (!attributes_.enable_caret) + visible = false; + + caret->visible(visible); + if (visible) + caret->position(coord); + + //Adjust the caret into screen when the caret position is modified by this function + if (stay_in_view && (!hit_text_area(coord))) + { + if (_m_adjust_view()) + impl_->cview->sync(false); + + impl_->try_refresh = sync_graph::refresh; + caret->visible(true); + return true; + } + return false; + } + + 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->reset_caret(); + } + + void text_editor::reset_caret_pixels() const + { + API::open_caret(window_, true).get()->dimension({ 1, line_height() }); + } + + void text_editor::reset_caret(bool stay_in_view) + { + 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).get()->visible(isshow); + } + + bool text_editor::selected() const + { + return (select_.a != select_.b); + } + + bool text_editor::get_selected_points(nana::upoint &a, nana::upoint &b) const + { + if (select_.a == select_.b) + return false; + + if (select_.a < select_.b) + { + a = select_.a; + b = select_.b; + } + else + { + a = select_.b; + b = select_.a; + } + + 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; + } + + bool text_editor::select_points(nana::upoint arg_a, nana::upoint arg_b) + { + select_.a = arg_a; + select_.b = arg_b; + select_.mode_selection = selection::mode::method_selected; 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; - - if (new_sel_end || (stay_in_view && this->_m_adjust_view())) - impl_->try_refresh = sync_graph::refresh; - } - - bool text_editor::hit_text_area(const point& pos) const - { - 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_selected_points(a, b)) + void text_editor::set_end_caret(bool stay_in_view) { - if (ignore_when_select_all) + bool new_sel_end = (select_.b != points_.caret); + select_.b = points_.caret; + + if (new_sel_end || (stay_in_view && this->_m_adjust_view())) + impl_->try_refresh = sync_graph::refresh; + } + + bool text_editor::hit_text_area(const point& pos) const + { + 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_selected_points(a, b)) { - if (a.x == 0 && a.y == 0 && (b.y + 1) == static_cast(textbase().lines())) + if (ignore_when_select_all) { - //is select all - if (b.x == static_cast(textbase().getline(b.y).size())) - return false; + if (a.x == 0 && a.y == 0 && (b.y + 1) == static_cast(textbase().lines())) + { + //is select all + if (b.x == static_cast(textbase().getline(b.y).size())) + return false; + } + } + + if ((pos.y > a.y || (pos.y == a.y && pos.x >= a.x)) && ((pos.y < b.y) || (pos.y == b.y && pos.x < b.x))) + return true; + } + return false; + } + + bool text_editor::move_select() + { + if (hit_select_area(points_.caret, true) || (select_.b == points_.caret)) + { + points_.caret = select_.b; + + if (this->_m_adjust_view()) + impl_->try_refresh = sync_graph::refresh; + + reset_caret(); + return true; + } + + if (_m_move_select(true)) + { + textbase().text_changed(); + this->_m_adjust_view(); + impl_->try_refresh = sync_graph::refresh; + return true; + } + return false; + } + + bool text_editor::mask(wchar_t ch) + { + if (mask_char_ == ch) + return false; + + mask_char_ = ch; + return true; + } + + unsigned text_editor::width_pixels() const + { + unsigned exclude_px = API::open_caret(window_, true).get()->dimension().width; + + if (attributes_.line_wrapped) + exclude_px += impl_->cview->extra_space(false); + + return (text_area_.area.width > exclude_px ? text_area_.area.width - exclude_px : 0); + } + + window text_editor::window_handle() const + { + return window_; + } + + const std::vector& text_editor::text_position() const + { + return impl_->text_position; + } + + void text_editor::focus_behavior(text_focus_behavior behavior) + { + select_.behavior = behavior; + } + + void text_editor::select_behavior(bool move_to_end) + { + select_.move_to_end = move_to_end; + } + + std::size_t text_editor::line_count(bool text_lines) const + { + if (text_lines) + return textbase().lines(); + + return impl_->capacities.behavior->take_lines(); + } + + std::shared_ptr text_editor::scroll_operation() const + { + return impl_->cview->scroll_operation(); + } + + void text_editor::draw_corner() + { + impl_->cview->draw_corner(graph_); + } + + void text_editor::render(bool has_focus) + { + const auto bgcolor = _m_bgcolor(); + + auto fgcolor = scheme_->foreground.get_color(); + if (!API::window_enabled(window_)) + fgcolor = fgcolor.blend(bgcolor, 0.5); //Thank to besh81 for getting the fgcolor to be changed + + if (API::widget_borderless(window_)) + graph_.rectangle(false, bgcolor); + + //Draw background + 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); + + if (impl_->counterpart.buffer && !text_area_.area.empty()) + impl_->counterpart.buffer.bitblt(rectangle{ text_area_.area.dimension() }, graph_, text_area_.area.position()); + + //Render the content when the text isn't empty or the window has got focus, + //otherwise draw the tip string. + if ((false == textbase().empty()) || has_focus) + { + auto text_pos = _m_render_text(fgcolor); + + if (text_pos.empty()) + text_pos.emplace_back(upoint{}); + + if ((impl_->text_position_origin != impl_->cview->origin().y) || (text_pos != impl_->text_position)) + { + impl_->text_position_origin = impl_->cview->origin().y; + impl_->text_position.swap(text_pos); + if (event_handler_) + event_handler_->text_exposed(impl_->text_position); + } + } + else //Draw tip string + { + 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{}); + + _m_draw_border(); + impl_->try_refresh = sync_graph::none; + } + //public: + void text_editor::put(std::wstring text, bool perform_event) + { + if (text.empty()) + return; + + auto undo_ptr = std::unique_ptr{ new undo_input_text(*this, text) }; + + undo_ptr->set_selected_text(); + + //Do not forget to assign the _m_erase_select() to caret + //because _m_put() will insert the text at the position where the caret is. + points_.caret = _m_erase_select(false); + + undo_ptr->set_caret_pos(); + points_.caret = _m_put(std::move(text), false); + + impl_->undo.push(std::move(undo_ptr)); + + _m_reset_content_size(true); + if (perform_event) + textbase().text_changed(); + + if (graph_) + { + if (this->_m_adjust_view()) + impl_->cview->sync(false); + + reset_caret(); + impl_->try_refresh = sync_graph::refresh; + } + } + + void text_editor::put(wchar_t ch) + { + std::wstring ch_str(1, ch); + + auto undo_ptr = std::unique_ptr{ new undo_input_text(*this, ch_str) }; + bool refresh = (select_.a != select_.b); + + undo_ptr->set_selected_text(); + if (refresh) + points_.caret = _m_erase_select(false); + + undo_ptr->set_caret_pos(); + + impl_->undo.push(std::move(undo_ptr)); + + auto secondary_before = impl_->capacities.behavior->take_lines(points_.caret.y); + textbase().insert(points_.caret, std::move(ch_str)); + _m_pre_calc_lines(points_.caret.y, 1); + + textbase().text_changed(); + + points_.caret.x++; + + _m_reset_content_size(); + + if (!refresh) + { + if (!_m_update_caret_line(secondary_before)) + draw_corner(); + } + else + impl_->try_refresh = sync_graph::refresh; + + } + + void text_editor::copy() const + { + //Disallows copying text if the text_editor is masked. + if (mask_char_) + return; + + auto text = _m_make_select_string(); + if (!text.empty()) + nana::system::dataexch().set(text, API::root(this->window_)); + } + + void text_editor::cut() + { + copy(); + del(); + } + + void text_editor::paste() + { + auto text = system::dataexch{}.wget(); + + if ((accepts::no_restrict == impl_->capacities.acceptive) || !impl_->capacities.pred_acceptive) + { + put(move(text), true); + return; + } + + //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), true); + } + break; + } + } + + void text_editor::enter(bool record_undo, bool perform_event) + { + if (false == attributes_.multi_lines) + return; + + auto undo_ptr = std::unique_ptr(new undo_input_text(*this, std::wstring(1, '\n'))); + + undo_ptr->set_selected_text(); + points_.caret = _m_erase_select(false); + + undo_ptr->set_caret_pos(); + + auto & textbase = this->textbase(); + + const string_type& lnstr = textbase.getline(points_.caret.y); + ++points_.caret.y; + + if (lnstr.size() > points_.caret.x) + { + //Breaks the line and moves the rest part to a new line + auto rest_part_len = lnstr.size() - points_.caret.x; //Firstly get the length of rest part, because lnstr may be invalid after insertln + textbase.insertln(points_.caret.y, lnstr.substr(points_.caret.x)); + textbase.erase(points_.caret.y - 1, points_.caret.x, rest_part_len); + } + else + { + if (textbase.lines() == 0) + textbase.insertln(0, std::wstring{}); + textbase.insertln(points_.caret.y, std::wstring{}); + } + + if (record_undo) + impl_->undo.push(std::move(undo_ptr)); + + impl_->capacities.behavior->add_lines(points_.caret.y - 1, 1); + _m_pre_calc_lines(points_.caret.y - 1, 2); + + points_.caret.x = 0; + + auto origin = impl_->cview->origin(); + origin.x = 0; + + if (impl_->indent.enabled) + { + if (impl_->indent.generator) + { + put(nana::to_wstring(impl_->indent.generator()), false); + } + else + { + auto & text = textbase.getline(points_.caret.y - 1); + auto indent_pos = text.find_first_not_of(L"\t "); + if (indent_pos != std::wstring::npos) + put(text.substr(0, indent_pos), false); + else + put(text, false); + } + } + else + _m_reset_content_size(); + + if (perform_event) + textbase.text_changed(); + + auto origin_moved = impl_->cview->move_origin(origin - impl_->cview->origin()); + + if (this->_m_adjust_view() || origin_moved) + impl_->cview->sync(true); + } + + void text_editor::del() + { + if (select_.a == select_.b) + { + if (textbase().getline(points_.caret.y).size() > points_.caret.x) + { + ++points_.caret.x; + } + else if (points_.caret.y + 1 < textbase().lines()) + { //Move to next line + points_.caret.x = 0; + ++points_.caret.y; + } + else + return; //No characters behind the caret + } + + backspace(true, true); + } + + void text_editor::backspace(bool record_undo, bool perform_event) + { + auto undo_ptr = std::unique_ptr(new undo_backspace(*this)); + bool has_to_redraw = true; + if (select_.a == select_.b) + { + auto & textbase = this->textbase(); + if (points_.caret.x) + { + unsigned erase_number = 1; + --points_.caret.x; + + auto& lnstr = textbase.getline(points_.caret.y); + + undo_ptr->set_caret_pos(); + undo_ptr->set_removed(lnstr.substr(points_.caret.x, erase_number)); + auto secondary = impl_->capacities.behavior->take_lines(points_.caret.y); + textbase.erase(points_.caret.y, points_.caret.x, erase_number); + _m_pre_calc_lines(points_.caret.y, 1); + + if (!this->_m_adjust_view()) + { + _m_update_line(points_.caret.y, secondary); + has_to_redraw = false; + } + } + else if (points_.caret.y) + { + points_.caret.x = static_cast(textbase.getline(--points_.caret.y).size()); + textbase.merge(points_.caret.y); + impl_->capacities.behavior->merge_lines(points_.caret.y, points_.caret.y + 1); + undo_ptr->set_caret_pos(); + undo_ptr->set_removed(std::wstring(1, '\n')); + } + else + undo_ptr.reset(); + } + else + { + undo_ptr->set_selected_text(); + points_.caret = _m_erase_select(false); + undo_ptr->set_caret_pos(); + } + + if (record_undo) + impl_->undo.push(std::move(undo_ptr)); + + _m_reset_content_size(false); + + if (perform_event) + textbase().text_changed(); + + textbase().text_changed(); + + if (has_to_redraw) + { + this->_m_adjust_view(); + impl_->try_refresh = sync_graph::refresh; + } + } + + void text_editor::undo(bool reverse) + { + if (reverse) + impl_->undo.redo(); + else + impl_->undo.undo(); + + _m_reset_content_size(true); + + this->_m_adjust_view(); + impl_->try_refresh = sync_graph::refresh; + } + + void text_editor::move_ns(bool to_north) + { + const bool redraw_required = _m_cancel_select(0); + if (_m_move_caret_ns(to_north) || redraw_required) + impl_->try_refresh = sync_graph::refresh; + } + + void text_editor::move_left() + { + bool pending = true; + if (_m_cancel_select(1) == false) + { + if (points_.caret.x) + { + --points_.caret.x; + pending = false; + if (this->_m_adjust_view()) + 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()); + else + pending = false; + } + + if (pending && this->_m_adjust_view()) + impl_->try_refresh = sync_graph::refresh; + } + + void text_editor::move_right() + { + bool do_render = false; + if (_m_cancel_select(2) == false) + { + auto lnstr = textbase().getline(points_.caret.y); + if (lnstr.size() > points_.caret.x) + { + ++points_.caret.x; + do_render = this->_m_adjust_view(); + } + else if (points_.caret.y + 1 < textbase().lines()) + { //Move to next line + points_.caret.x = 0; + ++points_.caret.y; + do_render = this->_m_adjust_view(); + } + } + else + do_render = this->_m_adjust_view(); + + if (do_render) + impl_->try_refresh = sync_graph::refresh; + } + + void text_editor::_m_handle_move_key(const arg_keyboard& arg) + { + if (arg.shift && (select_.a == select_.b)) + select_.a = select_.b = points_.caret; + + auto origin = impl_->cview->origin(); + auto pos = points_.caret; + auto coord = _m_caret_to_coordinate(points_.caret, false); + auto coord_org = coord; + + wchar_t key = arg.key; + + auto const line_px = this->line_height(); + + //The number of text lines + auto const line_count = textbase().lines(); + + //The number of charecters in the line of caret + auto const text_length = textbase().getline(points_.caret.y).size(); + + switch (key) { + case keyboard::os_arrow_left: + if (select_.move_to_end && (select_.a != select_.b) && (!arg.shift)) + { + pos = select_.a; + } + else if (pos.x != 0) + { + --pos.x; + } + else if (pos.y != 0) { + --pos.y; + pos.x = static_cast(textbase().getline(pos.y).size()); + } + break; + case keyboard::os_arrow_right: + if (select_.move_to_end && (select_.a != select_.b) && (!arg.shift)) + { + pos = select_.b; + } + else if (pos.x < text_length) + { + ++pos.x; + } + else if (pos.y != line_count - 1) + { + ++pos.y; + pos.x = 0; + } + break; + case keyboard::os_arrow_up: + coord.y -= static_cast(line_px); + break; + case keyboard::os_arrow_down: + coord.y += static_cast(line_px); + break; + case keyboard::os_home: + //move the caret to the begining of the line + pos.x = 0; + + //move the caret to the begining of the text if Ctrl is pressed + if (arg.ctrl) + pos.y = 0; + break; + case keyboard::os_end: + + //move the caret to the end of the text if Ctrl is pressed + if (arg.ctrl) { + coord.y = static_cast((line_count - 1) * line_px); + //The number of charecters of the bottom line + auto const text_length = textbase().getline(std::max(0, line_count - 1)).size(); + //move the caret to the end of the line + pos.x = static_cast(text_length); + } + else { + //move the caret to the end of the line + pos.x = static_cast(text_length); + } + break; + case keyboard::os_pageup: + if (origin.y > 0) + { + auto off = coord - origin; + origin.y -= (std::min)(origin.y, static_cast(impl_->cview->view_area().height)); + coord = off + origin; + } + break; + case keyboard::os_pagedown: + if (impl_->cview->content_size().height > impl_->cview->view_area().height) + { + auto off = coord - origin; + origin.y = (std::min)(origin.y + static_cast(impl_->cview->view_area().height), static_cast(impl_->cview->content_size().height - impl_->cview->view_area().height)); + coord = off + origin; + } + break; + } + + if (coord != coord_org) + { + auto pos_x = pos.x; + impl_->cview->move_origin(origin - impl_->cview->origin()); + pos = _m_coordinate_to_caret(coord, false); + pos.x = pos_x; + } + + if (pos != points_.caret) { + if (arg.shift) { + switch (key) { + case keyboard::os_arrow_left: + case keyboard::os_arrow_up: + case keyboard::os_home: + case keyboard::os_pageup: + select_.b = pos; + break; + case keyboard::os_arrow_right: + case keyboard::os_arrow_down: + case keyboard::os_end: + case keyboard::os_pagedown: + select_.b = pos; + break; + } + } + else { + select_.b = pos; + select_.a = pos; + } + points_.caret = pos; + impl_->try_refresh = sync_graph::refresh; + this->_m_adjust_view(); + impl_->cview->sync(true); + this->reset_caret(); + } + } + + 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_)) + { + if (impl_->customized_renderers.border) + { + impl_->customized_renderers.border(graph_, _m_bgcolor()); + } + else + { + ::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()); } } - if((pos.y > a.y || (pos.y == a.y && pos.x >= a.x)) && ((pos.y < b.y) || (pos.y == b.y && pos.x < b.x))) - return true; + draw_corner(); } - return false; - } - bool text_editor::move_select() - { - if(hit_select_area(points_.caret, true) || (select_.b == points_.caret)) + const upoint& text_editor::mouse_caret(const point& scrpos, bool stay_in_view) //From screen position { - points_.caret = select_.b; + points_.caret = _m_coordinate_to_caret(scrpos); - if (this->_m_adjust_view()) + if (stay_in_view && this->_m_adjust_view()) impl_->try_refresh = sync_graph::refresh; reset_caret(); - return true; + return points_.caret; } - if (_m_move_select(true)) + const upoint& text_editor::caret() const noexcept { - textbase().text_changed(); - this->_m_adjust_view(); - impl_->try_refresh = sync_graph::refresh; - return true; + return points_.caret; } - return false; - } - bool text_editor::mask(wchar_t ch) - { - if (mask_char_ == ch) + point text_editor::caret_screen_pos() const + { + return _m_caret_to_coordinate(points_.caret); + } + + bool text_editor::scroll(bool upwards, bool vert) + { + impl_->cview->scroll(!upwards, !vert); return false; - - mask_char_ = ch; - return true; - } - - unsigned text_editor::width_pixels() const - { - unsigned exclude_px = API::open_caret(window_, true).get()->dimension().width; - - if (attributes_.line_wrapped) - exclude_px += impl_->cview->extra_space(false); - - return (text_area_.area.width > exclude_px ? text_area_.area.width - exclude_px : 0); - } - - window text_editor::window_handle() const - { - return window_; - } - - const std::vector& text_editor::text_position() const - { - return impl_->text_position; - } - - void text_editor::focus_behavior(text_focus_behavior behavior) - { - select_.behavior = behavior; - } - - void text_editor::select_behavior(bool move_to_end) - { - select_.move_to_end = move_to_end; - } - - std::size_t text_editor::line_count(bool text_lines) const - { - if (text_lines) - return textbase().lines(); - - return impl_->capacities.behavior->take_lines(); - } - - std::shared_ptr text_editor::scroll_operation() const - { - return impl_->cview->scroll_operation(); - } - - void text_editor::draw_corner() - { - impl_->cview->draw_corner(graph_); - } - - void text_editor::render(bool has_focus) - { - const auto bgcolor = _m_bgcolor(); - - auto fgcolor = scheme_->foreground.get_color(); - if (!API::window_enabled(window_)) - fgcolor.blend(bgcolor, 0.5); - - if (API::widget_borderless(window_)) - graph_.rectangle(false, bgcolor); - - //Draw background - 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); - - if(impl_->counterpart.buffer && !text_area_.area.empty()) - impl_->counterpart.buffer.bitblt(rectangle{ text_area_.area.dimension() }, graph_, text_area_.area.position()); - - //Render the content when the text isn't empty or the window has got focus, - //otherwise draw the tip string. - if ((false == textbase().empty()) || has_focus) + color text_editor::_m_draw_colored_area(paint::graphics& graph, const std::pair& row, bool whole_line) { - auto text_pos = _m_render_text(fgcolor); - - if (text_pos.empty()) - text_pos.emplace_back(upoint{}); - - if ((impl_->text_position_origin != impl_->cview->origin().y) || (text_pos != impl_->text_position)) + auto area = impl_->colored_area.find(row.first); + if (area) { - impl_->text_position_origin = impl_->cview->origin().y; - impl_->text_position.swap(text_pos); - if (event_handler_) - event_handler_->text_exposed(impl_->text_position); - } - } - else //Draw tip string - { - 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{}); - - _m_draw_border(); - impl_->try_refresh = sync_graph::none; - } - //public: - void text_editor::put(std::wstring text, bool perform_event) - { - if (text.empty()) - return; - - auto undo_ptr = std::unique_ptr{ new undo_input_text(*this, text) }; - - undo_ptr->set_selected_text(); - - //Do not forget to assign the _m_erase_select() to caret - //because _m_put() will insert the text at the position where the caret is. - points_.caret = _m_erase_select(false); - - undo_ptr->set_caret_pos(); - points_.caret = _m_put(std::move(text), false); - - impl_->undo.push(std::move(undo_ptr)); - - _m_reset_content_size(true); - if (perform_event) - textbase().text_changed(); - - if(graph_) - { - if(this->_m_adjust_view()) - impl_->cview->sync(false); - - reset_caret(); - impl_->try_refresh = sync_graph::refresh; - } - } - - void text_editor::put(wchar_t ch) - { - std::wstring ch_str(1, ch); - - auto undo_ptr = std::unique_ptr{new undo_input_text(*this, ch_str)}; - bool refresh = (select_.a != select_.b); - - undo_ptr->set_selected_text(); - if(refresh) - points_.caret = _m_erase_select(false); - - undo_ptr->set_caret_pos(); - - impl_->undo.push(std::move(undo_ptr)); - - auto secondary_before = impl_->capacities.behavior->take_lines(points_.caret.y); - textbase().insert(points_.caret, std::move(ch_str)); - _m_pre_calc_lines(points_.caret.y, 1); - - textbase().text_changed(); - - points_.caret.x ++; - - _m_reset_content_size(); - - if (!refresh) - { - if (!_m_update_caret_line(secondary_before)) - draw_corner(); - } - else - impl_->try_refresh = sync_graph::refresh; - - } - - void text_editor::copy() const - { - //Disallows copying text if the text_editor is masked. - if (mask_char_) - return; - - auto text = _m_make_select_string(); - if (!text.empty()) - nana::system::dataexch().set(text, API::root(this->window_)); - } - - void text_editor::cut() - { - copy(); - del(); - } - - void text_editor::paste() - { - auto text = system::dataexch{}.wget(); - - if ((accepts::no_restrict == impl_->capacities.acceptive) || !impl_->capacities.pred_acceptive) - { - put(move(text), true); - return; - } - - //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), true); - } - break; - } - } - - void text_editor::enter(bool record_undo, bool perform_event) - { - if(false == attributes_.multi_lines) - return; - - auto undo_ptr = std::unique_ptr(new undo_input_text(*this, std::wstring(1, '\n'))); - - undo_ptr->set_selected_text(); - points_.caret = _m_erase_select(false); - - undo_ptr->set_caret_pos(); - - auto & textbase = this->textbase(); - - const string_type& lnstr = textbase.getline(points_.caret.y); - ++points_.caret.y; - - if(lnstr.size() > points_.caret.x) - { - //Breaks the line and moves the rest part to a new line - auto rest_part_len = lnstr.size() - points_.caret.x; //Firstly get the length of rest part, because lnstr may be invalid after insertln - textbase.insertln(points_.caret.y, lnstr.substr(points_.caret.x)); - textbase.erase(points_.caret.y - 1, points_.caret.x, rest_part_len); - } - else - { - if (textbase.lines() == 0) - textbase.insertln(0, std::wstring{}); - textbase.insertln(points_.caret.y, std::wstring{}); - } - - if (record_undo) - impl_->undo.push(std::move(undo_ptr)); - - impl_->capacities.behavior->add_lines(points_.caret.y - 1, 1); - _m_pre_calc_lines(points_.caret.y - 1, 2); - - points_.caret.x = 0; - - auto origin = impl_->cview->origin(); - origin.x = 0; - - if (impl_->indent.enabled) - { - if (impl_->indent.generator) - { - put(nana::to_wstring(impl_->indent.generator()), false); - } - else - { - auto & text = textbase.getline(points_.caret.y - 1); - auto indent_pos = text.find_first_not_of(L"\t "); - if (indent_pos != std::wstring::npos) - put(text.substr(0, indent_pos), false); - else - put(text, false); - } - } - else - _m_reset_content_size(); - - if (perform_event) - textbase.text_changed(); - - auto origin_moved = impl_->cview->move_origin(origin - impl_->cview->origin()); - - if (this->_m_adjust_view() || origin_moved) - impl_->cview->sync(true); - } - - void text_editor::del() - { - if(select_.a == select_.b) - { - if(textbase().getline(points_.caret.y).size() > points_.caret.x) - { - ++points_.caret.x; - } - else if (points_.caret.y + 1 < textbase().lines()) - { //Move to next line - points_.caret.x = 0; - ++points_.caret.y; - } - else - return; //No characters behind the caret - } - - backspace(true, true); - } - - void text_editor::backspace(bool record_undo, bool perform_event) - { - auto undo_ptr = std::unique_ptr(new undo_backspace(*this)); - bool has_to_redraw = true; - if(select_.a == select_.b) - { - auto & textbase = this->textbase(); - if(points_.caret.x) - { - unsigned erase_number = 1; - --points_.caret.x; - - auto& lnstr = textbase.getline(points_.caret.y); - - undo_ptr->set_caret_pos(); - undo_ptr->set_removed(lnstr.substr(points_.caret.x, erase_number)); - auto secondary = impl_->capacities.behavior->take_lines(points_.caret.y); - textbase.erase(points_.caret.y, points_.caret.x, erase_number); - _m_pre_calc_lines(points_.caret.y, 1); - - if (!this->_m_adjust_view()) + if (!area->bgcolor.invisible()) { - _m_update_line(points_.caret.y, secondary); - has_to_redraw = false; + auto const height = line_height(); + + auto top = _m_caret_to_coordinate(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 += static_cast(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; } - else if (points_.caret.y) - { - points_.caret.x = static_cast(textbase.getline(--points_.caret.y).size()); - textbase.merge(points_.caret.y); - impl_->capacities.behavior->merge_lines(points_.caret.y, points_.caret.y + 1); - undo_ptr->set_caret_pos(); - undo_ptr->set_removed(std::wstring(1, '\n')); - } - else - undo_ptr.reset(); + return{}; } - else + + std::vector text_editor::_m_render_text(const color& text_color) { - undo_ptr->set_selected_text(); - points_.caret = _m_erase_select(false); - undo_ptr->set_caret_pos(); - } + std::vector line_indexes; + auto const behavior = this->impl_->capacities.behavior; + auto const line_count = textbase().lines(); - if (record_undo) - impl_->undo.push(std::move(undo_ptr)); + auto row = behavior->text_position_from_screen(impl_->cview->view_area().y); - _m_reset_content_size(false); + if (row.first >= line_count || graph_.empty()) + return line_indexes; - if (perform_event) - textbase().text_changed(); - - textbase().text_changed(); - - if(has_to_redraw) - { - this->_m_adjust_view(); - impl_->try_refresh = sync_graph::refresh; - } - } - - void text_editor::undo(bool reverse) - { - if (reverse) - impl_->undo.redo(); - else - impl_->undo.undo(); - - _m_reset_content_size(true); - - this->_m_adjust_view(); - impl_->try_refresh = sync_graph::refresh; - } - - void text_editor::set_undo_queue_length(std::size_t len) - { - impl_->undo.max_steps(len); - } - - void text_editor::move_ns(bool to_north) - { - const bool redraw_required = _m_cancel_select(0); - if (_m_move_caret_ns(to_north) || redraw_required) - impl_->try_refresh = sync_graph::refresh; - } - - void text_editor::move_left() - { - bool pending = true; - if(_m_cancel_select(1) == false) - { - if(points_.caret.x) + auto sections = behavior->line(row.first); + if (row.second < sections.size()) { - --points_.caret.x; - pending = false; - if (this->_m_adjust_view()) - 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()); - else - pending = false; - } + nana::upoint str_pos(0, static_cast(row.first)); + str_pos.x = static_cast(sections[row.second].begin - textbase().getline(row.first).c_str()); - if (pending && this->_m_adjust_view()) - impl_->try_refresh = sync_graph::refresh; - } + int top = _m_text_top_base() - (impl_->cview->origin().y % line_height()); + const unsigned pixels = line_height(); - void text_editor::move_right() - { - bool do_render = false; - if (_m_cancel_select(2) == false) - { - auto lnstr = textbase().getline(points_.caret.y); - if (lnstr.size() > points_.caret.x) - { - ++points_.caret.x; - do_render = this->_m_adjust_view(); - } - else if (points_.caret.y + 1 < textbase().lines()) - { //Move to next line - points_.caret.x = 0; - ++points_.caret.y; - do_render = this->_m_adjust_view(); - } - } - else - do_render = this->_m_adjust_view(); - - if (do_render) - impl_->try_refresh = sync_graph::refresh; - } - - void text_editor::_m_handle_move_key(const arg_keyboard& arg) - { - if (arg.shift && (select_.a == select_.b)) - select_.a = select_.b = points_.caret; - - auto origin = impl_->cview->origin(); - auto pos = points_.caret; - auto coord = _m_caret_to_coordinate(points_.caret, false); - - wchar_t key = arg.key; - - auto const line_px = this->line_height(); - - //The number of text lines - auto const line_count = textbase().lines(); - - //The number of charecters in the line of caret - auto const text_length = textbase().getline(points_.caret.y).size(); - - - switch (key) { - case keyboard::os_arrow_left: - if (select_.move_to_end && (select_.a != select_.b) && (!arg.shift)) - { - pos = select_.a; - } - else if (pos.x != 0) - { - --pos.x; - } - else if (pos.y != 0) { - --pos.y; - pos.x = static_cast(textbase().getline(pos.y).size()); - } - break; - case keyboard::os_arrow_right: - if (select_.move_to_end && (select_.a != select_.b) && (!arg.shift)) - { - pos = select_.b; - } - else if (pos.x < text_length) - { - ++pos.x; - } - else if (pos.y != line_count - 1) - { - ++pos.y; - pos.x = 0; - } - break; - case keyboard::os_arrow_up: - coord.y -= static_cast(line_px); - break; - case keyboard::os_arrow_down: - coord.y += static_cast(line_px); - break; - case keyboard::os_home: - //move the caret to the begining of the line - pos.x = 0; - - //move the caret to the begining of the text if Ctrl is pressed - if (arg.ctrl) - pos.y = 0; - break; - case keyboard::os_end: - //move the caret to the end of the line - pos.x = static_cast(text_length); - - //move the caret to the end of the text if Ctrl is pressed - if(arg.ctrl) - pos.y = static_cast((line_count - 1) * line_px); - break; - case keyboard::os_pageup: - if(origin.y > 0) - { - auto off = coord - origin; - origin.y -= (std::min)(origin.y, static_cast(impl_->cview->view_area().height)); - coord = off + origin; - } - break; - case keyboard::os_pagedown: - if (impl_->cview->content_size().height > impl_->cview->view_area().height) - { - auto off = coord - origin; - origin.y = (std::min)(origin.y + static_cast(impl_->cview->view_area().height), static_cast(impl_->cview->content_size().height - impl_->cview->view_area().height)); - coord = off + origin; - } - break; - } - - if (pos == points_.caret) - { - impl_->cview->move_origin(origin - impl_->cview->origin()); - pos = _m_coordinate_to_caret(coord, false); - } - - if (pos != points_.caret) { - if (arg.shift) { - switch (key) { - case keyboard::os_arrow_left: - case keyboard::os_arrow_up: - case keyboard::os_home: - case keyboard::os_pageup: - select_.b = pos; - break; - case keyboard::os_arrow_right: - case keyboard::os_arrow_down: - case keyboard::os_end: - case keyboard::os_pagedown: - select_.b = pos; - break; - } - } - else { - select_.b = pos; - select_.a = pos; - } - points_.caret = pos; - impl_->try_refresh = sync_graph::refresh; - this->_m_adjust_view(); - impl_->cview->sync(true); - this->reset_caret(); - } - } - - 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_)) - { - if (impl_->customized_renderers.border) - { - impl_->customized_renderers.border(graph_, _m_bgcolor()); - } - else - { - ::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, bool stay_in_view) //From screen position - { - points_.caret = _m_coordinate_to_caret(scrpos); - - if (stay_in_view && this->_m_adjust_view()) - impl_->try_refresh = sync_graph::refresh; - - reset_caret(); - return points_.caret; - } - - const upoint& text_editor::caret() const - { - return points_.caret; - } - - point text_editor::caret_screen_pos() const - { - return _m_caret_to_coordinate(points_.caret); - } - - bool text_editor::scroll(bool upwards, bool vert) - { - 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) - { - if (!area->bgcolor.invisible()) - { - auto const height = line_height(); - - auto top = _m_caret_to_coordinate(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 += static_cast(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()) + const std::size_t scrlines = screen_lines() + 1; + for (std::size_t pos = 0; pos < scrlines; ++pos, top += pixels) { - auto const & sct = sections[row.second]; + if (row.first >= line_count) + break; - _m_draw_string(top, fgcolor, str_pos, sct, true); - line_indexes.emplace_back(str_pos); - ++row.second; - if (row.second >= sections.size()) + 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()) { - ++row.first; - row.second = 0; - str_pos.x = 0; - ++str_pos.y; + 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 - str_pos.x += static_cast(sct.end - sct.begin); + break; } - else - break; } + + return line_indexes; } - return line_indexes; - } - - void text_editor::_m_pre_calc_lines(std::size_t line_off, std::size_t lines) - { - unsigned width_px = width_pixels(); - for (auto pos = line_off, end = line_off + lines; pos != end; ++pos) - this->impl_->capacities.behavior->pre_calc_line(pos, width_px); - } - - nana::point text_editor::_m_caret_to_coordinate(nana::upoint pos, bool to_screen_coordinate) 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) + void text_editor::_m_pre_calc_lines(std::size_t line_off, std::size_t lines) { - lines += behavior->take_lines(i); + unsigned width_px = width_pixels(); + for (auto pos = line_off, end = line_off + lines; pos != end; ++pos) + this->impl_->capacities.behavior->pre_calc_line(pos, width_px); } - const text_section * sct_ptr = nullptr; - nana::point scrpos; - if (0 != pos.x) + nana::point text_editor::_m_caret_to_coordinate(nana::upoint pos, bool to_screen_coordinate) const { - std::wstring str; + auto const behavior = impl_->capacities.behavior; + auto const sections = behavior->line(pos.y); - std::size_t sct_pos = 0; - for (auto & sct : sections) + std::size_t lines = 0; //lines before the caret line; + for (std::size_t i = 0; i < pos.y; ++i) { - 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); + lines += behavior->take_lines(i); + } - //In line-wrapped mode. If the caret is at the end of a line which is not the last section, - //the caret should be moved to the beginning of next section line. - if ((sct_pos + 1 < sections.size()) ? (pos.x < chsize) : (pos.x <= chsize)) + const text_section * sct_ptr = nullptr; + nana::point scrpos; + if (0 != pos.x) + { + std::wstring str; + + std::size_t sct_pos = 0; + for (auto & sct : sections) { - sct_ptr = &sct; - if (pos.x == chsize) - scrpos.x = _m_text_extent_size(str.c_str(), sct.end - sct.begin).width; + std::size_t chsize = sct.end - sct.begin; + str.clear(); + if (mask_char_) + str.append(chsize, mask_char_); else - scrpos.x = _m_pixels_by_char(str, pos.x); - break; + str.append(sct.begin, sct.end); + + //In line-wrapped mode. If the caret is at the end of a line which is not the last section, + //the caret should be moved to the beginning of next section line. + if ((sct_pos + 1 < sections.size()) ? (pos.x < chsize) : (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; + } + + ++sct_pos; } + } + + if (!sct_ptr) + { + if (sections.empty()) + scrpos.x += _m_text_x({}); else - { - pos.x -= static_cast(chsize); - ++lines; - } - - ++sct_pos; + scrpos.x += _m_text_x(sections.front()); } + else + scrpos.x += _m_text_x(*sct_ptr); + + scrpos.y = static_cast(lines * line_height()); + + if (!to_screen_coordinate) + { + //_m_text_x includes origin x and text_area x. remove these factors + scrpos.x += (impl_->cview->origin().x - text_area_.area.x); + } + else + scrpos.y += this->_m_text_top_base() - impl_->cview->origin().y; + + return scrpos; } - if (!sct_ptr) + upoint text_editor::_m_coordinate_to_caret(point scrpos, bool from_screen_coordinate) const { + if (!from_screen_coordinate) + scrpos -= (impl_->cview->origin() - point{ text_area_.area.x, this->_m_text_top_base() }); + + 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()) - scrpos.x += _m_text_x({}); - else - scrpos.x += _m_text_x(sections.front()); - } - else - scrpos.x += _m_text_x(*sct_ptr); + return{ 0, static_cast(row.first) }; - scrpos.y = static_cast(lines * line_height()); + //First of all, find the text of secondary. + auto real_str = sections[row.second]; - if (!to_screen_coordinate) - { - //_m_text_x includes origin x and text_area x. remove these factors - scrpos.x += (impl_->cview->origin().x - text_area_.area.x); - } - else - scrpos.y += this->_m_text_top_base() - impl_->cview->origin().y; + auto text_ptr = real_str.begin; + const auto text_size = real_str.end - real_str.begin; - return scrpos; - } - - upoint text_editor::_m_coordinate_to_caret(point scrpos, bool from_screen_coordinate) const - { - if (!from_screen_coordinate) - scrpos -= (impl_->cview->origin() - point{ text_area_.area.x, this->_m_text_top_base() }); - - 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(); - } - - auto const reordered = unicode_reorder(text_ptr, text_size); - - nana::upoint res(static_cast(real_str.begin - sections.front().begin), static_cast(row.first)); - - scrpos.x = (std::max)(0, (scrpos.x - _m_text_x(sections[row.second]))); - - 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) + std::wstring mask_str; + if (mask_char_) { - res.x += _m_char_by_pixels(ent, scrpos.x) + static_cast(ent.begin - text_ptr); - return res; + mask_str.resize(text_size, mask_char_); + text_ptr = mask_str.c_str(); } - scrpos.x -= str_px; - } - //move the caret to the end of this section. - res.x = static_cast(text_size); - for (std::size_t i = 0; i < row.second; ++i) - res.x += static_cast(sections[i].end - sections[i].begin); + auto const reordered = unicode_reorder(text_ptr, text_size); - return res; - } + nana::upoint res(static_cast(real_str.begin - sections.front().begin), static_cast(row.first)); - bool text_editor::_m_pos_from_secondary(std::size_t textline, const nana::upoint& secondary, unsigned & pos) - { - if (textline >= textbase().lines()) - return false; + scrpos.x = (std::max)(0, (scrpos.x - _m_text_x(sections[row.second]))); - 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) + for (auto & ent : reordered) { - 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 str_px = static_cast(_m_text_extent_size(ent.begin, ent.end - ent.begin).width); + if (scrpos.x <= str_px) { - auto origin = impl_->cview->origin(); - origin.y = static_cast(points_.caret.y) * line_height(); - impl_->cview->move_origin(origin - impl_->cview->origin()); + res.x += _m_char_by_pixels(ent, scrpos.x) + static_cast(ent.begin - text_ptr); + return res; } - - --secondary_pos.y; + scrpos.x -= str_px; } - } - 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; - } + //move the caret to the end of this section. + res.x = static_cast(text_size); + for (std::size_t i = 0; i < row.second; ++i) + res.x += static_cast(sections[i].end - sections[i].begin); + + return res; } - _m_pos_from_secondary(points_.caret.y, secondary_pos, points_.caret.x); - return this->_m_adjust_view(); - } - - 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) + bool text_editor::_m_pos_from_secondary(std::size_t textline, const nana::upoint& secondary, unsigned & pos) { - auto top = _m_caret_to_coordinate(upoint{ 0, static_cast(pos) }).y; + if (textline >= textbase().lines()) + return false; - const unsigned pixels = line_height(); - const rectangle update_area = { text_area_.area.x, top, width_pixels(), static_cast(pixels * secondary_count_before) }; + auto sections = impl_->capacities.behavior->line(textline); - 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); + if (secondary.y >= sections.size()) + return false; - auto fgcolor = API::fgcolor(window_); - auto text_ptr = textbase().getline(pos).c_str(); + auto const & sct = sections[secondary.y]; - 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 (impl_->capacities.pred_acceptive) - return impl_->capacities.pred_acceptive(ch); + 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; } - //Checks the input whether it meets the requirement for a numeric. - auto str = text(); - if ('+' == ch || '-' == ch) - return str.empty(); - - if((accepts::real == impl_->capacities.acceptive) && ('.' == ch)) - return (str.find(L'.') == str.npos); - - return ('0' <= ch && ch <= '9'); - } - - ::nana::color text_editor::_m_bgcolor() const - { - return (!API::window_enabled(window_) ? static_cast(0xE0E0E0) : API::bgcolor(window_)); - } - - void text_editor::_m_reset_content_size(bool calc_lines) - { - size csize; - - if (this->attributes_.line_wrapped) + bool text_editor::_m_pos_secondary(const nana::upoint& charpos, nana::upoint& secondary_pos) const { - //detect if vertical scrollbar is required - auto const max_lines = screen_lines(true); + if (charpos.y >= textbase().lines()) + return false; - if (calc_lines) + secondary_pos.x = charpos.x; + secondary_pos.y = 0; + + auto sections = impl_->capacities.behavior->line(charpos.y); + unsigned len = 0; + for (auto & sct : sections) { - auto text_lines = textbase().lines(); - if (text_lines <= max_lines) + 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) { - impl_->capacities.behavior->prepare(); + if (0 == points_.caret.y) + return false; - auto const width_px = _m_width_px(true); - - std::size_t lines = 0; - for (std::size_t i = 0; i < text_lines; ++i) + --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()) { - impl_->capacities.behavior->pre_calc_line(i, width_px); - lines += impl_->capacities.behavior->take_lines(i); + auto origin = impl_->cview->origin(); + origin.y = static_cast(points_.caret.y) * line_height(); + impl_->cview->move_origin(origin - impl_->cview->origin()); + } - if (lines > max_lines) + --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 this->_m_adjust_view(); + } + + 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_coordinate(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 (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(); + + if ('+' == ch || '-' == ch) + return str.empty(); + + if ((accepts::real == impl_->capacities.acceptive) && ('.' == ch)) + return (str.find(L'.') == str.npos); + + return ('0' <= ch && ch <= '9'); + } + + ::nana::color text_editor::_m_bgcolor() const + { + return (!API::window_enabled(window_) ? static_cast(0xE0E0E0) : API::bgcolor(window_)); + } + + void text_editor::_m_reset_content_size(bool calc_lines) + { + size csize; + + if (this->attributes_.line_wrapped) + { + //detect if vertical scrollbar is required + auto const max_lines = screen_lines(true); + + if (calc_lines) + { + auto text_lines = textbase().lines(); + if (text_lines <= max_lines) + { + impl_->capacities.behavior->prepare(); + + auto const width_px = _m_width_px(true); + + std::size_t lines = 0; + for (std::size_t i = 0; i < text_lines; ++i) { - text_lines = lines; - break; + impl_->capacities.behavior->pre_calc_line(i, width_px); + 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 + { + csize.width = impl_->cview->content_size().width; } - - //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 { - csize.width = impl_->cview->content_size().width; - } - } - else - { - if (calc_lines) - impl_->capacities.behavior->pre_calc_lines(width_pixels()); + if (calc_lines) + impl_->capacities.behavior->pre_calc_lines(width_pixels()); - auto maxline = textbase().max_line(); - csize.width = _m_text_extent_size(textbase().getline(maxline.first).c_str(), maxline.second).width + caret_size().width; - } - - csize.height = static_cast(impl_->capacities.behavior->take_lines() * line_height()); - - impl_->cview->content_size(csize); - } - - void text_editor::_m_reset() - { - points_.caret.x = points_.caret.y = 0; - impl_->cview->move_origin(point{} -impl_->cview->origin()); - select_.a = select_.b; - } - - nana::upoint text_editor::_m_put(std::wstring text, bool perform_event) - { - auto & textbase = this->textbase(); - auto crtpos = points_.caret; - std::vector> lines; - if (_m_resolve_text(text, lines) && attributes_.multi_lines) - { - auto str_orig = textbase.getline(crtpos.y); - - auto const subpos = lines.front(); - auto substr = text.substr(subpos.first, subpos.second - subpos.first); - - if (str_orig.size() == crtpos.x) - textbase.insert(crtpos, std::move(substr)); - else - textbase.replace(crtpos.y, str_orig.substr(0, crtpos.x) + substr); - - //There are at least 2 elements in lines - for (auto i = lines.begin() + 1, end = lines.end() - 1; i != end; ++i) - { - textbase.insertln(++crtpos.y, text.substr(i->first, i->second - i->first)); + auto maxline = textbase().max_line(); + csize.width = _m_text_extent_size(textbase().getline(maxline.first).c_str(), maxline.second).width + caret_size().width; } - auto backpos = lines.back(); - textbase.insertln(++crtpos.y, text.substr(backpos.first, backpos.second - backpos.first) + str_orig.substr(crtpos.x)); - crtpos.x = static_cast(backpos.second - backpos.first); + csize.height = static_cast(impl_->capacities.behavior->take_lines() * line_height()); - impl_->capacities.behavior->add_lines(points_.caret.y, lines.size() - 1); - _m_pre_calc_lines(points_.caret.y, lines.size()); + impl_->cview->content_size(csize); } - else + + void text_editor::_m_reset() { - //Just insert the first line of text if the text is multilines. - if (lines.size() > 1) - text = text.substr(lines.front().first, lines.front().second - lines.front().first); - - crtpos.x += static_cast(text.size()); - textbase.insert(points_.caret, std::move(text)); - - _m_pre_calc_lines(crtpos.y, 1); + points_.caret.x = points_.caret.y = 0; + impl_->cview->move_origin(point{} -impl_->cview->origin()); + select_.a = select_.b; } - if (perform_event) - textbase.text_changed(); - - return crtpos; - } - - nana::upoint text_editor::_m_erase_select(bool perform_event) - { - nana::upoint a, b; - if (get_selected_points(a, b)) + nana::upoint text_editor::_m_put(std::wstring text, bool perform_event) { auto & textbase = this->textbase(); - if(a.y != b.y) + auto crtpos = points_.caret; + std::vector> lines; + if (_m_resolve_text(text, lines) && attributes_.multi_lines) { - textbase.erase(a.y, a.x, std::wstring::npos); - textbase.erase(a.y + 1, b.y - a.y - 1); + auto str_orig = textbase.getline(crtpos.y); - textbase.erase(a.y + 1, 0, b.x); - textbase.merge(a.y); + auto const subpos = lines.front(); + auto substr = text.substr(subpos.first, subpos.second - subpos.first); - impl_->capacities.behavior->merge_lines(a.y, b.y); + if (str_orig.size() == crtpos.x) + textbase.insert(crtpos, std::move(substr)); + else + textbase.replace(crtpos.y, str_orig.substr(0, crtpos.x) + substr); + + //There are at least 2 elements in lines + for (auto i = lines.begin() + 1, end = lines.end() - 1; i != end; ++i) + { + textbase.insertln(++crtpos.y, text.substr(i->first, i->second - i->first)); + } + + auto backpos = lines.back(); + textbase.insertln(++crtpos.y, text.substr(backpos.first, backpos.second - backpos.first) + str_orig.substr(crtpos.x)); + crtpos.x = static_cast(backpos.second - backpos.first); + + impl_->capacities.behavior->add_lines(points_.caret.y, lines.size() - 1); + _m_pre_calc_lines(points_.caret.y, lines.size()); } else { - textbase.erase(a.y, a.x, b.x - a.x); - _m_pre_calc_lines(a.y, 1); + //Just insert the first line of text if the text is multilines. + if (lines.size() > 1) + text = text.substr(lines.front().first, lines.front().second - lines.front().first); + + crtpos.x += static_cast(text.size()); + textbase.insert(points_.caret, std::move(text)); + + _m_pre_calc_lines(crtpos.y, 1); } if (perform_event) textbase.text_changed(); - select_.a = select_.b; - return a; + return crtpos; } - return points_.caret; - } - - std::wstring text_editor::_m_make_select_string() const - { - std::wstring text; - - nana::upoint a, b; - if (get_selected_points(a, b)) + nana::upoint text_editor::_m_erase_select(bool perform_event) { - auto & textbase = this->textbase(); - - if (a.y != b.y) + nana::upoint a, b; + if (get_selected_points(a, b)) { - text = textbase.getline(a.y).substr(a.x); - text += L"\r\n"; - for (unsigned i = a.y + 1; i < b.y; ++i) + auto & textbase = this->textbase(); + if (a.y != b.y) { - text += textbase.getline(i); - text += L"\r\n"; + textbase.erase(a.y, a.x, std::wstring::npos); + textbase.erase(a.y + 1, b.y - a.y - 1); + + textbase.erase(a.y + 1, 0, b.x); + textbase.merge(a.y); + + impl_->capacities.behavior->merge_lines(a.y, b.y); } - text += textbase.getline(b.y).substr(0, b.x); - } - else - return textbase.getline(a.y).substr(a.x, b.x - a.x); - } - - return text; - } - - std::size_t eat_endl(const wchar_t* str, std::size_t pos) - { - auto ch = str[pos]; - if (0 == ch) - return pos; - - const wchar_t * endlstr; - switch (ch) - { - case L'\n': - endlstr = L"\n\r"; - break; - case L'\r': - endlstr = L"\r\n"; - break; - default: - return pos; - } - - if (std::memcmp(str + pos, endlstr, sizeof(wchar_t) * 2) == 0) - return pos + 2; - - return pos + 1; - } - - bool text_editor::_m_resolve_text(const std::wstring& text, std::vector> & lines) - { - auto const text_str = text.c_str(); - std::size_t begin = 0; - while (true) - { - auto pos = text.find_first_of(L"\r\n", begin); - if (text.npos == pos) - { - if (!lines.empty()) - lines.emplace_back(begin, text.size()); - break; - } - - lines.emplace_back(begin, pos); - - pos = eat_endl(text_str, pos); - - begin = text.find_first_not_of(L"\r\n", pos); - - //The number of new lines minus one - const auto chp_end = text_str + (begin == text.npos ? text.size() : begin); - - for (auto chp = text_str + pos; chp != chp_end; ++chp) - { - auto eats = eat_endl(chp, 0); - if (eats) + else { - lines.emplace_back(); - chp += (eats - 1); + textbase.erase(a.y, a.x, b.x - a.x); + _m_pre_calc_lines(a.y, 1); } + + if (perform_event) + textbase.text_changed(); + + select_.a = select_.b; + return a; } - if (text.npos == begin) - { - lines.emplace_back(); - break; - } - } - return !lines.empty(); - } - - bool text_editor::_m_cancel_select(int align) - { - upoint a, b; - if (get_selected_points(a, b)) - { - //domain of algin = [0, 2] - if (align) - { - this->points_.caret = (1 == align ? a : b); - this->_m_adjust_view(); - } - - select_.a = select_.b = points_.caret; - reset_caret(); - return true; - } - return false; - } - - nana::size text_editor::_m_text_extent_size(const char_type* str, size_type n) const - { - if(mask_char_) - { - std::wstring maskstr; - maskstr.append(n, mask_char_); - return graph_.text_extent_size(maskstr); - } -#ifdef _nana_std_has_string_view - return graph_.text_extent_size(std::basic_string_view(str, n)); -#else - return graph_.text_extent_size(str, static_cast(n)); -#endif - } - - bool text_editor::_m_adjust_view() - { - auto const view_area = impl_->cview->view_area(); - - auto const line_px = static_cast(this->line_height()); - auto coord = _m_caret_to_coordinate(points_.caret, true); - - if (view_area.is_hit(coord) && view_area.is_hit({coord.x, coord.y + line_px})) - return false; - - unsigned extra_count_horz = 4; - unsigned extra_count_vert = 0; - - auto const origin = impl_->cview->origin(); - coord = _m_caret_to_coordinate(points_.caret, false); - - point moved_origin; - - //adjust x-axis if it isn't line-wrapped mode - if (!attributes_.line_wrapped) - { - auto extra = points_.caret; - - if (coord.x < origin.x) - { - extra.x -= (std::min)(extra_count_horz, points_.caret.x); - moved_origin.x = _m_caret_to_coordinate(extra, false).x - origin.x; - } - else if (coord.x + static_cast(caret_size().width) >= origin.x + static_cast(view_area.width)) - { - extra.x = (std::min)(static_cast(textbase().getline(points_.caret.y).size()), points_.caret.x + extra_count_horz); - auto new_origin = _m_caret_to_coordinate(extra, false).x + static_cast(caret_size().width) - static_cast(view_area.width); - moved_origin.x = new_origin - origin.x; - } + return points_.caret; } - auto extra_px = static_cast(line_px * extra_count_vert); - - if (coord.y < origin.y) + std::wstring text_editor::_m_make_select_string() const { - //Top of caret is less than the top of view - - moved_origin.y = (std::max)(0, coord.y - extra_px) - origin.y; - } - else if (coord.y + line_px >= origin.y + static_cast(view_area.height)) - { - //Bottom of caret is greater than the bottom of view - - auto const bottom = static_cast(impl_->capacities.behavior->take_lines() * line_px); - auto new_origin = (std::min)(coord.y + line_px + extra_px, bottom) - static_cast(view_area.height); - moved_origin.y = new_origin - origin.y; - } - - return impl_->cview->move_origin(moved_origin); - } - - bool text_editor::_m_move_select(bool record_undo) - { - if (!attributes_.editable) - return false; - - nana::upoint caret = points_.caret; - const auto text = _m_make_select_string(); - if (!text.empty()) - { - auto undo_ptr = std::unique_ptr(new undo_move_text(*this)); - undo_ptr->set_selected_text(); - - //Determines whether the caret is at left or at right. The select_.b indicates the caret position when finish selection - const bool at_left = (select_.b < select_.a); + std::wstring text; nana::upoint 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(); - - _m_erase_select(false); - _m_put(text, false); - - select_.a = caret; - select_.b.y = b.y + (caret.y - a.y); - } - else if (b.y < caret.y || (caret.y == b.y && b.x < caret.x)) + if (get_selected_points(a, b)) { - undo_ptr->set_caret_pos(); + auto & textbase = this->textbase(); - _m_put(text, false); - _m_erase_select(false); - - select_.b.y = caret.y; - select_.a.y = caret.y - (b.y - a.y); - select_.a.x = caret.x - (caret.y == b.y ? (b.x - a.x) : 0); + if (a.y != b.y) + { + text = textbase.getline(a.y).substr(a.x); + text += L"\r\n"; + for (unsigned i = a.y + 1; i < b.y; ++i) + { + text += textbase.getline(i); + text += L"\r\n"; + } + text += textbase.getline(b.y).substr(0, b.x); + } + else + return textbase.getline(a.y).substr(a.x, b.x - a.x); } - select_.b.x = b.x + (a.y == b.y ? (select_.a.x - a.x) : 0); - //restores the caret at the proper end. - if ((select_.b < select_.a) != at_left) - std::swap(select_.a, select_.b); + return text; + } - if (record_undo) + std::size_t eat_endl(const wchar_t* str, std::size_t pos) + { + auto ch = str[pos]; + if (0 == ch) + return pos; + + const wchar_t * endlstr; + switch (ch) { - undo_ptr->set_destination(select_.a, select_.b); - impl_->undo.push(std::move(undo_ptr)); + case L'\n': + endlstr = L"\n\r"; + break; + case L'\r': + endlstr = L"\r\n"; + break; + default: + return pos; } - points_.caret = select_.b; - reset_caret(); + if (std::memcmp(str + pos, endlstr, sizeof(wchar_t) * 2) == 0) + return pos + 2; + + return pos + 1; + } + + bool text_editor::_m_resolve_text(const std::wstring& text, std::vector> & lines) + { + auto const text_str = text.c_str(); + std::size_t begin = 0; + while (true) + { + auto pos = text.find_first_of(L"\r\n", begin); + if (text.npos == pos) + { + if (!lines.empty()) + lines.emplace_back(begin, text.size()); + break; + } + + lines.emplace_back(begin, pos); + + pos = eat_endl(text_str, pos); + + begin = text.find_first_not_of(L"\r\n", pos); + + //The number of new lines minus one + const auto chp_end = text_str + (begin == text.npos ? text.size() : begin); + + for (auto chp = text_str + pos; chp != chp_end; ++chp) + { + auto eats = eat_endl(chp, 0); + if (eats) + { + lines.emplace_back(); + chp += (eats - 1); + } + } + + if (text.npos == begin) + { + lines.emplace_back(); + break; + } + } + return !lines.empty(); + } + + bool text_editor::_m_cancel_select(int align) + { + upoint a, b; + if (get_selected_points(a, b)) + { + //domain of algin = [0, 2] + if (align) + { + this->points_.caret = (1 == align ? a : b); + this->_m_adjust_view(); + } + + select_.a = select_.b = points_.caret; + reset_caret(); + return true; + } + return false; + } + + nana::size text_editor::_m_text_extent_size(const char_type* str, size_type n) const + { + if (mask_char_) + { + std::wstring maskstr; + maskstr.append(n, mask_char_); + return graph_.text_extent_size(maskstr); + } +#ifdef _nana_std_has_string_view + return graph_.text_extent_size(std::basic_string_view(str, n)); +#else + return graph_.text_extent_size(str, static_cast(n)); +#endif + } + + bool text_editor::_m_adjust_view() + { + auto const view_area = impl_->cview->view_area(); + + auto const line_px = static_cast(this->line_height()); + auto coord = _m_caret_to_coordinate(points_.caret, true); + + if (view_area.is_hit(coord) && view_area.is_hit({ coord.x, coord.y + line_px })) + return false; + + unsigned extra_count_horz = 4; + unsigned extra_count_vert = 0; + + auto const origin = impl_->cview->origin(); + coord = _m_caret_to_coordinate(points_.caret, false); + + point moved_origin; + + //adjust x-axis if it isn't line-wrapped mode + if (!attributes_.line_wrapped) + { + auto extra = points_.caret; + + if (coord.x < origin.x) + { + extra.x -= (std::min)(extra_count_horz, points_.caret.x); + moved_origin.x = _m_caret_to_coordinate(extra, false).x - origin.x; + } + else if (coord.x + static_cast(caret_size().width) >= origin.x + static_cast(view_area.width)) + { + extra.x = (std::min)(static_cast(textbase().getline(points_.caret.y).size()), points_.caret.x + extra_count_horz); + auto new_origin = _m_caret_to_coordinate(extra, false).x + static_cast(caret_size().width) - static_cast(view_area.width); + moved_origin.x = new_origin - origin.x; + } + } + + auto extra_px = static_cast(line_px * extra_count_vert); + + if (coord.y < origin.y) + { + //Top of caret is less than the top of view + + moved_origin.y = (std::max)(0, coord.y - extra_px) - origin.y; + } + else if (coord.y + line_px >= origin.y + static_cast(view_area.height)) + { + //Bottom of caret is greater than the bottom of view + + auto const bottom = static_cast(impl_->capacities.behavior->take_lines() * line_px); + auto new_origin = (std::min)(coord.y + line_px + extra_px, bottom) - static_cast(view_area.height); + moved_origin.y = new_origin - origin.y; + } + + return impl_->cview->move_origin(moved_origin); + } + + bool text_editor::_m_move_select(bool record_undo) + { + if (!attributes_.editable) + return false; + + nana::upoint caret = points_.caret; + const auto text = _m_make_select_string(); + if (!text.empty()) + { + auto undo_ptr = std::unique_ptr(new undo_move_text(*this)); + undo_ptr->set_selected_text(); + + //Determines whether the caret is at left or at right. The select_.b indicates the caret position when finish selection + const bool at_left = (select_.b < select_.a); + + nana::upoint 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(); + + _m_erase_select(false); + _m_put(text, false); + + select_.a = caret; + select_.b.y = b.y + (caret.y - a.y); + } + else if (b.y < caret.y || (caret.y == b.y && b.x < caret.x)) + { + undo_ptr->set_caret_pos(); + + _m_put(text, false); + _m_erase_select(false); + + select_.b.y = caret.y; + select_.a.y = caret.y - (b.y - a.y); + select_.a.x = caret.x - (caret.y == b.y ? (b.x - a.x) : 0); + } + select_.b.x = b.x + (a.y == b.y ? (select_.a.x - a.x) : 0); + + //restores the caret at the proper end. + if ((select_.b < select_.a) != at_left) + std::swap(select_.a, select_.b); + + if (record_undo) + { + undo_ptr->set_destination(select_.a, select_.b); + impl_->undo.push(std::move(undo_ptr)); + } + + points_.caret = select_.b; + reset_caret(); + return true; + } + return false; + } + + int text_editor::_m_text_top_base() const + { + if (false == attributes_.multi_lines) + { + unsigned px = line_height(); + if (text_area_.area.height > px) + return text_area_.area.y + static_cast((text_area_.area.height - px) >> 1); + } + 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) : 0); + } + + 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 + { +#ifdef _nana_std_has_string_view + graph_.string(pos, { str, len }, fgcolor); +#else + graph_.palette(true, fgcolor); + graph_.string(pos, str, len); +#endif + if (parser.entities().empty()) + return; + +#ifdef _nana_std_has_string_view + auto glyph_px = graph_.glyph_pixels({ str, len }); +#else + std::unique_ptr glyph_px(new unsigned[len]); + graph_.glyph_pixels(str, len, glyph_px.get()); +#endif + auto glyphs = glyph_px.get(); + + auto px_h = line_height(); + auto px_w = std::accumulate(glyphs, glyphs + len, unsigned{}); + + ::nana::paint::graphics canvas; + canvas.make({ px_w, px_h }); + canvas.typeface(graph_.typeface()); + + auto ent_pos = pos; + const auto str_end = str + len; + auto & entities = parser.entities(); + + for (auto & ent : entities) + { + const wchar_t* ent_begin = nullptr; + + int ent_off = 0; + if (str <= ent.begin && ent.begin < str_end) + { + ent_begin = ent.begin; + ent_off = static_cast(std::accumulate(glyphs, glyphs + (ent.begin - str), unsigned{})); + } + else if (ent.begin <= str && str < ent.end) + ent_begin = str; + + if (ent_begin) + { + auto ent_end = (ent.end < str_end ? ent.end : str_end); + auto ent_pixels = std::accumulate(glyphs + (ent_begin - str), glyphs + (ent_end - str), unsigned{}); + + canvas.palette(false, ent.scheme->bgcolor.invisible() ? _m_bgcolor() : ent.scheme->bgcolor); + canvas.palette(true, ent.scheme->fgcolor.invisible() ? fgcolor : ent.scheme->fgcolor); + + canvas.rectangle(true); + + ent_pos.x = pos.x + ent_off; + +#ifdef _nana_std_has_string_view + std::wstring_view ent_sv; + if (rtl) + { + //draw the whole text if it is a RTL text, because Arbic language is transformable. + ent_sv = { str, len }; + } + else + { + ent_sv = { ent_begin, static_cast(ent_end - ent_begin) }; + ent_off = 0; + } + canvas.string({}, ent_sv); +#else + if (rtl) + { + //draw the whole text if it is a RTL text, because Arbic language is transformable. + canvas.string({}, str, len); + } + else + { + canvas.string({}, ent_begin, ent_end - ent_begin); + ent_off = 0; + } +#endif + graph_.bitblt(rectangle{ ent_pos, size{ ent_pixels, canvas.height() } }, canvas, point{ ent_off, 0 }); + } + } + } + + class text_editor::helper_pencil + { + public: + helper_pencil(paint::graphics& graph, const text_editor& editor, keyword_parser& parser) : + graph_(graph), + editor_(editor), + parser_(parser), + line_px_(editor.line_height()) + {} + + color selection_color(bool fgcolor, bool focused) const + { + if (fgcolor) + return (focused ? editor_.scheme_->selection_text : editor_.scheme_->foreground).get_color(); + + return (focused ? editor_.scheme_->selection : editor_.scheme_->selection_unfocused).get_color(); + } + + void write_selection(const point& text_pos, unsigned text_px, const wchar_t* text, std::size_t len, bool has_focused) + { +#ifdef _nana_std_has_string_view + graph_.rectangle(::nana::rectangle{ text_pos,{ text_px, line_px_ } }, true, + selection_color(false, has_focused)); + + graph_.string(text_pos, { text, len }, selection_color(true, has_focused)); +#else + graph_.palette(true, selection_color(true, has_focused)); + + graph_.rectangle(::nana::rectangle{ text_pos,{ text_px, line_px_ } }, true, + selection_color(false, has_focused)); + + graph_.string(text_pos, text, len); +#endif + } + + 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_color(true, has_focused), str, len); + + //Draw selected part + paint::graphics graph({ glyph_selected, line_px_ }); + graph.typeface(this->graph_.typeface()); + graph.rectangle(true, selection_color(false, has_focused)); + + int sel_xpos = static_cast(str_px - (glyph_front + glyph_selected)); + +#ifdef _nana_std_has_string_view + graph.string({ -sel_xpos, 0 }, { str, len }, selection_color(true, has_focused)); +#else + graph.palette(true, selection_color(true, has_focused)); + graph.string({ -sel_xpos, 0 }, str, len); +#endif + graph_.bitblt(nana::rectangle(strpos.x + sel_xpos, strpos.y, glyph_selected, line_px_), graph); + }; + private: + paint::graphics& graph_; + const text_editor& editor_; + keyword_parser & parser_; + unsigned line_px_; + }; + + 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{ _m_text_x(sct), top }; + + 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_len, mask_char_); + text_ptr = mask_str.c_str(); + } + + const auto focused = API::is_focus_ready(window_); + + auto const reordered = unicode_reorder(text_ptr, text_len); + + //Parse highlight keywords + keyword_parser parser; + parser.parse(text_ptr, text_len, impl_->keywords); + + const auto line_h_pixels = line_height(); + + helper_pencil pencil(graph_, *this, parser); + + graph_.palette(true, clr); + graph_.palette(false, scheme_->selection.get_color()); + + + //Get the selection begin and end position of the current text. + const wchar_t *sbegin = nullptr, *send = nullptr; + + nana::upoint a, b; + if (get_selected_points(a, b)) + { + if (a.y < text_coord.y && text_coord.y < b.y) + { + 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 + text_len, b.x); + + if (sbegin_pos < send_pos) + { + 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_len) + { + sbegin = text_ptr; + if (text_coord.x < a.x) + sbegin += (a.x - text_coord.x); + + send = text_ptr + text_len; + } + } + else if (b.y == text_coord.y) + { + if (text_coord.x < b.x) + { + sbegin = text_ptr; + send = text_ptr + (std::min)(b.x - text_coord.x, text_len); + } + } + } + + //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; + + //Create a flag for indicating whether the whole line is selected + 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 ((!sbegin || !send) || text_selected) + { + for (auto & ent : reordered) + { + std::size_t len = ent.end - ent.begin; + +#ifdef _nana_std_has_string_view + unsigned str_w = graph_.text_extent_size(std::wstring_view{ ent.begin, len }).width; +#else + unsigned str_w = graph_.text_extent_size(ent.begin, len).width; +#endif + + if ((text_draw_pos.x + static_cast(str_w) > text_area_.area.x) && (text_draw_pos.x < text_right)) + { + 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); + } + text_draw_pos.x += static_cast(str_w); + } + + extra_space = text_selected; + } + else + { + for (auto & ent : reordered) + { + const auto len = ent.end - ent.begin; +#ifdef _nana_std_has_string_view + auto ent_px = graph_.text_extent_size(std::wstring_view(ent.begin, len)).width; +#else + auto ent_px = graph_.text_extent_size(ent.begin, len).width; +#endif + + extra_space = false; + + //Only draw the text which is in the visual rectangle. + if ((text_draw_pos.x + static_cast(ent_px) > text_area_.area.x) && (text_draw_pos.x < text_right)) + { + if (send <= ent.begin || ent.end <= sbegin) + { + //this string is not selected + _m_draw_parse_string(parser, false, text_draw_pos, clr, ent.begin, len); + } + else if (sbegin <= ent.begin && ent.end <= send) + { + //this string is completed selected + pencil.write_selection(text_draw_pos, ent_px, ent.begin, len, focused); + extra_space = true; + } + else + { + //a part of string is selected + + //get the selected range of this string. + auto ent_sbegin = (std::max)(sbegin, ent.begin); + auto ent_send = (std::min)(send, ent.end); + + unsigned select_pos = static_cast(ent_sbegin != ent.begin ? ent_sbegin - ent.begin : 0); + unsigned select_len = static_cast(ent_send - ent_sbegin); + +#ifdef _nana_std_has_string_view + auto pxbuf = graph_.glyph_pixels({ ent.begin, static_cast(len) }); +#else + std::unique_ptr pxbuf{ new unsigned[len] }; + graph_.glyph_pixels(ent.begin, len, pxbuf.get()); +#endif + + auto head_px = std::accumulate(pxbuf.get(), pxbuf.get() + select_pos, unsigned{}); + auto select_px = std::accumulate(pxbuf.get() + select_pos, pxbuf.get() + select_pos + select_len, unsigned{}); + + 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, focused); + } + else + { //LTR + _m_draw_parse_string(parser, false, text_draw_pos, clr, ent.begin, select_pos); + + 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, focused); + + if (ent_send < ent.end) + { + part_pos.x += static_cast(select_px); + _m_draw_parse_string(parser, false, part_pos, clr, ent_send, ent.end - ent_send); + } + } + + extra_space = (select_pos + select_len == text_len); + } + } + text_draw_pos.x += static_cast(ent_px); + }//end for + } + + //extra_space is true if the end of line is selected + 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_len; + if (b.x != pos || text_coord.y != b.y) + { +#ifdef _nana_std_has_string_view + auto whitespace_w = graph_.text_extent_size(std::wstring_view{ L" ", 1 }).width; +#else + auto whitespace_w = graph_.text_extent_size(L" ", 1).width; +#endif + graph_.rectangle(::nana::rectangle{ text_draw_pos,{ whitespace_w, line_h_pixels } }, true); + } + } + } + + bool text_editor::_m_update_caret_line(std::size_t secondary_before) + { + if (false == this->_m_adjust_view()) + { + if (_m_caret_to_coordinate(points_.caret).x < impl_->cview->view_area().right()) + { + _m_update_line(points_.caret.y, secondary_before); + return false; + } + } + else + { + //The content view is adjusted, now syncs it with active mode to avoid updating. + impl_->cview->sync(false); + } + impl_->try_refresh = sync_graph::refresh; return true; } - return false; - } - int text_editor::_m_text_top_base() const - { - if(false == attributes_.multi_lines) + unsigned text_editor::_m_char_by_pixels(const unicode_bidi::entity& ent, unsigned pos) const { - unsigned px = line_height(); - if(text_area_.area.height > px) - return text_area_.area.y + static_cast((text_area_.area.height - px) >> 1); - } - 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 - { + auto len = static_cast(ent.end - ent.begin); #ifdef _nana_std_has_string_view - graph_.string(pos, { str, len }, fgcolor); + auto pxbuf = graph_.glyph_pixels({ ent.begin, len }); + if (pxbuf) #else - graph_.palette(true, fgcolor); - graph_.string(pos, str, len); + std::unique_ptr pxbuf(new unsigned[len]); + if (graph_.glyph_pixels(ent.begin, len, pxbuf.get())) #endif - if (parser.entities().empty()) - return; - -#ifdef _nana_std_has_string_view - auto glyph_px = graph_.glyph_pixels({ str, len }); -#else - std::unique_ptr glyph_px(new unsigned[len]); - graph_.glyph_pixels(str, len, glyph_px.get()); -#endif - auto glyphs = glyph_px.get(); - - auto px_h = line_height(); - auto px_w = std::accumulate(glyphs, glyphs + len, unsigned{}); - - ::nana::paint::graphics canvas; - canvas.make({ px_w, px_h }); - canvas.typeface(graph_.typeface()); - ::nana::point canvas_text_pos; - - auto ent_pos = pos; - const auto str_end = str + len; - auto & entities = parser.entities(); - - for (auto & ent : entities) - { - const wchar_t* ent_begin = nullptr; - - int ent_off = 0; - if (str <= ent.begin && ent.begin < str_end) { - ent_begin = ent.begin; - ent_off = static_cast(std::accumulate(glyphs, glyphs + (ent.begin - str), unsigned{})); - } - else if (ent.begin <= str && str < ent.end) - ent_begin = str; + const auto px_end = pxbuf.get() + len; - if (ent_begin) - { - auto ent_end = (ent.end < str_end ? ent.end : str_end); - auto ent_pixels = std::accumulate(glyphs + (ent_begin - str), glyphs + (ent_end - str), unsigned{}); - - canvas.palette(false, ent.scheme->bgcolor.invisible() ? _m_bgcolor() : ent.scheme->bgcolor); - canvas.palette(true, ent.scheme->fgcolor.invisible() ? fgcolor : ent.scheme->fgcolor); - - canvas.rectangle(true); - - ent_pos.x = pos.x + ent_off; - -#ifdef _nana_std_has_string_view - std::wstring_view ent_sv; - if (rtl) + if (is_right_text(ent)) { - //draw the whole text if it is a RTL text, because Arbic language is transformable. - ent_sv = { str, len }; + auto total_px = std::accumulate(pxbuf.get(), px_end, unsigned{}); + + for (auto p = pxbuf.get(); p != px_end; ++p) + { + auto chpos = total_px - *p; + if ((chpos <= pos) && (pos < total_px)) + { + if ((*p < 2) || (pos <= chpos + (*p >> 1))) + return static_cast(p - pxbuf.get()) + 1; + + return static_cast(p - pxbuf.get()); + } + total_px = chpos; + } } else { - ent_sv = { ent_begin, static_cast(ent_end - ent_begin) }; - ent_off = 0; - } - canvas.string({}, ent_sv); -#else - if (rtl) - { - //draw the whole text if it is a RTL text, because Arbic language is transformable. - canvas.string({}, str, len); - } - else - { - canvas.string({}, ent_begin, ent_end - ent_begin); - ent_off = 0; - } -#endif - graph_.bitblt(rectangle{ ent_pos, size{ ent_pixels, canvas.height() } }, canvas, point{ ent_off, 0 }); - } - } - } - - class text_editor::helper_pencil - { - public: - helper_pencil(paint::graphics& graph, const text_editor& editor, keyword_parser& parser): - graph_( graph ), - editor_( editor ), - parser_( parser ), - line_px_( editor.line_height() ) - {} - - color selection_color(bool fgcolor, bool focused) const - { - if (fgcolor) - return (focused ? editor_.scheme_->selection_text : editor_.scheme_->foreground).get_color(); - - return (focused ? editor_.scheme_->selection : editor_.scheme_->selection_unfocused).get_color(); - } - - void write_selection(const point& text_pos, unsigned text_px, const wchar_t* text, std::size_t len, bool has_focused) - { -#ifdef _nana_std_has_string_view - graph_.rectangle(::nana::rectangle{ text_pos,{ text_px, line_px_ } }, true, - selection_color(false, has_focused)); - - graph_.string(text_pos, { text, len }, selection_color(true, has_focused)); -#else - graph_.palette(true, selection_color(true, has_focused)); - - graph_.rectangle(::nana::rectangle{ text_pos, { text_px, line_px_ } }, true, - selection_color(false, has_focused)); - - graph_.string(text_pos, text, len); -#endif - } - - 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_color(true, has_focused), str, len); - - //Draw selected part - paint::graphics graph({ glyph_selected, line_px_ }); - graph.typeface(this->graph_.typeface()); - graph.rectangle(true, selection_color(false, has_focused)); - - int sel_xpos = static_cast(str_px - (glyph_front + glyph_selected)); - -#ifdef _nana_std_has_string_view - graph.string({ -sel_xpos, 0 }, { str, len }, selection_color(true, has_focused)); -#else - graph.palette(true, selection_color(true, has_focused)); - graph.string({ -sel_xpos, 0 }, str, len); -#endif - graph_.bitblt(nana::rectangle(strpos.x + sel_xpos, strpos.y, glyph_selected, line_px_), graph); - }; - private: - paint::graphics& graph_; - const text_editor& editor_; - keyword_parser & parser_; - unsigned line_px_; - }; - - 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{ _m_text_x(sct), top }; - - 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_len, mask_char_); - text_ptr = mask_str.c_str(); - } - - const auto focused = API::is_focus_ready(window_); - - auto const reordered = unicode_reorder(text_ptr, text_len); - - //Parse highlight keywords - keyword_parser parser; - parser.parse(text_ptr, text_len, impl_->keywords); - - const auto line_h_pixels = line_height(); - - helper_pencil pencil(graph_, *this, parser); - - graph_.palette(true, clr); - graph_.palette(false, scheme_->selection.get_color()); - - - //Get the selection begin and end position of the current text. - const wchar_t *sbegin = nullptr, *send = nullptr; - - nana::upoint a, b; - if (get_selected_points(a, b)) - { - if (a.y < text_coord.y && text_coord.y < b.y) - { - 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 + text_len, b.x); - - if (sbegin_pos < send_pos) - { - sbegin = text_ptr + (sbegin_pos - text_coord.x); - send = text_ptr + (send_pos - text_coord.x); + for (auto p = pxbuf.get(); p != px_end; ++p) + { + if (pos <= *p) + { + if ((*p > 1) && (pos >(*p >> 1))) + return static_cast(p - pxbuf.get()) + 1; + return static_cast(p - pxbuf.get()); + } + pos -= *p; + } } } - else if (a.y == text_coord.y) - { - if (a.x < text_coord.x + text_len) - { - sbegin = text_ptr; - if (text_coord.x < a.x) - sbegin += (a.x - text_coord.x); - send = text_ptr + text_len; - } - } - else if (b.y == text_coord.y) - { - if (text_coord.x < b.x) - { - sbegin = text_ptr; - send = text_ptr + (std::min)(b.x - text_coord.x, text_len); - } - } + return 0; } - //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; - - //Create a flag for indicating whether the whole line is selected - 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 ((!sbegin || !send) || text_selected) + unsigned text_editor::_m_pixels_by_char(const std::wstring& lnstr, std::size_t pos) const { + if (pos > lnstr.size()) + return 0; + + auto const reordered = unicode_reorder(lnstr.c_str(), lnstr.size()); + + auto target = lnstr.c_str() + pos; + + unsigned text_w = 0; for (auto & ent : reordered) { std::size_t len = ent.end - ent.begin; - -#ifdef _nana_std_has_string_view - unsigned str_w = graph_.text_extent_size(std::wstring_view{ ent.begin, len }).width; -#else - unsigned str_w = graph_.text_extent_size(ent.begin, len).width; -#endif - - if ((text_draw_pos.x + static_cast(str_w) > text_area_.area.x) && (text_draw_pos.x < text_right)) + if (ent.begin <= target && target <= ent.end) { - 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); - } - text_draw_pos.x += static_cast(str_w); - } - - extra_space = text_selected; - } - else - { - for (auto & ent : reordered) - { - const auto len = ent.end - ent.begin; + if (is_right_text(ent)) + { + //Characters of some bidi languages may transform in a word. + //RTL #ifdef _nana_std_has_string_view - auto ent_px = graph_.text_extent_size(std::wstring_view(ent.begin, len)).width; + auto pxbuf = graph_.glyph_pixels({ ent.begin, len }); #else - auto ent_px = graph_.text_extent_size(ent.begin, len).width; -#endif - - extra_space = false; - - //Only draw the text which is in the visual rectangle. - if ((text_draw_pos.x + static_cast(ent_px) > text_area_.area.x) && (text_draw_pos.x < text_right)) - { - if (send <= ent.begin || ent.end <= sbegin) - { - //this string is not selected - _m_draw_parse_string(parser, false, text_draw_pos, clr, ent.begin, len); - } - else if (sbegin <= ent.begin && ent.end <= send) - { - //this string is completed selected - pencil.write_selection(text_draw_pos, ent_px, ent.begin, len, focused); - extra_space = true; - } - else - { - //a part of string is selected - - //get the selected range of this string. - auto ent_sbegin = (std::max)(sbegin, ent.begin); - auto ent_send = (std::min)(send, ent.end); - - unsigned select_pos = static_cast(ent_sbegin != ent.begin ? ent_sbegin - ent.begin : 0); - unsigned select_len = static_cast(ent_send - ent_sbegin); - -#ifdef _nana_std_has_string_view - auto pxbuf = graph_.glyph_pixels({ ent.begin, static_cast(len) }); -#else - std::unique_ptr pxbuf{ new unsigned[len] }; + std::unique_ptr pxbuf(new unsigned[len]); graph_.glyph_pixels(ent.begin, len, pxbuf.get()); #endif - - auto head_px = std::accumulate(pxbuf.get(), pxbuf.get() + select_pos, unsigned{}); - auto select_px = std::accumulate(pxbuf.get() + select_pos, pxbuf.get() + select_pos + select_len, unsigned{}); - - 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, focused); - } - else - { //LTR - _m_draw_parse_string(parser, false, text_draw_pos, clr, ent.begin, select_pos); - - 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, focused); - - if (ent_send < ent.end) - { - part_pos.x += static_cast(select_px); - _m_draw_parse_string(parser, false, part_pos, clr, ent_send, ent.end - ent_send); - } - } - - extra_space = (select_pos + select_len == text_len); + return std::accumulate(pxbuf.get() + (target - ent.begin), pxbuf.get() + len, text_w); } + //LTR + return text_w + _m_text_extent_size(ent.begin, target - ent.begin).width; } - text_draw_pos.x += static_cast(ent_px); - }//end for - } - - //extra_space is true if the end of line is selected - 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_len; - if (b.x != pos || text_coord.y != b.y) - { -#ifdef _nana_std_has_string_view - auto whitespace_w = graph_.text_extent_size(std::wstring_view{ L" ", 1 }).width; -#else - auto whitespace_w = graph_.text_extent_size(L" ", 1).width; -#endif - graph_.rectangle(::nana::rectangle{ text_draw_pos, { whitespace_w, line_h_pixels } }, true); - } - } - } - - bool text_editor::_m_update_caret_line(std::size_t secondary_before) - { - if (false == this->_m_adjust_view()) - { - if (_m_caret_to_coordinate(points_.caret).x < impl_->cview->view_area().right()) - { - _m_update_line(points_.caret.y, secondary_before); - return false; - } - } - else - { - //The content view is adjusted, now syncs it with active mode to avoid updating. - impl_->cview->sync(false); - } - impl_->try_refresh = sync_graph::refresh; - return true; - } - - unsigned text_editor::_m_char_by_pixels(const unicode_bidi::entity& ent, unsigned pos) const - { - auto len = static_cast(ent.end - ent.begin); -#ifdef _nana_std_has_string_view - auto pxbuf = graph_.glyph_pixels({ ent.begin, len }); - if (pxbuf) -#else - std::unique_ptr pxbuf(new unsigned[len]); - if (graph_.glyph_pixels(ent.begin, len, pxbuf.get())) -#endif - { - const auto px_end = pxbuf.get() + len; - - if (is_right_text(ent)) - { - auto total_px = std::accumulate(pxbuf.get(), px_end, unsigned{}); - - for (auto p = pxbuf.get(); p != px_end; ++p) - { - auto chpos = total_px - *p; - if ((chpos <= pos) && (pos < total_px)) - { - if ((*p < 2) || (pos <= chpos + (*p >> 1))) - return static_cast(p - pxbuf.get()) + 1; - - return static_cast(p - pxbuf.get()); - } - total_px = chpos; - } - } - else - { - for (auto p = pxbuf.get(); p != px_end; ++p) - { - if (pos <= *p) - { - if ((*p > 1) && (pos >(*p >> 1))) - return static_cast(p - pxbuf.get()) + 1; - return static_cast(p - pxbuf.get()); - } - pos -= *p; - } + else + text_w += _m_text_extent_size(ent.begin, len).width; } + return text_w; } - return 0; - } - - unsigned text_editor::_m_pixels_by_char(const std::wstring& lnstr, std::size_t pos) const - { - if (pos > lnstr.size()) - return 0; - - auto const reordered = unicode_reorder(lnstr.c_str(), lnstr.size()); - - auto target = lnstr.c_str() + pos; - - unsigned text_w = 0; - for (auto & ent : reordered) - { - std::size_t len = ent.end - ent.begin; - if (ent.begin <= target && target <= ent.end) - { - if (is_right_text(ent)) - { - //Characters of some bidi languages may transform in a word. - //RTL -#ifdef _nana_std_has_string_view - auto pxbuf = graph_.glyph_pixels({ent.begin, len}); -#else - std::unique_ptr pxbuf(new unsigned[len]); - graph_.glyph_pixels(ent.begin, len, pxbuf.get()); -#endif - return std::accumulate(pxbuf.get() + (target - ent.begin), pxbuf.get() + len, text_w); - } - //LTR - return text_w + _m_text_extent_size(ent.begin, target - ent.begin).width; - } - else - text_w += _m_text_extent_size(ent.begin, len).width; - } - return text_w; - } - - //end class text_editor - }//end namespace skeletons -}//end namespace widgets + //end class text_editor + }//end namespace skeletons + }//end namespace widgets }//end namespace nana diff --git a/source/gui/widgets/slider.cpp b/source/gui/widgets/slider.cpp index 20109237..572eba99 100644 --- a/source/gui/widgets/slider.cpp +++ b/source/gui/widgets/slider.cpp @@ -245,7 +245,7 @@ namespace nana }; public: enum class parts{none, bar, slider}; - + using graph_reference = drawer_trigger::graph_reference; model() @@ -256,7 +256,7 @@ namespace nana proto_.renderer = pat::cloneable{interior_renderer{}}; attr_.seek_dir = seekdir::bilateral; - + attr_.is_draw_adorn = false; attr_.vcur = 0; attr_.vmax = 10; @@ -361,7 +361,7 @@ namespace nana parts seek_where(::nana::point pos) const { nana::rectangle r = _m_bar_area(); - + if (attr_.slider.vert) { std::swap(pos.x, pos.y); @@ -373,7 +373,7 @@ namespace nana return parts::slider; sdpos = static_cast(attr_.slider.weight) / 2; - + if (sdpos <= pos.x && pos.x < sdpos + static_cast(r.width)) { if(pos.y < r.bottom()) @@ -446,7 +446,7 @@ namespace nana bool move_slider(const ::nana::point& pos) { int adorn_pos = slider_state_.snap_pos + (attr_.slider.vert ? pos.y : pos.x) - slider_state_.refpos.x; - + if (adorn_pos > 0) { int range = static_cast(_m_range()); @@ -691,7 +691,7 @@ namespace nana window wd; nana::slider * widget; }other_; - + struct prototype_tag { pat::cloneable renderer; @@ -758,6 +758,10 @@ namespace nana void trigger::mouse_move(graph_reference graph, const arg_mouse& arg) { + // check if slider is disabled + if(!API::get_widget(arg.window_handle)->enabled()) + return; // do nothing + bool updated = false; if (model_ptr_->if_trace_slider()) { @@ -800,7 +804,7 @@ namespace nana //class slider slider::slider(){} - + slider::slider(window wd, bool visible) { create(wd, rectangle(), visible); @@ -840,10 +844,14 @@ namespace nana return get_drawer_trigger().get_model()->attribute().vmax; } - void slider::value(unsigned v) + void slider::value(int v) { if(handle()) { + // limit to positive values, vcur expects unsigned + if( v < 0 ) + v = 0; + if(get_drawer_trigger().get_model()->vcur(v)) API::refresh_window(handle()); } diff --git a/source/gui/widgets/spinbox.cpp b/source/gui/widgets/spinbox.cpp index 6ed1f3ed..4a631c2c 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-2017 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -467,7 +467,7 @@ namespace nana std::wstring text; - if (API::is_focus_ready(editor_->window_handle())) + if (API::is_focus_ready(editor_->window_handle()) && editor_->attr().editable) text = to_wstring(range_->value()); else text = to_wstring(modifier_.prefix + range_->value() + modifier_.suffix); diff --git a/source/gui/widgets/tabbar.cpp b/source/gui/widgets/tabbar.cpp index 8970089b..cf58e0f6 100644 --- a/source/gui/widgets/tabbar.cpp +++ b/source/gui/widgets/tabbar.cpp @@ -492,9 +492,17 @@ namespace nana return (trace_.what != trace_.null); } - bool active_by_trace() + bool active_by_trace(const arg_mouse& arg) { - return ((trace_.what == trace_.item) && (trace_.item_part != trace_.close)? activate(trace_.u.index) : false); + if((trace_.what == trace_.item) && (trace_.item_part != trace_.close)) + { + if(false == evt_agent_->click(arg, trace_.u.index)) + return activate(trace_.u.index); + + return true; + } + + return false; } bool release() @@ -751,15 +759,27 @@ namespace nana if((pos == npos) || (pos >= list_.size())) { pos = list_.size(); + + if(evt_agent_) + if(!evt_agent_->adding(pos)) + return false; + this->list_.emplace_back(); } else + { + if(evt_agent_) + if(!evt_agent_->adding(pos)) + return false; + list_.emplace(iterator_at(pos)); + } basis_.active = pos; if(evt_agent_) { evt_agent_->added(pos); + erase(pos); evt_agent_->activated(pos); } return true; @@ -1273,7 +1293,7 @@ namespace nana { if(layouter_->press()) { - if(false == layouter_->active_by_trace()) + if(false == layouter_->active_by_trace(arg)) layouter_->toolbox_answer(arg); layouter_->render(); API::dev::lazy_refresh(); diff --git a/source/gui/widgets/textbox.cpp b/source/gui/widgets/textbox.cpp index 96c88d6c..6ea644e2 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-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -233,31 +233,31 @@ namespace drawerbase { create(wd, r, visible); } - void textbox::load(std::string file) + void textbox::load(const std::filesystem::path& file) { internal_scope_guard lock; auto editor = get_drawer_trigger().editor(); - if (editor && editor->load(file.data())) + if (editor && editor->load(file)) { if (editor->try_refresh()) API::update_window(handle()); } } - void textbox::store(std::string file) + void textbox::store(const std::filesystem::path& file) { internal_scope_guard lock; auto editor = get_drawer_trigger().editor(); if (editor) - editor->textbase().store(std::move(file), false, nana::unicode::utf8); //3rd parameter is just for syntax, it will be ignored + editor->textbase().store(file, false, nana::unicode::utf8); //3rd parameter is just for syntax, it will be ignored } - void textbox::store(std::string file, nana::unicode encoding) + void textbox::store(const std::filesystem::path& file, nana::unicode encoding) { internal_scope_guard lock; auto editor = get_drawer_trigger().editor(); if (editor) - editor->textbase().store(std::move(file), true, encoding); + editor->textbase().store(file, true, encoding); } textbox::colored_area_access_interface* textbox::colored_area_access() @@ -300,7 +300,8 @@ namespace drawerbase { if (end_caret) editor->move_caret_end(true); - editor->textbase().reset(); + //Reset the edited status and the saved filename + editor->textbase().reset_status(false); if (editor->try_refresh()) API::update_window(this->handle()); @@ -308,7 +309,7 @@ namespace drawerbase { return *this; } - std::string textbox::filename() const + textbox::path_type textbox::filename() const { internal_scope_guard lock; auto editor = get_drawer_trigger().editor(); @@ -330,7 +331,7 @@ namespace drawerbase { internal_scope_guard lock; auto editor = get_drawer_trigger().editor(); if (editor) - editor->textbase().edited_reset(); + editor->textbase().reset_status(true); return *this; } @@ -584,7 +585,16 @@ namespace drawerbase { { internal_scope_guard lock; auto editor = get_drawer_trigger().editor(); - if(editor && editor->select(yes)) + if (editor && editor->select(yes)) + API::refresh_window(*this); + } + + + void textbox::select_points(nana::upoint arg_a, nana::upoint arg_b) + { + auto editor = get_drawer_trigger().editor(); + internal_scope_guard lock; + if (editor && editor->select_points(arg_a, arg_b)) API::refresh_window(*this); } @@ -661,7 +671,7 @@ namespace drawerbase { void textbox::clear_undo() { - get_drawer_trigger().editor()->clear_undo(); + get_drawer_trigger().editor()->undo_clear(); } void textbox::set_highlight(const std::string& name, const ::nana::color& fgcolor, const ::nana::color& bgcolor) @@ -782,7 +792,7 @@ namespace drawerbase { internal_scope_guard lock; auto editor = get_drawer_trigger().editor(); if (editor) - editor->set_undo_queue_length(len); + editor->undo_max_steps(len); } std::size_t textbox::display_line_count() const noexcept diff --git a/source/gui/widgets/treebox.cpp b/source/gui/widgets/treebox.cpp index 3cbf61b7..1d1a6c13 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-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -180,13 +180,13 @@ namespace nana } };//end class tooltip_window - //item_locator should be defined before the definition of basic_implement + //item_locator should be defined before the definition of implementation class trigger::item_locator { public: using node_type = tree_cont_type::node_type; - item_locator(implement * impl, int item_pos, int x, int y); + item_locator(implementation * impl, int item_pos, int x, int y); int operator()(node_type &node, int affect); node_type * node() const; component what() const; @@ -194,7 +194,7 @@ namespace nana nana::rectangle text_pos() const; private: - trigger::implement * impl_; + implementation * const impl_; nana::point item_pos_; const nana::point pos_; //Mouse pointer position component what_; @@ -212,17 +212,96 @@ namespace nana } }; - //struct implement + //struct implementation //@brief: some data for treebox trigger - template - struct trigger::basic_implement + class trigger::implementation { + class item_rendering_director + : public compset_interface + { + public: + using node_type = tree_cont_type::node_type; + + item_rendering_director(implementation * impl, const nana::point& pos): + impl_(impl), + pos_(pos) + { + } + + //affect + //0 = Sibling, the last is a sibling of node + //1 = Owner, the last is the owner of node + //>=2 = Children, the last is a child of a node that before this node. + int operator()(const node_type& node, int affect) + { + iterated_node_ = &node; + switch (affect) + { + case 1: + pos_.x += impl_->data.scheme_ptr->indent_displacement; + break; + default: + if (affect >= 2) + pos_.x -= impl_->data.scheme_ptr->indent_displacement * (affect - 1); + } + + auto & comp_placer = impl_->data.comp_placer; + + impl_->assign_node_attr(node_attr_, iterated_node_); + node_r_.x = node_r_.y = 0; + node_r_.width = comp_placer->item_width(*impl_->data.graph, node_attr_); + node_r_.height = comp_placer->item_height(*impl_->data.graph); + + auto renderer = impl_->data.renderer; + renderer->begin_paint(*impl_->data.widget_ptr); + renderer->bground(*impl_->data.graph, this); + renderer->expander(*impl_->data.graph, this); + renderer->crook(*impl_->data.graph, this); + renderer->icon(*impl_->data.graph, this); + renderer->text(*impl_->data.graph, this); + + pos_.y += node_r_.height; + + if (pos_.y > static_cast(impl_->data.graph->height())) + return 0; + + return (node.child && node.value.second.expanded ? 1 : 2); + } + private: + //Overrides compset_interface + virtual const item_attribute_t& item_attribute() const override + { + return node_attr_; + } + + virtual bool comp_attribute(component_t comp, comp_attribute_t& attr) const override + { + attr.area = node_r_; + if (impl_->data.comp_placer->locate(comp, node_attr_, &attr.area)) + { + attr.mouse_pointed = node_attr_.mouse_pointed; + attr.area.x += pos_.x; + attr.area.y += pos_.y; + return true; + } + return false; + } + private: + implementation * const impl_; + ::nana::point pos_; + const node_type * iterated_node_; + item_attribute_t node_attr_; + ::nana::rectangle node_r_; + }; + + public: using node_type = trigger::node_type; struct rep_tag { nana::paint::graphics * graph; ::nana::treebox * widget_ptr; + ::nana::treebox::scheme_type* scheme_ptr; trigger * trigger_ptr; pat::cloneable comp_placer; @@ -232,13 +311,11 @@ namespace nana struct shape_tag { - nana::upoint border; std::shared_ptr> scroll; mutable std::map image_table; tree_cont_type::node_type * first; //The node at the top of screen - int indent_pixels; int offset_x; }shape; @@ -271,14 +348,13 @@ namespace nana nana::timer timer; }adjust; public: - basic_implement() + implementation() { data.graph = nullptr; data.widget_ptr = nullptr; data.stop_drawing = false; shape.first = nullptr; - shape.indent_pixels = 10; shape.offset_x = 0; shape.scroll = std::make_shared>(); @@ -353,6 +429,11 @@ namespace nana return true; } + static constexpr unsigned margin_top_bottom() + { + return 1; + } + bool draw(bool reset_scroll, bool ignore_update = false, bool ignore_auto_draw = false) { if(data.graph && (false == data.stop_drawing)) @@ -368,7 +449,7 @@ namespace nana 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))); + attr.tree_cont.for_each(shape.first, item_rendering_director(this, nana::point(static_cast(attr.tree_cont.indent_size(shape.first) * data.scheme_ptr->indent_displacement) - shape.offset_x, margin_top_bottom()))); if (!ignore_update) API::update_window(data.widget_ptr->handle()); @@ -460,6 +541,128 @@ namespace nana return nullptr; } + node_type* last(bool ignore_folded_children) const + { + auto p = attr.tree_cont.get_root(); + + while (true) + { + while (p->next) + p = p->next; + + if (p->child) + { + if (p->value.second.expanded || !ignore_folded_children) + { + p = p->child; + continue; + } + } + + break; + } + + return p; + } + + std::size_t screen_capacity(bool completed) const + { + auto const item_px = data.comp_placer->item_height(*data.graph); + auto screen_px = data.graph->size().height - (margin_top_bottom() << 1); + + if (completed || ((screen_px % item_px) == 0)) + return screen_px / item_px; + + return screen_px / item_px + 1; + } + + bool scroll_into_view(node_type* node, bool use_bearing, align_v bearing) + { + auto & tree = attr.tree_cont; + + auto parent = node->owner; + + std::vector parent_path; + while (parent) + { + parent_path.push_back(parent); + parent = parent->owner; + } + + bool has_expanded = false; + + //Expands the shrinked nodes which are ancestors of node + for (auto i = parent_path.rbegin(); i != parent_path.rend(); ++i) + { + if (!(*i)->value.second.expanded) + { + has_expanded = true; + (*i)->value.second.expanded = true; + item_proxy iprx(data.trigger_ptr, *i); + data.widget_ptr->events().expanded.emit(::nana::arg_treebox{ *data.widget_ptr, iprx, true }, data.widget_ptr->handle()); + } + } + + auto pos = tree.distance_if(node, pred_allow_child{}); + auto last_pos = tree.distance_if(last(true), pred_allow_child{}); + + auto const capacity = screen_capacity(true); + + //If use_bearing is false, it calculates a bearing depending on the current + //position of the requested item. + if (!use_bearing) + { + auto first_pos = tree.distance_if(shape.first, pred_allow_child{}); + + if (pos < first_pos) + bearing = align_v::top; + else if (pos >= first_pos + capacity) + bearing = align_v::bottom; + else + { + //The item is already in the view. + //Returns true if a draw operation is needed + return has_expanded; + } + } + + if (align_v::top == bearing) + { + if (last_pos - pos + 1 < capacity) + { + if (last_pos + 1 >= capacity) + pos = last_pos + 1 - capacity; + else + pos = 0; + } + } + else if (align_v::center == bearing) + { + auto const short_side = (std::min)(pos, last_pos - pos); + if (short_side >= capacity / 2) + pos -= capacity / 2; + else if (short_side == pos || (last_pos + 1 < capacity)) + pos = 0; + else + pos = last_pos + 1 - capacity; + } + else if (align_v::bottom == bearing) + { + if (pos + 1 >= capacity) + pos = pos + 1 - capacity; + else + pos = 0; + } + + auto prv_first = shape.first; + shape.first = attr.tree_cont.advance_if(nullptr, pos, drawerbase::treebox::pred_allow_child{}); + + //Update the position of scroll + show_scroll(); + + return has_expanded || (prv_first != shape.first); + } + bool make_adjust(node_type * node, int reason) { if(!node) return false; @@ -506,7 +709,7 @@ namespace nana case 3: //param is the begin pos of an item in absolute. { - int beg = static_cast(tree.indent_size(node) * shape.indent_pixels) - shape.offset_x; + int beg = static_cast(tree.indent_size(node) * data.scheme_ptr->indent_displacement) - shape.offset_x; int end = beg + static_cast(node_w_pixels(node)); bool take_adjust = false; @@ -705,7 +908,7 @@ namespace nana bool track_mouse(int x, int y) { - int xpos = attr.tree_cont.indent_size(shape.first) * shape.indent_pixels - shape.offset_x; + int xpos = attr.tree_cont.indent_size(shape.first) * data.scheme_ptr->indent_displacement - shape.offset_x; item_locator nl(this, xpos, x, y); attr.tree_cont.template for_each(shape.first, nl); @@ -1127,8 +1330,10 @@ namespace nana class internal_placer : public compset_placer_interface { - static const unsigned item_offset = 16; - static const unsigned text_offset = 4; + public: + internal_placer(const scheme& schm): + scheme_(schm) + {} private: //Implement the compset_locator_interface @@ -1137,10 +1342,10 @@ namespace nana switch(comp) { case component_t::crook: - pixels_crook_ = (enabled ? 16 : 0); + enable_crook_ = enabled; break; case component_t::icon: - pixels_icon_ = (enabled ? 16 : 0); + enable_icon_ = enabled; break; default: break; @@ -1152,9 +1357,9 @@ namespace nana switch(comp) { case component_t::crook: - return (0 != pixels_crook_); + return enable_crook_; case component_t::icon: - return (0 != pixels_icon_); + return enable_icon_; default: break; } @@ -1163,16 +1368,16 @@ namespace nana virtual unsigned item_height(graph_reference graph) const override { -#ifdef _nana_std_has_string_view - return graph.text_extent_size(std::wstring_view{ L"jH{", 3 }).height + 8; -#else - return graph.text_extent_size(L"jH{", 3).height + 8; -#endif + auto m = std::max((enable_crook_ ? scheme_.crook_size : 0), (enable_icon_ ? scheme_.icon_size : 0)); + + unsigned as = 0, ds = 0, il; + graph.text_metrics(as, ds, il); + return std::max(as + ds + 8, m); } virtual unsigned item_width(graph_reference graph, const item_attribute_t& attr) const override { - return graph.text_extent_size(attr.text).width + pixels_crook_ + pixels_icon_ + (text_offset << 1) + item_offset; + return graph.text_extent_size(attr.text).width + (enable_crook_ ? scheme_.crook_size : 0) + (enable_icon_ ? scheme_.icon_size : 0) + (scheme_.text_offset << 1) + scheme_.item_offset; } // Locate a component through the specified coordinate. @@ -1187,35 +1392,35 @@ namespace nana case component_t::expander: if(attr.has_children) { - r->width = item_offset; + r->width = scheme_.item_offset; return true; } return false; case component_t::bground: return true; case component_t::crook: - if(pixels_crook_) + if(enable_crook_) { - r->x += item_offset; - r->width = pixels_crook_; + r->x += scheme_.item_offset; + r->width = scheme_.crook_size; return true; } return false; case component_t::icon: - if(pixels_icon_) + if(enable_icon_) { - r->x += item_offset + pixels_crook_; + r->x += scheme_.item_offset + (enable_crook_ ? scheme_.crook_size : 0); r->y = 2; - r->width = pixels_icon_; + r->width = scheme_.icon_size; r->height -= 2; return true; } return false; case component_t::text: { - auto text_pos = item_offset + pixels_crook_ + pixels_icon_ + text_offset; + auto text_pos = scheme_.item_offset + (enable_crook_ ? scheme_.crook_size : 0) + (enable_icon_ ? scheme_.icon_size : 0) + scheme_.text_offset; r->x += text_pos; - r->width -= (text_pos + text_offset); + r->width -= (text_pos + scheme_.text_offset); }; return true; default: @@ -1224,8 +1429,9 @@ namespace nana return false; } private: - unsigned pixels_crook_{0}; - unsigned pixels_icon_{0}; + const scheme& scheme_; + bool enable_crook_{ false }; + bool enable_icon_{ false }; }; class internal_renderer @@ -1244,37 +1450,42 @@ namespace nana if(compset->comp_attribute(component::bground, attr)) { - const ::nana::color color_table[][2] = { { { 0xE8, 0xF5, 0xFD }, { 0xD8, 0xF0, 0xFA } }, //highlighted - { { 0xC4, 0xE8, 0xFA }, { 0xB6, 0xE6, 0xFB } }, //Selected and highlighted - { { 0xD5, 0xEF, 0xFC }, {0x99, 0xDE, 0xFD } } //Selected but not highlighted - }; + auto scheme_ptr = static_cast<::nana::treebox::scheme_type*>(API::dev::get_scheme(window_handle_)); - const ::nana::color *clrptr = nullptr; + const ::nana::color_proxy *bg_ptr = nullptr, *fg_ptr = nullptr; if(compset->item_attribute().mouse_pointed) { if(compset->item_attribute().selected) - clrptr = color_table[1]; + { + bg_ptr = &scheme_ptr->item_bg_selected_and_highlighted; + fg_ptr = &scheme_ptr->item_fg_selected_and_highlighted; + } else - clrptr = color_table[0]; + { + bg_ptr = &scheme_ptr->item_bg_highlighted; + fg_ptr = &scheme_ptr->item_fg_highlighted; + } } else if(compset->item_attribute().selected) - clrptr = color_table[2]; + { + bg_ptr = &scheme_ptr->item_bg_selected; + fg_ptr = &scheme_ptr->item_fg_selected; + } - if (clrptr) + if(bg_ptr) { 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); - + item_graph.rectangle(false, *fg_ptr); + item_graph.rectangle(rectangle{ attr.area.dimension() }.pare_off(1), true, *bg_ptr); 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); + graph.rectangle(attr.area, false, *fg_ptr); + graph.rectangle(attr.area.pare_off(1), true, *bg_ptr); } } } @@ -1317,15 +1528,16 @@ namespace nana { const nana::paint::image * img = nullptr; auto & item_attr = compset->item_attribute(); - if (item_attr.mouse_pointed) - img = &(item_attr.icon_hover); - else if (item_attr.expended) - img = &(item_attr.icon_expanded); + if (item_attr.expended) + img = &(item_attr.icon_expanded); + else if (item_attr.mouse_pointed) + img = &(item_attr.icon_hover); + if((nullptr == img) || img->empty()) img = &(item_attr.icon_normal); - if(! img->empty()) + if(!img->empty()) { auto size = img->size(); if(size.width > attr.area.width || size.height > attr.area.height) @@ -1356,7 +1568,7 @@ namespace nana //class trigger::item_locator - trigger::item_locator::item_locator(implement * impl, int item_pos, int x, int y) + trigger::item_locator::item_locator(implementation * impl, int item_pos, int x, int y) : impl_(impl), item_pos_(item_pos, 1), pos_(x, y), @@ -1366,15 +1578,13 @@ namespace nana int trigger::item_locator::operator()(node_type &node, int affect) { - auto & node_desc = impl_->shape; - switch(affect) { case 0: break; - case 1: item_pos_.x += static_cast(node_desc.indent_pixels); break; + case 1: item_pos_.x += static_cast(impl_->data.scheme_ptr->indent_displacement); break; default: if(affect >= 2) - item_pos_.x -= static_cast(node_desc.indent_pixels) * (affect - 1); + item_pos_.x -= static_cast(impl_->data.scheme_ptr->indent_displacement) * (affect - 1); } impl_->assign_node_attr(node_attr_, &node); @@ -1439,86 +1649,6 @@ namespace nana return{node_text_r_.x + item_pos_.x, node_text_r_.y + item_pos_.y, node_text_r_.width, node_text_r_.height}; } //end class item_locator - - class trigger::item_renderer - : public compset_interface - { - public: - typedef tree_cont_type::node_type node_type; - - item_renderer(implement * impl, const nana::point& pos) - : impl_(impl), - pos_(pos) - { - } - - //affect - //0 = Sibling, the last is a sibling of node - //1 = Owner, the last is the owner of node - //>=2 = Children, the last is a child of a node that before this node. - int operator()(const node_type& node, int affect) - { - implement * draw_impl = impl_; - - iterated_node_ = &node; - switch(affect) - { - case 1: - pos_.x += draw_impl->shape.indent_pixels; - break; - default: - if(affect >= 2) - pos_.x -= draw_impl->shape.indent_pixels * (affect - 1); - } - - auto & comp_placer = impl_->data.comp_placer; - - impl_->assign_node_attr(node_attr_, iterated_node_); - node_r_.x = node_r_.y = 0; - node_r_.width = comp_placer->item_width(*impl_->data.graph, node_attr_); - node_r_.height = comp_placer->item_height(*impl_->data.graph); - - auto renderer = draw_impl->data.renderer; - 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); - renderer->icon(*draw_impl->data.graph, this); - renderer->text(*draw_impl->data.graph, this); - - pos_.y += node_r_.height; - - if(pos_.y > static_cast(draw_impl->data.graph->height())) - return 0; - - return (node.child && node.value.second.expanded ? 1 : 2); - } - private: - //Overrides compset_interface - virtual const item_attribute_t& item_attribute() const override - { - return node_attr_; - } - - virtual bool comp_attribute(component_t comp, comp_attribute_t& attr) const override - { - attr.area = node_r_; - if (impl_->data.comp_placer->locate(comp, node_attr_, &attr.area)) - { - attr.mouse_pointed = node_attr_.mouse_pointed; - attr.area.x += pos_.x; - attr.area.y += pos_.y; - return true; - } - return false; - } - private: - trigger::implement * impl_; - ::nana::point pos_; - const node_type * iterated_node_; - item_attribute_t node_attr_; - ::nana::rectangle node_r_; - }; } //Treebox Implementation @@ -1548,11 +1678,10 @@ namespace nana //end struct treebox_node_type trigger::trigger() - : impl_(new implement) + : impl_(new implementation) { impl_->data.trigger_ptr = this; impl_->data.renderer = nana::pat::cloneable(internal_renderer()); - impl_->data.comp_placer = nana::pat::cloneable(internal_placer()); impl_->adjust.timer.elapse([this] { @@ -1615,7 +1744,7 @@ namespace nana delete impl_; } - trigger::implement * trigger::impl() const + trigger::implementation * trigger::impl() const { return impl_; } @@ -1687,12 +1816,7 @@ namespace nana } } - void trigger::renderer(::nana::pat::cloneable&& r) - { - impl_->data.renderer = std::move(r); - } - - const ::nana::pat::cloneable& trigger::renderer() const + ::nana::pat::cloneable& trigger::renderer() const { return impl_->data.renderer; } @@ -1729,18 +1853,7 @@ namespace nana return x; } - trigger::node_type* trigger::selected() const - { - return impl_->node_state.selected; - } - - void trigger::selected(node_type* node) - { - if(impl_->attr.tree_cont.verify(node) && impl_->set_selected(node)) - impl_->draw(true); - } - - node_image_tag& trigger::icon(const std::string& id) const + node_image_tag& trigger::icon(const std::string& id) { auto i = impl_->shape.image_table.find(id); if(i != impl_->shape.image_table.end()) @@ -1805,12 +1918,17 @@ namespace nana impl_->data.graph = &graph; widget.bgcolor(colors::white); - impl_->data.widget_ptr = static_cast< ::nana::treebox*>(&widget); + impl_->data.widget_ptr = static_cast<::nana::treebox*>(&widget); + impl_->data.scheme_ptr = static_cast<::nana::treebox::scheme_type*>(API::dev::get_scheme(widget)); + impl_->data.comp_placer = nana::pat::cloneable(internal_placer{ *impl_->data.scheme_ptr }); + widget.caption("nana treebox"); } void trigger::detached() { + //Reset the comp_placer, because after deteching, the scheme refered by comp_placer will be released + impl_->data.comp_placer.reset(); impl_->data.graph = nullptr; } @@ -1824,7 +1942,7 @@ namespace nana { auto & shape = impl_->shape; - int xpos = impl_->attr.tree_cont.indent_size(shape.first) * shape.indent_pixels - shape.offset_x; + int xpos = impl_->attr.tree_cont.indent_size(shape.first) * impl_->data.scheme_ptr->indent_displacement - shape.offset_x; item_locator nl(impl_, xpos, arg.pos.x, arg.pos.y); impl_->attr.tree_cont.for_each(shape.first, nl); @@ -1849,7 +1967,7 @@ namespace nana { auto & shape = impl_->shape; - int xpos = impl_->attr.tree_cont.indent_size(shape.first) * shape.indent_pixels - shape.offset_x; + int xpos = impl_->attr.tree_cont.indent_size(shape.first) * impl_->data.scheme_ptr->indent_displacement - shape.offset_x; item_locator nl(impl_, xpos, arg.pos.x, arg.pos.y); impl_->attr.tree_cont.for_each(shape.first, nl); @@ -1863,7 +1981,7 @@ namespace nana } else if (node_state.selected != node_state.pressed_node) { - impl_->set_selected(node_state.pressed_node); + //impl_->set_selected(node_state.pressed_node); // todo: emit selected after checked } else return; @@ -1876,7 +1994,7 @@ namespace nana { auto & shape = impl_->shape; - int xpos = impl_->attr.tree_cont.indent_size(shape.first) * shape.indent_pixels - shape.offset_x; + int xpos = impl_->attr.tree_cont.indent_size(shape.first) * impl_->data.scheme_ptr->indent_displacement - shape.offset_x; item_locator nl(impl_, xpos, arg.pos.x, arg.pos.y); impl_->attr.tree_cont.for_each(shape.first, nl); @@ -1886,27 +2004,23 @@ namespace nana if(!nl.node()) return; - if (pressed_node == nl.node()) - { - 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; + if (pressed_node != nl.node()) + return; //Do not refresh - check(nl.node(), cs); - } - else - return; //Do not refresh + if (nl.what() == component::crook) + { + checkstate cs = checkstate::unchecked; + if (checkstate::unchecked == nl.node()->value.second.checked) + cs = checkstate::checked; + + check(nl.node(), cs); + } + if ((impl_->node_state.selected != nl.node()) && (nl.item_body() || nl.what() == component::crook)) + { + impl_->set_selected(nl.node()); + if (impl_->make_adjust(impl_->node_state.selected, 1)) + impl_->adjust.scroll_timestamp = 1; } - else - return; //Don't refresh impl_->draw(true); API::dev::lazy_refresh(); @@ -2167,7 +2281,7 @@ namespace nana impl->draw(true); } - treebox::node_image_type& treebox::icon(const std::string& id) const + treebox::node_image_type& treebox::icon(const std::string& id) { return get_drawer_trigger().icon(id); } @@ -2233,7 +2347,23 @@ namespace nana treebox::item_proxy treebox::selected() const { - return item_proxy(const_cast(&get_drawer_trigger()), get_drawer_trigger().selected()); + auto dw = &get_drawer_trigger(); + return item_proxy(const_cast(dw), dw->impl()->node_state.selected); + } + + void treebox::scroll_into_view(item_proxy item, align_v bearing) + { + internal_scope_guard lock; + if(get_drawer_trigger().impl()->scroll_into_view(item._m_node(), true, bearing)) + API::refresh_window(*this); + } + + void treebox::scroll_into_view(item_proxy item) + { + internal_scope_guard lock; + //The third argument for scroll_into_view is ignored if the second argument is false. + if(get_drawer_trigger().impl()->scroll_into_view(item._m_node(), false, align_v::center)) + API::refresh_window(*this); } std::shared_ptr treebox::_m_scroll_operation() diff --git a/source/internationalization.cpp b/source/internationalization.cpp index d41b775c..ba3ffcd6 100644 --- a/source/internationalization.cpp +++ b/source/internationalization.cpp @@ -193,9 +193,10 @@ namespace nana 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_INVALID_FILENAME"] = "\"%arg0\"\nThe 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_DIRECTORY_NOT_EXISTING_AND_RETRY"] = "The directory \"%arg0\"\n is not existing. Please check and retry."; + table["NANA_FILEBOX_ERROR_DIRECTORY_INVALID"] = "The directory \"%arg0\"\n is invalid. Please check and retry."; table["NANA_FILEBOX_ERROR_QUERY_REWRITE_BECAUSE_OF_EXISTING"] = "The input file is existing, do you want to overwrite it?"; } }; diff --git a/source/paint/detail/image_bmp.hpp b/source/paint/detail/image_bmp.hpp index e3893c61..ac25812e 100644 --- a/source/paint/detail/image_bmp.hpp +++ b/source/paint/detail/image_bmp.hpp @@ -111,11 +111,11 @@ namespace nana{ namespace paint return true; } - bool open(const std::experimental::filesystem::path& filename) override + bool open(const std::filesystem::path& filename) override { std::ifstream ifs(filename.string(), std::ios::binary); - auto const bytes = static_cast(std::experimental::filesystem::file_size(filename)); + auto const bytes = static_cast(std::filesystem::file_size(filename)); if (ifs && (bytes > static_cast(sizeof(bitmap_file_header)))) { std::unique_ptr buffer{ new char[bytes] }; diff --git a/source/paint/detail/image_ico.hpp b/source/paint/detail/image_ico.hpp index 4f78cad6..ffa4d693 100644 --- a/source/paint/detail/image_ico.hpp +++ b/source/paint/detail/image_ico.hpp @@ -241,7 +241,7 @@ public: #endif } - bool open(const std::experimental::filesystem::path& ico_file) override + bool open(const std::filesystem::path& ico_file) override { std::ifstream file(ico_file.string(), std::ios::binary); if (!file.is_open()) return false; @@ -290,7 +290,7 @@ public: #endif } private: - std::experimental::filesystem::path path_; + std::filesystem::path path_; #if defined(NANA_WINDOWS) void* native_handle_{nullptr}; #endif diff --git a/source/paint/detail/image_ico_resource.hpp b/source/paint/detail/image_ico_resource.hpp index dd451bf0..f7e85818 100644 --- a/source/paint/detail/image_ico_resource.hpp +++ b/source/paint/detail/image_ico_resource.hpp @@ -29,7 +29,7 @@ namespace nana{ namespace paint :public image::image_impl_interface { public: - bool open(const std::experimental::filesystem::path& filename) override + bool open(const std::filesystem::path& filename) override { #if defined(NANA_WINDOWS) SHFILEINFO sfi; diff --git a/source/paint/detail/image_jpeg.hpp b/source/paint/detail/image_jpeg.hpp index d63a2c1d..0b1ecd2c 100644 --- a/source/paint/detail/image_jpeg.hpp +++ b/source/paint/detail/image_jpeg.hpp @@ -47,7 +47,7 @@ namespace nana } } public: - bool open(const std::experimental::filesystem::path& jpeg_file) override + bool open(const std::filesystem::path& jpeg_file) override { auto fp = ::fopen(to_osmbstr(to_utf8(jpeg_file.native())).c_str(), "rb"); if(nullptr == fp) return false; diff --git a/source/paint/detail/image_png.hpp b/source/paint/detail/image_png.hpp index f3cff150..91dd8e2b 100644 --- a/source/paint/detail/image_png.hpp +++ b/source/paint/detail/image_png.hpp @@ -123,7 +123,7 @@ namespace nana delete[] row_ptrs; } public: - bool open(const std::experimental::filesystem::path& png_file) override + bool open(const std::filesystem::path& png_file) override { auto fp = ::fopen(to_osmbstr(to_utf8(png_file.native())).c_str(), "rb"); if(nullptr == fp) return false; diff --git a/source/paint/detail/image_process_provider.cpp b/source/paint/detail/image_process_provider.cpp index 414d68dc..89324e04 100644 --- a/source/paint/detail/image_process_provider.cpp +++ b/source/paint/detail/image_process_provider.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include "image_processor.hpp" namespace nana { diff --git a/include/nana/paint/detail/image_processor.hpp b/source/paint/detail/image_processor.hpp similarity index 87% rename from include/nana/paint/detail/image_processor.hpp rename to source/paint/detail/image_processor.hpp index 3832456f..1c706b94 100644 --- a/include/nana/paint/detail/image_processor.hpp +++ b/source/paint/detail/image_processor.hpp @@ -1,7 +1,7 @@ /* * Image Processor Algorithm Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -15,8 +15,8 @@ #ifndef NANA_PAINT_DETAIL_IMAGE_PROCESSOR_HPP #define NANA_PAINT_DETAIL_IMAGE_PROCESSOR_HPP -#include "../image_process_interface.hpp" #include +#include #include #include @@ -421,15 +421,19 @@ namespace detail { virtual void process(paint::pixel_buffer & pixbuf, const nana::point& pos_beg, const nana::point& pos_end, const ::nana::color& clr, double fade_rate) const { + //Return if it is completely transparent + if (fade_rate <= 0) + return; + auto rgb_color = clr.px_color().value; const std::size_t bytes_pl = pixbuf.bytes_per_line(); unsigned char * fade_table = nullptr; std::unique_ptr autoptr; nana::pixel_argb_t rgb_imd = {}; - if(fade_rate != 0.0) + if(fade_rate < 1) { - autoptr = detail::alloc_fade_table(1 - fade_rate); + autoptr = detail::alloc_fade_table(1.0 - fade_rate); fade_table = autoptr.get(); rgb_imd.value = rgb_color; rgb_imd = detail::fade_color_intermedia(rgb_imd, fade_table); @@ -551,55 +555,65 @@ namespace detail { void process(pixel_buffer& pixbuf, const nana::rectangle& area, std::size_t u_radius) const { - int radius = static_cast(u_radius); - int w = area.width; - int h = area.height; - int wm = w - 1; - int hm = h - 1; - int wh = w * h; - int div = (radius << 1) + 1; + const int radius = static_cast(u_radius); + const int safe_radius = std::min(radius, static_cast(area.height) - 2); + const int radius_plus_one = radius + 1; - int large_edge = (w > h ? w : h); - const int div_256 = div * 256; + const int width_3times = static_cast(area.width * 3); + const int wm = area.width - 1; + const int hm = area.height - 1; + const int wh = area.width * area.height; + const int div = (radius << 1) + 1; - std::unique_ptr all_table(new int[(wh << 1) + wh + (large_edge << 1) + div_256]); + const int large_edge = std::max(area.width, area.height); + std::unique_ptr table_rgb(new int[wh * 3 + (large_edge << 1) + div * 256]); - int * r = all_table.get(); - int * g = r + wh; - int * b = g + wh; - - int * vmin = b + wh; - int * vmax = vmin + large_edge; + int * tbl_rgb = table_rgb.get(); + int * const vmin = tbl_rgb + 3 * wh; + int * const vmax = vmin + large_edge; int * dv = vmax + large_edge; - int end_div = div - 1; - for(int i = 0, *dv_block = dv; i < 256; ++i) + + for (int i = 0; i < 256; ++i) { - for(int u = 0; u < end_div; u += 2) + dv[0] = i; + for (int u = 1; u < div; u += 2) { - dv_block[u] = i; - dv_block[u + 1] = i; + dv[u] = i; + dv[u + 1] = i; } - dv_block[div - 1] = i; - dv_block += div; + dv += div; } + dv = vmax + large_edge; + auto linepix = pixbuf.raw_ptr(area.y) + area.x; - int yi = 0; - for(int y = 0; y < h; ++y) + for(int x = 0; x < static_cast(area.width); ++x) + { + vmin[x] = std::min(x + radius_plus_one, wm); + vmax[x] = std::max(x - radius, 0); + } + + for(int y = 0; y < static_cast(area.height); ++y) { int sum_r = 0, sum_g = 0, sum_b = 0; if(radius <= wm) { - for(int i = - radius; i <= radius; ++i) + auto px = linepix; + + sum_r = int(px->element.red) * radius_plus_one; + sum_g = int(px->element.blue) * radius_plus_one; + sum_b = int(px->element.blue) * radius_plus_one; + + auto radius_px_end = px + radius_plus_one; + for (++px; px < radius_px_end; ++px) { - auto px = linepix[(i > 0 ? i : 0)]; - sum_r += px.element.red; - sum_g += px.element.green; - sum_b += px.element.blue; - } + sum_r += px->element.red; + sum_g += px->element.green; + sum_b += px->element.blue; + } } else { @@ -612,75 +626,62 @@ namespace detail } } - for(int x = 0; x < w; ++x) + for(int x = 0; x < static_cast(area.width); ++x) { - r[yi] = dv[sum_r]; - g[yi] = dv[sum_g]; - b[yi] = dv[sum_b]; + tbl_rgb[0] = dv[sum_r]; + tbl_rgb[1] = dv[sum_g]; + tbl_rgb[2] = dv[sum_b]; + tbl_rgb += 3; - if(0 == y) - { - vmin[x] = std::min(x + radius + 1, wm); - vmax[x] = std::max(x - radius, 0); - } - - auto p1 = linepix[vmin[x]]; - auto p2 = linepix[vmax[x]]; + auto& p1 = linepix[vmin[x]]; + auto& p2 = linepix[vmax[x]]; sum_r += p1.element.red - p2.element.red; sum_g += p1.element.green - p2.element.green; sum_b += p1.element.blue - p2.element.blue; - ++yi; } linepix = pixbuf.raw_ptr(area.y + y) + area.x; } - const int yp_init = -radius * w; - const std::size_t bytes_pl = pixbuf.bytes_per_line(); - for(int x = 0; x < w; ++x) - { - int sum_r = 0, sum_g = 0, sum_b = 0; - int yp = yp_init; - for(int i = -radius; i <= radius; ++i) + tbl_rgb = table_rgb.get(); + + for (int y = 0; y < static_cast(area.height); ++y) + { + vmin[y] = std::min(y + radius_plus_one, hm) * width_3times; + vmax[y] = std::max(y - radius, 0) * width_3times; + } + + for(int x = 0; x < static_cast(area.width); ++x) + { + int sum_r = int(tbl_rgb[0]) * radius_plus_one; + int sum_g = int(tbl_rgb[1]) * radius_plus_one; + int sum_b = int(tbl_rgb[2]) * radius_plus_one; + + int nextln = width_3times; + for (int i = 0; i < safe_radius; ++i) { - if(yp < 1) - { - sum_r += r[x]; - sum_g += g[x]; - sum_b += b[x]; - } - else - { - int yi = yp + x; - sum_r += r[yi]; - sum_g += g[yi]; - sum_b += b[yi]; - } - yp += w; + sum_r += tbl_rgb[nextln]; + sum_g += tbl_rgb[nextln + 1]; + sum_b += tbl_rgb[nextln + 2]; + nextln += width_3times; } - linepix = pixbuf.raw_ptr(area.y) + x; - - for(int y = 0; y < h; ++y) + linepix = pixbuf.raw_ptr(area.y) + x + area.x; + for(int y = 0; y < static_cast(area.height); ++y) { linepix->value = 0xFF000000 | (dv[sum_r] << 16) | (dv[sum_g] << 8) | dv[sum_b]; - if(x == 0) - { - vmin[y] = std::min(y + radius + 1, hm) * w; - vmax[y] = std::max(y - radius, 0) * w; - } - int pt1 = x + vmin[y]; - int pt2 = x + vmax[y]; - - sum_r += r[pt1] - r[pt2]; - sum_g += g[pt1] - g[pt2]; - sum_b += b[pt1] - b[pt2]; + int pt1 = vmin[y]; + int pt2 = vmax[y]; + sum_r += tbl_rgb[pt1] - tbl_rgb[pt2]; + sum_g += tbl_rgb[pt1 + 1] - tbl_rgb[pt2 + 1]; + sum_b += tbl_rgb[pt1 + 2] - tbl_rgb[pt2 + 2]; linepix = pixel_at(linepix, bytes_pl); } + tbl_rgb += 3; } } };//end class superfast_blur diff --git a/source/paint/image.cpp b/source/paint/image.cpp index 1781a657..857c31f7 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-2017 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -38,7 +38,7 @@ #include "detail/image_ico_resource.hpp" #include "detail/image_ico.hpp" -namespace fs = std::experimental::filesystem; +namespace fs = std::filesystem; namespace nana { @@ -225,11 +225,16 @@ namespace paint else { #if defined(NANA_ENABLE_JPEG) - //JFIF - if (bytes > 11 && (0xe0ffd8ff == *reinterpret_cast(data)) && 0x4649464A == *reinterpret_cast(reinterpret_cast(data)+6)) - ptr = std::make_shared(); - else if (bytes > 9 && (0x66697845 == *reinterpret_cast(reinterpret_cast(data)+5))) //Exif - ptr = std::make_shared(); + if ((bytes > 11) && (0xd8ff == *reinterpret_cast(data))) + { + switch(*reinterpret_cast(reinterpret_cast(data)+6)) + { + case 0x4649464A: //JFIF + case 0x66697845: //Exif + ptr = std::make_shared(); + } + } + else #endif if ((!ptr) && (bytes > 40)) { diff --git a/source/paint/truetype.hpp b/source/paint/truetype.hpp index 5b94cd34..d8ce4b9b 100644 --- a/source/paint/truetype.hpp +++ b/source/paint/truetype.hpp @@ -47,7 +47,7 @@ namespace nana std::uint16_t string_offset; //from start of storage area }; public: - using path_type = ::std::experimental::filesystem::path; + using path_type = ::std::filesystem::path; truetype(const path_type& filename) {