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..2d7ff43f 100644 --- a/build/bakefile/nana.bkl +++ b/build/bakefile/nana.bkl @@ -62,7 +62,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/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..e1a59d80 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) + { #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) {