diff --git a/.github/scripts/install-cmake.sh b/.github/scripts/install-cmake.sh index 1fbf9c6e79..2850138675 100755 --- a/.github/scripts/install-cmake.sh +++ b/.github/scripts/install-cmake.sh @@ -1,9 +1,9 @@ case $(uname -m) in - x86_64) curl -L https://github.com/Kitware/CMake/releases/download/v3.25.2/cmake-3.25.2-linux-x86_64.sh --output ./do-install-cmake.sh ;; - arm) curl -L https://github.com/Kitware/CMake/releases/download/v3.25.2/cmake-3.25.2-linux-aarch64.sh --output ./do-install-cmake.sh ;; - arm64) curl -L https://github.com/Kitware/CMake/releases/download/v3.25.2/cmake-3.25.2-linux-aarch64.sh --output ./do-install-cmake.sh ;; - aarch64) curl -L https://github.com/Kitware/CMake/releases/download/v3.25.2/cmake-3.25.2-linux-aarch64.sh --output ./do-install-cmake.sh ;; + x86_64) curl -L https://github.com/Kitware/CMake/releases/download/v3.31.0/cmake-3.31.0-linux-x86_64.sh --output ./do-install-cmake.sh ;; + arm) curl -L https://github.com/Kitware/CMake/releases/download/v3.31.0/cmake-3.31.0-linux-aarch64.sh --output ./do-install-cmake.sh ;; + arm64) curl -L https://github.com/Kitware/CMake/releases/download/v3.31.0/cmake-3.31.0-linux-aarch64.sh --output ./do-install-cmake.sh ;; + aarch64) curl -L https://github.com/Kitware/CMake/releases/download/v3.31.0/cmake-3.31.0-linux-aarch64.sh --output ./do-install-cmake.sh ;; esac chmod +x do-install-cmake.sh -./do-install-cmake.sh --skip-license --prefix=/usr \ No newline at end of file +./do-install-cmake.sh --skip-license --prefix=/usr diff --git a/.github/scripts/package-Windows.sh b/.github/scripts/package-Windows.sh index 31d9ebb28a..f1085d92bd 100644 --- a/.github/scripts/package-Windows.sh +++ b/.github/scripts/package-Windows.sh @@ -1,4 +1,4 @@ -VERSION=0.9.2 +VERSION=0.9.3 X64BitMode="" if [[ $1 == "plugdata-Win64.msi" ]]; then @@ -27,7 +27,7 @@ cat > ./plugdata.wxs <<-EOL - + diff --git a/.github/scripts/package-macOS.sh b/.github/scripts/package-macOS.sh index 18f9889151..9357337bd4 100755 --- a/.github/scripts/package-macOS.sh +++ b/.github/scripts/package-macOS.sh @@ -35,6 +35,7 @@ build_flavor() flavorprod=$2 ident=$3 loc=$4 + min_os=$5 echo --- BUILDING ${PRODUCT_NAME}_${flavor}.pkg --- @@ -44,7 +45,7 @@ build_flavor() pkgbuild --analyze --root $TMPDIR ${PKG_DIR}/${PRODUCT_NAME}_${flavor}.plist plutil -replace BundleIsRelocatable -bool NO ${PKG_DIR}/${PRODUCT_NAME}_${flavor}.plist plutil -replace BundleIsVersionChecked -bool NO ${PKG_DIR}/${PRODUCT_NAME}_${flavor}.plist - pkgbuild --root $TMPDIR --identifier $ident --version $VERSION --install-location $loc --component-plist ${PKG_DIR}/${PRODUCT_NAME}_${flavor}.plist ${PKG_DIR}/${PRODUCT_NAME}_${flavor}.pkg + pkgbuild --root $TMPDIR --identifier $ident --version $VERSION --install-location $loc --min-os-version $min_os --compression latest --component-plist ${PKG_DIR}/${PRODUCT_NAME}_${flavor}.plist ${PKG_DIR}/${PRODUCT_NAME}_${flavor}.pkg rm -r $TMPDIR } @@ -64,29 +65,36 @@ if [ -n "$AC_USERNAME" ]; then fi +BUILD_TYPE=$1 +if [[ "$BUILD_TYPE" == "Universal" ]]; then + MIN_OS_VERSION="10.15" +else + MIN_OS_VERSION="10.11" # Use default/legacy for 10.11 support +fi + # # try to build VST3 package if [[ -d $VST3 ]]; then - build_flavor "VST3" $VST3 "com.plugdata.vst3.pkg.${PRODUCT_NAME}" "/Library/Audio/Plug-Ins/VST3" + build_flavor "VST3" $VST3 "com.plugdata.vst3.pkg.${PRODUCT_NAME}" "/Library/Audio/Plug-Ins/VST3" "$MIN_OS_VERSION" fi # try to build LV2 package if [[ -d $LV2 ]]; then - build_flavor "LV2" $LV2 "com.plugdata.lv2.pkg.${PRODUCT_NAME}" "/Library/Audio/Plug-Ins/LV2" + build_flavor "LV2" $LV2 "com.plugdata.lv2.pkg.${PRODUCT_NAME}" "/Library/Audio/Plug-Ins/LV2" "$MIN_OS_VERSION" fi # # try to build AU package if [[ -d $AU ]]; then - build_flavor "AU" $AU "com.plugdata.au.pkg.${PRODUCT_NAME}" "/Library/Audio/Plug-Ins/Components" + build_flavor "AU" $AU "com.plugdata.au.pkg.${PRODUCT_NAME}" "/Library/Audio/Plug-Ins/Components" "$MIN_OS_VERSION" fi # # try to build CLAP package if [[ -d $CLAP ]]; then - build_flavor "CLAP" $CLAP "com.plugdata.clap.pkg.${PRODUCT_NAME}" "/Library/Audio/Plug-Ins/CLAP" + build_flavor "CLAP" $CLAP "com.plugdata.clap.pkg.${PRODUCT_NAME}" "/Library/Audio/Plug-Ins/CLAP" "$MIN_OS_VERSION" fi # try to build App package if [[ -d $APP ]]; then - build_flavor "APP" $APP "com.plugdata.app.pkg.${PRODUCT_NAME}" "/Applications" + build_flavor "APP" $APP "com.plugdata.app.pkg.${PRODUCT_NAME}" "/Applications" "$MIN_OS_VERSION" fi diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 04df94ba5d..e3f37c9299 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -61,6 +61,7 @@ jobs: aws configure set default.region eu-north-1 aws s3 cp ./plugdata-macOS-Universal.pkg s3://plugdata-nightly/ aws s3 cp ./plugdata-macOS-Universal.pkg.txt s3://plugdata-nightly/ + aws cloudfront create-invalidation --distribution-id E3OV68BQRGDGE2 --paths "/plugdata-macOS-Universal.pkg" "/plugdata-macOS-Universal.pkg.txt" - name: Archive Artifacts uses: actions/upload-artifact@v4 @@ -77,7 +78,7 @@ jobs: files: plugdata-macOS-Universal macos-legacy-build: - runs-on: macos-13 + runs-on: macos-14 steps: - uses: actions/checkout@v4 @@ -131,6 +132,7 @@ jobs: aws configure set default.region eu-north-1 aws s3 cp ./plugdata-macOS-Legacy.pkg s3://plugdata-nightly/ aws s3 cp ./plugdata-macOS-Legacy.pkg.txt s3://plugdata-nightly/ + aws cloudfront create-invalidation --distribution-id E3OV68BQRGDGE2 --paths "/plugdata-macOS-Legacy.pkg" "/plugdata-macOS-Legacy.pkg.txt" - name: Archive Artifacts uses: actions/upload-artifact@v4 @@ -191,6 +193,7 @@ jobs: aws configure set default.region eu-north-1 aws s3 cp ./plugdata-Win64.msi s3://plugdata-nightly/ aws s3 cp ./plugdata-Win64.msi.txt s3://plugdata-nightly/ + aws cloudfront create-invalidation --distribution-id E3OV68BQRGDGE2 --paths "/plugdata-Win64.msi" "/plugdata-Win64.msi.txt" - name: Archive Artifacts uses: actions/upload-artifact@v4 @@ -244,6 +247,7 @@ jobs: aws configure set default.region eu-north-1 aws s3 cp ./plugdata-Win32.msi s3://plugdata-nightly/ aws s3 cp ./plugdata-Win32.msi.txt s3://plugdata-nightly/ + aws cloudfront create-invalidation --distribution-id E3OV68BQRGDGE2 --paths "/plugdata-Win32.msi" "/plugdata-Win32.msi.txt" - name: Archive Artifacts uses: actions/upload-artifact@v4 @@ -305,11 +309,11 @@ jobs: - name: Install Dependencies (zypper) if: ${{ matrix.pacman == 'zypper' }} - run: zypper refresh && zypper install -y git rsync wget bzip2 xz tar cmake alsa-devel libXinerama-devel libXi-devel libcurl-devel libXcomposite-devel freeglut-devel libXrandr-devel libXcursor-devel freetype2-devel gcc gcc-c++ curl ccache python3 libjack-devel gawk unzip ninja + run: zypper dup -y && zypper install -y git rsync wget bzip2 xz tar cmake alsa-devel libXinerama-devel libXi-devel libcurl-devel libXcomposite-devel freeglut-devel libXrandr-devel libXcursor-devel freetype2-devel gcc gcc-c++ curl ccache python3 libjack-devel gawk unzip ninja - name: Install Dependencies (pacman) if: ${{ matrix.pacman == 'pacman' }} - run: pacman -Sy && pacman -S --noconfirm cmake wget bzip2 git alsa-lib freetype2 libx11 libxcursor libxi libxext libxinerama libxrandr libxrender webkit2gtk cmake make gcc pkgconf python python-pip curl ccache freeglut mesa glfw-x11 glew jack2 openssl unzip ninja && pacman --noconfirm -Syu + run: pacman --noconfirm -Syu && pacman -S --noconfirm tar cmake wget bzip2 git alsa-lib freetype2 libx11 libxcursor libxi libxext libxinerama libxrandr libxrender webkit2gtk cmake make gcc pkgconf python python-pip curl ccache freeglut mesa glfw-x11 glew jack2 openssl unzip ninja && pacman --noconfirm -Syu - uses: actions/checkout@v4 with: @@ -349,6 +353,7 @@ jobs: aws configure set default.region eu-north-1 aws s3 cp ./plugdata-${{ matrix.name }}.tar.xz s3://plugdata-nightly/ aws s3 cp ./plugdata-${{ matrix.name }}.tar.xz.txt s3://plugdata-nightly/ + aws cloudfront create-invalidation --distribution-id E3OV68BQRGDGE2 --paths "/plugdata-${{ matrix.name }}.tar.xz" "/plugdata-${{ matrix.name }}.tar.xz.txt" - name: Archive Artifacts uses: actions/upload-artifact@v4 @@ -392,6 +397,12 @@ jobs: - name: Fedora-42-aarch64 os: fedora:42 pacman: dnf + - name: OpenSUSE-Tumbleweed-aarch64 + os: opensuse/tumbleweed + pacman: zypper + - name: Arch-aarch64 + os: agners/archlinuxarm + pacman: pacman steps: - name: Install Dependencies (dnf) @@ -402,6 +413,14 @@ jobs: if: ${{ matrix.pacman == 'apt' }} run: apt update && DEBIAN_FRONTEND=noninteractive TZ="Europe/Amsterdam" apt install -y cmake git wget bzip2 build-essential libasound2-dev libjack-jackd2-dev curl libcurl4-openssl-dev libfreetype6-dev libx11-dev libxi-dev libxcomposite-dev libxcursor-dev libxcursor-dev libxext-dev libxrandr-dev libxinerama-dev ccache python3 python3-pip freeglut3-dev unzip ninja-build + - name: Install Dependencies (zypper) + if: ${{ matrix.pacman == 'zypper' }} + run: zypper dup -y && zypper install -y git rsync wget bzip2 xz tar cmake alsa-devel libXinerama-devel libXi-devel libcurl-devel libXcomposite-devel freeglut-devel libXrandr-devel libXcursor-devel freetype2-devel gcc gcc-c++ curl ccache python3 libjack-devel gawk unzip ninja + + - name: Install Dependencies (pacman) + if: ${{ matrix.pacman == 'pacman' }} + run: pacman --noconfirm -Syu && pacman -S --noconfirm tar cmake wget bzip2 git alsa-lib freetype2 libx11 libxcursor libxi libxext libxinerama libxrandr libxrender webkit2gtk cmake make gcc pkgconf python python-pip curl ccache freeglut mesa glfw-x11 glew jack2 openssl unzip ninja + - uses: actions/checkout@v4 with: submodules: recursive @@ -439,6 +458,7 @@ jobs: aws configure set default.region eu-north-1 aws s3 cp ./plugdata-${{ matrix.name }}.tar.xz s3://plugdata-nightly/ aws s3 cp ./plugdata-${{ matrix.name }}.tar.xz.txt s3://plugdata-nightly/ + aws cloudfront create-invalidation --distribution-id E3OV68BQRGDGE2 --paths "/plugdata-${{ matrix.name }}.tar.xz" "/plugdata-${{ matrix.name }}.tar.xz.txt" - name: Archive Artifacts uses: actions/upload-artifact@v4 @@ -453,3 +473,49 @@ jobs: prerelease: true draft: true files: plugdata-${{ matrix.name }} + + push-nightlies: + runs-on: ubuntu-latest + needs: + [ + macos-universal-build, + macos-legacy-build, + windows-64-build, + windows-32-build, + linux-x64-build, + linux-arm-build, + ] + if: always() && github.ref == 'refs/heads/develop' + steps: + - name: Invalidate CloudFront Cache + run: | + aws configure set aws_access_key_id ${{ secrets.AWS_ACCESS_KEY }} + aws configure set aws_secret_access_key ${{ secrets.AWS_SECRET_KEY }} + aws configure set default.region eu-north-1 + aws cloudfront create-invalidation \ + --distribution-id E3OV68BQRGDGE2 \ + --paths "/*" + + package-all-artifacts: + runs-on: ubuntu-latest + needs: + [ + macos-universal-build, + macos-legacy-build, + windows-64-build, + windows-32-build, + linux-x64-build, + linux-arm-build, + ] + if: github.ref == 'refs/heads/main' + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: release-package + + - name: Upload combined package + uses: actions/upload-artifact@v4 + with: + name: plugdata-all-platforms + path: release-package diff --git a/CMakeLists.txt b/CMakeLists.txt index c4c9123284..488f44f47d 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,7 @@ cmake_minimum_required(VERSION 3.15) +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + find_package(Python3 COMPONENTS Interpreter REQUIRED) if(NOT Python3_FOUND) @@ -70,13 +72,21 @@ if(NOT CMAKE_BUILD_TYPE) endif() set(HARDENED_RUNTIME_ENABLED YES) -set(HARDENED_RUNTIME_OPTIONS com.apple.security.device.audio-input com.apple.security.device.camera) +set(HARDENED_RUNTIME_OPTIONS com.apple.security.device.audio-input com.apple.security.device.camera com.apple.security.cs.disable-library-validation) if("${CMAKE_SYSTEM_NAME}" MATCHES "iOS") list(APPEND HARDENED_RUNTIME_OPTIONS com.apple.developer.networking.multicast) endif() -project(plugdata VERSION 0.9.2 LANGUAGES C CXX) +project(plugdata VERSION 0.9.3 LANGUAGES C CXX) + +include(CheckIPOSupported) +check_ipo_supported(RESULT ipo_supported OUTPUT ipo_error) + +if (ipo_supported) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION + $<$:TRUE>) +endif() if(QUICK_BUILD) set(ENABLE_SFIZZ OFF) @@ -91,17 +101,62 @@ set(ENABLE_GEM OFF) set(ENABLE_SFIZZ OFF) endif() +function(get_all_targets var) + set(targets) + get_all_targets_recursive(targets ${CMAKE_CURRENT_SOURCE_DIR}) + set(${var} ${targets} PARENT_SCOPE) +endfunction() + +macro(get_all_targets_recursive targets dir) + get_property(subdirectories DIRECTORY ${dir} PROPERTY SUBDIRECTORIES) + foreach(subdir ${subdirectories}) + get_all_targets_recursive(${targets} ${subdir}) + endforeach() + get_property(current_targets DIRECTORY ${dir} PROPERTY BUILDSYSTEM_TARGETS) + list(APPEND ${targets} ${current_targets}) +endmacro() + +# Get targets before Libraries/ +get_all_targets(targets_before) + add_subdirectory(Libraries/ EXCLUDE_FROM_ALL) if(ENABLE_PERFETTO) add_subdirectory(Libraries/melatonin_perfetto EXCLUDE_FROM_ALL) endif() -set(PLUGDATA_VERSION "0.9.2") +get_all_targets(targets_after) +# Find new targets (those added by Libraries subdirectory) +set(library_targets ${targets_after}) +if(targets_before) + list(REMOVE_ITEM library_targets ${targets_before}) +endif() + +# Group all the folders in the Libraries Cmakelists into a single folder so the IDE project stays clean +foreach(target ${library_targets}) + if(TARGET ${target}) + get_target_property(target_type ${target} TYPE) + # Only organize actual build targets, skip INTERFACE libraries and utilities + if(NOT target_type STREQUAL "INTERFACE_LIBRARY") + set_target_properties(${target} PROPERTIES FOLDER "Libraries") + endif() + endif() +endforeach() + +set(PLUGDATA_VERSION "0.9.3") set_directory_properties(PROPERTIES JUCE_COMPANY_NAME "plugdata") set_directory_properties(PROPERTIES JUCE_COMPANY_COPYRIGHT "plugdata by Timothy Schoen") set_directory_properties(PROPERTIES JUCE_COMPANY_WEBSITE "plugdata.org") -cmake_policy(SET CMP0091 NEW) +if(POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) +endif() +if(POLICY CMP0175) + cmake_policy(SET CMP0175 NEW) +endif() +if(POLICY CMP0177) + cmake_policy(SET CMP0177 NEW) +endif() + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") # Make sure missing return types will fail compilation with Clang @@ -112,12 +167,12 @@ endif() if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-ffunction-sections -fdata-sections) add_link_options(-Wl,-dead_strip) -elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") +elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU") add_compile_options(-ffunction-sections -fdata-sections) add_link_options(-Wl,--gc-sections) elseif (MSVC) # MSVC equivalent for dead code stripping - add_link_options(/OPT:REF /OPT:ICF) + add_link_options(/OPT:REF /OPT:ICF /INCREMENTAL:NO) endif() if(ENABLE_ASAN) @@ -127,10 +182,10 @@ endif() if(MSVC) add_compile_options(/MP /wd4244 /wd4311 /wd4003 /wd4047 /wd4477 /wd4068 /wd4133 /wd4311) - add_compile_options("$<$:/bigobj>") + add_compile_options("$<$,$>:/bigobj>") add_link_options(/IGNORE:4286 /IGNORE:4217) else() - add_compile_options(-Wall -Wstrict-aliasing -Wuninitialized -Wno-conversion -Wno-overloaded-virtual -Wno-sign-compare -Wno-comment -Wno-unknown-pragmas -Wno-unused-result) + add_compile_options(-Wall -Wstrict-aliasing -Wuninitialized -Wno-conversion -Wno-overloaded-virtual -Wno-sign-compare -Wno-comment -Wno-unknown-pragmas -Wno-unused-result -Wno-attributes) add_link_options(-Wno-psabi) endif() @@ -149,9 +204,7 @@ if(NOT PACKAGE_RESOURCES_RESULT EQUAL 0) message(FATAL_ERROR "Resource packaging failed with error code ${PACKAGE_RESOURCES_RESULT}") endif() -if("${CMAKE_SYSTEM_NAME}" MATCHES "iOS") - set(PLUGDATA_ICON_BIG "${CMAKE_CURRENT_SOURCE_DIR}/Resources/Icons/plugdata_logo_ios.png") -elseif(APPLE) +if(APPLE) set(PLUGDATA_ICON_BIG "${CMAKE_CURRENT_SOURCE_DIR}/Resources/Icons/plugdata_logo_mac.png") else() set(PLUGDATA_ICON_BIG "${CMAKE_CURRENT_SOURCE_DIR}/Resources/Icons/plugdata_logo.png") @@ -170,6 +223,7 @@ file(GLOB plugdata_resources ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Fonts/InterVariable.ttf ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Fonts/InterRegular.ttf ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Fonts/RobotoMono-Regular.ttf + ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Fonts/RobotoMono-Bold.ttf ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Icons/plugdata_large_logo.png ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Icons/plugdata_logo.png # Generated resources @@ -183,9 +237,6 @@ file(GLOB StandaloneBinarySources ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Extra/GeneralUser_GS.sf3 ) -file(GLOB plugdata_global_sources - ${CMAKE_CACHEFILE_DIR}/plugdata_artefacts/JuceLibraryCode/JuceHeader.h) - file(GLOB plugdata_sources ${SOURCES_DIRECTORY}/*.h ${SOURCES_DIRECTORY}/*.cpp @@ -205,6 +256,10 @@ file(GLOB plugdata_sources ${SOURCES_DIRECTORY}/Pd/*.h ${SOURCES_DIRECTORY}/Heavy/*.cpp ${SOURCES_DIRECTORY}/Heavy/*.h + ${SOURCES_DIRECTORY}/Standalone/InternalSynth.h + ${SOURCES_DIRECTORY}/Standalone/InternalSynth.cpp + ${SOURCES_DIRECTORY}/Standalone/PlugDataApp.h + ${SOURCES_DIRECTORY}/Standalone/PlugDataWindow.h ) @@ -221,8 +276,6 @@ endif() if(APPLE) list(APPEND plugdata_sources ${SOURCES_DIRECTORY}/Utility/FileSystemWatcher.mm ${SOURCES_DIRECTORY}/Utility/OSUtils.mm) -else() - list(APPEND plugdata_sources ${SOURCES_DIRECTORY}/Utility/FileSystemWatcher.cxx) endif() # Get branch name @@ -265,6 +318,8 @@ if(LINUX) list(APPEND JUCE_COMPILE_DEFINITIONS JUCE_ALSA=1 JUCE_JACK=1 JUCE_JACK_CLIENT_NAME="plugdata") elseif(UNIX AND NOT APPLE) # BSD list(APPEND JUCE_COMPILE_DEFINITIONS JUCE_JACK=1 JUCE_JACK_CLIENT_NAME="plugdata") +elseif(MSVC) + list(APPEND JUCE_COMPILE_DEFINITIONS JUCE_ASIO=1) endif() set(PLUGDATA_COMPILE_DEFINITIONS @@ -318,8 +373,6 @@ target_link_libraries(juce ) endif() -target_compile_options(juce PUBLIC $<$:${JUCE_LTO_FLAGS}>) - if(NANOVG_METAL_IMPLEMENTATION) add_compile_definitions(NANOVG_METAL_IMPLEMENTATION=1) elseif(NANOVG_GLES_IMPLEMENTATION) @@ -332,6 +385,10 @@ file(GLOB BINARY_DATA_FILES ${CMAKE_CURRENT_BINARY_DIR}/BinaryData/*.cpp) add_library(BinaryData STATIC ${BINARY_DATA_FILES}) target_include_directories(BinaryData INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/BinaryData) +if(MSVC) + target_include_directories(juce PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Libraries/ASIO/common) +endif() + set(libs juce BinaryData @@ -368,15 +425,13 @@ if(ENABLE_PERFETTO) endif() list(APPEND PLUGDATA_INCLUDE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/Source/") -add_library(plugdata_core STATIC ${plugdata_sources} ${plugdata_global_sources}) +add_library(plugdata_core STATIC ${plugdata_sources}) target_compile_definitions(plugdata_core PUBLIC ${PLUGDATA_COMPILE_DEFINITIONS}) target_include_directories(plugdata_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/Tests ${CMAKE_CURRENT_SOURCE_DIR}/Libraries/raw-keyboard-input-module ${CMAKE_CURRENT_SOURCE_DIR}/Libraries/pure-data/src ${CMAKE_CURRENT_SOURCE_DIR}/Libraries/pure-data/src ${CMAKE_CURRENT_SOURCE_DIR}/Source ${CMAKE_CURRENT_SOURCE_DIR}/Libraries) target_link_libraries(plugdata_core PUBLIC ${libs}) target_include_directories(plugdata_core PUBLIC "$") include_directories(./Libraries/nanovg/src/) -source_group("Source" FILES ${plugdata_global_sources}) - foreach(core_SOURCE ${plugdata_sources}) # Get the path of the file relative to the current source directory file(RELATIVE_PATH core_SOURCE_relative "${SOURCES_DIRECTORY}" "${core_SOURCE}") @@ -387,15 +442,9 @@ foreach(core_SOURCE ${plugdata_sources}) # Convert forward slashes to backslashes to get source group identifiers string(REPLACE "/" "\\" core_SOURCE_group "${core_SOURCE_dir}") - source_group("Source\\${core_SOURCE_group}" FILES "${core_SOURCE}") + source_group("${core_SOURCE_group}" FILES "${core_SOURCE}") endforeach() -file(GLOB plugdata_standalone_sources - ${SOURCES_DIRECTORY}/Standalone/PlugDataApp.cpp - ${SOURCES_DIRECTORY}/Standalone/PlugDataWindow.h - ${SOURCES_DIRECTORY}/Standalone/InternalSynth.h) -source_group("Source\\Standalone" FILES ${plugdata_standalone_sources}) - if(NOT "${CMAKE_SYSTEM_NAME}" MATCHES "iOS") set(INSTRUMENT_NAME "plugdata") set(AU_EFFECT_TYPE kAudioUnitType_MusicEffect) @@ -418,7 +467,7 @@ set(AU_EFFECT_TYPE kAudioUnitType_Effect) juce_add_plugin(plugdata_standalone VERSION ${PLUGDATA_VERSION} PLUGIN_DESCRIPTION "A plugin that loads Pure Data patches" - ICON_BIG ${PLUGDATA_ICON_BIG} + CUSTOM_XCASSETS_FOLDER ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Icons/plugdata_icon_ios.xcassets LAUNCH_STORYBOARD_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Icons/LaunchScreen.storyboard IPHONE_SCREEN_ORIENTATIONS UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight IPAD_SCREEN_ORIENTATIONS UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight @@ -652,11 +701,11 @@ else() message(NOTICE "CLAP plugin build will be disabed\n\n") endif() -target_sources(plugdata_standalone PUBLIC ${plugdata_standalone_sources} ${SOURCES_DIRECTORY}/Utility/Config.cpp ${SOURCES_DIRECTORY}/Standalone/InternalSynth.cpp) -target_sources(plugdata PUBLIC ${SOURCES_DIRECTORY}/Utility/Config.cpp ${SOURCES_DIRECTORY}/Standalone/InternalSynth.cpp) -target_sources(plugdata_fx PUBLIC ${SOURCES_DIRECTORY}/Utility/Config.cpp ${SOURCES_DIRECTORY}/Standalone/InternalSynth.cpp) +target_sources(plugdata_standalone PUBLIC ${SOURCES_DIRECTORY}/Standalone/PlugDataMain.cpp ${SOURCES_DIRECTORY}/Utility/Config.cpp) +target_sources(plugdata PUBLIC ${SOURCES_DIRECTORY}/Utility/Config.cpp) +target_sources(plugdata_fx PUBLIC ${SOURCES_DIRECTORY}/Utility/Config.cpp) if(APPLE) -target_sources(plugdata_midi PUBLIC ${SOURCES_DIRECTORY}/Utility/Config.cpp ${SOURCES_DIRECTORY}/Standalone/InternalSynth.cpp) +target_sources(plugdata_midi PUBLIC ${SOURCES_DIRECTORY}/Utility/Config.cpp) endif() target_compile_definitions(plugdata_standalone PUBLIC ${PLUGDATA_COMPILE_DEFINITIONS} JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1 PLUGDATA_STANDALONE=1) @@ -694,6 +743,43 @@ if("${CMAKE_SYSTEM_NAME}" MATCHES "iOS") ) endif() +set_target_properties(plugdata_core PROPERTIES FOLDER "Source") + +set_target_properties(plugdata PROPERTIES FOLDER "Plugins/Instrument") +set_target_properties(plugdata_fx PROPERTIES FOLDER "Plugins/Effect") +if(APPLE) + set_target_properties(plugdata_midi PROPERTIES FOLDER "Plugins/MIDI") +endif() + +# Organize JUCE-generated plugin format targets +foreach(format AU AUv3 VST3 LV2 CLAP Standalone All) + if(TARGET plugdata_${format}) + set_target_properties(plugdata_${format} PROPERTIES FOLDER "Plugins/Instrument") + endif() + if(TARGET plugdata_fx_${format}) + set_target_properties(plugdata_fx_${format} PROPERTIES FOLDER "Plugins/Effect") + endif() + if(APPLE AND TARGET plugdata_midi_${format}) + set_target_properties(plugdata_midi_${format} PROPERTIES FOLDER "Plugins/MIDI") + endif() + if(TARGET plugdata_standalone_${format}) + set_target_properties(plugdata_standalone_${format} PROPERTIES FOLDER "Plugins/Standalone") + endif() + if(TARGET plugdata_standalone) + set_target_properties(plugdata_standalone PROPERTIES FOLDER "Plugins/Standalone") + endif() +endforeach() + +if(TARGET juce) + set_target_properties(juce PROPERTIES FOLDER "Libraries") +endif() +if(TARGET juce_vst3_helper) + set_target_properties(juce_vst3_helper PROPERTIES FOLDER "Libraries") +endif() +if(TARGET juce_lv2_helper) + set_target_properties(juce_lv2_helper PROPERTIES FOLDER "Libraries") +endif() + if(LINUX) target_link_libraries(plugdata_standalone PRIVATE plugdata_core pd-src externals "-Wl,-export-dynamic") target_link_libraries(plugdata PRIVATE plugdata_core pd-src-multi externals-multi) diff --git a/Libraries/ASIO/LICENSE.txt b/Libraries/ASIO/LICENSE.txt new file mode 100644 index 0000000000..84abc282f0 --- /dev/null +++ b/Libraries/ASIO/LICENSE.txt @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2025, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +This license applies only to files referencing this license, +for other files of the Software Development Kit the respective embedded license text +is applicable. + +This Software Development Kit is licensed under the terms of the Steinberg ASIO License, +or alternatively under the terms of the General Public License (GPL) Version 3. +You may use the Software Development Kit according to either of these licenses as it is +most appropriate for your project on a case-by-case basis (commercial or not). + +a) Proprietary Steinberg ASIO License +The Software Development Kit may not be distributed in parts or its entirety +without prior written agreement by Steinberg Media Technologies GmbH. +The SDK must not be used to re-engineer or manipulate any technology used +in any Steinberg or Third-party application or software module, +unless permitted by law. +Neither the name of the Steinberg Media Technologies GmbH nor the names of its +contributors may be used to endorse or promote products derived from this +software without specific prior written permission. +Before publishing a software under the proprietary license, you need to obtain a copy +of the License Agreement signed by Steinberg Media Technologies GmbH. +The Steinberg ASIO SDK License Agreement can be found at: +www.steinberg.net/en/company/developers.html + +THE SDK IS PROVIDED BY STEINBERG MEDIA TECHNOLOGIES GMBH "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 STEINBERG MEDIA TECHNOLOGIES GMBH 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. + +b) General Public License (GPL) Version 3 +Details of these licenses can be found at: www.gnu.org/licenses/gpl-3.0.html + +Best Practice recommendation: +Please refer to the Steinberg ASIO usage guidelines for the use of ASIO, ASIO logo and ASIO +compatible logos. +//---------------------------------------------------------------------------------- diff --git a/Libraries/ASIO/README.md b/Libraries/ASIO/README.md new file mode 100644 index 0000000000..6de4c500d3 --- /dev/null +++ b/Libraries/ASIO/README.md @@ -0,0 +1,167 @@ +# Welcome to ASIO 2.3 SDK Interfaces + +

+ASIO Logo + +

+ +Here are located all interfaces definitions and examples of **ASIO 2.3** (Audio Stream Input/Outpu). The low-latency, high-performance audio standard. + +## Table Of Contents + +1. [Introduction](#100) +1. [Dual Licensing](#200) +1. [Trademark and Logo Usage](#300) +1. [Frequently Asked Questions (FAQ)](#400) +1. [Additional Resources](#500) +1. [Contributing](#600) +1. [SDK Content](#700) + +
+ +## Introduction + + +Audio Stream Input/Output (ASIO) fulfills the requirements of professional audio recording and playback by supporting variable bit depths and sample rates, multi-channel operation, and synchronization. This ensures low latency, high performance, straightforward setup, and stable audio recording for the user. + +The entire system becomes controllable and offers complete and immediate access to the audio system's capabilities. Since its introduction, ASIO has become a supported standard by leading audio hardware manufacturers. + +
+ +## Dual Licensing + +Developers can choose between two options: + - Use of the **proprietary ASIO SDK license**, for use cases that benefit from closed-source licensing. + - Adopt the [General Public License (GPL) Version 3](https://www.gnu.org/licenses/gpl-3.0.en.html) for full open-source integration. + +Both options grant access to the same SDK. + +
+ +## Trademark and Logo Usage + +### General Principle + +Trademark usage (e.g. "**ASIO**" name or logo) is optional under [GPLv3 License](https://www.gnu.org/licenses/gpl-3.0.en.html), but, if used, must comply with Steinberg's official trademark rules, just like under the proprietary license. + +ASIO Logo + +### Permitted Use (if selected) + +If you choose to use the **ASIO** Compatible Logo or refer to the **ASIO** trademark under either license: + +- Logos must be used in unaltered form. +- Use must follow the official usage guidelines provided in the SDK. +- The trademark/logo must appear only in clear product-related contexts, such as: + - Splash screens + - About boxes + - Product websites + - Documentation and manuals + +This ensures legal compliance and brand consistency. + +### Prohibited Use + +- Using “ASIO” (or derivatives like “ASIOi”) in company or product names. +- Applying the logo or trademark to non-ASIO-compatible products. +- Associating the trademark with offensive, obscene, or inappropriate content. + +--- +### ASIO trademark and/or ASIO Compatible Logo + +(Applies If Trademark or Logo Is Used — **GPLv3** or **Proprietary**) + +If you choose to display the **ASIO** trademark and/or **ASIO Compatible Logo**, you must observe the *Steinberg ASIO Usage Guidelines*. + +*Purpose*: To assist Steinberg in enforcing and protecting the **ASIO** trademark in international jurisdictions. + + +### Notes for Open-Source Developers + +- The *Steinberg ASIO Usage Guidelines* are included in the SDK. +- Use of branding is not required under [GPLv3 License](https://www.gnu.org/licenses/gpl-3.0.en.html), but if you choose to use it, trademark compliance is mandatory. +- Non-compliance with logo guidelines does not affect your **GPLv3** rights (but may result in trademark violations). + +### Best Practice Recommendation + +Although optional, we encourage developers using **ASIO** under [GPLv3 License](https://www.gnu.org/licenses/gpl-3.0.en.html) to follow the *Steinberg ASIO Usage Guidelines*: + +It fosters ecosystem-wide recognition, improves end-user trust, and supports the long-term visibility of the **ASIO** brand. + +
+ +## Frequently Asked Questions (FAQ) + +### Do I need to update my existing ASIO-based products to [GPLv3 License](https://www.gnu.org/licenses/gpl-3.0.en.html)? +- No. You may continue using the proprietary license for existing products. + +### Can I switch to [GPLv3 License](https://www.gnu.org/licenses/gpl-3.0.en.html)? +- Yes, if your product is released under [GPLv3 License](https://www.gnu.org/licenses/gpl-3.0.en.html) terms (including source code disclosure). + +### Can I use the ASIO logo under [GPLv3 License](https://www.gnu.org/licenses/gpl-3.0.en.html)? +- Yes, but only if you comply with the trademark rules outlined above. + +
+ +## Additional Resources + +- [GPLv3 License Text (GNU.org)](https://www.gnu.org/licenses/gpl-3.0.en.html) +- [Wikipedia: GNU General Public License Version 3](https://en.wikipedia.org/wiki/GNU_General_Public_License#Version_3) +- [ASIO Forum](https://forums.steinberg.net/c/developer/asio) +- Questions? Contact: [reception@steinberg.de](mailto:reception@steinberg.de) + +
+ +## Contributing + +For bug reports and features requests, please visit the [ASIO/VST3 Developer Forum](https://sdk.steinberg.net). + +
+ +## SDK Content + +| Filename | Comment | +| --------------------- | -------------------------------------------------- | +|README.md | this file | +|changes.txt | contains change information between SDK releases | +|Steinberg ASIO SDK 2.3.pdf | ASIO SDK 2.3 specification documentation | +|Steinberg ASIO Licensing Agreement.pdf | Proprietary Licencing Agreement | +|Steinberg ASIO Usage Guidelines.pdf | Usage Guidlines | +|LICENSE.txt | Dual license: Proprietary Agreement and GPLv3 | +|**common** | | +|asio.h | ASIO C definition | +|iasiodrv.h | interface definition for the ASIO driver class | +|asio.cpp | asio host interface (not used on MacOS) | +|asiodrvr.h | | +|asiodrvr.cpp | ASIO driver class base definition | +|combase.h | (Windows only) | +|combase.cpp | COM base definitions (Windows only) | +|dllentry.cpp | DLL functions (Windows only) | +|register.cpp | driver self registration functionality | +|wxdebug.h | | +|debugmessage.cpp | some debugging help | +|LICENSE.txt | license applied to all file in this folder | +|**host** | | +|asiodrivers.h | | +|asiodrivers.cpp | ASIO driver managment (enumeration and instantiation)| +|ASIOConvertSamples.h | | +|ASIOConvertSamples.cpp | sample data format conversion class | +|ginclude.h | platform specific definitions | +|**host/pc** | | +|asiolist.h | (Windows only) | +|asiolist.cpp | instantiates an ASIO driver via the COM model (Windows only) | +|**host/sample** | | +|hostsample.cpp | a simple console app which shows ASIO hosting | +|hostsample.dsp | MSVC++ 5.0 project | +|hostsample.vcproj | Visual Studio 2005 project (32 and 64 bit targets) | +|**driver/asiosample** | | +|asiosmpl.h | | +|asiosmpl.cpp | ASIO 2.0 sample driver | +|wintimer.cpp | bufferSwitch() wakeup thread (Windows only) | +|asiosample.def | Windows DLL module export definition (Windows only) | +|mactimer.cpp | bufferSwitch() wakeup thread (MacOS only) | +|macnanosecs.cpp | Macintosh system reference time (MacOS only) | +|makesamp.cpp | Macintosh driver object instantiation (MacOS only) | +|**driver/asiosample/asiosample** | | +|asiosample.dsp | MSVC++ 5.0 project | +|asiosample.vcproj | Visual Studio 2005 project (32 and 64 bit targets) | diff --git a/Libraries/ASIO/asio/asio.dsw b/Libraries/ASIO/asio/asio.dsw new file mode 100644 index 0000000000..e4c06d7392 --- /dev/null +++ b/Libraries/ASIO/asio/asio.dsw @@ -0,0 +1,41 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "asiosample"="..\driver\asiosample\asiosample\asiosample.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "hostsample"="..\host\sample\hostsample.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/Libraries/ASIO/asio/asio.opt b/Libraries/ASIO/asio/asio.opt new file mode 100644 index 0000000000..ef444d622b Binary files /dev/null and b/Libraries/ASIO/asio/asio.opt differ diff --git a/Libraries/ASIO/changes.txt b/Libraries/ASIO/changes.txt new file mode 100644 index 0000000000..eb23b5983d --- /dev/null +++ b/Libraries/ASIO/changes.txt @@ -0,0 +1,26 @@ +Changes in ASIO 2.3.4 +- new DUAL licensing: Proprietary and GPVv3 +- updated usage guidelines document + +Changes in ASIO 2.3.3 +- again, updated license text and ASIO logo usage guideline + +Changes in ASIO 2.3.2 +- updated license text and ASIO logo usage guideline + +Changes in ASIO 2.3.1 +- amendment of the licensing agreement +- added support for Windows 10 + +Changes in ASIO 2.3 +- added host queries to detect the driver's buffering and drop-out detection capabilities +- some additional documentation on MMCSS; see page 46 of 'ASIO SDK 2.3.pdf' +- dropped support for Mac OS 8 and 9 + +Changes in ASIO 2.2 +- added support for Windows 64 bit + +Changes in ASIO 2.1 +- Sony DSD support added +- fixed Windows registry sample to HKEY_LOCAL_MACHINE +- fixed a definition problem for input monitoring diff --git a/Libraries/ASIO/common/LICENSE.txt b/Libraries/ASIO/common/LICENSE.txt new file mode 100644 index 0000000000..84abc282f0 --- /dev/null +++ b/Libraries/ASIO/common/LICENSE.txt @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2025, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +This license applies only to files referencing this license, +for other files of the Software Development Kit the respective embedded license text +is applicable. + +This Software Development Kit is licensed under the terms of the Steinberg ASIO License, +or alternatively under the terms of the General Public License (GPL) Version 3. +You may use the Software Development Kit according to either of these licenses as it is +most appropriate for your project on a case-by-case basis (commercial or not). + +a) Proprietary Steinberg ASIO License +The Software Development Kit may not be distributed in parts or its entirety +without prior written agreement by Steinberg Media Technologies GmbH. +The SDK must not be used to re-engineer or manipulate any technology used +in any Steinberg or Third-party application or software module, +unless permitted by law. +Neither the name of the Steinberg Media Technologies GmbH nor the names of its +contributors may be used to endorse or promote products derived from this +software without specific prior written permission. +Before publishing a software under the proprietary license, you need to obtain a copy +of the License Agreement signed by Steinberg Media Technologies GmbH. +The Steinberg ASIO SDK License Agreement can be found at: +www.steinberg.net/en/company/developers.html + +THE SDK IS PROVIDED BY STEINBERG MEDIA TECHNOLOGIES GMBH "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 STEINBERG MEDIA TECHNOLOGIES GMBH 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. + +b) General Public License (GPL) Version 3 +Details of these licenses can be found at: www.gnu.org/licenses/gpl-3.0.html + +Best Practice recommendation: +Please refer to the Steinberg ASIO usage guidelines for the use of ASIO, ASIO logo and ASIO +compatible logos. +//---------------------------------------------------------------------------------- diff --git a/Libraries/ASIO/common/asio.cpp b/Libraries/ASIO/common/asio.cpp new file mode 100644 index 0000000000..82b8dc3562 --- /dev/null +++ b/Libraries/ASIO/common/asio.cpp @@ -0,0 +1,265 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Interfaces +// Filename : common/asio.cpp +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O API v2.3 +// ASIO functions entries which translate the ASIO interface to the +// asiodrvr class methods +// +//----------------------------------------------------------------------------- +// This file is part of a Steinberg SDK. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this distribution +// and at www.steinberg.net/sdklicenses. +// No part of the SDK, including this file, may be copied, modified, propagated, +// or distributed except according to the terms contained in the LICENSE file. +//----------------------------------------------------------------------------- + +#include +#include "asiosys.h" // platform definition +#include "asio.h" + +#if MAC +#include "asiodrvr.h" + +#pragma export on + +AsioDriver *theAsioDriver = 0; + +extern "C" +{ + +long main() +{ + return 'ASIO'; +} + +#elif WINDOWS + +#include "windows.h" +#include "iasiodrv.h" +#include "asiodrivers.h" + +IASIO *theAsioDriver = 0; +extern AsioDrivers *asioDrivers; + +#elif SGI || SUN || BEOS || LINUX +#include "asiodrvr.h" +static AsioDriver *theAsioDriver = 0; +#endif + +//----------------------------------------------------------------------------------------------------- +ASIOError ASIOInit(ASIODriverInfo *info) +{ +#if MAC || SGI || SUN || BEOS || LINUX + if(theAsioDriver) + { + delete theAsioDriver; + theAsioDriver = 0; + } + info->driverVersion = 0; + strcpy(info->name, "No ASIO Driver"); + theAsioDriver = getDriver(); + if(!theAsioDriver) + { + strcpy(info->errorMessage, "Not enough memory for the ASIO driver!"); + return ASE_NotPresent; + } + if(!theAsioDriver->init(info->sysRef)) + { + theAsioDriver->getErrorMessage(info->errorMessage); + delete theAsioDriver; + theAsioDriver = 0; + return ASE_NotPresent; + } + strcpy(info->errorMessage, "No ASIO Driver Error"); + theAsioDriver->getDriverName(info->name); + info->driverVersion = theAsioDriver->getDriverVersion(); + return ASE_OK; + +#else + + info->driverVersion = 0; + strcpy(info->name, "No ASIO Driver"); + if(theAsioDriver) // must be loaded! + { + if(!theAsioDriver->init(info->sysRef)) + { + theAsioDriver->getErrorMessage(info->errorMessage); + theAsioDriver = 0; + return ASE_NotPresent; + } + + strcpy(info->errorMessage, "No ASIO Driver Error"); + theAsioDriver->getDriverName(info->name); + info->driverVersion = theAsioDriver->getDriverVersion(); + return ASE_OK; + } + return ASE_NotPresent; + +#endif // !MAC +} + +ASIOError ASIOExit(void) +{ + if(theAsioDriver) + { +#if WINDOWS + asioDrivers->removeCurrentDriver(); +#else + delete theAsioDriver; +#endif + } + theAsioDriver = 0; + return ASE_OK; +} + +ASIOError ASIOStart(void) +{ + if(!theAsioDriver) + return ASE_NotPresent; + return theAsioDriver->start(); +} + +ASIOError ASIOStop(void) +{ + if(!theAsioDriver) + return ASE_NotPresent; + return theAsioDriver->stop(); +} + +ASIOError ASIOGetChannels(long *numInputChannels, long *numOutputChannels) +{ + if(!theAsioDriver) + { + *numInputChannels = *numOutputChannels = 0; + return ASE_NotPresent; + } + return theAsioDriver->getChannels(numInputChannels, numOutputChannels); +} + +ASIOError ASIOGetLatencies(long *inputLatency, long *outputLatency) +{ + if(!theAsioDriver) + { + *inputLatency = *outputLatency = 0; + return ASE_NotPresent; + } + return theAsioDriver->getLatencies(inputLatency, outputLatency); +} + +ASIOError ASIOGetBufferSize(long *minSize, long *maxSize, long *preferredSize, long *granularity) +{ + if(!theAsioDriver) + { + *minSize = *maxSize = *preferredSize = *granularity = 0; + return ASE_NotPresent; + } + return theAsioDriver->getBufferSize(minSize, maxSize, preferredSize, granularity); +} + +ASIOError ASIOCanSampleRate(ASIOSampleRate sampleRate) +{ + if(!theAsioDriver) + return ASE_NotPresent; + return theAsioDriver->canSampleRate(sampleRate); +} + +ASIOError ASIOGetSampleRate(ASIOSampleRate *currentRate) +{ + if(!theAsioDriver) + return ASE_NotPresent; + return theAsioDriver->getSampleRate(currentRate); +} + +ASIOError ASIOSetSampleRate(ASIOSampleRate sampleRate) +{ + if(!theAsioDriver) + return ASE_NotPresent; + return theAsioDriver->setSampleRate(sampleRate); +} + +ASIOError ASIOGetClockSources(ASIOClockSource *clocks, long *numSources) +{ + if(!theAsioDriver) + { + *numSources = 0; + return ASE_NotPresent; + } + return theAsioDriver->getClockSources(clocks, numSources); +} + +ASIOError ASIOSetClockSource(long reference) +{ + if(!theAsioDriver) + return ASE_NotPresent; + return theAsioDriver->setClockSource(reference); +} + +ASIOError ASIOGetSamplePosition(ASIOSamples *sPos, ASIOTimeStamp *tStamp) +{ + if(!theAsioDriver) + return ASE_NotPresent; + return theAsioDriver->getSamplePosition(sPos, tStamp); +} + +ASIOError ASIOGetChannelInfo(ASIOChannelInfo *info) +{ + if(!theAsioDriver) + { + info->channelGroup = -1; + info->type = ASIOSTInt16MSB; + strcpy(info->name, "None"); + return ASE_NotPresent; + } + return theAsioDriver->getChannelInfo(info); +} + +ASIOError ASIOCreateBuffers(ASIOBufferInfo *bufferInfos, long numChannels, + long bufferSize, ASIOCallbacks *callbacks) +{ + if(!theAsioDriver) + { + ASIOBufferInfo *info = bufferInfos; + for(long i = 0; i < numChannels; i++, info++) + info->buffers[0] = info->buffers[1] = 0; + return ASE_NotPresent; + } + return theAsioDriver->createBuffers(bufferInfos, numChannels, bufferSize, callbacks); +} + +ASIOError ASIODisposeBuffers(void) +{ + if(!theAsioDriver) + return ASE_NotPresent; + return theAsioDriver->disposeBuffers(); +} + +ASIOError ASIOControlPanel(void) +{ + if(!theAsioDriver) + return ASE_NotPresent; + return theAsioDriver->controlPanel(); +} + +ASIOError ASIOFuture(long selector, void *opt) +{ + if(!theAsioDriver) + return ASE_NotPresent; + return theAsioDriver->future(selector, opt); +} + +ASIOError ASIOOutputReady(void) +{ + if(!theAsioDriver) + return ASE_NotPresent; + return theAsioDriver->outputReady(); +} + +#if MAC +} // extern "C" +#pragma export off +#endif + + diff --git a/Libraries/ASIO/common/asio.h b/Libraries/ASIO/common/asio.h new file mode 100644 index 0000000000..75d48dcfbe --- /dev/null +++ b/Libraries/ASIO/common/asio.h @@ -0,0 +1,1078 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Interfaces +// Filename : common/asio.h +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O API v2.3 +// ASIO functions entries which translate the ASIO interface to the +// asiodrvr class methods +// 2005 - Added support for DSD sample data (in cooperation with Sony) +// 2012 - Added support for drop out detection +// +//----------------------------------------------------------------------------- +// This file is part of a Steinberg SDK. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this distribution +// and at www.steinberg.net/sdklicenses. +// No part of the SDK, including this file, may be copied, modified, propagated, +// or distributed except according to the terms contained in the LICENSE file. +//----------------------------------------------------------------------------- + +/* + ASIO Interface Specification v 2.3 + + basic concept is an i/o synchronous double-buffer scheme: + + on bufferSwitch(index == 0), host will read/write: + + after ASIOStart(), the + read first input buffer A (index 0) + | will be invalid (empty) + * ------------------------ + |------------------------|-----------------------| + | | | + | Input Buffer A (0) | Input Buffer B (1) | + | | | + |------------------------|-----------------------| + | | | + | Output Buffer A (0) | Output Buffer B (1) | + | | | + |------------------------|-----------------------| + * ------------------------- + | before calling ASIOStart(), + write host will have filled output + buffer B (index 1) already + + *please* take special care of proper statement of input + and output latencies (see ASIOGetLatencies()), these + control sequencer sync accuracy + +*/ + +//--------------------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------------- + +/* +prototypes summary: + +ASIOError ASIOInit(ASIODriverInfo *info); +ASIOError ASIOExit(void); +ASIOError ASIOStart(void); +ASIOError ASIOStop(void); +ASIOError ASIOGetChannels(long *numInputChannels, long *numOutputChannels); +ASIOError ASIOGetLatencies(long *inputLatency, long *outputLatency); +ASIOError ASIOGetBufferSize(long *minSize, long *maxSize, long *preferredSize, long *granularity); +ASIOError ASIOCanSampleRate(ASIOSampleRate sampleRate); +ASIOError ASIOGetSampleRate(ASIOSampleRate *currentRate); +ASIOError ASIOSetSampleRate(ASIOSampleRate sampleRate); +ASIOError ASIOGetClockSources(ASIOClockSource *clocks, long *numSources); +ASIOError ASIOSetClockSource(long reference); +ASIOError ASIOGetSamplePosition (ASIOSamples *sPos, ASIOTimeStamp *tStamp); +ASIOError ASIOGetChannelInfo(ASIOChannelInfo *info); +ASIOError ASIOCreateBuffers(ASIOBufferInfo *bufferInfos, long numChannels, + long bufferSize, ASIOCallbacks *callbacks); +ASIOError ASIODisposeBuffers(void); +ASIOError ASIOControlPanel(void); +void *ASIOFuture(long selector, void *params); +ASIOError ASIOOutputReady(void); + +*/ + +//--------------------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------------- + +#ifndef __ASIO_H +#define __ASIO_H + +// force 4 byte alignment +#if defined(_MSC_VER) && !defined(__MWERKS__) +#pragma pack(push,4) +#elif PRAGMA_ALIGN_SUPPORTED +#pragma options align = native +#endif + +//- - - - - - - - - - - - - - - - - - - - - - - - - +// Type definitions +//- - - - - - - - - - - - - - - - - - - - - - - - - + +// number of samples data type is 64 bit integer +#if NATIVE_INT64 + typedef long long int ASIOSamples; +#else + typedef struct ASIOSamples { + unsigned long hi; + unsigned long lo; + } ASIOSamples; +#endif + +// Timestamp data type is 64 bit integer, +// Time format is Nanoseconds. +#if NATIVE_INT64 + typedef long long int ASIOTimeStamp ; +#else + typedef struct ASIOTimeStamp { + unsigned long hi; + unsigned long lo; + } ASIOTimeStamp; +#endif + +// Samplerates are expressed in IEEE 754 64 bit double float, +// native format as host computer +#if IEEE754_64FLOAT + typedef double ASIOSampleRate; +#else + typedef struct ASIOSampleRate { + char ieee[8]; + } ASIOSampleRate; +#endif + +// Boolean values are expressed as long +typedef long ASIOBool; +enum { + ASIOFalse = 0, + ASIOTrue = 1 +}; + +// Sample Types are expressed as long +typedef long ASIOSampleType; +enum { + ASIOSTInt16MSB = 0, + ASIOSTInt24MSB = 1, // used for 20 bits as well + ASIOSTInt32MSB = 2, + ASIOSTFloat32MSB = 3, // IEEE 754 32 bit float + ASIOSTFloat64MSB = 4, // IEEE 754 64 bit double float + + // these are used for 32 bit data buffer, with different alignment of the data inside + // 32 bit PCI bus systems can be more easily used with these + ASIOSTInt32MSB16 = 8, // 32 bit data with 16 bit alignment + ASIOSTInt32MSB18 = 9, // 32 bit data with 18 bit alignment + ASIOSTInt32MSB20 = 10, // 32 bit data with 20 bit alignment + ASIOSTInt32MSB24 = 11, // 32 bit data with 24 bit alignment + + ASIOSTInt16LSB = 16, + ASIOSTInt24LSB = 17, // used for 20 bits as well + ASIOSTInt32LSB = 18, + ASIOSTFloat32LSB = 19, // IEEE 754 32 bit float, as found on Intel x86 architecture + ASIOSTFloat64LSB = 20, // IEEE 754 64 bit double float, as found on Intel x86 architecture + + // these are used for 32 bit data buffer, with different alignment of the data inside + // 32 bit PCI bus systems can more easily used with these + ASIOSTInt32LSB16 = 24, // 32 bit data with 18 bit alignment + ASIOSTInt32LSB18 = 25, // 32 bit data with 18 bit alignment + ASIOSTInt32LSB20 = 26, // 32 bit data with 20 bit alignment + ASIOSTInt32LSB24 = 27, // 32 bit data with 24 bit alignment + + // ASIO DSD format. + ASIOSTDSDInt8LSB1 = 32, // DSD 1 bit data, 8 samples per byte. First sample in Least significant bit. + ASIOSTDSDInt8MSB1 = 33, // DSD 1 bit data, 8 samples per byte. First sample in Most significant bit. + ASIOSTDSDInt8NER8 = 40, // DSD 8 bit data, 1 sample per byte. No Endianness required. + + ASIOSTLastEntry +}; + +/*----------------------------------------------------------------------------- +// DSD operation and buffer layout +// Definition by Steinberg/Sony Oxford. +// +// We have tried to treat DSD as PCM and so keep a consistant structure across +// the ASIO interface. +// +// DSD's sample rate is normally referenced as a multiple of 44.1Khz, so +// the standard sample rate is refered to as 64Fs (or 2.8224Mhz). We looked +// at making a special case for DSD and adding a field to the ASIOFuture that +// would allow the user to select the Over Sampleing Rate (OSR) as a seperate +// entity but decided in the end just to treat it as a simple value of +// 2.8224Mhz and use the standard interface to set it. +// +// The second problem was the "word" size, in PCM the word size is always a +// greater than or equal to 8 bits (a byte). This makes life easy as we can +// then pack the samples into the "natural" size for the machine. +// In DSD the "word" size is 1 bit. This is not a major problem and can easily +// be dealt with if we ensure that we always deal with a multiple of 8 samples. +// +// DSD brings with it another twist to the Endianness religion. How are the +// samples packed into the byte. It would be nice to just say the most significant +// bit is always the first sample, however there would then be a performance hit +// on little endian machines. Looking at how some of the processing goes... +// Little endian machines like the first sample to be in the Least Significant Bit, +// this is because when you write it to memory the data is in the correct format +// to be shifted in and out of the words. +// Big endian machine prefer the first sample to be in the Most Significant Bit, +// again for the same reasion. +// +// And just when things were looking really muddy there is a proposed extension to +// DSD that uses 8 bit word sizes. It does not care what endianness you use. +// +// Switching the driver between DSD and PCM mode +// ASIOFuture allows for extending the ASIO API quite transparently. +// See kAsioSetIoFormat, kAsioGetIoFormat, kAsioCanDoIoFormat +// +//-----------------------------------------------------------------------------*/ + + +//- - - - - - - - - - - - - - - - - - - - - - - - - +// Error codes +//- - - - - - - - - - - - - - - - - - - - - - - - - + +typedef long ASIOError; +enum { + ASE_OK = 0, // This value will be returned whenever the call succeeded + ASE_SUCCESS = 0x3f4847a0, // unique success return value for ASIOFuture calls + ASE_NotPresent = -1000, // hardware input or output is not present or available + ASE_HWMalfunction, // hardware is malfunctioning (can be returned by any ASIO function) + ASE_InvalidParameter, // input parameter invalid + ASE_InvalidMode, // hardware is in a bad mode or used in a bad mode + ASE_SPNotAdvancing, // hardware is not running when sample position is inquired + ASE_NoClock, // sample clock or rate cannot be determined or is not present + ASE_NoMemory // not enough memory for completing the request +}; + +//--------------------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------------- + +//- - - - - - - - - - - - - - - - - - - - - - - - - +// Time Info support +//- - - - - - - - - - - - - - - - - - - - - - - - - + +typedef struct ASIOTimeCode +{ + double speed; // speed relation (fraction of nominal speed) + // optional; set to 0. or 1. if not supported + ASIOSamples timeCodeSamples; // time in samples + unsigned long flags; // some information flags (see below) + char future[64]; +} ASIOTimeCode; + +typedef enum ASIOTimeCodeFlags +{ + kTcValid = 1, + kTcRunning = 1 << 1, + kTcReverse = 1 << 2, + kTcOnspeed = 1 << 3, + kTcStill = 1 << 4, + + kTcSpeedValid = 1 << 8 +} ASIOTimeCodeFlags; + +typedef struct AsioTimeInfo +{ + double speed; // absolute speed (1. = nominal) + ASIOTimeStamp systemTime; // system time related to samplePosition, in nanoseconds + // on mac, must be derived from Microseconds() (not UpTime()!) + // on windows, must be derived from timeGetTime() + ASIOSamples samplePosition; + ASIOSampleRate sampleRate; // current rate + unsigned long flags; // (see below) + char reserved[12]; +} AsioTimeInfo; + +typedef enum AsioTimeInfoFlags +{ + kSystemTimeValid = 1, // must always be valid + kSamplePositionValid = 1 << 1, // must always be valid + kSampleRateValid = 1 << 2, + kSpeedValid = 1 << 3, + + kSampleRateChanged = 1 << 4, + kClockSourceChanged = 1 << 5 +} AsioTimeInfoFlags; + +typedef struct ASIOTime // both input/output +{ + long reserved[4]; // must be 0 + struct AsioTimeInfo timeInfo; // required + struct ASIOTimeCode timeCode; // optional, evaluated if (timeCode.flags & kTcValid) +} ASIOTime; + +/* + +using time info: +it is recommended to use the new method with time info even if the asio +device does not support timecode; continuous calls to ASIOGetSamplePosition +and ASIOGetSampleRate are avoided, and there is a more defined relationship +between callback time and the time info. + +see the example below. +to initiate time info mode, after you have received the callbacks pointer in +ASIOCreateBuffers, you will call the asioMessage callback with kAsioSupportsTimeInfo +as the argument. if this returns 1, host has accepted time info mode. +now host expects the new callback bufferSwitchTimeInfo to be used instead +of the old bufferSwitch method. the ASIOTime structure is assumed to be valid +and accessible until the callback returns. + +using time code: +if the device supports reading time code, it will call host's asioMessage callback +with kAsioSupportsTimeCode as the selector. it may then fill the according +fields and set the kTcValid flag. +host will call the future method with the kAsioEnableTimeCodeRead selector when +it wants to enable or disable tc reading by the device. you should also support +the kAsioCanTimeInfo and kAsioCanTimeCode selectors in ASIOFuture (see example). + +note: +the AsioTimeInfo/ASIOTimeCode pair is supposed to work in both directions. +as a matter of convention, the relationship between the sample +position counter and the time code at buffer switch time is +(ignoring offset between tc and sample pos when tc is running): + +on input: sample 0 -> input buffer sample 0 -> time code 0 +on output: sample 0 -> output buffer sample 0 -> time code 0 + +this means that for 'real' calculations, one has to take into account +the according latencies. + +example: + +ASIOTime asioTime; + +in createBuffers() +{ + memset(&asioTime, 0, sizeof(ASIOTime)); + AsioTimeInfo* ti = &asioTime.timeInfo; + ti->sampleRate = theSampleRate; + ASIOTimeCode* tc = &asioTime.timeCode; + tc->speed = 1.; + timeInfoMode = false; + canTimeCode = false; + if(callbacks->asioMessage(kAsioSupportsTimeInfo, 0, 0, 0) == 1) + { + timeInfoMode = true; +#if kCanTimeCode + if(callbacks->asioMessage(kAsioSupportsTimeCode, 0, 0, 0) == 1) + canTimeCode = true; +#endif + } +} + +void switchBuffers(long doubleBufferIndex, bool processNow) +{ + if(timeInfoMode) + { + AsioTimeInfo* ti = &asioTime.timeInfo; + ti->flags = kSystemTimeValid | kSamplePositionValid | kSampleRateValid; + ti->systemTime = theNanoSeconds; + ti->samplePosition = theSamplePosition; + if(ti->sampleRate != theSampleRate) + ti->flags |= kSampleRateChanged; + ti->sampleRate = theSampleRate; + +#if kCanTimeCode + if(canTimeCode && timeCodeEnabled) + { + ASIOTimeCode* tc = &asioTime.timeCode; + tc->timeCodeSamples = tcSamples; // tc in samples + tc->flags = kTcValid | kTcRunning | kTcOnspeed; // if so... + } + ASIOTime* bb = callbacks->bufferSwitchTimeInfo(&asioTime, doubleBufferIndex, processNow ? ASIOTrue : ASIOFalse); +#else + callbacks->bufferSwitchTimeInfo(&asioTime, doubleBufferIndex, processNow ? ASIOTrue : ASIOFalse); +#endif + } + else + callbacks->bufferSwitch(doubleBufferIndex, ASIOFalse); +} + +ASIOError ASIOFuture(long selector, void *params) +{ + switch(selector) + { + case kAsioEnableTimeCodeRead: + timeCodeEnabled = true; + return ASE_SUCCESS; + case kAsioDisableTimeCodeRead: + timeCodeEnabled = false; + return ASE_SUCCESS; + case kAsioCanTimeInfo: + return ASE_SUCCESS; + #if kCanTimeCode + case kAsioCanTimeCode: + return ASE_SUCCESS; + #endif + } + return ASE_NotPresent; +}; + +*/ + +//- - - - - - - - - - - - - - - - - - - - - - - - - +// application's audio stream handler callbacks +//- - - - - - - - - - - - - - - - - - - - - - - - - + +typedef struct ASIOCallbacks +{ + void (*bufferSwitch) (long doubleBufferIndex, ASIOBool directProcess); + // bufferSwitch indicates that both input and output are to be processed. + // the current buffer half index (0 for A, 1 for B) determines + // - the output buffer that the host should start to fill. the other buffer + // will be passed to output hardware regardless of whether it got filled + // in time or not. + // - the input buffer that is now filled with incoming data. Note that + // because of the synchronicity of i/o, the input always has at + // least one buffer latency in relation to the output. + // directProcess suggests to the host whether it should immedeately + // start processing (directProcess == ASIOTrue), or whether its process + // should be deferred because the call comes from a very low level + // (for instance, a high level priority interrupt), and direct processing + // would cause timing instabilities for the rest of the system. If in doubt, + // directProcess should be set to ASIOFalse. + // Note: bufferSwitch may be called at interrupt time for highest efficiency. + + void (*sampleRateDidChange) (ASIOSampleRate sRate); + // gets called when the AudioStreamIO detects a sample rate change + // If sample rate is unknown, 0 is passed (for instance, clock loss + // when externally synchronized). + + long (*asioMessage) (long selector, long value, void* message, double* opt); + // generic callback for various purposes, see selectors below. + // note this is only present if the asio version is 2 or higher + + ASIOTime* (*bufferSwitchTimeInfo) (ASIOTime* params, long doubleBufferIndex, ASIOBool directProcess); + // new callback with time info. makes ASIOGetSamplePosition() and various + // calls to ASIOGetSampleRate obsolete, + // and allows for timecode sync etc. to be preferred; will be used if + // the driver calls asioMessage with selector kAsioSupportsTimeInfo. +} ASIOCallbacks; + +// asioMessage selectors +enum +{ + kAsioSelectorSupported = 1, // selector in , returns 1L if supported, + // 0 otherwise + kAsioEngineVersion, // returns engine (host) asio implementation version, + // 2 or higher + kAsioResetRequest, // request driver reset. if accepted, this + // will close the driver (ASIO_Exit() ) and + // re-open it again (ASIO_Init() etc). some + // drivers need to reconfigure for instance + // when the sample rate changes, or some basic + // changes have been made in ASIO_ControlPanel(). + // returns 1L; note the request is merely passed + // to the application, there is no way to determine + // if it gets accepted at this time (but it usually + // will be). + kAsioBufferSizeChange, // not yet supported, will currently always return 0L. + // for now, use kAsioResetRequest instead. + // once implemented, the new buffer size is expected + // in , and on success returns 1L + kAsioResyncRequest, // the driver went out of sync, such that + // the timestamp is no longer valid. this + // is a request to re-start the engine and + // slave devices (sequencer). returns 1 for ok, + // 0 if not supported. + kAsioLatenciesChanged, // the drivers latencies have changed. The engine + // will refetch the latencies. + kAsioSupportsTimeInfo, // if host returns true here, it will expect the + // callback bufferSwitchTimeInfo to be called instead + // of bufferSwitch + kAsioSupportsTimeCode, // + kAsioMMCCommand, // unused - value: number of commands, message points to mmc commands + kAsioSupportsInputMonitor, // kAsioSupportsXXX return 1 if host supports this + kAsioSupportsInputGain, // unused and undefined + kAsioSupportsInputMeter, // unused and undefined + kAsioSupportsOutputGain, // unused and undefined + kAsioSupportsOutputMeter, // unused and undefined + kAsioOverload, // driver detected an overload + + kAsioNumMessageSelectors +}; + +//--------------------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------------- + +//- - - - - - - - - - - - - - - - - - - - - - - - - +// (De-)Construction +//- - - - - - - - - - - - - - - - - - - - - - - - - + +typedef struct ASIODriverInfo +{ + long asioVersion; // currently, 2 + long driverVersion; // driver specific + char name[32]; + char errorMessage[124]; + void *sysRef; // on input: system reference + // (Windows: application main window handle, Mac & SGI: 0) +} ASIODriverInfo; + +ASIOError ASIOInit(ASIODriverInfo *info); +/* Purpose: + Initialize the AudioStreamIO. + Parameter: + info: pointer to an ASIODriver structure: + - asioVersion: + - on input, the host version. *** Note *** this is 0 for earlier asio + implementations, and the asioMessage callback is implemeted + only if asioVersion is 2 or greater. sorry but due to a design fault + the driver doesn't have access to the host version in ASIOInit :-( + added selector for host (engine) version in the asioMessage callback + so we're ok from now on. + - on return, asio implementation version. + older versions are 1 + if you support this version (namely, ASIO_outputReady() ) + this should be 2 or higher. also see the note in + ASIO_getTimeStamp() ! + - version: on return, the driver version (format is driver specific) + - name: on return, a null-terminated string containing the driver's name + - error message: on return, should contain a user message describing + the type of error that occured during ASIOInit(), if any. + - sysRef: platform specific + Returns: + If neither input nor output is present ASE_NotPresent + will be returned. + ASE_NoMemory, ASE_HWMalfunction are other possible error conditions +*/ + +ASIOError ASIOExit(void); +/* Purpose: + Terminates the AudioStreamIO. + Parameter: + None. + Returns: + If neither input nor output is present ASE_NotPresent + will be returned. + Notes: this implies ASIOStop() and ASIODisposeBuffers(), + meaning that no host callbacks must be accessed after ASIOExit(). +*/ + +//- - - - - - - - - - - - - - - - - - - - - - - - - +// Start/Stop +//- - - - - - - - - - - - - - - - - - - - - - - - - + +ASIOError ASIOStart(void); +/* Purpose: + Start input and output processing synchronously. + This will + - reset the sample counter to zero + - start the hardware (both input and output) + The first call to the hosts' bufferSwitch(index == 0) then tells + the host to read from input buffer A (index 0), and start + processing to output buffer A while output buffer B (which + has been filled by the host prior to calling ASIOStart()) + is possibly sounding (see also ASIOGetLatencies()) + Parameter: + None. + Returns: + If neither input nor output is present, ASE_NotPresent + will be returned. + If the hardware fails to start, ASE_HWMalfunction will be returned. + Notes: + There is no restriction on the time that ASIOStart() takes + to perform (that is, it is not considered a realtime trigger). +*/ + +ASIOError ASIOStop(void); +/* Purpose: + Stops input and output processing altogether. + Parameter: + None. + Returns: + If neither input nor output is present ASE_NotPresent + will be returned. + Notes: + On return from ASIOStop(), the driver must in no + case call the hosts' bufferSwitch() routine. +*/ + +//- - - - - - - - - - - - - - - - - - - - - - - - - +// Inquiry methods and sample rate +//- - - - - - - - - - - - - - - - - - - - - - - - - + +ASIOError ASIOGetChannels(long *numInputChannels, long *numOutputChannels); +/* Purpose: + Returns number of individual input/output channels. + Parameter: + numInputChannels will hold the number of available input channels + numOutputChannels will hold the number of available output channels + Returns: + If no input/output is present ASE_NotPresent will be returned. + If only inputs, or only outputs are available, the according + other parameter will be zero, and ASE_OK is returned. +*/ + +ASIOError ASIOGetLatencies(long *inputLatency, long *outputLatency); +/* Purpose: + Returns the input and output latencies. This includes + device specific delays, like FIFOs etc. + Parameter: + inputLatency will hold the 'age' of the first sample frame + in the input buffer when the hosts reads it in bufferSwitch() + (this is theoretical, meaning it does not include the overhead + and delay between the actual physical switch, and the time + when bufferSitch() enters). + This will usually be the size of one block in sample frames, plus + device specific latencies. + + outputLatency will specify the time between the buffer switch, + and the time when the next play buffer will start to sound. + The next play buffer is defined as the one the host starts + processing after (or at) bufferSwitch(), indicated by the + index parameter (0 for buffer A, 1 for buffer B). + It will usually be either one block, if the host writes directly + to a dma buffer, or two or more blocks if the buffer is 'latched' by + the driver. As an example, on ASIOStart(), the host will have filled + the play buffer at index 1 already; when it gets the callback (with + the parameter index == 0), this tells it to read from the input + buffer 0, and start to fill the play buffer 0 (assuming that now + play buffer 1 is already sounding). In this case, the output + latency is one block. If the driver decides to copy buffer 1 + at that time, and pass it to the hardware at the next slot (which + is most commonly done, but should be avoided), the output latency + becomes two blocks instead, resulting in a total i/o latency of at least + 3 blocks. As memory access is the main bottleneck in native dsp processing, + and to acheive less latency, it is highly recommended to try to avoid + copying (this is also why the driver is the owner of the buffers). To + summarize, the minimum i/o latency can be acheived if the input buffer + is processed by the host into the output buffer which will physically + start to sound on the next time slice. Also note that the host expects + the bufferSwitch() callback to be accessed for each time slice in order + to retain sync, possibly recursively; if it fails to process a block in + time, it will suspend its operation for some time in order to recover. + Returns: + If no input/output is present ASE_NotPresent will be returned. +*/ + +ASIOError ASIOGetBufferSize(long *minSize, long *maxSize, long *preferredSize, long *granularity); +/* Purpose: + Returns min, max, and preferred buffer sizes for input/output + Parameter: + minSize will hold the minimum buffer size + maxSize will hold the maxium possible buffer size + preferredSize will hold the preferred buffer size (a size which + best fits performance and hardware requirements) + granularity will hold the granularity at which buffer sizes + may differ. Usually, the buffer size will be a power of 2; + in this case, granularity will hold -1 on return, signalling + possible buffer sizes starting from minSize, increased in + powers of 2 up to maxSize. + Returns: + If no input/output is present ASE_NotPresent will be returned. + Notes: + When minimum and maximum buffer size are equal, + the preferred buffer size has to be the same value as well; granularity + should be 0 in this case. +*/ + +ASIOError ASIOCanSampleRate(ASIOSampleRate sampleRate); +/* Purpose: + Inquires the hardware for the available sample rates. + Parameter: + sampleRate is the rate in question. + Returns: + If the inquired sample rate is not supported, ASE_NoClock will be returned. + If no input/output is present ASE_NotPresent will be returned. +*/ +ASIOError ASIOGetSampleRate(ASIOSampleRate *currentRate); +/* Purpose: + Get the current sample Rate. + Parameter: + currentRate will hold the current sample rate on return. + Returns: + If sample rate is unknown, sampleRate will be 0 and ASE_NoClock will be returned. + If no input/output is present ASE_NotPresent will be returned. + Notes: +*/ + +ASIOError ASIOSetSampleRate(ASIOSampleRate sampleRate); +/* Purpose: + Set the hardware to the requested sample Rate. If sampleRate == 0, + enable external sync. + Parameter: + sampleRate: on input, the requested rate + Returns: + If sampleRate is unknown ASE_NoClock will be returned. + If the current clock is external, and sampleRate is != 0, + ASE_InvalidMode will be returned + If no input/output is present ASE_NotPresent will be returned. + Notes: +*/ + +typedef struct ASIOClockSource +{ + long index; // as used for ASIOSetClockSource() + long associatedChannel; // for instance, S/PDIF or AES/EBU + long associatedGroup; // see channel groups (ASIOGetChannelInfo()) + ASIOBool isCurrentSource; // ASIOTrue if this is the current clock source + char name[32]; // for user selection +} ASIOClockSource; + +ASIOError ASIOGetClockSources(ASIOClockSource *clocks, long *numSources); +/* Purpose: + Get the available external audio clock sources + Parameter: + clocks points to an array of ASIOClockSource structures: + - index: this is used to identify the clock source + when ASIOSetClockSource() is accessed, should be + an index counting from zero + - associatedInputChannel: the first channel of an associated + input group, if any. + - associatedGroup: the group index of that channel. + groups of channels are defined to seperate for + instance analog, S/PDIF, AES/EBU, ADAT connectors etc, + when present simultaniously. Note that associated channel + is enumerated according to numInputs/numOutputs, means it + is independant from a group (see also ASIOGetChannelInfo()) + inputs are associated to a clock if the physical connection + transfers both data and clock (like S/PDIF, AES/EBU, or + ADAT inputs). if there is no input channel associated with + the clock source (like Word Clock, or internal oscillator), both + associatedChannel and associatedGroup should be set to -1. + - isCurrentSource: on exit, ASIOTrue if this is the current clock + source, ASIOFalse else + - name: a null-terminated string for user selection of the available sources. + numSources: + on input: the number of allocated array members + on output: the number of available clock sources, at least + 1 (internal clock generator). + Returns: + If no input/output is present ASE_NotPresent will be returned. + Notes: +*/ + +ASIOError ASIOSetClockSource(long index); +/* Purpose: + Set the audio clock source + Parameter: + index as obtained from an inquiry to ASIOGetClockSources() + Returns: + If no input/output is present ASE_NotPresent will be returned. + If the clock can not be selected because an input channel which + carries the current clock source is active, ASE_InvalidMode + *may* be returned (this depends on the properties of the driver + and/or hardware). + Notes: + Should *not* return ASE_NoClock if there is no clock signal present + at the selected source; this will be inquired via ASIOGetSampleRate(). + It should call the host callback procedure sampleRateHasChanged(), + if the switch causes a sample rate change, or if no external clock + is present at the selected source. +*/ + +ASIOError ASIOGetSamplePosition (ASIOSamples *sPos, ASIOTimeStamp *tStamp); +/* Purpose: + Inquires the sample position/time stamp pair. + Parameter: + sPos will hold the sample position on return. The sample + position is reset to zero when ASIOStart() gets called. + tStamp will hold the system time when the sample position + was latched. + Returns: + If no input/output is present, ASE_NotPresent will be returned. + If there is no clock, ASE_SPNotAdvancing will be returned. + Notes: + + in order to be able to synchronise properly, + the sample position / time stamp pair must refer to the current block, + that is, the engine will call ASIOGetSamplePosition() in its bufferSwitch() + callback and expect the time for the current block. thus, when requested + in the very first bufferSwitch after ASIO_Start(), the sample position + should be zero, and the time stamp should refer to the very time where + the stream was started. it also means that the sample position must be + block aligned. the driver must ensure proper interpolation if the system + time can not be determined for the block position. the driver is responsible + for precise time stamps as it usually has most direct access to lower + level resources. proper behaviour of ASIO_GetSamplePosition() and ASIO_GetLatencies() + are essential for precise media synchronization! +*/ + +typedef struct ASIOChannelInfo +{ + long channel; // on input, channel index + ASIOBool isInput; // on input + ASIOBool isActive; // on exit + long channelGroup; // dto + ASIOSampleType type; // dto + char name[32]; // dto +} ASIOChannelInfo; + +ASIOError ASIOGetChannelInfo(ASIOChannelInfo *info); +/* Purpose: + retreive information about the nature of a channel + Parameter: + info: pointer to a ASIOChannelInfo structure with + - channel: on input, the channel index of the channel in question. + - isInput: on input, ASIOTrue if info for an input channel is + requested, else output + - channelGroup: on return, the channel group that the channel + belongs to. For drivers which support different types of + channels, like analog, S/PDIF, AES/EBU, ADAT etc interfaces, + there should be a reasonable grouping of these types. Groups + are always independant form a channel index, that is, a channel + index always counts from 0 to numInputs/numOutputs regardless + of the group it may belong to. + There will always be at least one group (group 0). Please + also note that by default, the host may decide to activate + channels 0 and 1; thus, these should belong to the most + useful type (analog i/o, if present). + - type: on return, contains the sample type of the channel + - isActive: on return, ASIOTrue if channel is active as it was + installed by ASIOCreateBuffers(), ASIOFalse else + - name: describing the type of channel in question. Used to allow + for user selection, and enabling of specific channels. examples: + "Analog In", "SPDIF Out" etc + Returns: + If no input/output is present ASE_NotPresent will be returned. + Notes: + If possible, the string should be organised such that the first + characters are most significantly describing the nature of the + port, to allow for identification even if the view showing the + port name is too small to display more than 8 characters, for + instance. +*/ + +//- - - - - - - - - - - - - - - - - - - - - - - - - +// Buffer preparation +//- - - - - - - - - - - - - - - - - - - - - - - - - + +typedef struct ASIOBufferInfo +{ + ASIOBool isInput; // on input: ASIOTrue: input, else output + long channelNum; // on input: channel index + void *buffers[2]; // on output: double buffer addresses +} ASIOBufferInfo; + +ASIOError ASIOCreateBuffers(ASIOBufferInfo *bufferInfos, long numChannels, + long bufferSize, ASIOCallbacks *callbacks); + +/* Purpose: + Allocates input/output buffers for all input and output channels to be activated. + Parameter: + bufferInfos is a pointer to an array of ASIOBufferInfo structures: + - isInput: on input, ASIOTrue if the buffer is to be allocated + for an input, output buffer else + - channelNum: on input, the index of the channel in question + (counting from 0) + - buffers: on exit, 2 pointers to the halves of the channels' double-buffer. + the size of the buffer(s) of course depend on both the ASIOSampleType + as obtained from ASIOGetChannelInfo(), and bufferSize + numChannels is the sum of all input and output channels to be created; + thus bufferInfos is a pointer to an array of numChannels ASIOBufferInfo + structures. + bufferSize selects one of the possible buffer sizes as obtained from + ASIOGetBufferSizes(). + callbacks is a pointer to an ASIOCallbacks structure. + Returns: + If not enough memory is available ASE_NoMemory will be returned. + If no input/output is present ASE_NotPresent will be returned. + If bufferSize is not supported, or one or more of the bufferInfos elements + contain invalid settings, ASE_InvalidMode will be returned. + Notes: + If individual channel selection is not possible but requested, + the driver has to handle this. namely, bufferSwitch() will only + have filled buffers of enabled outputs. If possible, processing + and buss activities overhead should be avoided for channels which + were not enabled here. +*/ + +ASIOError ASIODisposeBuffers(void); +/* Purpose: + Releases all buffers for the device. + Parameter: + None. + Returns: + If no buffer were ever prepared, ASE_InvalidMode will be returned. + If no input/output is present ASE_NotPresent will be returned. + Notes: + This implies ASIOStop(). +*/ + +ASIOError ASIOControlPanel(void); +/* Purpose: + request the driver to start a control panel component + for device specific user settings. This will not be + accessed on some platforms (where the component is accessed + instead). + Parameter: + None. + Returns: + If no panel is available ASE_NotPresent will be returned. + Actually, the return code is ignored. + Notes: + if the user applied settings which require a re-configuration + of parts or all of the enigine and/or driver (such as a change of + the block size), the asioMessage callback can be used (see + ASIO_Callbacks). +*/ + +ASIOError ASIOFuture(long selector, void *params); +/* Purpose: + various + Parameter: + selector: operation Code as to be defined. zero is reserved for + testing purposes. + params: depends on the selector; usually pointer to a structure + for passing and retreiving any type and amount of parameters. + Returns: + the return value is also selector dependant. if the selector + is unknown, ASE_InvalidParameter should be returned to prevent + further calls with this selector. on success, ASE_SUCCESS + must be returned (note: ASE_OK is *not* sufficient!) + Notes: + see selectors defined below. +*/ + +enum +{ + kAsioEnableTimeCodeRead = 1, // no arguments + kAsioDisableTimeCodeRead, // no arguments + kAsioSetInputMonitor, // ASIOInputMonitor* in params + kAsioTransport, // ASIOTransportParameters* in params + kAsioSetInputGain, // ASIOChannelControls* in params, apply gain + kAsioGetInputMeter, // ASIOChannelControls* in params, fill meter + kAsioSetOutputGain, // ASIOChannelControls* in params, apply gain + kAsioGetOutputMeter, // ASIOChannelControls* in params, fill meter + kAsioCanInputMonitor, // no arguments for kAsioCanXXX selectors + kAsioCanTimeInfo, + kAsioCanTimeCode, + kAsioCanTransport, + kAsioCanInputGain, + kAsioCanInputMeter, + kAsioCanOutputGain, + kAsioCanOutputMeter, + kAsioOptionalOne, + + // DSD support + // The following extensions are required to allow switching + // and control of the DSD subsystem. + kAsioSetIoFormat = 0x23111961, /* ASIOIoFormat * in params. */ + kAsioGetIoFormat = 0x23111983, /* ASIOIoFormat * in params. */ + kAsioCanDoIoFormat = 0x23112004, /* ASIOIoFormat * in params. */ + + // Extension for drop out detection + kAsioCanReportOverload = 0x24042012, /* return ASE_SUCCESS if driver can detect and report overloads */ + + kAsioGetInternalBufferSamples = 0x25042012 /* ASIOInternalBufferInfo * in params. Deliver size of driver internal buffering, return ASE_SUCCESS if supported */ +}; + +typedef struct ASIOInputMonitor +{ + long input; // this input was set to monitor (or off), -1: all + long output; // suggested output for monitoring the input (if so) + long gain; // suggested gain, ranging 0 - 0x7fffffffL (-inf to +12 dB) + ASIOBool state; // ASIOTrue => on, ASIOFalse => off + long pan; // suggested pan, 0 => all left, 0x7fffffff => right +} ASIOInputMonitor; + +typedef struct ASIOChannelControls +{ + long channel; // on input, channel index + ASIOBool isInput; // on input + long gain; // on input, ranges 0 thru 0x7fffffff + long meter; // on return, ranges 0 thru 0x7fffffff + char future[32]; +} ASIOChannelControls; + +typedef struct ASIOTransportParameters +{ + long command; // see enum below + ASIOSamples samplePosition; + long track; + long trackSwitches[16]; // 512 tracks on/off + char future[64]; +} ASIOTransportParameters; + +enum +{ + kTransStart = 1, + kTransStop, + kTransLocate, // to samplePosition + kTransPunchIn, + kTransPunchOut, + kTransArmOn, // track + kTransArmOff, // track + kTransMonitorOn, // track + kTransMonitorOff, // track + kTransArm, // trackSwitches + kTransMonitor // trackSwitches +}; + +/* +// DSD support +// Some notes on how to use ASIOIoFormatType. +// +// The caller will fill the format with the request types. +// If the board can do the request then it will leave the +// values unchanged. If the board does not support the +// request then it will change that entry to Invalid (-1) +// +// So to request DSD then +// +// ASIOIoFormat NeedThis={kASIODSDFormat}; +// +// if(ASE_SUCCESS != ASIOFuture(kAsioSetIoFormat,&NeedThis) ){ +// // If the board did not accept one of the parameters then the +// // whole call will fail and the failing parameter will +// // have had its value changes to -1. +// } +// +// Note: Switching between the formats need to be done before the "prepared" +// state (see ASIO 2 documentation) is entered. +*/ +typedef long int ASIOIoFormatType; +enum ASIOIoFormatType_e +{ + kASIOFormatInvalid = -1, + kASIOPCMFormat = 0, + kASIODSDFormat = 1, +}; + +typedef struct ASIOIoFormat_s +{ + ASIOIoFormatType FormatType; + char future[512-sizeof(ASIOIoFormatType)]; +} ASIOIoFormat; + +// Extension for drop detection +// Note: Refers to buffering that goes beyond the double buffer e.g. used by USB driver designs +typedef struct ASIOInternalBufferInfo +{ + long inputSamples; // size of driver's internal input buffering which is included in getLatencies + long outputSamples; // size of driver's internal output buffering which is included in getLatencies +} ASIOInternalBufferInfo; + + +ASIOError ASIOOutputReady(void); +/* Purpose: + this tells the driver that the host has completed processing + the output buffers. if the data format required by the hardware + differs from the supported asio formats, but the hardware + buffers are DMA buffers, the driver will have to convert + the audio stream data; as the bufferSwitch callback is + usually issued at dma block switch time, the driver will + have to convert the *previous* host buffer, which increases + the output latency by one block. + when the host finds out that ASIOOutputReady() returns + true, it will issue this call whenever it completed + output processing. then the driver can convert the + host data directly to the dma buffer to be played next, + reducing output latency by one block. + another way to look at it is, that the buffer switch is called + in order to pass the *input* stream to the host, so that it can + process the input into the output, and the output stream is passed + to the driver when the host has completed its process. + Parameter: + None + Returns: + only if the above mentioned scenario is given, and a reduction + of output latency can be acheived by this mechanism, should + ASE_OK be returned. otherwise (and usually), ASE_NotPresent + should be returned in order to prevent further calls to this + function. note that the host may want to determine if it is + to use this when the system is not yet fully initialized, so + ASE_OK should always be returned if the mechanism makes sense. + Notes: + please remeber to adjust ASIOGetLatencies() according to + whether ASIOOutputReady() was ever called or not, if your + driver supports this scenario. + also note that the engine may fail to call ASIO_OutputReady() + in time in overload cases. as already mentioned, bufferSwitch + should be called for every block regardless of whether a block + could be processed in time. +*/ + +// restore old alignment +#if defined(_MSC_VER) && !defined(__MWERKS__) +#pragma pack(pop) +#elif PRAGMA_ALIGN_SUPPORTED +#pragma options align = reset +#endif + +#endif + diff --git a/Libraries/ASIO/common/asiodrvr.cpp b/Libraries/ASIO/common/asiodrvr.cpp new file mode 100644 index 0000000000..2da9c19759 --- /dev/null +++ b/Libraries/ASIO/common/asiodrvr.cpp @@ -0,0 +1,147 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Interfaces +// Filename : common/asiodrvr.cpp +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O Helpers +// c++ superclass to implement asio functionality. +// From this, you can derive whatever required +// +//----------------------------------------------------------------------------- +// This file is part of a Steinberg SDK. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this distribution +// and at www.steinberg.net/sdklicenses. +// No part of the SDK, including this file, may be copied, modified, propagated, +// or distributed except according to the terms contained in the LICENSE file. +//----------------------------------------------------------------------------- + +#include +#include "asiosys.h" +#include "asiodrvr.h" + +#if WINDOWS +#error do not use this +AsioDriver::AsioDriver (LPUNKNOWN pUnk, HRESULT *phr) : CUnknown("My AsioDriver", pUnk, phr) +{ +} + +#else + +AsioDriver::AsioDriver() +{ +} + +#endif + +AsioDriver::~AsioDriver() +{ +} + +ASIOBool AsioDriver::init(void *sysRef) +{ + return ASE_NotPresent; +} + +void AsioDriver::getDriverName(char *name) +{ + strcpy(name, "No Driver"); +} + +long AsioDriver::getDriverVersion() +{ + return 0; +} + +void AsioDriver::getErrorMessage(char *string) +{ + strcpy(string, "ASIO Driver Implementation Error!"); +} + +ASIOError AsioDriver::start() +{ + return ASE_NotPresent; +} + +ASIOError AsioDriver::stop() +{ + return ASE_NotPresent; +} + +ASIOError AsioDriver::getChannels(long *numInputChannels, long *numOutputChannels) +{ + return ASE_NotPresent; +} + +ASIOError AsioDriver::getLatencies(long *inputLatency, long *outputLatency) +{ + return ASE_NotPresent; +} + +ASIOError AsioDriver::getBufferSize(long *minSize, long *maxSize, + long *preferredSize, long *granularity) +{ + return ASE_NotPresent; +} + +ASIOError AsioDriver::canSampleRate(ASIOSampleRate sampleRate) +{ + return ASE_NotPresent; +} + +ASIOError AsioDriver::getSampleRate(ASIOSampleRate *sampleRate) +{ + return ASE_NotPresent; +} + +ASIOError AsioDriver::setSampleRate(ASIOSampleRate sampleRate) +{ + return ASE_NotPresent; +} + +ASIOError AsioDriver::getClockSources(ASIOClockSource *clocks, long *numSources) +{ + *numSources = 0; + return ASE_NotPresent; +} + +ASIOError AsioDriver::setClockSource(long reference) +{ + return ASE_NotPresent; +} + +ASIOError AsioDriver::getSamplePosition(ASIOSamples *sPos, ASIOTimeStamp *tStamp) +{ + return ASE_NotPresent; +} + +ASIOError AsioDriver::getChannelInfo(ASIOChannelInfo *info) +{ + return ASE_NotPresent; +} + +ASIOError AsioDriver::createBuffers(ASIOBufferInfo *channelInfos, long numChannels, + long bufferSize, ASIOCallbacks *callbacks) +{ + return ASE_NotPresent; +} + +ASIOError AsioDriver::disposeBuffers() +{ + return ASE_NotPresent; +} + +ASIOError AsioDriver::controlPanel() +{ + return ASE_NotPresent; +} + +ASIOError AsioDriver::future(long selector, void *opt) +{ + return ASE_NotPresent; +} + +ASIOError AsioDriver::outputReady() +{ + return ASE_NotPresent; +} \ No newline at end of file diff --git a/Libraries/ASIO/common/asiodrvr.h b/Libraries/ASIO/common/asiodrvr.h new file mode 100644 index 0000000000..e8967287d9 --- /dev/null +++ b/Libraries/ASIO/common/asiodrvr.h @@ -0,0 +1,84 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Interfaces +// Filename : common/asiodrvr.h +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O Helpers +// c++ superclass to implement asio functionality. +// From this, you can derive whatever required +// +//----------------------------------------------------------------------------- +// This file is part of a Steinberg SDK. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this distribution +// and at www.steinberg.net/sdklicenses. +// No part of the SDK, including this file, may be copied, modified, propagated, +// or distributed except according to the terms contained in the LICENSE file. +//----------------------------------------------------------------------------- + +#ifndef _asiodrvr_ +#define _asiodrvr_ + +// cpu and os system we are running on +#include "asiosys.h" +// basic "C" interface +#include "asio.h" + +class AsioDriver; +extern AsioDriver *getDriver(); // for generic constructor + +#if WINDOWS +#include +#include "combase.h" +#include "iasiodrv.h" +class AsioDriver : public IASIO ,public CUnknown +{ +public: + AsioDriver(LPUNKNOWN pUnk, HRESULT *phr); + + DECLARE_IUNKNOWN + // Factory method + static CUnknown *CreateInstance(LPUNKNOWN pUnk, HRESULT *phr); + // IUnknown + virtual HRESULT STDMETHODCALLTYPE NonDelegatingQueryInterface(REFIID riid,void **ppvObject); + +#else + +class AsioDriver +{ +public: + AsioDriver(); +#endif + virtual ~AsioDriver(); + + virtual ASIOBool init(void* sysRef); + virtual void getDriverName(char *name); // max 32 bytes incl. terminating zero + virtual long getDriverVersion(); + virtual void getErrorMessage(char *string); // max 124 bytes incl. + + virtual ASIOError start(); + virtual ASIOError stop(); + + virtual ASIOError getChannels(long *numInputChannels, long *numOutputChannels); + virtual ASIOError getLatencies(long *inputLatency, long *outputLatency); + virtual ASIOError getBufferSize(long *minSize, long *maxSize, + long *preferredSize, long *granularity); + + virtual ASIOError canSampleRate(ASIOSampleRate sampleRate); + virtual ASIOError getSampleRate(ASIOSampleRate *sampleRate); + virtual ASIOError setSampleRate(ASIOSampleRate sampleRate); + virtual ASIOError getClockSources(ASIOClockSource *clocks, long *numSources); + virtual ASIOError setClockSource(long reference); + + virtual ASIOError getSamplePosition(ASIOSamples *sPos, ASIOTimeStamp *tStamp); + virtual ASIOError getChannelInfo(ASIOChannelInfo *info); + + virtual ASIOError createBuffers(ASIOBufferInfo *bufferInfos, long numChannels, + long bufferSize, ASIOCallbacks *callbacks); + virtual ASIOError disposeBuffers(); + + virtual ASIOError controlPanel(); + virtual ASIOError future(long selector, void *opt); + virtual ASIOError outputReady(); +}; +#endif diff --git a/Libraries/ASIO/common/asiosys.h b/Libraries/ASIO/common/asiosys.h new file mode 100644 index 0000000000..335a3607e1 --- /dev/null +++ b/Libraries/ASIO/common/asiosys.h @@ -0,0 +1,97 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Interfaces +// Filename : common/asiosys.h +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O +// +//----------------------------------------------------------------------------- +// This file is part of a Steinberg SDK. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this distribution +// and at www.steinberg.net/sdklicenses. +// No part of the SDK, including this file, may be copied, modified, propagated, +// or distributed except according to the terms contained in the LICENSE file. +//----------------------------------------------------------------------------- + +#ifndef __asiosys__ + #define __asiosys__ + + #if defined(_WIN32) || defined(_WIN64) + #undef MAC + #define PPC 0 + #define WINDOWS 1 + #define SGI 0 + #define SUN 0 + #define LINUX 0 + #define BEOS 0 + + #define NATIVE_INT64 0 + #define IEEE754_64FLOAT 1 + + #elif BEOS + #define MAC 0 + #define PPC 0 + #define WINDOWS 0 + #define PC 0 + #define SGI 0 + #define SUN 0 + #define LINUX 0 + + #define NATIVE_INT64 0 + #define IEEE754_64FLOAT 1 + + #ifndef DEBUG + #define DEBUG 0 + #if DEBUG + void DEBUGGERMESSAGE(char *string); + #else + #define DEBUGGERMESSAGE(a) + #endif + #endif + + #elif SGI + #define MAC 0 + #define PPC 0 + #define WINDOWS 0 + #define PC 0 + #define SUN 0 + #define LINUX 0 + #define BEOS 0 + + #define NATIVE_INT64 0 + #define IEEE754_64FLOAT 1 + + #ifndef DEBUG + #define DEBUG 0 + #if DEBUG + void DEBUGGERMESSAGE(char *string); + #else + #define DEBUGGERMESSAGE(a) + #endif + #endif + + #else // MAC + #define MAC 1 + #define PPC 1 + #define WINDOWS 0 + #define PC 0 + #define SGI 0 + #define SUN 0 + #define LINUX 0 + #define BEOS 0 + + #define NATIVE_INT64 0 + #define IEEE754_64FLOAT 1 + + #ifndef DEBUG + #define DEBUG 0 + #if DEBUG + void DEBUGGERMESSAGE(char *string); + #else + #define DEBUGGERMESSAGE(a) + #endif + #endif + #endif + +#endif diff --git a/Libraries/ASIO/common/combase.cpp b/Libraries/ASIO/common/combase.cpp new file mode 100644 index 0000000000..eec44d7f93 --- /dev/null +++ b/Libraries/ASIO/common/combase.cpp @@ -0,0 +1,202 @@ +//==========================================================================; +// +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR +// PURPOSE. +// +// Copyright (c) 1992 - 1996 Microsoft Corporation. All Rights Reserved. +// +//--------------------------------------------------------------------------; + +// Base class hierachy for creating COM objects, December 1994 + +#include +#include "wxdebug.h" +#include "combase.h" +#pragma warning( disable : 4514 ) // Disable warnings re unused inline functions + + +/* Define the static member variable */ + +LONG CBaseObject::m_cObjects = 0; + + +/* Constructor */ + +CBaseObject::CBaseObject(TCHAR *pName) +{ + /* Increment the number of active objects */ + InterlockedIncrement(&m_cObjects); + +#ifdef DEBUG + m_dwCookie = DbgRegisterObjectCreation(pName); +#endif +} + + +/* Destructor */ + +CBaseObject::~CBaseObject() +{ + /* Decrement the number of objects active */ + InterlockedDecrement(&m_cObjects); + +#ifdef DEBUG + DbgRegisterObjectDestruction(m_dwCookie); +#endif +} + + +/* Constructor */ + +// We know we use "this" in the initialization list, we also know we don't modify *phr. +#pragma warning( disable : 4355 4100 ) +CUnknown::CUnknown(TCHAR *pName, LPUNKNOWN pUnk, HRESULT *phr) +: CBaseObject(pName) +/* Start the object with a reference count of zero - when the */ +/* object is queried for it's first interface this may be */ +/* incremented depending on whether or not this object is */ +/* currently being aggregated upon */ +, m_cRef(0) +/* Set our pointer to our IUnknown interface. */ +/* If we have an outer, use its, otherwise use ours. */ +/* This pointer effectivly points to the owner of */ +/* this object and can be accessed by the GetOwner() method. */ +, m_pUnknown( pUnk != 0 ? pUnk : reinterpret_cast( static_cast(this) ) ) + /* Why the double cast? Well, the inner cast is a type-safe cast */ + /* to pointer to a type from which we inherit. The second is */ + /* type-unsafe but works because INonDelegatingUnknown "behaves */ + /* like" IUnknown. (Only the names on the methods change.) */ +{ + // Everything we need to do has been done in the initializer list +} +#pragma warning( default : 4355 4100 ) + +/* QueryInterface */ + +STDMETHODIMP CUnknown::NonDelegatingQueryInterface(REFIID riid, void ** ppv) +{ + CheckPointer(ppv,E_POINTER); + ValidateReadWritePtr(ppv,sizeof(PVOID)); + + /* We know only about IUnknown */ + + if (riid == IID_IUnknown) { + GetInterface((LPUNKNOWN) (PNDUNKNOWN) this, ppv); + return NOERROR; + } else { + *ppv = NULL; + return E_NOINTERFACE; + } +} + +/* We have to ensure that we DON'T use a max macro, since these will typically */ +/* lead to one of the parameters being evaluated twice. Since we are worried */ +/* about concurrency, we can't afford to access the m_cRef twice since we can't */ +/* afford to run the risk that its value having changed between accesses. */ +#ifdef max + #undef max +#endif + +template inline static T max( const T & a, const T & b ) +{ + return a > b ? a : b; +} + +/* AddRef */ + +STDMETHODIMP_(ULONG) CUnknown::NonDelegatingAddRef() +{ + LONG lRef = InterlockedIncrement( &m_cRef ); + ASSERT(lRef > 0); + DbgLog((LOG_MEMORY,3,TEXT(" Obj %d ref++ = %d"), + m_dwCookie, m_cRef)); + return max(ULONG(m_cRef), 1ul); +} + + + +/* Release */ + +STDMETHODIMP_(ULONG) CUnknown::NonDelegatingRelease() +{ + /* If the reference count drops to zero delete ourselves */ + + LONG lRef = InterlockedDecrement( &m_cRef ); + ASSERT(lRef >= 0); + + DbgLog((LOG_MEMORY,3,TEXT(" Object %d ref-- = %d"), + m_dwCookie, m_cRef)); + if (lRef == 0) { + + // COM rules say we must protect against re-entrancy. + // If we are an aggregator and we hold our own interfaces + // on the aggregatee, the QI for these interfaces will + // addref ourselves. So after doing the QI we must release + // a ref count on ourselves. Then, before releasing the + // private interface, we must addref ourselves. When we do + // this from the destructor here it will result in the ref + // count going to 1 and then back to 0 causing us to + // re-enter the destructor. Hence we add an extra refcount here + // once we know we will delete the object. + // for an example aggregator see filgraph\distrib.cpp. + + m_cRef++; + + delete this; + return ULONG(0); + } else { + return max(ULONG(m_cRef), 1ul); + } +} + + +/* Return an interface pointer to a requesting client + performing a thread safe AddRef as necessary */ + +HRESULT CUnknown::GetInterface(LPUNKNOWN pUnk, void **ppv) +{ + CheckPointer(ppv, E_POINTER); + *ppv = pUnk; + pUnk->AddRef(); + return NOERROR; +} + + +/* Compares two interfaces and returns TRUE if they are on the same object */ + +BOOL IsEqualObject(IUnknown *pFirst, IUnknown *pSecond) +{ + /* Different objects can't have the same interface pointer for + any interface + */ + if (pFirst == pSecond) { + return TRUE; + } + /* OK - do it the hard way - check if they have the same + IUnknown pointers - a single object can only have one of these + */ + LPUNKNOWN pUnknown1; // Retrieve the IUnknown interface + LPUNKNOWN pUnknown2; // Retrieve the other IUnknown interface + HRESULT hr; // General OLE return code + + ASSERT(pFirst); + ASSERT(pSecond); + + /* See if the IUnknown pointers match */ + + hr = pFirst->QueryInterface(IID_IUnknown,(void **) &pUnknown1); + ASSERT(SUCCEEDED(hr)); + ASSERT(pUnknown1); + + hr = pSecond->QueryInterface(IID_IUnknown,(void **) &pUnknown2); + ASSERT(SUCCEEDED(hr)); + ASSERT(pUnknown2); + + /* Release the extra interfaces we hold */ + + pUnknown1->Release(); + pUnknown2->Release(); + return (pUnknown1 == pUnknown2); +} diff --git a/Libraries/ASIO/common/combase.h b/Libraries/ASIO/common/combase.h new file mode 100644 index 0000000000..fc51c94959 --- /dev/null +++ b/Libraries/ASIO/common/combase.h @@ -0,0 +1,284 @@ +//==========================================================================; +// +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR +// PURPOSE. +// +// Copyright (c) 1992 - 1996 Microsoft Corporation. All Rights Reserved. +// +//--------------------------------------------------------------------------; + +// Base class hierachy for creating COM objects, December 1994 + +/* + +a. Derive your COM object from CUnknown + +b. Make a static CreateInstance function that takes an LPUNKNOWN, an HRESULT * + and a TCHAR *. The LPUNKNOWN defines the object to delegate IUnknown calls + to. The HRESULT * allows error codes to be passed around constructors and + the TCHAR * is a descriptive name that can be printed on the debugger. + + It is important that constructors only change the HRESULT * if they have + to set an ERROR code, if it was successful then leave it alone or you may + overwrite an error code from an object previously created. + + When you call a constructor the descriptive name should be in static store + as we do not copy the string. To stop large amounts of memory being used + in retail builds by all these static strings use the NAME macro, + + CMyFilter = new CImplFilter(NAME("My filter"),pUnknown,phr); + if (FAILED(hr)) { + return hr; + } + + In retail builds NAME(_x_) compiles to NULL, the base CBaseObject class + knows not to do anything with objects that don't have a name. + +c. Have a constructor for your object that passes the LPUNKNOWN, HRESULT * and + TCHAR * to the CUnknown constructor. You can set the HRESULT if you have an + error, or just simply pass it through to the constructor. + + The object creation will fail in the class factory if the HRESULT indicates + an error (ie FAILED(HRESULT) == TRUE) + +d. Create a FactoryTemplate with your object's class id and CreateInstance + function. + +Then (for each interface) either + +Multiple inheritance + +1. Also derive it from ISomeInterface +2. Include DECLARE_IUNKNOWN in your class definition to declare + implementations of QueryInterface, AddRef and Release that + call the outer unknown +3. Override NonDelegatingQueryInterface to expose ISomeInterface by + code something like + + if (riid == IID_ISomeInterface) { + return GetInterface((ISomeInterface *) this, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } + +4. Declare and implement the member functions of ISomeInterface. + +or: Nested interfaces + +1. Declare a class derived from CUnknown +2. Include DECLARE_IUNKNOWN in your class definition +3. Override NonDelegatingQueryInterface to expose ISomeInterface by + code something like + + if (riid == IID_ISomeInterface) { + return GetInterface((ISomeInterface *) this, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } + +4. Implement the member functions of ISomeInterface. Use GetOwner() to + access the COM object class. + +And in your COM object class: + +5. Make the nested class a friend of the COM object class, and declare + an instance of the nested class as a member of the COM object class. + + NOTE that because you must always pass the outer unknown and an hResult + to the CUnknown constructor you cannot use a default constructor, in + other words you will have to make the member variable a pointer to the + class and make a NEW call in your constructor to actually create it. + +6. override the NonDelegatingQueryInterface with code like this: + + if (riid == IID_ISomeInterface) { + return m_pImplFilter-> + NonDelegatingQueryInterface(IID_ISomeInterface, ppv); + } else { + return CUnknown::NonDelegatingQueryInterface(riid, ppv); + } + +You can have mixed classes which support some interfaces via multiple +inheritance and some via nested classes + +*/ + +#ifndef __COMBASE__ +#define __COMBASE__ + +/* The DLLENTRY module initialises the module handle on loading */ + +extern HINSTANCE g_hInst; + +/* On DLL load remember which platform we are running on */ + +extern DWORD g_amPlatform; +extern OSVERSIONINFO g_osInfo; // Filled in by GetVersionEx + +/* Version of IUnknown that is renamed to allow a class to support both + non delegating and delegating IUnknowns in the same COM object */ + +DECLARE_INTERFACE(INonDelegatingUnknown) +{ + STDMETHOD(NonDelegatingQueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG, NonDelegatingAddRef)(THIS) PURE; + STDMETHOD_(ULONG, NonDelegatingRelease)(THIS) PURE; +}; + +typedef INonDelegatingUnknown *PNDUNKNOWN; + + +/* This is the base object class that supports active object counting. As + part of the debug facilities we trace every time a C++ object is created + or destroyed. The name of the object has to be passed up through the class + derivation list during construction as you cannot call virtual functions + in the constructor. The downside of all this is that every single object + constructor has to take an object name parameter that describes it */ + +class CBaseObject +{ + +private: + + // Disable the copy constructor and assignment by default so you will get + // compiler errors instead of unexpected behaviour if you pass objects + // by value or assign objects. + CBaseObject(const CBaseObject& objectSrc); // no implementation + void operator=(const CBaseObject& objectSrc); // no implementation + +private: + static LONG m_cObjects; /* Total number of objects active */ + +protected: +#ifdef DEBUG + DWORD m_dwCookie; /* Cookie identifying this object */ +#endif + + +public: + + /* These increment and decrement the number of active objects */ + + CBaseObject(TCHAR *pName); + ~CBaseObject(); + + /* Call this to find if there are any CUnknown derived objects active */ + + static LONG ObjectsActive() { + return m_cObjects; + }; +}; + + +/* An object that supports one or more COM interfaces will be based on + this class. It supports counting of total objects for DLLCanUnloadNow + support, and an implementation of the core non delegating IUnknown */ + +class CUnknown : public INonDelegatingUnknown, + public CBaseObject +{ + +private: + const LPUNKNOWN m_pUnknown; /* Owner of this object */ + +protected: /* So we can override NonDelegatingRelease() */ + volatile LONG m_cRef; /* Number of reference counts */ + +public: + + CUnknown(TCHAR *pName, LPUNKNOWN pUnk, HRESULT *phr); + virtual ~CUnknown() {}; + + /* Return the owner of this object */ + + LPUNKNOWN GetOwner() const { + return m_pUnknown; + }; + + /* Called from the class factory to create a new instance, it is + pure virtual so it must be overriden in your derived class */ + + /* static CUnknown *CreateInstance(LPUNKNOWN, HRESULT *) */ + + /* Non delegating unknown implementation */ + + STDMETHODIMP NonDelegatingQueryInterface(REFIID, void **); + STDMETHODIMP_(ULONG) NonDelegatingAddRef(); + STDMETHODIMP_(ULONG) NonDelegatingRelease(); + + /* Return an interface pointer to a requesting client + performing a thread safe AddRef as necessary */ + + HRESULT GetInterface(LPUNKNOWN pUnk, void **ppv); + + +}; + +#if WINVER < 0x0501 + +/* The standard InterlockedXXX functions won't take volatiles */ +static inline LONG InterlockedIncrement( volatile LONG * plong ) +{ return InterlockedIncrement( const_cast( plong ) ); } + +static inline LONG InterlockedDecrement( volatile LONG * plong ) +{ return InterlockedDecrement( const_cast( plong ) ); } + +static inline LONG InterlockedExchange( volatile LONG * plong, LONG new_value ) +{ return InterlockedExchange( const_cast( plong ), new_value ); } + +#endif + +/* A function that can create a new COM object */ + +typedef CUnknown *(*LPFNNewCOMObject)(LPUNKNOWN pUnkOuter, HRESULT *phr); + +/* A function (can be NULL) which is called from the DLL entrypoint + routine for each factory template: + + bLoading - TRUE on DLL load, FALSE on DLL unload + rclsid - the m_ClsID of the entry +*/ +typedef void (*LPFNInitRoutine)(BOOL bLoading, const CLSID *rclsid); + +/* Create one of these per object class in an array so that + the default class factory code can create new instances */ + +class CFactoryTemplate { + +public: + + const WCHAR *m_Name; + const CLSID *m_ClsID; + LPFNNewCOMObject m_lpfnNew; + LPFNInitRoutine m_lpfnInit; + + BOOL IsClassID(REFCLSID rclsid) const { + return (IsEqualCLSID(*m_ClsID,rclsid)); + }; + + CUnknown *CreateInstance(LPUNKNOWN pUnk, HRESULT *phr) const { + return m_lpfnNew(pUnk, phr); + }; +}; + + +/* You must override the (pure virtual) NonDelegatingQueryInterface to return + interface pointers (using GetInterface) to the interfaces your derived + class supports (the default implementation only supports IUnknown) */ + +#define DECLARE_IUNKNOWN \ + STDMETHODIMP QueryInterface(REFIID riid, void **ppv) { \ + return GetOwner()->QueryInterface(riid,ppv); \ + }; \ + STDMETHODIMP_(ULONG) AddRef() { \ + return GetOwner()->AddRef(); \ + }; \ + STDMETHODIMP_(ULONG) Release() { \ + return GetOwner()->Release(); \ + }; + +#endif /* __COMBASE__ */ + + diff --git a/Libraries/ASIO/common/debugmessage.cpp b/Libraries/ASIO/common/debugmessage.cpp new file mode 100644 index 0000000000..dbbb903834 --- /dev/null +++ b/Libraries/ASIO/common/debugmessage.cpp @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Helpers +// Filename : common/debugmessage.cpp +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O Helpers +// +//----------------------------------------------------------------------------- +// This file is part of a Steinberg SDK. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this distribution +// and at www.steinberg.net/sdklicenses. +// No part of the SDK, including this file, may be copied, modified, propagated, +// or distributed except according to the terms contained in the LICENSE file. +//----------------------------------------------------------------------------- + +#include "asiosys.h" + +#if DEBUG +#if MAC +#include +void DEBUGGERMESSAGE(char *string) +{ + c2pstr(string); + DebugStr((unsigned char *)string); +} +#else +#error debugmessage +#endif +#endif diff --git a/Libraries/ASIO/common/dllentry.cpp b/Libraries/ASIO/common/dllentry.cpp new file mode 100644 index 0000000000..d2b9e0a79d --- /dev/null +++ b/Libraries/ASIO/common/dllentry.cpp @@ -0,0 +1,323 @@ +//==========================================================================; +// +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR +// PURPOSE. +// +// Copyright (c) 1992 - 1996 Microsoft Corporation. All Rights Reserved. +// +//--------------------------------------------------------------------------; + +// +// classes used to support dll entrypoints for COM objects. +// +// #include "switches.h" + +#include +#include "wxdebug.h" +#include "combase.h" +#ifdef DEBUG +#include +#endif + +#include + +extern CFactoryTemplate g_Templates[]; +extern int g_cTemplates; + +HINSTANCE hinstance = 0; +DWORD g_amPlatform; // VER_PLATFORM_WIN32_WINDOWS etc... (from GetVersionEx) +OSVERSIONINFO g_osInfo; + +// +// an instance of this is created by the DLLGetClassObject entrypoint +// it uses the CFactoryTemplate object it is given to support the +// IClassFactory interface + +class CClassFactory : public IClassFactory +{ + +private: + const CFactoryTemplate * m_pTemplate; + + ULONG m_cRef; + + static int m_cLocked; +public: + CClassFactory(const CFactoryTemplate *); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void ** ppv); + STDMETHODIMP_(ULONG)AddRef(); + STDMETHODIMP_(ULONG)Release(); + + // IClassFactory + STDMETHODIMP CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, void **pv); + STDMETHODIMP LockServer(BOOL fLock); + + // allow DLLGetClassObject to know about global server lock status + static BOOL IsLocked() { + return (m_cLocked > 0); + }; +}; + +// process-wide dll locked state +int CClassFactory::m_cLocked = 0; + +CClassFactory::CClassFactory(const CFactoryTemplate *pTemplate) +{ + m_cRef = 0; + m_pTemplate = pTemplate; +} + + +STDMETHODIMP CClassFactory::QueryInterface(REFIID riid,void **ppv) +{ + CheckPointer(ppv,E_POINTER) + ValidateReadWritePtr(ppv,sizeof(PVOID)); + *ppv = NULL; + + // any interface on this object is the object pointer. + if ((riid == IID_IUnknown) || (riid == IID_IClassFactory)) { + *ppv = (LPVOID) this; + // AddRef returned interface pointer + ((LPUNKNOWN) *ppv)->AddRef(); + return NOERROR; + } + + return ResultFromScode(E_NOINTERFACE); +} + + +STDMETHODIMP_(ULONG) CClassFactory::AddRef() +{ + return ++m_cRef; +} + +STDMETHODIMP_(ULONG) CClassFactory::Release() +{ + LONG rc; + + if (--m_cRef == 0) { + delete this; + rc = 0; + } else rc = m_cRef; + + return rc; +} + +STDMETHODIMP CClassFactory::CreateInstance(LPUNKNOWN pUnkOuter,REFIID riid,void **pv) +{ + CheckPointer(pv,E_POINTER) + ValidateReadWritePtr(pv,sizeof(void *)); + + /* Enforce the normal OLE rules regarding interfaces and delegation */ + + if (pUnkOuter != NULL) { + if (IsEqualIID(riid,IID_IUnknown) == FALSE) { + return ResultFromScode(E_NOINTERFACE); + } + } + + /* Create the new object through the derived class's create function */ + + HRESULT hr = NOERROR; + CUnknown *pObj = m_pTemplate->CreateInstance(pUnkOuter, &hr); + + if (pObj == NULL) { + return E_OUTOFMEMORY; + } + + /* Delete the object if we got a construction error */ + + if (FAILED(hr)) { + delete pObj; + return hr; + } + + /* Get a reference counted interface on the object */ + + /* We wrap the non-delegating QI with NDAddRef & NDRelease. */ + /* This protects any outer object from being prematurely */ + /* released by an inner object that may have to be created */ + /* in order to supply the requested interface. */ + pObj->NonDelegatingAddRef(); + hr = pObj->NonDelegatingQueryInterface(riid, pv); + pObj->NonDelegatingRelease(); + /* Note that if NonDelegatingQueryInterface fails, it will */ + /* not increment the ref count, so the NonDelegatingRelease */ + /* will drop the ref back to zero and the object will "self-*/ + /* destruct". Hence we don't need additional tidy-up code */ + /* to cope with NonDelegatingQueryInterface failing. */ + + if (SUCCEEDED(hr)) { + ASSERT(*pv); + } + + return hr; +} + +STDMETHODIMP CClassFactory::LockServer(BOOL fLock) +{ + if (fLock) { + m_cLocked++; + } else { + m_cLocked--; + } + return NOERROR; +} + + +// --- COM entrypoints ----------------------------------------- +// DllRegisterServer + +//called by COM to get the class factory object for a given class +STDAPI DllGetClassObject(REFCLSID rClsID,REFIID riid,void **pv) +{ + // DebugBreak(); + + if (!(riid == IID_IUnknown) && !(riid == IID_IClassFactory)) { + return E_NOINTERFACE; + } + + // traverse the array of templates looking for one with this + // class id + for (int i = 0; i < g_cTemplates; i++) { + const CFactoryTemplate * pT = &g_Templates[i]; + if (pT->IsClassID(rClsID)) { + + // found a template - make a class factory based on this + // template + + *pv = (LPVOID) (LPUNKNOWN) new CClassFactory(pT); + if (*pv == NULL) { + return E_OUTOFMEMORY; + } + ((LPUNKNOWN)*pv)->AddRef(); + return NOERROR; + } + } + return CLASS_E_CLASSNOTAVAILABLE; +} + +// +// Call any initialization routines +// +void DllInitClasses(BOOL bLoading) +{ + int i; + + // DebugBreak(); + + // traverse the array of templates calling the init routine + // if they have one + for (i = 0; i < g_cTemplates; i++) { + const CFactoryTemplate * pT = &g_Templates[i]; + if (pT->m_lpfnInit != NULL) { + (*pT->m_lpfnInit)(bLoading, pT->m_ClsID); + } + } + +} + +// called by COM to determine if this dll can be unloaded +// return ok unless there are outstanding objects or a lock requested +// by IClassFactory::LockServer +// +// CClassFactory has a static function that can tell us about the locks, +// and CCOMObject has a static function that can tell us about the active +// object count +STDAPI DllCanUnloadNow() +{ + // DebugBreak(); + + DbgLog((LOG_MEMORY,2,TEXT("DLLCanUnloadNow called - IsLocked = %d, Active objects = %d"), + CClassFactory::IsLocked(), + CBaseObject::ObjectsActive())); + + if (CClassFactory::IsLocked() || CBaseObject::ObjectsActive()) { + + return S_FALSE; + } + else { + return S_OK; + } +} + + +// --- standard WIN32 entrypoints -------------------------------------- + + +//extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID); +//BOOL WINAPI DllEntryPoint(HINSTANCE hInstance,ULONG ulReason,LPVOID pv) +//BOOL WINAPI DllMain (HINSTANCE hInstance,ULONG ulReason,LPVOID pv) +BOOL WINAPI DllEntryPoint (HINSTANCE hInstance,ULONG ulReason,LPVOID pv) +{ + + // DebugBreak(); + + switch (ulReason) { + + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hInstance); + DbgInitialise(hInstance); + { + // The platform identifier is used to work out whether + // full unicode support is available or not. Hence the + // default will be the lowest common denominator - i.e. N/A + g_amPlatform = VER_PLATFORM_WIN32_WINDOWS; // win95 assumed in case GetVersionEx fails + + g_osInfo.dwOSVersionInfoSize = sizeof(g_osInfo); + if (GetVersionEx(&g_osInfo)) { + g_amPlatform = g_osInfo.dwPlatformId; + } + else { + DbgLog((LOG_ERROR, 1, "Failed to get the OS platform, assuming Win95")); + } + } + hinstance = hInstance; + DllInitClasses(TRUE); + + break; + + case DLL_PROCESS_DETACH: + DllInitClasses(FALSE); + +#ifdef DEBUG + if (CBaseObject::ObjectsActive()) { + DbgSetModuleLevel(LOG_MEMORY, 2); + TCHAR szInfo[512]; + extern TCHAR m_ModuleName[]; // Cut down module name + + TCHAR FullName[_MAX_PATH]; // Load the full path and module name + TCHAR *pName; // Searches from the end for a backslash + + GetModuleFileName(NULL,FullName,_MAX_PATH); + pName = _tcsrchr(FullName,'\\'); + if (pName == NULL) { + pName = FullName; + } + else { + pName++; + } + + DWORD cch = wsprintf(szInfo, TEXT("Executable: %s Pid %x Tid %x. "), + pName, GetCurrentProcessId(), GetCurrentThreadId()); + + wsprintf(szInfo+cch, TEXT("Module %s, %d objects left active!"), + m_ModuleName, CBaseObject::ObjectsActive()); + DbgAssert(szInfo, TEXT(__FILE__),__LINE__); + + // If running remotely wait for the Assert to be acknowledged + // before dumping out the object register + DbgDumpObjectRegister(); + } + DbgTerminate(); +#endif + break; + } + return TRUE; +} + + diff --git a/Libraries/ASIO/common/iasiodrv.h b/Libraries/ASIO/common/iasiodrv.h new file mode 100644 index 0000000000..4721b9ad03 --- /dev/null +++ b/Libraries/ASIO/common/iasiodrv.h @@ -0,0 +1,53 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Interfaces +// Filename : common/iasiodrv.h +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O API v2.3 +// +//----------------------------------------------------------------------------- +// This file is part of a Steinberg SDK. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this distribution +// and at www.steinberg.net/sdklicenses. +// No part of the SDK, including this file, may be copied, modified, propagated, +// or distributed except according to the terms contained in the LICENSE file. +//----------------------------------------------------------------------------- + +#include "asiosys.h" +#include "asio.h" + +/* Forward Declarations */ + +#ifndef __ASIODRIVER_FWD_DEFINED__ +#define __ASIODRIVER_FWD_DEFINED__ +typedef interface IASIO IASIO; +#endif /* __ASIODRIVER_FWD_DEFINED__ */ + +interface IASIO : public IUnknown +{ + + virtual ASIOBool init(void *sysHandle) = 0; + virtual void getDriverName(char *name) = 0; + virtual long getDriverVersion() = 0; + virtual void getErrorMessage(char *string) = 0; + virtual ASIOError start() = 0; + virtual ASIOError stop() = 0; + virtual ASIOError getChannels(long *numInputChannels, long *numOutputChannels) = 0; + virtual ASIOError getLatencies(long *inputLatency, long *outputLatency) = 0; + virtual ASIOError getBufferSize(long *minSize, long *maxSize, + long *preferredSize, long *granularity) = 0; + virtual ASIOError canSampleRate(ASIOSampleRate sampleRate) = 0; + virtual ASIOError getSampleRate(ASIOSampleRate *sampleRate) = 0; + virtual ASIOError setSampleRate(ASIOSampleRate sampleRate) = 0; + virtual ASIOError getClockSources(ASIOClockSource *clocks, long *numSources) = 0; + virtual ASIOError setClockSource(long reference) = 0; + virtual ASIOError getSamplePosition(ASIOSamples *sPos, ASIOTimeStamp *tStamp) = 0; + virtual ASIOError getChannelInfo(ASIOChannelInfo *info) = 0; + virtual ASIOError createBuffers(ASIOBufferInfo *bufferInfos, long numChannels, + long bufferSize, ASIOCallbacks *callbacks) = 0; + virtual ASIOError disposeBuffers() = 0; + virtual ASIOError controlPanel() = 0; + virtual ASIOError future(long selector,void *opt) = 0; + virtual ASIOError outputReady() = 0; +}; diff --git a/Libraries/ASIO/common/register.cpp b/Libraries/ASIO/common/register.cpp new file mode 100644 index 0000000000..ad59ce6435 --- /dev/null +++ b/Libraries/ASIO/common/register.cpp @@ -0,0 +1,348 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Helpers +// Filename : common/register.cpp +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O Helpers +// +//----------------------------------------------------------------------------- +// This file is part of a Steinberg SDK. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this distribution +// and at www.steinberg.net/sdklicenses. +// No part of the SDK, including this file, may be copied, modified, propagated, +// or distributed except according to the terms contained in the LICENSE file. +//----------------------------------------------------------------------------- + +#include +#include + +typedef struct keylist +{ + HKEY mainKey; + HKEY subKey; + char *keyname; + struct keylist *next; +} KEYLIST, *LPKEYLIST; + +#define CLSID_STRING_LEN 100 +#define MAX_PATH_LEN 360 + +#define DEV_ERR_SELFREG -100 +#define ERRSREG_MODULE_NOT_FOUND DEV_ERR_SELFREG-1 +#define ERRSREG_MODPATH_NOT_FOUND DEV_ERR_SELFREG-2 +#define ERRSREG_STRING_FROM_CLSID DEV_ERR_SELFREG-3 +#define ERRSREG_CHAR_TO_MULTIBYTE DEV_ERR_SELFREG-4 + +#define SZREGSTR_DESCRIPTION "Description" +#define SZREGSTR_CLSID "CLSID" +#define SZREGSTR_INPROCSERVER32 "InprocServer32" +#define SZREGSTR_THREADINGMODEL "ThreadingModel" +#define SZREGSTR_SOFTWARE "SOFTWARE" +#define SZREGSTR_ASIO "ASIO" + +LONG RegisterAsioDriver (CLSID,char *,char *,char *,char *); +LONG UnregisterAsioDriver (CLSID,char *,char *); + +static LONG findRegPath (HKEY,char *); +static LONG createRegPath (HKEY,char *,char *); +static LONG createRegStringValue (HKEY,char *,char *,char *); +static LONG getRegString (HKEY,char *,char *,LPVOID,DWORD); +static LPKEYLIST findAllSubKeys (HKEY,HKEY,DWORD,char *,LPKEYLIST); +static LONG deleteRegPath (HKEY,char *,char *); + +static char subkeybuf[MAX_PATH_LEN]; + + +// ------------------------------------------ +// Global Functions +// ------------------------------------------ +LONG RegisterAsioDriver (CLSID clsid,char *szdllname,char *szregname,char *szasiodesc,char *szthreadmodel) +{ + HMODULE hMod; + char szDLLPath[MAX_PATH_LEN]; + char szModuleName[MAX_PATH_LEN]; + char szregpath[MAX_PATH_LEN]; + char szclsid[CLSID_STRING_LEN]; + LPOLESTR wszCLSID = NULL; + BOOL newregentry = FALSE; + LONG rc; + + hMod = GetModuleHandle(szdllname); + if (!hMod) return ERRSREG_MODULE_NOT_FOUND; + szModuleName[0] = 0; + GetModuleFileName(hMod,szModuleName,MAX_PATH_LEN); + if (!szModuleName[0]) return ERRSREG_MODPATH_NOT_FOUND; + CharLower((LPTSTR)szModuleName); + + rc = (LONG)StringFromCLSID(clsid,&wszCLSID); + if (rc != S_OK) return ERRSREG_STRING_FROM_CLSID; + rc = (LONG)WideCharToMultiByte(CP_ACP,0,(LPWSTR)wszCLSID,-1,szclsid,CLSID_STRING_LEN,0,0); + if (!rc) return ERRSREG_CHAR_TO_MULTIBYTE; + + sprintf(szregpath,"%s\\%s",SZREGSTR_CLSID,szclsid); + rc = findRegPath(HKEY_CLASSES_ROOT,szregpath); + if (rc) { + sprintf(szregpath,"%s\\%s\\%s",SZREGSTR_CLSID,szclsid,SZREGSTR_INPROCSERVER32); + getRegString (HKEY_CLASSES_ROOT,szregpath,0,(LPVOID)szDLLPath,MAX_PATH_LEN); + CharLower((LPTSTR)szDLLPath); + rc = (LONG)strcmp(szDLLPath,szModuleName); + if (rc) { + // delete old regentry + sprintf(szregpath,"%s",SZREGSTR_CLSID); + deleteRegPath(HKEY_CLASSES_ROOT,szregpath,szclsid); + newregentry = TRUE; + } + } + else newregentry = TRUE; + + if (newregentry) { + // HKEY_CLASSES_ROOT\CLSID\{...} + sprintf(szregpath,"%s",SZREGSTR_CLSID); + createRegPath (HKEY_CLASSES_ROOT,szregpath,szclsid); + // HKEY_CLASSES_ROOT\CLSID\{...} -> Description + sprintf(szregpath,"%s\\%s",SZREGSTR_CLSID,szclsid); + createRegStringValue(HKEY_CLASSES_ROOT,szregpath,0,szasiodesc); + // HKEY_CLASSES_ROOT\CLSID\InProcServer32 + sprintf(szregpath,"%s\\%s",SZREGSTR_CLSID,szclsid); + createRegPath (HKEY_CLASSES_ROOT,szregpath,SZREGSTR_INPROCSERVER32); + // HKEY_CLASSES_ROOT\CLSID\InProcServer32 -> DLL path + sprintf(szregpath,"%s\\%s\\%s",SZREGSTR_CLSID,szclsid,SZREGSTR_INPROCSERVER32); + createRegStringValue(HKEY_CLASSES_ROOT,szregpath,0,szModuleName); + // HKEY_CLASSES_ROOT\CLSID\InProcServer32 -> ThreadingModel + createRegStringValue(HKEY_CLASSES_ROOT,szregpath,SZREGSTR_THREADINGMODEL,szthreadmodel); + } + + // HKEY_LOCAL_MACHINE\SOFTWARE\ASIO + sprintf(szregpath,"%s\\%s",SZREGSTR_SOFTWARE,SZREGSTR_ASIO); + rc = findRegPath(HKEY_LOCAL_MACHINE,szregpath); + if (rc) { + sprintf(szregpath,"%s\\%s\\%s",SZREGSTR_SOFTWARE,SZREGSTR_ASIO,szregname); + rc = findRegPath(HKEY_LOCAL_MACHINE,szregpath); + if (rc) { + sprintf(szregpath,"%s\\%s",SZREGSTR_SOFTWARE,SZREGSTR_ASIO); + deleteRegPath(HKEY_LOCAL_MACHINE,szregpath,szregname); + } + } + else { + // HKEY_LOCAL_MACHINE\SOFTWARE\ASIO + sprintf(szregpath,"%s",SZREGSTR_SOFTWARE); + createRegPath (HKEY_LOCAL_MACHINE,szregpath,SZREGSTR_ASIO); + } + + // HKEY_LOCAL_MACHINE\SOFTWARE\ASIO\ + sprintf(szregpath,"%s\\%s",SZREGSTR_SOFTWARE,SZREGSTR_ASIO); + createRegPath (HKEY_LOCAL_MACHINE,szregpath,szregname); + + // HKEY_LOCAL_MACHINE\SOFTWARE\ASIO\ -> CLSID + // HKEY_LOCAL_MACHINE\SOFTWARE\ASIO\ -> Description + sprintf(szregpath,"%s\\%s\\%s",SZREGSTR_SOFTWARE,SZREGSTR_ASIO,szregname); + createRegStringValue(HKEY_LOCAL_MACHINE,szregpath,SZREGSTR_CLSID,szclsid); + createRegStringValue(HKEY_LOCAL_MACHINE,szregpath,SZREGSTR_DESCRIPTION,szasiodesc); + + return 0; +} + + +LONG UnregisterAsioDriver (CLSID clsid,char *szdllname,char *szregname) +{ + LONG rc; + HMODULE hMod; + char szregpath[MAX_PATH_LEN]; + char szModuleName[MAX_PATH_LEN]; + char szclsid[CLSID_STRING_LEN]; + LPOLESTR wszCLSID = NULL; + + + hMod = GetModuleHandle(szdllname); + if (!hMod) return ERRSREG_MODULE_NOT_FOUND; + szModuleName[0] = 0; + GetModuleFileName(hMod,szModuleName,MAX_PATH_LEN); + if (!szModuleName[0]) return ERRSREG_MODPATH_NOT_FOUND; + CharLower((LPTSTR)szModuleName); + + rc = (LONG)StringFromCLSID(clsid,&wszCLSID) ; + if (rc != S_OK) return ERRSREG_STRING_FROM_CLSID; + rc = (LONG)WideCharToMultiByte(CP_ACP,0,(LPWSTR)wszCLSID,-1,szclsid,CLSID_STRING_LEN,0,0); + if (!rc) return ERRSREG_CHAR_TO_MULTIBYTE; + + + sprintf(szregpath,"%s\\%s",SZREGSTR_CLSID,szclsid); + rc = findRegPath(HKEY_CLASSES_ROOT,szregpath); + if (rc) { + // delete old regentry + sprintf(szregpath,"%s",SZREGSTR_CLSID); + deleteRegPath(HKEY_CLASSES_ROOT,szregpath,szclsid); + } + + + // HKEY_LOCAL_MACHINE\SOFTWARE\ASIO + sprintf(szregpath,"%s\\%s",SZREGSTR_SOFTWARE,SZREGSTR_ASIO); + rc = findRegPath(HKEY_LOCAL_MACHINE,szregpath); + if (rc) { + sprintf(szregpath,"%s\\%s\\%s",SZREGSTR_SOFTWARE,SZREGSTR_ASIO,szregname); + rc = findRegPath(HKEY_LOCAL_MACHINE,szregpath); + if (rc) { + sprintf(szregpath,"%s\\%s",SZREGSTR_SOFTWARE,SZREGSTR_ASIO); + deleteRegPath(HKEY_LOCAL_MACHINE,szregpath,szregname); + } + } + + return 0; +} + + +// ------------------------------------------ +// Local Functions +// ------------------------------------------ +static LONG findRegPath (HKEY mainkey,char *szregpath) +{ + HKEY hkey; + LONG cr,rc = -1; + + if (szregpath) { + if ((cr = RegOpenKey(mainkey,szregpath,&hkey)) == ERROR_SUCCESS) { + RegCloseKey(hkey); + rc = 1; + } + else rc = 0; + } + + return rc; +} + +static LONG createRegPath (HKEY mainkey,char *szregpath,char *sznewpath) +{ + HKEY hkey,hksub; + LONG cr,rc = -1; + char newregpath[MAX_PATH_LEN]; + + sprintf(newregpath,"%s\\%s",szregpath,sznewpath); + if (!(cr = findRegPath(mainkey,newregpath))) { + if ((cr = RegOpenKey(mainkey,szregpath,&hkey)) == ERROR_SUCCESS) { + if ((cr = RegCreateKey(hkey,sznewpath,&hksub)) == ERROR_SUCCESS) { + RegCloseKey(hksub); + rc = 0; + } + RegCloseKey(hkey); + } + } + else if (cr > 0) rc = 0; + + return rc; +} + +static LONG createRegStringValue (HKEY mainkey,char *szregpath,char *valname,char *szvalstr) +{ + LONG cr,rc = -1; + HKEY hkey; + + if (szregpath) { + if ((cr = RegOpenKey(mainkey,szregpath,&hkey)) == ERROR_SUCCESS) { + cr = RegSetValueEx(hkey,(LPCTSTR)valname,0,REG_SZ,(const BYTE *)szvalstr,(DWORD)strlen(szvalstr)); + RegCloseKey(hkey); + if (cr == ERROR_SUCCESS) rc = 0; + } + } + + return rc; +} + + +static LONG getRegString (HKEY mainkey,char *szregpath,char *valname,LPVOID pval,DWORD vsize) +{ + HKEY hkey; + LONG cr,rc = -1; + DWORD hsize,htype; + + if (szregpath) { + if ((cr = RegOpenKey(mainkey,szregpath,&hkey)) == ERROR_SUCCESS) { + cr = RegQueryValueEx(hkey,valname,0,&htype,0,&hsize); + if (cr == ERROR_SUCCESS) { + if (hsize <= vsize) { + cr = RegQueryValueEx(hkey,valname,0,&htype,(LPBYTE)pval,&hsize); + rc = 0; + } + } + RegCloseKey(hkey); + } + } + return rc; +} + +static LPKEYLIST findAllSubKeys (HKEY hkey,HKEY hksub,DWORD index,char *keyname,LPKEYLIST kl) +{ + HKEY hknew = 0; + char *newkey; + LONG cr; + + if (!hksub) { + cr = RegOpenKeyEx(hkey,(LPCTSTR)keyname,0,KEY_ALL_ACCESS,&hknew); + if (cr != ERROR_SUCCESS) return kl; + } + else hknew = hksub; + + cr = RegEnumKey(hknew,index,(LPTSTR)subkeybuf,MAX_PATH_LEN); + if (cr == ERROR_SUCCESS) { + newkey = new char[strlen(subkeybuf)+1]; + strcpy(newkey,subkeybuf); + + kl = findAllSubKeys(hknew,0,0,newkey,kl); + kl = findAllSubKeys(hkey,hknew,index+1,keyname,kl); + + return kl; + + } + + if (!kl->next) { + kl->next = new KEYLIST[1]; + kl = kl->next; + kl->mainKey = hkey; + kl->subKey = hknew; + kl->keyname = keyname; + kl->next = 0; + } + + return kl; +} + +static LONG deleteRegPath (HKEY mainkey,char *szregpath,char *szdelpath) +{ + HKEY hkey; + LONG cr,rc = -1; + KEYLIST klist; + LPKEYLIST pkl,k; + char *keyname = 0; + + if ((cr = RegOpenKey(mainkey,szregpath,&hkey)) == ERROR_SUCCESS) { + + keyname = new char[strlen(szdelpath)+1]; + if (!keyname) { + RegCloseKey(hkey); + return rc; + } + strcpy(keyname,szdelpath); + klist.next = 0; + + findAllSubKeys(hkey,0,0,keyname,&klist); + + if (klist.next) { + pkl = klist.next; + + while (pkl) { + RegCloseKey(pkl->subKey); + cr = RegDeleteKey(pkl->mainKey,pkl->keyname); + delete pkl->keyname; + k = pkl; + pkl = pkl->next; + delete k; + } + rc = 0; + } + + RegCloseKey(hkey); + } + + return rc; +} + diff --git a/Libraries/ASIO/common/wxdebug.h b/Libraries/ASIO/common/wxdebug.h new file mode 100644 index 0000000000..3d8585cf51 --- /dev/null +++ b/Libraries/ASIO/common/wxdebug.h @@ -0,0 +1,326 @@ +//==========================================================================; +// +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR +// PURPOSE. +// +// Copyright (c) 1992 - 1996 Microsoft Corporation. All Rights Reserved. +// +//--------------------------------------------------------------------------; + +// Debugging facilities, January 1995 + +#ifndef __WXDEBUG__ +#define __WXDEBUG__ + +// Avoid conflict with MFC +#undef ASSERT + +// This library provides fairly straight forward debugging functionality, this +// is split into two main sections. The first is assertion handling, there are +// three types of assertions provided here. The most commonly used one is the +// ASSERT(condition) macro which will pop up a message box including the file +// and line number if the condition evaluates to FALSE. Then there is the +// EXECUTE_ASSERT macro which is the same as ASSERT except the condition will +// still be executed in NON debug builds. The final type of assertion is the +// KASSERT macro which is more suitable for pure (perhaps kernel) filters as +// the condition is printed onto the debugger rather than in a message box. +// +// The other part of the debug module facilties is general purpose logging. +// This is accessed by calling DbgLog(). The function takes a type and level +// field which define the type of informational string you are presenting and +// it's relative importance. The type field can be a combination (one or more) +// of LOG_TIMING, LOG_TRACE, LOG_MEMORY, LOG_LOCKING and LOG_ERROR. The level +// is a DWORD value where zero defines highest important. Use of zero as the +// debug logging level is to be encouraged ONLY for major errors or events as +// they will ALWAYS be displayed on the debugger. Other debug output has it's +// level matched against the current debug output level stored in the registry +// for this module and if less than the current setting it will be displayed. +// +// Each module or executable has it's own debug output level for each of the +// five types. These are read in when the DbgInitialise function is called +// for DLLs linking to STRMBASE.LIB this is done automatically when the DLL +// is loaded, executables must call it explicitely with the module instance +// handle given to them through the WINMAIN entry point. An executable must +// also call DbgTerminate when they have finished to clean up the resources +// the debug library uses, once again this is done automatically for DLLs + +// These are the five different categories of logging information + +enum { LOG_TIMING = 0x01, // Timing and performance measurements + LOG_TRACE = 0x02, // General step point call tracing + LOG_MEMORY = 0x04, // Memory and object allocation/destruction + LOG_LOCKING = 0x08, // Locking/unlocking of critical sections + LOG_ERROR = 0x10 }; // Debug error notification + +enum { CDISP_HEX = 0x01, + CDISP_DEC = 0x02}; + +// For each object created derived from CBaseObject (in debug builds) we +// create a descriptor that holds it's name (statically allocated memory) +// and a cookie we assign it. We keep a list of all the active objects +// we have registered so that we can dump a list of remaining objects + +typedef struct tag_ObjectDesc { + TCHAR *m_pName; + DWORD m_dwCookie; + tag_ObjectDesc *m_pNext; +} ObjectDesc; + +#define DLLIMPORT __declspec(dllimport) +#define DLLEXPORT __declspec(dllexport) + +#ifdef DEBUG + + #define NAME(x) TEXT(x) + + // These are used internally by the debug library (PRIVATE) + + void DbgInitKeyLevels(HKEY hKey); + void DbgInitGlobalSettings(); + void DbgInitModuleSettings(); + void DbgInitModuleName(); + DWORD DbgRegisterObjectCreation(TCHAR *pObjectName); + BOOL DbgRegisterObjectDestruction(DWORD dwCookie); + + // These are the PUBLIC entry points + + BOOL DbgCheckModuleLevel(DWORD Type,DWORD Level); + void DbgSetModuleLevel(DWORD Type,DWORD Level); + + // Initialise the library with the module handle + + void DbgInitialise(HINSTANCE hInst); + void DbgTerminate(); + + void DbgDumpObjectRegister(); + + // Display error and logging to the user + + void DbgAssert(const TCHAR *pCondition,const TCHAR *pFileName,INT iLine); + void DbgBreakPoint(const TCHAR *pCondition,const TCHAR *pFileName,INT iLine); + void DbgKernelAssert(const TCHAR *pCondition,const TCHAR *pFileName,INT iLine); + void DbgLogInfo(DWORD Type,DWORD Level,const TCHAR *pFormat,...); + void DbgOutString(LPCTSTR psz); + + // Debug infinite wait stuff + DWORD DbgWaitForSingleObject(HANDLE h); + DWORD DbgWaitForMultipleObjects(DWORD nCount, + CONST HANDLE *lpHandles, + BOOL bWaitAll); + void DbgSetWaitTimeout(DWORD dwTimeout); + +#ifdef __strmif_h__ + void DisplayType(LPSTR label, const AM_MEDIA_TYPE *pmtIn); +#endif + + #define KASSERT(_x_) if (!(_x_)) \ + DbgKernelAssert(TEXT(#_x_),TEXT(__FILE__),__LINE__) + + // Break on the debugger without putting up a message box + // message goes to debugger instead + + #define KDbgBreak(_x_) \ + DbgKernelAssert(TEXT(#_x_),TEXT(__FILE__),__LINE__) + + #define ASSERT(_x_) if (!(_x_)) \ + DbgAssert(TEXT(#_x_),TEXT(__FILE__),__LINE__) + + // Put up a message box informing the user of a halt + // condition in the program + + #define DbgBreak(_x_) \ + DbgBreakPoint(TEXT(#_x_),TEXT(__FILE__),__LINE__) + + #define EXECUTE_ASSERT(_x_) ASSERT(_x_) + #define DbgLog(_x_) DbgLogInfo _x_ + + // MFC style trace macros + + #define NOTE(_x_) DbgLog((LOG_TRACE,5,TEXT(_x_))); + #define NOTE1(_x_,a) DbgLog((LOG_TRACE,5,TEXT(_x_),a)); + #define NOTE2(_x_,a,b) DbgLog((LOG_TRACE,5,TEXT(_x_),a,b)); + #define NOTE3(_x_,a,b,c) DbgLog((LOG_TRACE,5,TEXT(_x_),a,b,c)); + #define NOTE4(_x_,a,b,c,d) DbgLog((LOG_TRACE,5,TEXT(_x_),a,b,c,d)); + #define NOTE5(_x_,a,b,c,d,e) DbgLog((LOG_TRACE,5,TEXT(_x_),a,b,c,d,e)); + +#else + + // Retail builds make public debug functions inert - WARNING the source + // files do not define or build any of the entry points in debug builds + // (public entry points compile to nothing) so if you go trying to call + // any of the private entry points in your source they won't compile + + #define NAME(_x_) NULL + + #define DbgInitialise(hInst) + #define DbgTerminate() + #define DbgLog(_x_) + #define DbgOutString(psz) + + #define DbgRegisterObjectCreation(pObjectName) + #define DbgRegisterObjectDestruction(dwCookie) + #define DbgDumpObjectRegister() + + #define DbgCheckModuleLevel(Type,Level) + #define DbgSetModuleLevel(Type,Level) + + #define DbgWaitForSingleObject(h) WaitForSingleObject(h, INFINITE) + #define DbgWaitForMultipleObjects(nCount, lpHandles, bWaitAll) \ + WaitForMultipleObjects(nCount, lpHandles, bWaitAll, INFINITE) + #define DbgSetWaitTimeout(dwTimeout) + + #define KDbgBreak(_x_) + #define DbgBreak(_x_) + + #define KASSERT(_x_) + #define ASSERT(_x_) + #define EXECUTE_ASSERT(_x_) _x_ + + // MFC style trace macros + + #define NOTE(_x_) + #define NOTE1(_x_,a) + #define NOTE2(_x_,a,b) + #define NOTE3(_x_,a,b,c) + #define NOTE4(_x_,a,b,c,d) + #define NOTE5(_x_,a,b,c,d,e) + + #define DisplayType(label, pmtIn) + +#endif + + +// Checks a pointer which should be non NULL - can be used as follows. + +#define CheckPointer(p,ret) {if((p)==NULL) return (ret);} + +// HRESULT Foo(VOID *pBar) +// { +// CheckPointer(pBar,E_INVALIDARG) +// } +// +// Or if the function returns a boolean +// +// BOOL Foo(VOID *pBar) +// { +// CheckPointer(pBar,FALSE) +// } + +// These validate pointers when symbol VFWROBUST is defined +// This will normally be defined in debug not retail builds + +#ifdef DEBUG + #define VFWROBUST +#endif + +#ifdef VFWROBUST + + #define ValidateReadPtr(p,cb) \ + {if(IsBadReadPtr((PVOID)p,cb) == TRUE) \ + DbgBreak("Invalid read pointer");} + + #define ValidateWritePtr(p,cb) \ + {if(IsBadWritePtr((PVOID)p,cb) == TRUE) \ + DbgBreak("Invalid write pointer");} + + #define ValidateReadWritePtr(p,cb) \ + {ValidateReadPtr(p,cb) ValidateWritePtr(p,cb)} + + #define ValidateStringPtr(p) \ + {if(IsBadStringPtr((LPCTSTR)p,INFINITE) == TRUE) \ + DbgBreak("Invalid string pointer");} + + #define ValidateStringPtrA(p) \ + {if(IsBadStringPtrA((LPCSTR)p,INFINITE) == TRUE) \ + DbgBreak("Invalid ANSII string pointer");} + + #define ValidateStringPtrW(p) \ + {if(IsBadStringPtrW((LPCWSTR)p,INFINITE) == TRUE) \ + DbgBreak("Invalid UNICODE string pointer");} + +#else + #define ValidateReadPtr(p,cb) + #define ValidateWritePtr(p,cb) + #define ValidateReadWritePtr(p,cb) + #define ValidateStringPtr(p) + #define ValidateStringPtrA(p) + #define ValidateStringPtrW(p) +#endif + + +#ifdef _OBJBASE_H_ + + // Outputting GUID names. If you want to include the name + // associated with a GUID (eg CLSID_...) then + // + // GuidNames[yourGUID] + // + // Returns the name defined in uuids.h as a string + + typedef struct { + TCHAR *szName; + GUID guid; + } GUID_STRING_ENTRY; + + class CGuidNameList { + public: + TCHAR *operator [] (const GUID& guid); + }; + + extern CGuidNameList GuidNames; + +#endif + + +// REMIND macro - generates warning as reminder to complete coding +// (eg) usage: +// +// #pragma message (REMIND("Add automation support")) + + +#define QUOTE(x) #x +#define QQUOTE(y) QUOTE(y) +#define REMIND(str) __FILE__ "(" QQUOTE(__LINE__) ") : " str + + +// Hack to display objects in a useful format +// +// eg If you want to display a LONGLONG ll in a debug string do (eg) +// +// DbgLog((LOG_TRACE, n, TEXT("Value is %s"), (LPCTSTR)CDisp(ll, CDISP_HEX))); + + +class CDispBasic +{ +public: + CDispBasic() { m_pString = m_String; }; + ~CDispBasic(); +protected: + PTCHAR m_pString; // normally points to m_String... unless too much data + TCHAR m_String[50]; +}; +class CDisp : public CDispBasic +{ +public: + CDisp(LONGLONG ll, int Format = CDISP_HEX); // Display a LONGLONG in CDISP_HEX or CDISP_DEC form + CDisp(REFCLSID clsid); // Display a GUID + CDisp(double d); // Display a floating point number +#ifdef __strmif_h__ +#ifdef __STREAMS__ + CDisp(CRefTime t); // Display a Reference Time +#endif + CDisp(IPin *pPin); // Display a pin as {filter clsid}(pin name) +#endif // __strmif_h__ + ~CDisp(); + + // Implement cast to (LPCTSTR) as parameter to logger + operator LPCTSTR() + { + return (LPCTSTR)m_pString; + }; +}; + +#endif // __WXDEBUG__ + diff --git a/Libraries/ASIO/driver/asiosample/asiosample.def b/Libraries/ASIO/driver/asiosample/asiosample.def new file mode 100644 index 0000000000..50c8ba5fff --- /dev/null +++ b/Libraries/ASIO/driver/asiosample/asiosample.def @@ -0,0 +1,9 @@ +LIBRARY ASIOSample +DESCRIPTION 'ASIO Sample Driver' +PROTMODE +EXPORTS + DllMain + DllGetClassObject + DllCanUnloadNow + DllRegisterServer + DllUnregisterServer diff --git a/Libraries/ASIO/driver/asiosample/asiosample.txt b/Libraries/ASIO/driver/asiosample/asiosample.txt new file mode 100644 index 0000000000..89e945e97a --- /dev/null +++ b/Libraries/ASIO/driver/asiosample/asiosample.txt @@ -0,0 +1,21 @@ +Asiosample + +This sample driver illustrates the implementation of an ASIO driver. + +Windows notes: + +For Windows a COM implementation is provided. The final driver can be +installed in the system by dragging the asiosample.dll onto the +regsvr32.exe in the windows\system directory. + +It is normal that the LNK4104 warning is issued for the following 4 +exported symbols: + +DllGetClassObject +DllCanUnloadNow +DllRegisterServer +DllUnregisterServer + + +Macintosh notes: +The supplied project is created with CodeWarrior Pro 5. diff --git a/Libraries/ASIO/driver/asiosample/asiosample/asiosample.dsp b/Libraries/ASIO/driver/asiosample/asiosample/asiosample.dsp new file mode 100644 index 0000000000..23ca67b2ef --- /dev/null +++ b/Libraries/ASIO/driver/asiosample/asiosample/asiosample.dsp @@ -0,0 +1,151 @@ +# Microsoft Developer Studio Project File - Name="asiosample" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=asiosample - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "asiosample.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "asiosample.mak" CFG="asiosample - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "asiosample - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "asiosample - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "asiosample - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "..\..\..\Common" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /subsystem:windows /dll /machine:I386 + +!ELSEIF "$(CFG)" == "asiosample - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /GZ /c +# ADD CPP /nologo /G5 /MTd /W3 /Gm /GX /Zi /Od /I "..\..\..\Common" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /subsystem:windows /dll /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "asiosample - Win32 Release" +# Name "asiosample - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\asiosample.def +# End Source File +# Begin Source File + +SOURCE=..\asiosmpl.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\combase.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\dllentry.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\register.cpp +# End Source File +# Begin Source File + +SOURCE=..\wintimer.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\..\..\common\asio.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\Asiodrvr.h +# End Source File +# Begin Source File + +SOURCE=..\asiosmpl.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\asiosys.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\combase.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\iasiodrv.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/Libraries/ASIO/driver/asiosample/asiosample/asiosample.vcproj b/Libraries/ASIO/driver/asiosample/asiosample/asiosample.vcproj new file mode 100644 index 0000000000..92728ece77 --- /dev/null +++ b/Libraries/ASIO/driver/asiosample/asiosample/asiosample.vcproj @@ -0,0 +1,629 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Libraries/ASIO/driver/asiosample/asiosmpl.cpp b/Libraries/ASIO/driver/asiosample/asiosmpl.cpp new file mode 100644 index 0000000000..7fcedfa1ef --- /dev/null +++ b/Libraries/ASIO/driver/asiosample/asiosmpl.cpp @@ -0,0 +1,689 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Helpers +// Filename : driver/asiosample/asiosmpl.cpp +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O Sample +// test implementation of ASIO +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2025, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +//----------------------------------------------------------------------------- +/* + sample implementation of asio. can be set to simulate input with some + stupid oscillators. + this driver doesn't output sound at all... + timing is done via the extended time manager on the mac, and + a simple thread on pc. + you may test various configurations by changing the kNumInputs/Outputs, + and kBlockFrames. note that when using wave generation, as i/o is not optimized + at all, it moves quite a bit of memory, too many i/o channels may make it + pretty slow. + + if you use this file as a template, make sure to resolve places where the + search string !!! can be found... +*/ + +#include +#include +#include "asiosmpl.h" +//#include "virtape.h" + +//------------------------------------------------------------------------------------------ + +// extern +void getNanoSeconds(ASIOTimeStamp *time); + +// local + +double AsioSamples2double (ASIOSamples* samples); + +static const double twoRaisedTo32 = 4294967296.; +static const double twoRaisedTo32Reciprocal = 1. / twoRaisedTo32; + +//------------------------------------------------------------------------------------------ +// on windows, we do the COM stuff. + +#if WINDOWS +#include "windows.h" +#include "mmsystem.h" + +// class id. !!! NOTE: !!! you will obviously have to create your own class id! +// {188135E1-D565-11d2-854F-00A0C99F5D19} +CLSID IID_ASIO_DRIVER = { 0x188135e1, 0xd565, 0x11d2, { 0x85, 0x4f, 0x0, 0xa0, 0xc9, 0x9f, 0x5d, 0x19 } }; + +CFactoryTemplate g_Templates[1] = { + {L"ASIOSAMPLE", &IID_ASIO_DRIVER, AsioSample::CreateInstance} +}; +int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]); + +CUnknown* AsioSample::CreateInstance (LPUNKNOWN pUnk, HRESULT *phr) +{ + return (CUnknown*)new AsioSample (pUnk,phr); +}; + +STDMETHODIMP AsioSample::NonDelegatingQueryInterface (REFIID riid, void ** ppv) +{ + if (riid == IID_ASIO_DRIVER) + { + return GetInterface (this, ppv); + } + return CUnknown::NonDelegatingQueryInterface (riid, ppv); +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// Register ASIO Driver +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +extern LONG RegisterAsioDriver (CLSID,char *,char *,char *,char *); +extern LONG UnregisterAsioDriver (CLSID,char *,char *); + +// +// Server registration, called on REGSVR32.EXE "the dllname.dll" +// +HRESULT _stdcall DllRegisterServer() +{ + LONG rc; + char errstr[128]; + + rc = RegisterAsioDriver (IID_ASIO_DRIVER,"ASIOSample.dll","ASIO Sample Driver","ASIO Sample","Apartment"); + + if (rc) { + memset(errstr,0,128); + sprintf(errstr,"Register Server failed ! (%d)",rc); + MessageBox(0,(LPCTSTR)errstr,(LPCTSTR)"ASIO sample Driver",MB_OK); + return -1; + } + + return S_OK; +} + +// +// Server unregistration +// +HRESULT _stdcall DllUnregisterServer() +{ + LONG rc; + char errstr[128]; + + rc = UnregisterAsioDriver (IID_ASIO_DRIVER,"ASIOSample.dll","ASIO Sample Driver"); + + if (rc) { + memset(errstr,0,128); + sprintf(errstr,"Unregister Server failed ! (%d)",rc); + MessageBox(0,(LPCTSTR)errstr,(LPCTSTR)"ASIO Korg1212 I/O Driver",MB_OK); + return -1; + } + + return S_OK; +} + +//------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------ +AsioSample::AsioSample (LPUNKNOWN pUnk, HRESULT *phr) + : CUnknown("ASIOSAMPLE", pUnk, phr) + +//------------------------------------------------------------------------------------------ + +#else + +// when not on windows, we derive from AsioDriver +AsioSample::AsioSample () : AsioDriver () + +#endif +{ + long i; + + blockFrames = kBlockFrames; + inputLatency = blockFrames; // typically + outputLatency = blockFrames * 2; + // typically blockFrames * 2; try to get 1 by offering direct buffer + // access, and using asioPostOutput for lower latency + samplePosition = 0; + sampleRate = 44100.; + milliSeconds = (long)((double)(kBlockFrames * 1000) / sampleRate); + active = false; + started = false; + timeInfoMode = false; + tcRead = false; + for (i = 0; i < kNumInputs; i++) + { + inputBuffers[i] = 0; + inMap[i] = 0; + } +#if TESTWAVES + sawTooth = sineWave = 0; +#endif + for (i = 0; i < kNumOutputs; i++) + { + outputBuffers[i] = 0; + outMap[i] = 0; + } + callbacks = 0; + activeInputs = activeOutputs = 0; + toggle = 0; +} + +//------------------------------------------------------------------------------------------ +AsioSample::~AsioSample () +{ + stop (); + outputClose (); + inputClose (); + disposeBuffers (); +} + +//------------------------------------------------------------------------------------------ +void AsioSample::getDriverName (char *name) +{ + strcpy (name, "Sample ASIO"); +} + +//------------------------------------------------------------------------------------------ +long AsioSample::getDriverVersion () +{ + return 0x00000001L; +} + +//------------------------------------------------------------------------------------------ +void AsioSample::getErrorMessage (char *string) +{ + strcpy (string, errorMessage); +} + +//------------------------------------------------------------------------------------------ +ASIOBool AsioSample::init (void* sysRef) +{ + sysRef = sysRef; + if (active) + return true; + strcpy (errorMessage, "ASIO Driver open Failure!"); + if (inputOpen ()) + { + if (outputOpen ()) + { + active = true; + return true; + } + } + timerOff (); // de-activate 'hardware' + + outputClose (); + inputClose (); + return false; +} + +//------------------------------------------------------------------------------------------ +ASIOError AsioSample::start () +{ + if (callbacks) + { + started = false; + samplePosition = 0; + theSystemTime.lo = theSystemTime.hi = 0; + toggle = 0; + + timerOn (); // activate 'hardware' + started = true; + + return ASE_OK; + } + return ASE_NotPresent; +} + +//------------------------------------------------------------------------------------------ +ASIOError AsioSample::stop () +{ + started = false; + timerOff (); // de-activate 'hardware' + return ASE_OK; +} + +//------------------------------------------------------------------------------------------ +ASIOError AsioSample::getChannels (long *numInputChannels, long *numOutputChannels) +{ + *numInputChannels = kNumInputs; + *numOutputChannels = kNumOutputs; + return ASE_OK; +} + +//------------------------------------------------------------------------------------------ +ASIOError AsioSample::getLatencies (long *_inputLatency, long *_outputLatency) +{ + *_inputLatency = inputLatency; + *_outputLatency = outputLatency; + return ASE_OK; +} + +//------------------------------------------------------------------------------------------ +ASIOError AsioSample::getBufferSize (long *minSize, long *maxSize, + long *preferredSize, long *granularity) +{ + *minSize = *maxSize = *preferredSize = blockFrames; // allow this size only + *granularity = 0; + return ASE_OK; +} + +//------------------------------------------------------------------------------------------ +ASIOError AsioSample::canSampleRate (ASIOSampleRate sampleRate) +{ + if (sampleRate == 44100. || sampleRate == 48000.) // allow these rates only + return ASE_OK; + return ASE_NoClock; +} + +//------------------------------------------------------------------------------------------ +ASIOError AsioSample::getSampleRate (ASIOSampleRate *sampleRate) +{ + *sampleRate = this->sampleRate; + return ASE_OK; +} + +//------------------------------------------------------------------------------------------ +ASIOError AsioSample::setSampleRate (ASIOSampleRate sampleRate) +{ + if (sampleRate != 44100. && sampleRate != 48000.) + return ASE_NoClock; + if (sampleRate != this->sampleRate) + { + this->sampleRate = sampleRate; + asioTime.timeInfo.sampleRate = sampleRate; + asioTime.timeInfo.flags |= kSampleRateChanged; + milliSeconds = (long)((double)(kBlockFrames * 1000) / this->sampleRate); + if (callbacks && callbacks->sampleRateDidChange) + callbacks->sampleRateDidChange (this->sampleRate); + } + return ASE_OK; +} + +//------------------------------------------------------------------------------------------ +ASIOError AsioSample::getClockSources (ASIOClockSource *clocks, long *numSources) +{ + // internal + clocks->index = 0; + clocks->associatedChannel = -1; + clocks->associatedGroup = -1; + clocks->isCurrentSource = ASIOTrue; + strcpy(clocks->name, "Internal"); + *numSources = 1; + return ASE_OK; +} + +//------------------------------------------------------------------------------------------ +ASIOError AsioSample::setClockSource (long index) +{ + if (!index) + { + asioTime.timeInfo.flags |= kClockSourceChanged; + return ASE_OK; + } + return ASE_NotPresent; +} + +//------------------------------------------------------------------------------------------ +ASIOError AsioSample::getSamplePosition (ASIOSamples *sPos, ASIOTimeStamp *tStamp) +{ + tStamp->lo = theSystemTime.lo; + tStamp->hi = theSystemTime.hi; + if (samplePosition >= twoRaisedTo32) + { + sPos->hi = (unsigned long)(samplePosition * twoRaisedTo32Reciprocal); + sPos->lo = (unsigned long)(samplePosition - (sPos->hi * twoRaisedTo32)); + } + else + { + sPos->hi = 0; + sPos->lo = (unsigned long)samplePosition; + } + return ASE_OK; +} + +//------------------------------------------------------------------------------------------ +ASIOError AsioSample::getChannelInfo (ASIOChannelInfo *info) +{ + if (info->channel < 0 || (info->isInput ? info->channel >= kNumInputs : info->channel >= kNumOutputs)) + return ASE_InvalidParameter; +#if WINDOWS + info->type = ASIOSTInt16LSB; +#else + info->type = ASIOSTInt16MSB; +#endif + info->channelGroup = 0; + info->isActive = ASIOFalse; + long i; + if (info->isInput) + { + for (i = 0; i < activeInputs; i++) + { + if (inMap[i] == info->channel) + { + info->isActive = ASIOTrue; + break; + } + } + } + else + { + for (i = 0; i < activeOutputs; i++) + { + if (outMap[i] == info->channel) + { + info->isActive = ASIOTrue; + break; + } + } + } + strcpy(info->name, "Sample "); + return ASE_OK; +} + +//------------------------------------------------------------------------------------------ +ASIOError AsioSample::createBuffers (ASIOBufferInfo *bufferInfos, long numChannels, + long bufferSize, ASIOCallbacks *callbacks) +{ + ASIOBufferInfo *info = bufferInfos; + long i; + bool notEnoughMem = false; + + activeInputs = 0; + activeOutputs = 0; + blockFrames = bufferSize; + for (i = 0; i < numChannels; i++, info++) + { + if (info->isInput) + { + if (info->channelNum < 0 || info->channelNum >= kNumInputs) + goto error; + inMap[activeInputs] = info->channelNum; + inputBuffers[activeInputs] = new short[blockFrames * 2]; // double buffer + if (inputBuffers[activeInputs]) + { + info->buffers[0] = inputBuffers[activeInputs]; + info->buffers[1] = inputBuffers[activeInputs] + blockFrames; + } + else + { + info->buffers[0] = info->buffers[1] = 0; + notEnoughMem = true; + } + activeInputs++; + if (activeInputs > kNumInputs) + { +error: + disposeBuffers(); + return ASE_InvalidParameter; + } + } + else // output + { + if (info->channelNum < 0 || info->channelNum >= kNumOutputs) + goto error; + outMap[activeOutputs] = info->channelNum; + outputBuffers[activeOutputs] = new short[blockFrames * 2]; // double buffer + if (outputBuffers[activeOutputs]) + { + info->buffers[0] = outputBuffers[activeOutputs]; + info->buffers[1] = outputBuffers[activeOutputs] + blockFrames; + } + else + { + info->buffers[0] = info->buffers[1] = 0; + notEnoughMem = true; + } + activeOutputs++; + if (activeOutputs > kNumOutputs) + { + activeOutputs--; + disposeBuffers(); + return ASE_InvalidParameter; + } + } + } + if (notEnoughMem) + { + disposeBuffers(); + return ASE_NoMemory; + } + + this->callbacks = callbacks; + if (callbacks->asioMessage (kAsioSupportsTimeInfo, 0, 0, 0)) + { + timeInfoMode = true; + asioTime.timeInfo.speed = 1.; + asioTime.timeInfo.systemTime.hi = asioTime.timeInfo.systemTime.lo = 0; + asioTime.timeInfo.samplePosition.hi = asioTime.timeInfo.samplePosition.lo = 0; + asioTime.timeInfo.sampleRate = sampleRate; + asioTime.timeInfo.flags = kSystemTimeValid | kSamplePositionValid | kSampleRateValid; + + asioTime.timeCode.speed = 1.; + asioTime.timeCode.timeCodeSamples.lo = asioTime.timeCode.timeCodeSamples.hi = 0; + asioTime.timeCode.flags = kTcValid | kTcRunning ; + } + else + timeInfoMode = false; + return ASE_OK; +} + +//--------------------------------------------------------------------------------------------- +ASIOError AsioSample::disposeBuffers() +{ + long i; + + callbacks = 0; + stop(); + for (i = 0; i < activeInputs; i++) + delete inputBuffers[i]; + activeInputs = 0; + for (i = 0; i < activeOutputs; i++) + delete outputBuffers[i]; + activeOutputs = 0; + return ASE_OK; +} + +//--------------------------------------------------------------------------------------------- +ASIOError AsioSample::controlPanel() +{ + return ASE_NotPresent; +} + +//--------------------------------------------------------------------------------------------- +ASIOError AsioSample::future (long selector, void* opt) // !!! check properties +{ + ASIOTransportParameters* tp = (ASIOTransportParameters*)opt; + switch (selector) + { + case kAsioEnableTimeCodeRead: tcRead = true; return ASE_SUCCESS; + case kAsioDisableTimeCodeRead: tcRead = false; return ASE_SUCCESS; + case kAsioSetInputMonitor: return ASE_SUCCESS; // for testing!!! + case kAsioCanInputMonitor: return ASE_SUCCESS; // for testing!!! + case kAsioCanTimeInfo: return ASE_SUCCESS; + case kAsioCanTimeCode: return ASE_SUCCESS; + } + return ASE_NotPresent; +} + +//-------------------------------------------------------------------------------------------------------- +// private methods +//-------------------------------------------------------------------------------------------------------- + +//-------------------------------------------------------------------------------------------------------- +// input +//-------------------------------------------------------------------------------------------------------- + +//--------------------------------------------------------------------------------------------- +bool AsioSample::inputOpen () +{ +#if TESTWAVES + sineWave = new short[blockFrames]; + if (!sineWave) + { + strcpy (errorMessage, "ASIO Sample Driver: Out of Memory!"); + return false; + } + makeSine (sineWave); + + sawTooth = new short[blockFrames]; + if (!sawTooth) + { + strcpy(errorMessage, "ASIO Sample Driver: Out of Memory!"); + return false; + } + makeSaw(sawTooth); +#endif + return true; +} + +#if TESTWAVES + +#include + +const double pi = 0.3141592654; + +//--------------------------------------------------------------------------------------------- +void AsioSample::makeSine (short *wave) +{ + double frames = (double)blockFrames; + double i, f = (pi * 2.) / frames; + + for (i = 0; i < frames; i++) + *wave++ = (short)((double)0x7fff * sin(f * i)); +} + +//--------------------------------------------------------------------------------------------- +void AsioSample::makeSaw(short *wave) +{ + double frames = (double)blockFrames; + double i, f = 2. / frames; + + for (i = 0; i < frames; i++) + *wave++ = (short)((double)0x7fff * (-1. + f * i)); +} +#endif + +//--------------------------------------------------------------------------------------------- +void AsioSample::inputClose () +{ +#if TESTWAVES + if (sineWave) + delete sineWave; + sineWave = 0; + if (sawTooth) + delete sawTooth; + sawTooth = 0; +#endif +} + +//--------------------------------------------------------------------------------------------- +void AsioSample::input() +{ +#if TESTWAVES + long i; + short *in = 0; + + for (i = 0; i < activeInputs; i++) + { + in = inputBuffers[i]; + if (in) + { + if (toggle) + in += blockFrames; + if ((i & 1) && sawTooth) + memcpy(in, sawTooth, (unsigned long)(blockFrames * 2)); + else if (sineWave) + memcpy(in, sineWave, (unsigned long)(blockFrames * 2)); + } + } +#endif +} + +//------------------------------------------------------------------------------------------------------------------ +// output +//------------------------------------------------------------------------------------------------------------------ + +//--------------------------------------------------------------------------------------------- +bool AsioSample::outputOpen() +{ + return true; +} + +//--------------------------------------------------------------------------------------------- +void AsioSample::outputClose () +{ +} + +//--------------------------------------------------------------------------------------------- +void AsioSample::output () +{ +} + +//--------------------------------------------------------------------------------------------- +void AsioSample::bufferSwitch () +{ + if (started && callbacks) + { + getNanoSeconds(&theSystemTime); // latch system time + input(); + output(); + samplePosition += blockFrames; + if (timeInfoMode) + bufferSwitchX (); + else + callbacks->bufferSwitch (toggle, ASIOFalse); + toggle = toggle ? 0 : 1; + } +} + +//--------------------------------------------------------------------------------------------- +// asio2 buffer switch +void AsioSample::bufferSwitchX () +{ + getSamplePosition (&asioTime.timeInfo.samplePosition, &asioTime.timeInfo.systemTime); + long offset = toggle ? blockFrames : 0; + if (tcRead) + { // Create a fake time code, which is 10 minutes ahead of the card's sample position + // Please note that for simplicity here time code will wrap after 32 bit are reached + asioTime.timeCode.timeCodeSamples.lo = asioTime.timeInfo.samplePosition.lo + 600.0 * sampleRate; + asioTime.timeCode.timeCodeSamples.hi = 0; + } + callbacks->bufferSwitchTimeInfo (&asioTime, toggle, ASIOFalse); + asioTime.timeInfo.flags &= ~(kSampleRateChanged | kClockSourceChanged); +} + +//--------------------------------------------------------------------------------------------- +ASIOError AsioSample::outputReady () +{ + return ASE_NotPresent; +} + +//--------------------------------------------------------------------------------------------- +double AsioSamples2double (ASIOSamples* samples) +{ + double a = (double)(samples->lo); + if (samples->hi) + a += (double)(samples->hi) * twoRaisedTo32; + return a; +} diff --git a/Libraries/ASIO/driver/asiosample/asiosmpl.h b/Libraries/ASIO/driver/asiosample/asiosmpl.h new file mode 100644 index 0000000000..01ca206377 --- /dev/null +++ b/Libraries/ASIO/driver/asiosample/asiosmpl.h @@ -0,0 +1,175 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Helpers +// Filename : driver/asiosample/asiosmpl.h +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O Sample +// test implementation of ASIO +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2025, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +//----------------------------------------------------------------------------- + +#ifndef _asiosmpl_ +#define _asiosmpl_ + +#include "asiosys.h" + +#define TESTWAVES 1 +// when true, will feed the left input (to host) with +// a sine wave, and the right one with a sawtooth + +enum +{ + kBlockFrames = 256, + kNumInputs = 16, + kNumOutputs = 16 +}; + +#if WINDOWS + +#include "rpc.h" +#include "rpcndr.h" +#ifndef COM_NO_WINDOWS_H +#include +#include "ole2.h" +#endif + +#include "combase.h" +#include "iasiodrv.h" + +class AsioSample : public IASIO, public CUnknown +{ +public: + AsioSample(LPUNKNOWN pUnk, HRESULT *phr); + ~AsioSample(); + + DECLARE_IUNKNOWN + //STDMETHODIMP QueryInterface(REFIID riid, void **ppv) { \ + // return GetOwner()->QueryInterface(riid,ppv); \ + //}; \ + //STDMETHODIMP_(ULONG) AddRef() { \ + // return GetOwner()->AddRef(); \ + //}; \ + //STDMETHODIMP_(ULONG) Release() { \ + // return GetOwner()->Release(); \ + //}; + + // Factory method + static CUnknown *CreateInstance(LPUNKNOWN pUnk, HRESULT *phr); + // IUnknown + virtual HRESULT STDMETHODCALLTYPE NonDelegatingQueryInterface(REFIID riid,void **ppvObject); +#else + +#include "asiodrvr.h" + +//--------------------------------------------------------------------------------------------- +class AsioSample : public AsioDriver +{ +public: + AsioSample (); + ~AsioSample (); +#endif + + ASIOBool init (void* sysRef); + void getDriverName (char *name); // max 32 bytes incl. terminating zero + long getDriverVersion (); + void getErrorMessage (char *string); // max 128 bytes incl. + + ASIOError start (); + ASIOError stop (); + + ASIOError getChannels (long *numInputChannels, long *numOutputChannels); + ASIOError getLatencies (long *inputLatency, long *outputLatency); + ASIOError getBufferSize (long *minSize, long *maxSize, + long *preferredSize, long *granularity); + + ASIOError canSampleRate (ASIOSampleRate sampleRate); + ASIOError getSampleRate (ASIOSampleRate *sampleRate); + ASIOError setSampleRate (ASIOSampleRate sampleRate); + ASIOError getClockSources (ASIOClockSource *clocks, long *numSources); + ASIOError setClockSource (long index); + + ASIOError getSamplePosition (ASIOSamples *sPos, ASIOTimeStamp *tStamp); + ASIOError getChannelInfo (ASIOChannelInfo *info); + + ASIOError createBuffers (ASIOBufferInfo *bufferInfos, long numChannels, + long bufferSize, ASIOCallbacks *callbacks); + ASIOError disposeBuffers (); + + ASIOError controlPanel (); + ASIOError future (long selector, void *opt); + ASIOError outputReady (); + + void bufferSwitch (); + long getMilliSeconds () {return milliSeconds;} + +private: +friend void myTimer(); + + bool inputOpen (); +#if TESTWAVES + void makeSine (short *wave); + void makeSaw (short *wave); +#endif + void inputClose (); + void input (); + + bool outputOpen (); + void outputClose (); + void output (); + + void timerOn (); + void timerOff (); + void bufferSwitchX (); + + double samplePosition; + double sampleRate; + ASIOCallbacks *callbacks; + ASIOTime asioTime; + ASIOTimeStamp theSystemTime; + short *inputBuffers[kNumInputs * 2]; + short *outputBuffers[kNumOutputs * 2]; +#if TESTWAVES + short *sineWave, *sawTooth; +#endif + long inMap[kNumInputs]; + long outMap[kNumOutputs]; + long blockFrames; + long inputLatency; + long outputLatency; + long activeInputs; + long activeOutputs; + long toggle; + long milliSeconds; + bool active, started; + bool timeInfoMode, tcRead; + char errorMessage[128]; +}; + +#endif diff --git a/Libraries/ASIO/driver/asiosample/macnanosecs.cpp b/Libraries/ASIO/driver/asiosample/macnanosecs.cpp new file mode 100644 index 0000000000..ff608f490e --- /dev/null +++ b/Libraries/ASIO/driver/asiosample/macnanosecs.cpp @@ -0,0 +1,52 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Helpers +// Filename : driver/asiosample/macnanosecs.cpp +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O Sample +// **** macOS specific **** +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2025, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +//----------------------------------------------------------------------------- + +#include "asio.h" +#include + +void getNanoSeconds(ASIOTimeStamp *time); + +static const double twoRaisedTo32 = 4294967296.; + +void getNanoSeconds(ASIOTimeStamp *time) +{ + UnsignedWide ys; + Microseconds(&ys); + double r = 1000. * ((double)ys.hi * twoRaisedTo32 + (double)ys.lo); + time->hi = (unsigned long)(r / twoRaisedTo32); + time->lo = (unsigned long)(r - (double)time->hi * twoRaisedTo32); +} diff --git a/Libraries/ASIO/driver/asiosample/mactimer.cpp b/Libraries/ASIO/driver/asiosample/mactimer.cpp new file mode 100644 index 0000000000..c1875dc2ea --- /dev/null +++ b/Libraries/ASIO/driver/asiosample/mactimer.cpp @@ -0,0 +1,79 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Helpers +// Filename : driver/asiosample/mactimer.cpp +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O Sample +// sample implementation of asio. +// time manager irq +// **** macOS specific **** +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2025, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +//----------------------------------------------------------------------------- + +#include +#include +#include +#include "asiosmpl.h" + +static TMTask myTmTask; +static bool tmTaskOn = false; +static AsioSample *theSound = 0; + +void myTimer(); +void myTimer() +{ + if(theSound) + theSound->bufferSwitch(); + PrimeTime((QElemPtr)&myTmTask, theSound->milliSeconds); +} + +void AsioSample::timerOn() +{ + theSound = this; // for irq + if(!tmTaskOn) + { + memset(&myTmTask, 0, sizeof(TMTask)); + myTmTask.tmAddr = NewTimerProc(myTimer); + myTmTask.tmWakeUp = 0; + tmTaskOn = true; + InsXTime((QElemPtr)&myTmTask); + PrimeTime((QElemPtr)&myTmTask, theSound->milliSeconds); + } +} + +void AsioSample::timerOff() +{ + if(tmTaskOn) + { + RmvTime((QElemPtr)&myTmTask); + tmTaskOn = false; + } + theSound = 0; +} diff --git a/Libraries/ASIO/driver/asiosample/makesamp.cpp b/Libraries/ASIO/driver/asiosample/makesamp.cpp new file mode 100644 index 0000000000..3af4a029eb --- /dev/null +++ b/Libraries/ASIO/driver/asiosample/makesamp.cpp @@ -0,0 +1,42 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Helpers +// Filename : driver/asiosample/makesamp.cpp +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O Sample +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2025, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +//----------------------------------------------------------------------------- + +#include "asiosmpl.h" + +AsioDriver *getDriver() +{ + return new AsioSample(); +} diff --git a/Libraries/ASIO/driver/asiosample/wintimer.cpp b/Libraries/ASIO/driver/asiosample/wintimer.cpp new file mode 100644 index 0000000000..7abfc3d65c --- /dev/null +++ b/Libraries/ASIO/driver/asiosample/wintimer.cpp @@ -0,0 +1,92 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Helpers +// Filename : driver/asiosample/wintimer.cpp +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O Sample +// **** Windows specific **** +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2025, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +//----------------------------------------------------------------------------- + +#include +#include "asiosmpl.h" + +static DWORD __stdcall ASIOThread (void *param); +static HANDLE ASIOThreadHandle = 0; +static bool done = false; +const double twoRaisedTo32 = 4294967296.; + +AsioSample* theDriver = 0; + +//------------------------------------------------------------------------------------------ +void getNanoSeconds (ASIOTimeStamp* ts) +{ + double nanoSeconds = (double)((unsigned long)timeGetTime ()) * 1000000.; + ts->hi = (unsigned long)(nanoSeconds / twoRaisedTo32); + ts->lo = (unsigned long)(nanoSeconds - (ts->hi * twoRaisedTo32)); +} + +//------------------------------------------------------------------------------------------ +void AsioSample::timerOn () +{ + theDriver = this; + DWORD asioId; + done = false; + ASIOThreadHandle = CreateThread (0, 0, &ASIOThread, 0, 0, &asioId); +} + +//------------------------------------------------------------------------------------------ +void AsioSample::timerOff () +{ + done = true; + if (ASIOThreadHandle) + WaitForSingleObject(ASIOThreadHandle, 1000); + ASIOThreadHandle = 0; +} + +//------------------------------------------------------------------------------------------ +static DWORD __stdcall ASIOThread (void *param) +{ + do + { + if (theDriver) + { + theDriver->bufferSwitch (); + Sleep (theDriver->getMilliSeconds ()); + } + else + { + double a = 1000. / 44100.; + Sleep ((long)(a * (double)kBlockFrames)); + + } + } while (!done); + return 0; +} diff --git a/Libraries/ASIO/host/ASIOConvertSamples.cpp b/Libraries/ASIO/host/ASIOConvertSamples.cpp new file mode 100644 index 0000000000..756a0b9631 --- /dev/null +++ b/Libraries/ASIO/host/ASIOConvertSamples.cpp @@ -0,0 +1,895 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Helpers +// Filename : host/ASIOConvertSamples.cpp +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O Helpers +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2025, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +//----------------------------------------------------------------------------- + +#include "ginclude.h" +#include "ASIOConvertSamples.h" +#include + +#if MAC +#define TRUNCATE 0 + +#elif ASIO_CPU_X86 || ASIO_CPU_SPARC || ASIO_CPU_MIPS +#define TRUNCATE 1 +#undef MAXFLOAT +#define MAXFLOAT 0x7fffff00L +#endif + +ASIOConvertSamples::ASIOConvertSamples() +{ +} + + +//------------------------------------------------------------------------------------------- +// mono + +void ASIOConvertSamples::convertMono8Unsigned(long *source, char *dest, long frames) +{ + unsigned char *c = (unsigned char *)source; + unsigned char a; + + dest--; + while(--frames >= 0) + { +#if ASIO_LITTLE_ENDIAN + a = c[3]; +#else + a = c[0]; +#endif + c += 4; + a -= 0x80U; + *++dest = a; + } +} + +void ASIOConvertSamples::convertMono8(long *source, char *dest, long frames) +{ + char *c = (char *)source; + char a; + + dest--; + while(--frames >= 0) + { +#if ASIO_LITTLE_ENDIAN + a = c[3]; +#else + a = c[0]; +#endif + c += 4; + *++dest = a; + } +} + +void ASIOConvertSamples::convertMono16(long *source, short *dest, long frames) +{ +#if ASIO_LITTLE_ENDIAN + char* s = (char*)source; + char* d = (char*)dest; + while(--frames >= 0) + { + *d++ = s[3]; // dest big endian, msb first + *d++ = s[2]; + s += 4; + } +#else + long l; + + source--; + dest--; + while(--frames >= 0) + { + l = *++source; + *++dest = (short)(l >> 16); + } +#endif +} + +void ASIOConvertSamples::convertMono24(long *source, char *dest, long frames) +{ + // work with chars in order to prevent misalignments + char *s = (char *)source; + char a, b, c; + + dest--; + while(--frames >= 0) + { +#if ASIO_LITTLE_ENDIAN + a = s[3]; // msb + b = s[2]; + c = s[1]; // lsb +#else + a = s[0]; + b = s[1]; + c = s[2]; +#endif + s += 4; + *++dest = a; // big endian, msb first + *++dest = b; + *++dest = c; + } +} + +// small endian + +void ASIOConvertSamples::convertMono16SmallEndian(long *source, short *dest, long frames) +{ + char *s = (char *)source; + char *d = (char *)dest; + char a, b; + + d--; + while(--frames >= 0) + { +#if ASIO_LITTLE_ENDIAN + a = s[3]; + b = s[2]; +#else + a = s[0]; + b = s[1]; +#endif + s += 4; + *++d = b; // dest small endian, lsb first + *++d = a; + } +} + +void ASIOConvertSamples::convertMono24SmallEndian(long *source, char *dest, long frames) +{ + // work with chars in order to prevent misalignments + char *s = (char *)source; + char a, b, c; + + dest--; + while(--frames >= 0) + { +#if ASIO_LITTLE_ENDIAN + a = s[3]; + b = s[2]; + c = s[1]; +#else + a = s[0]; + b = s[1]; + c = s[2]; +#endif + s += 4; + *++dest = c; // lsb first + *++dest = b; + *++dest = a; + } +} + + +//------------------------------------------------------------------------------------------- +// stereo interleaved + +void ASIOConvertSamples::convertStereo8InterleavedUnsigned(long *left, long *right, char *dest, long frames) +{ + unsigned char *cl = (unsigned char *)left; + unsigned char *cr = (unsigned char *)right; + unsigned char a, b; + + dest--; + while(--frames >= 0) + { +#if ASIO_LITTLE_ENDIAN + a = cl[3]; + b = cr[3]; +#else + a = cl[0]; + b = cr[0]; +#endif + cl += 4; + cr += 4; + a -= 0x80U; + b -= 0x80U; + *++dest = a; + *++dest = b; + } +} + +void ASIOConvertSamples::convertStereo8Interleaved(long *left, long *right, char *dest, long frames) +{ + unsigned char *cl = (unsigned char *)left; + unsigned char *cr = (unsigned char *)right; + unsigned char a, b; + + dest--; + while(--frames >= 0) + { +#if ASIO_LITTLE_ENDIAN + a = cl[3]; + b = cr[3]; +#else + a = cl[0]; + b = cr[0]; +#endif + cl += 4; + cr += 4; + *++dest = a; + *++dest = b; + } +} + +void ASIOConvertSamples::convertStereo16Interleaved(long *left, long *right, short *dest, long frames) +{ +#if ASIO_LITTLE_ENDIAN + char* sl = (char*)left; + char* sr = (char*)right; + char* d = (char*)dest; + while(--frames >= 0) + { + *d++ = sl[3]; // msb first + *d++ = sl[2]; + *d++ = sr[3]; + *d++ = sr[2]; + sl += 4; + sr += 4; + } +#else + long l, r; + + left--; + right--; + dest--; + while(--frames >= 0) + { + l = *++left; + r = *++right; + *++dest = (short)(l >> 16); + *++dest = (short)(r >> 16); + } +#endif +} + +void ASIOConvertSamples::convertStereo24Interleaved(long *left, long *right, char *dest, long frames) +{ + // work with chars in order to prevent misalignments + char *sl = (char *)left; + char *sr = (char *)right; + char al, bl, cl, ar, br, cr; + + dest--; + while(--frames >= 0) + { +#if ASIO_LITTLE_ENDIAN + al = sl[3]; + bl = sl[2]; + cl = sl[1]; + ar = sr[3]; + br = sr[2]; + cr = sr[1]; +#else + al = sl[0]; + bl = sl[1]; + cl = sl[2]; + ar = sr[0]; + br = sr[1]; + cr = sr[2]; +#endif + sl += 4; + sr += 4; + *++dest = al; + *++dest = bl; + *++dest = cl; + *++dest = ar; + *++dest = br; + *++dest = cr; + } +} + +void ASIOConvertSamples::convertStereo16InterleavedSmallEndian(long *left, long *right, short *dest, long frames) +{ + char *sl = (char *)left; + char *sr = (char *)right; + char *d = (char *)dest; + char al, bl, ar, br; + + d--; + while(--frames >= 0) + { +#if ASIO_LITTLE_ENDIAN + al = sl[3]; + bl = sl[2]; + ar = sr[3]; + br = sr[2]; +#else + al = sl[0]; + bl = sl[1]; + ar = sr[0]; + br = sr[1]; +#endif + sl += 4; + sr += 4; + *++d = bl; // lsb first + *++d = al; + *++d = br; + *++d = ar; + } +} + +void ASIOConvertSamples::convertStereo24InterleavedSmallEndian(long *left, long *right, char *dest, long frames) +{ + // work with chars in order to prevent misalignments + char *sl = (char *)left; + char *sr = (char *)right; + char al, bl, cl, ar, br, cr; + + dest--; + while(--frames >= 0) + { +#if ASIO_LITTLE_ENDIAN + al = sl[3]; + bl = sl[2]; + cl = sl[1]; + ar = sr[3]; + br = sr[2]; + cr = sr[1]; +#else + al = sl[0]; + bl = sl[1]; + cl = sl[2]; + ar = sr[0]; + br = sr[1]; + cr = sr[2]; +#endif + sl += 4; + sr += 4; + *++dest = cl; + *++dest = bl; + *++dest = al; + *++dest = cr; + *++dest = br; + *++dest = ar; + } +} + + +//------------------------------------------------------------------------------------------- +// stereo split + +void ASIOConvertSamples::convertStereo8Unsigned(long *left, long *right, char *dLeft, char *dRight, long frames) +{ + unsigned char *cl = (unsigned char *)left; + unsigned char *cr = (unsigned char *)right; + unsigned char a, b; + + dLeft--; + dRight--; + while(--frames >= 0) + { +#if ASIO_LITTLE_ENDIAN + a = cl[3]; + b = cr[3]; +#else + a = cl[0]; + b = cr[0]; +#endif + cl += 4; + cr += 4; + a -= 0x80U; + b -= 0x80U; + *++dLeft = a; + *++dRight = b; + } +} + +void ASIOConvertSamples::convertStereo8(long *left, long *right, char *dLeft, char *dRight, long frames) +{ + unsigned char *cl = (unsigned char *)left; + unsigned char *cr = (unsigned char *)right; + unsigned char a, b; + + dLeft--; + dRight--; + while(--frames >= 0) + { +#if ASIO_LITTLE_ENDIAN + a = cl[3]; + b = cr[3]; +#else + a = cl[0]; + b = cr[0]; +#endif + cl += 4; + cr += 4; + *++dLeft = a; + *++dRight = b; + } +} + +void ASIOConvertSamples::convertStereo16(long *left, long *right, short *dLeft, short *dRight, long frames) +{ +#if ASIO_LITTLE_ENDIAN + char* sl = (char*)left; + char* sr = (char*)right; + char* dl = (char*)dLeft; + char* dr = (char*)dRight; + while(--frames >= 0) + { + *dl++ = sl[3]; // msb first + *dl++ = sl[2]; + *dr++ = sr[3]; + *dr++ = sr[2]; + sl += 4; + sr += 4; + } +#else + long l, r; + + left--; + right--; + dLeft--; + dRight--; + while(--frames >= 0) + { + l = *++left; + r = *++right; + *++dLeft = (short)(l >> 16); + *++dRight = (short)(r >> 16); + } +#endif +} + +void ASIOConvertSamples::convertStereo24(long *left, long *right, char *dLeft, char *dRight, long frames) +{ + // work with chars in order to prevent misalignments + char *sl = (char *)left; + char *sr = (char *)right; + char al, bl, cl, ar, br, cr; + + dLeft--; + dRight--; + while(--frames >= 0) + { +#if ASIO_LITTLE_ENDIAN + al = sl[3]; + bl = sl[2]; + cl = sl[1]; + ar = sr[3]; + br = sr[2]; + cr = sr[1]; +#else + al = sl[0]; + bl = sl[1]; + cl = sl[2]; + ar = sr[0]; + br = sr[1]; + cr = sr[2]; +#endif + sl += 4; + sr += 4; + *++dLeft = al; + *++dLeft = bl; + *++dLeft = cl; + *++dRight = ar; + *++dRight = br; + *++dRight = cr; + } +} + +// small endian +void ASIOConvertSamples::convertStereo16SmallEndian(long *left, long *right, short *dLeft, short *dRight, long frames) +{ + char *sl = (char *)left; + char *sr = (char *)right; + char *dl = (char *)dLeft; + char *dr = (char *)dRight; + char al, bl, ar, br; + + dl--; + dr--; + while(--frames >= 0) + { +#if ASIO_LITTLE_ENDIAN + al = sl[3]; + bl = sl[2]; + ar = sr[3]; + br = sr[2]; +#else + al = sl[0]; + bl = sl[1]; + ar = sr[0]; + br = sr[1]; +#endif + sl += 4; + sr += 4; + *++dl = bl; + *++dl = al; + *++dr = br; + *++dr = ar; + } +} + +void ASIOConvertSamples::convertStereo24SmallEndian(long *left, long *right, char *dLeft, char *dRight, long frames) +{ + // work with chars in order to prevent misalignments + char *sl = (char *)left; + char *sr = (char *)right; + char al, bl, cl, ar, br, cr; + + dLeft--; + dRight--; + while(--frames >= 0) + { +#if ASIO_LITTLE_ENDIAN + al = sl[3]; + bl = sl[2]; + cl = sl[1]; + ar = sr[3]; + br = sr[2]; + cr = sr[1]; +#else + al = sl[0]; + bl = sl[1]; + cl = sl[2]; + ar = sr[0]; + br = sr[1]; + cr = sr[2]; +#endif + sl += 4; + sr += 4; + *++dLeft = cl; + *++dLeft = bl; + *++dLeft = al; + *++dRight = cr; + *++dRight = br; + *++dRight = ar; + } +} + +//------------------------------------------------------------------------------------------ +// in place integer conversions + +void ASIOConvertSamples::int32msb16to16inPlace(long *in, long frames) +{ + short *d1 = (short *)in; + short* out = d1; +#if ASIO_LITTLE_ENDIAN + d1++; +#endif + while(--frames >= 0) + { + *out++ = *d1; + d1 += 2; + } +} + +void ASIOConvertSamples::int32lsb16to16inPlace(long *in, long frames) +{ + short *d1 = (short *)in; + short* out = d1; +#if !ASIO_LITTLE_ENDIAN + d1++; +#endif + while(--frames >= 0) + { + *out++ = *d1; + d1 += 2; + } +} + +void ASIOConvertSamples::int32msb16shiftedTo16inPlace(long *in, long frames, long shift) +{ + short* out = (short*)in; + while(--frames >= 0) + *out++ = (short)(*in++ >> shift); +} + +void ASIOConvertSamples::int24msbto16inPlace(unsigned char *in, long frames) +{ + short a; + short* out = (short*)in; + while(--frames >= 0) + { +#if ASIO_LITTLE_ENDIAN + a = (short)in[2]; + a <<= 8; + a |= (in[1] & 0xff); +#else + a = (short)in[0]; + a <<= 8; + a |= (in[1] & 0xff); +#endif + *out++ = a; + in += 3; + } +} + +//----------------------------------------------------------------------------------------- + +void ASIOConvertSamples::shift32(void* buffer, long shiftAmount, long targetByteWidth, + bool revertEndian, long sampleFrames) +{ + long a; + long frames = sampleFrames; + long* source = (long*)buffer; + if(revertEndian) + { + reverseEndian(buffer, 4, sampleFrames); + revertEndian = false; + } + + if(targetByteWidth == 2) + { + short* dest = (short*)buffer; + short* al = (short*)&a; +#if ASIO_LITTLE_ENDIAN + al++; +#endif + while(--frames >= 0) + { + a = *source++; + a <<= shiftAmount; + *dest++ = *al; + } + } + + else if(targetByteWidth == 3) + { + char* dest = (char*)buffer; + long* source = (long*)buffer; + char* aa = (char*)&a; + while(--frames >= 0) + { + a = *source++; + a <<= shiftAmount; +#if ASIO_LITTLE_ENDIAN + dest[0] = aa[1]; // lsb + dest[1] = aa[2]; + dest[2] = aa[3]; // msb +#else + dest[0] = aa[0]; // msb + dest[1] = aa[1]; + dest[2] = aa[2]; // lsb +#endif + dest += 3; + } + } + + else if(targetByteWidth == 4) + { + long* dest = source; + while(--frames >= 0) + *dest++ = *source++ << shiftAmount; + } +} + +void ASIOConvertSamples::reverseEndian(void* buffer, long byteWidth, long frames) +{ + char* a = (char*)buffer; + char* b = a; + char c; + if(byteWidth == 2) + { + while(--frames >= 0) + { + c = a[0]; + a[0] = a[1]; + a[1] = c; + a += 2; + } + } + else if(byteWidth == 3) + { + while(--frames >= 0) + { + c = a[0]; + a[0] = a[2]; + a[2] = c; + a += 3; + } + } + else if(byteWidth == 4) + { + while(--frames >= 0) + { + c = a[0]; + a[0] = a[3]; + a[3] = c; + c = a[1]; + a[1] = a[2]; + a[2] = c; + a += 4; + } + } +} + +//------------------------------------------------------------------------------------------------- + +void ASIOConvertSamples::int32to16inPlace(void* buffer, long frames) +{ + short* in = (short*)buffer; + short* out = in; +#if ASIO_LITTLE_ENDIAN + in++; +#endif + while(--frames >= 0) + { + *out++ = *in; + in += 2; + } +} + +void ASIOConvertSamples::int24to16inPlace(void* buffer, long frames) +{ + char* from = (char*)buffer; + char* to = from; + while(--frames >= 0) + { +#if ASIO_LITTLE_ENDIAN + to[0] = from[1]; + to[1] = from[2]; +#else + to[0] = from[0]; + to[1] = from[1]; +#endif + from += 3; + to += 2; + } +} + +void ASIOConvertSamples::int32to24inPlace(void* buffer, long frames) +{ + long* in = (long*)buffer; + char* out = (char*)buffer; + long a; + while(--frames >= 0) + { + a = *in++; + a >>= 8; // 32->24 +#if ASIO_LITTLE_ENDIAN + out[0] = (char)a; // lsb + a >>= 8; + out[1] = (char)a; + a >>= 8; + out[2] = (char)a; +#else + out[2] = (char)a; // lsb + a >>= 8; + out[1] = (char)a; + a >>= 8; + out[0] = (char)a; +#endif + out += 3; + } +} + +void ASIOConvertSamples::int16to24inPlace(void* buffer, long frames) +{ + char* in = (char*)buffer; + char* out = (char*)buffer; + in += frames * 2; + out += frames * 3; + while(--frames >= 0) + { + out -= 3; + in -= 2; +#if ASIO_LITTLE_ENDIAN + out[2] = in[1]; // msb + out[1] = in[0]; // lsb + out[0] = 0; +#else + out[2] = 0; + out[1] = in[1]; // lsb + out[0] = in[0]; // msb +#endif + } +} + +void ASIOConvertSamples::int24to32inPlace(void* buffer, long frames) +{ + long a, b, c; + char* in = (char*)buffer; + long* out = (long*)buffer; + in += (frames * 3); + out += frames; + while(--frames >= 0) + { +#if ASIO_LITTLE_ENDIAN + a = (long)in[-1]; // msb + b = (long)in[-2]; + c = (long)in[-3]; +#else + a = (long)in[-3]; // msb + b = (long)in[-2]; + c = (long)in[-1]; +#endif + a <<= 24; + b <<= 16; + b &= 0x00ff0000; + a |= b; + c <<= 8; + c &= 0x0000ff00; + a |= c; + *--out = a; + in -= 3; + } +} + +void ASIOConvertSamples::int16to32inPlace(void* buffer, long frames) +{ + short* in = (short*)buffer; + long* out = (long*)buffer; + in += frames; + out += frames; + while(--frames >= 0) + *--out = ((long)(*--in)) << 16; +} + +//------------------------------------------------------------------------------------------ +// float to int + +const double fScaler16 = (double)0x7fffL; +const double fScaler24 = (double)0x7fffffL; +const double fScaler32 = (double)0x7fffffffL; + +void ASIOConvertSamples::float32toInt16inPlace(float* buffer, long frames) +{ + double sc = fScaler16 + .49999; + short* b = (short*)buffer; + while(--frames >= 0) + *b++ = (short)((double)(*buffer++) * sc); +} + +void ASIOConvertSamples::float32toInt24inPlace(float* buffer, long frames) +{ + double sc = fScaler24 + .49999; + long a; + char* b = (char*)buffer; + char* aa = (char*)&a; + + while(--frames >= 0) + { + a = (long)((double)(*buffer++) * sc); +#if ASIO_LITTLE_ENDIAN + *b++ = aa[3]; + *b++ = aa[2]; + *b++ = aa[1]; +#else + *b++ = aa[1]; + *b++ = aa[2]; + *b++ = aa[3]; +#endif + } +} + +void ASIOConvertSamples::float32toInt32inPlace(float* buffer, long frames) +{ + double sc = fScaler32 + .49999; + long* b = (long*)buffer; + while(--frames >= 0) + *b++ = (long)((double)(*buffer++) * sc); +} + diff --git a/Libraries/ASIO/host/ASIOConvertSamples.h b/Libraries/ASIO/host/ASIOConvertSamples.h new file mode 100644 index 0000000000..44314ecd35 --- /dev/null +++ b/Libraries/ASIO/host/ASIOConvertSamples.h @@ -0,0 +1,98 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Helpers +// Filename : host/ASIOConvertSamples.h +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O Helpers +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2025, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +//----------------------------------------------------------------------------- + +#ifndef __ASIOConvertSamples__ +#define __ASIOConvertSamples__ + +class ASIOConvertSamples +{ +public: + ASIOConvertSamples(); + ~ASIOConvertSamples() {} + + // format converters, input 32 bit integer + // mono + void convertMono8(long *source, char *dest, long frames); + void convertMono8Unsigned(long *source, char *dest, long frames); + void convertMono16(long *source, short *dest, long frames); + void convertMono16SmallEndian(long *source, short *dest, long frames); + void convertMono24(long *source, char *dest, long frames); + void convertMono24SmallEndian(long *source, char *dest, long frames); + + // stereo interleaved + void convertStereo8Interleaved(long *left, long *right, char *dest, long frames); + void convertStereo8InterleavedUnsigned(long *left, long *right, char *dest, long frames); + void convertStereo16Interleaved(long *left, long *right, short *dest, long frames); + void convertStereo16InterleavedSmallEndian(long *left, long *right, short *dest, long frames); + void convertStereo24Interleaved(long *left, long *right, char *dest, long frames); + void convertStereo24InterleavedSmallEndian(long *left, long *right, char *dest, long frames); + + // stereo split + void convertStereo8(long *left, long *right, char *dLeft, char *dRight, long frames); + void convertStereo8Unsigned(long *left, long *right, char *dLeft, char *dRight, long frames); + void convertStereo16(long *left, long *right, short *dLeft, short *dRight, long frames); + void convertStereo16SmallEndian(long *left, long *right, short *dLeft, short *dRight, long frames); + void convertStereo24(long *left, long *right, char *dLeft, char *dRight, long frames); + void convertStereo24SmallEndian(long *left, long *right, char *dLeft, char *dRight, long frames); + + // integer in place conversions + + void int32msb16to16inPlace(long *in, long frames); + void int32lsb16to16inPlace(long *in, long frames); + void int32msb16shiftedTo16inPlace(long *in1, long frames, long shift); + void int24msbto16inPlace(unsigned char *in, long frames); + + // integer to integer + + void shift32(void* buffer, long shiftAmount, long targetByteWidth, + bool reverseEndian, long frames); + void reverseEndian(void* buffer, long byteWidth, long frames); + + void int32to16inPlace(void* buffer, long frames); + void int24to16inPlace(void* buffer, long frames); + void int32to24inPlace(void* buffer, long frames); + void int16to24inPlace(void* buffer, long frames); + void int24to32inPlace(void* buffer, long frames); + void int16to32inPlace(void* buffer, long frames); + + // float to integer + + void float32toInt16inPlace(float* buffer, long frames); + void float32toInt24inPlace(float* buffer, long frames); + void float32toInt32inPlace(float* buffer, long frames); +}; + +#endif diff --git a/Libraries/ASIO/host/asiodrivers.cpp b/Libraries/ASIO/host/asiodrivers.cpp new file mode 100644 index 0000000000..732ca7bc8d --- /dev/null +++ b/Libraries/ASIO/host/asiodrivers.cpp @@ -0,0 +1,222 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Helpers +// Filename : host/asiodrivers.cpp +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O Helpers +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2025, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +//----------------------------------------------------------------------------- + +#include +#include "asiodrivers.h" + +AsioDrivers* asioDrivers = 0; + +bool loadAsioDriver(char *name); + +bool loadAsioDriver(char *name) +{ + if(!asioDrivers) + asioDrivers = new AsioDrivers(); + if(asioDrivers) + return asioDrivers->loadDriver(name); + return false; +} + +//------------------------------------------------------------------------------------ + +#if MAC + +bool resolveASIO(unsigned long aconnID); + +AsioDrivers::AsioDrivers() : CodeFragments("ASIO Drivers", 'AsDr', 'Asio') +{ + connID = -1; + curIndex = -1; +} + +AsioDrivers::~AsioDrivers() +{ + removeCurrentDriver(); +} + +bool AsioDrivers::getCurrentDriverName(char *name) +{ + if(curIndex >= 0) + return getName(curIndex, name); + return false; +} + +long AsioDrivers::getDriverNames(char **names, long maxDrivers) +{ + for(long i = 0; i < getNumFragments() && i < maxDrivers; i++) + getName(i, names[i]); + return getNumFragments() < maxDrivers ? getNumFragments() : maxDrivers; +} + +bool AsioDrivers::loadDriver(char *name) +{ + char dname[64]; + unsigned long newID; + + for(long i = 0; i < getNumFragments(); i++) + { + if(getName(i, dname) && !strcmp(name, dname)) + { + if(newInstance(i, &newID)) + { + if(resolveASIO(newID)) + { + if(connID != -1) + removeInstance(curIndex, connID); + curIndex = i; + connID = newID; + return true; + } + } + break; + } + } + return false; +} + +void AsioDrivers::removeCurrentDriver() +{ + if(connID != -1) + removeInstance(curIndex, connID); + connID = -1; + curIndex = -1; +} + +//------------------------------------------------------------------------------------ + +#elif WINDOWS + +#include "iasiodrv.h" + +extern IASIO* theAsioDriver; + +AsioDrivers::AsioDrivers() : AsioDriverList() +{ + curIndex = -1; +} + +AsioDrivers::~AsioDrivers() +{ +} + +bool AsioDrivers::getCurrentDriverName(char *name) +{ + if(curIndex >= 0) + return asioGetDriverName(curIndex, name, 32) == 0 ? true : false; + name[0] = 0; + return false; +} + +long AsioDrivers::getDriverNames(char **names, long maxDrivers) +{ + for(long i = 0; i < asioGetNumDev() && i < maxDrivers; i++) + asioGetDriverName(i, names[i], 32); + return asioGetNumDev() < maxDrivers ? asioGetNumDev() : maxDrivers; +} + +bool AsioDrivers::loadDriver(char *name) +{ + char dname[64]; + char curName[64]; + + for(long i = 0; i < asioGetNumDev(); i++) + { + if(!asioGetDriverName(i, dname, 32) && !strcmp(name, dname)) + { + curName[0] = 0; + getCurrentDriverName(curName); // in case we fail... + removeCurrentDriver(); + + if(!asioOpenDriver(i, (void **)&theAsioDriver)) + { + curIndex = i; + return true; + } + else + { + theAsioDriver = 0; + if(curName[0] && strcmp(dname, curName)) + loadDriver(curName); // try restore + } + break; + } + } + return false; +} + +void AsioDrivers::removeCurrentDriver() +{ + if(curIndex != -1) + asioCloseDriver(curIndex); + curIndex = -1; +} + +#elif SGI || BEOS + +#include "asiolist.h" + +AsioDrivers::AsioDrivers() + : AsioDriverList() +{ + curIndex = -1; +} + +AsioDrivers::~AsioDrivers() +{ +} + +bool AsioDrivers::getCurrentDriverName(char *name) +{ + return false; +} + +long AsioDrivers::getDriverNames(char **names, long maxDrivers) +{ + return 0; +} + +bool AsioDrivers::loadDriver(char *name) +{ + return false; +} + +void AsioDrivers::removeCurrentDriver() +{ +} + +#else +#error implement me +#endif diff --git a/Libraries/ASIO/host/asiodrivers.h b/Libraries/ASIO/host/asiodrivers.h new file mode 100644 index 0000000000..fa57011ca1 --- /dev/null +++ b/Libraries/ASIO/host/asiodrivers.h @@ -0,0 +1,77 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Helpers +// Filename : host/asiodrivers.h +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O Helpers +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2025, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +//----------------------------------------------------------------------------- + +#ifndef __AsioDrivers__ +#define __AsioDrivers__ + +#include "ginclude.h" + +#if MAC +#include "CodeFragments.hpp" + +class AsioDrivers : public CodeFragments + +#elif WINDOWS +#include +#include "asiolist.h" + +class AsioDrivers : public AsioDriverList + +#elif SGI || BEOS +#include "asiolist.h" + +class AsioDrivers : public AsioDriverList + +#else +#error implement me +#endif + +{ +public: + AsioDrivers(); + ~AsioDrivers(); + + bool getCurrentDriverName(char *name); + long getDriverNames(char **names, long maxDrivers); + bool loadDriver(char *name); + void removeCurrentDriver(); + long getCurrentDriverIndex() {return curIndex;} +protected: + unsigned long connID; + long curIndex; +}; + +#endif diff --git a/Libraries/ASIO/host/ginclude.h b/Libraries/ASIO/host/ginclude.h new file mode 100644 index 0000000000..41b8c35609 --- /dev/null +++ b/Libraries/ASIO/host/ginclude.h @@ -0,0 +1,74 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Helpers +// Filename : host/ginclude.h +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O Helpers +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2025, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +//----------------------------------------------------------------------------- + +#ifndef __gInclude__ +#define __gInclude__ + +#if SGI + #undef BEOS + #undef MAC + #undef WINDOWS + // + #define ASIO_BIG_ENDIAN 1 + #define ASIO_CPU_MIPS 1 +#elif defined(_WIN32) || defined(_WIN64) + #undef BEOS + #undef MAC + #undef SGI + #define WINDOWS 1 + #define ASIO_LITTLE_ENDIAN 1 + #define ASIO_CPU_X86 1 +#elif BEOS + #undef MAC + #undef SGI + #undef WINDOWS + #define ASIO_LITTLE_ENDIAN 1 + #define ASIO_CPU_X86 1 + // +#else + #define MAC 1 + #undef BEOS + #undef WINDOWS + #undef SGI + #define ASIO_BIG_ENDIAN 1 + #define ASIO_CPU_PPC 1 +#endif + +// always +#define NATIVE_INT64 0 +#define IEEE754_64FLOAT 1 + +#endif // __gInclude__ diff --git a/Libraries/ASIO/host/pc/asiolist.cpp b/Libraries/ASIO/host/pc/asiolist.cpp new file mode 100644 index 0000000000..320a98a0c9 --- /dev/null +++ b/Libraries/ASIO/host/pc/asiolist.cpp @@ -0,0 +1,314 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Helpers +// Filename : host/pc/asiolist.cpp +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O Helpers +// a simple Windows ASIO host example +// - instantiates the driver +// - get the information from the driver +// - built up some audio channels +// - plays silence for 20 seconds +// - destruct the driver +// Note: This sample cannot work with the "ASIO DirectX Driver" as it does +// not have a valid Application Window handle, which is used as sysRef +// on the Windows platform. +// **** Windows specific **** +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2025, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +//----------------------------------------------------------------------------- + +#include +#include "iasiodrv.h" +#include "asiolist.h" + +#define ASIODRV_DESC "description" +#define INPROC_SERVER "InprocServer32" +#define ASIO_PATH "software\\asio" +#define COM_CLSID "clsid" + +// ****************************************************************** +// Local Functions +// ****************************************************************** +static LONG findDrvPath (char *clsidstr,char *dllpath,int dllpathsize) +{ + HKEY hkEnum,hksub,hkpath; + char databuf[512]; + LONG cr,rc = -1; + DWORD datatype,datasize; + DWORD index; + OFSTRUCT ofs; + HFILE hfile; + BOOL found = FALSE; + + CharLowerBuff(clsidstr,strlen(clsidstr)); + if ((cr = RegOpenKey(HKEY_CLASSES_ROOT,COM_CLSID,&hkEnum)) == ERROR_SUCCESS) { + + index = 0; + while (cr == ERROR_SUCCESS && !found) { + cr = RegEnumKey(hkEnum,index++,(LPTSTR)databuf,512); + if (cr == ERROR_SUCCESS) { + CharLowerBuff(databuf,strlen(databuf)); + if (!(strcmp(databuf,clsidstr))) { + if ((cr = RegOpenKeyEx(hkEnum,(LPCTSTR)databuf,0,KEY_READ,&hksub)) == ERROR_SUCCESS) { + if ((cr = RegOpenKeyEx(hksub,(LPCTSTR)INPROC_SERVER,0,KEY_READ,&hkpath)) == ERROR_SUCCESS) { + datatype = REG_SZ; datasize = (DWORD)dllpathsize; + cr = RegQueryValueEx(hkpath,0,0,&datatype,(LPBYTE)dllpath,&datasize); + if (cr == ERROR_SUCCESS) { + memset(&ofs,0,sizeof(OFSTRUCT)); + ofs.cBytes = sizeof(OFSTRUCT); + hfile = OpenFile(dllpath,&ofs,OF_EXIST); + if (hfile) rc = 0; + } + RegCloseKey(hkpath); + } + RegCloseKey(hksub); + } + found = TRUE; // break out + } + } + } + RegCloseKey(hkEnum); + } + return rc; +} + + +static LPASIODRVSTRUCT newDrvStruct (HKEY hkey,char *keyname,int drvID,LPASIODRVSTRUCT lpdrv) +{ + HKEY hksub; + char databuf[256]; + char dllpath[MAXPATHLEN]; + WORD wData[100]; + CLSID clsid; + DWORD datatype,datasize; + LONG cr,rc; + + if (!lpdrv) { + if ((cr = RegOpenKeyEx(hkey,(LPCTSTR)keyname,0,KEY_READ,&hksub)) == ERROR_SUCCESS) { + + datatype = REG_SZ; datasize = 256; + cr = RegQueryValueEx(hksub,COM_CLSID,0,&datatype,(LPBYTE)databuf,&datasize); + if (cr == ERROR_SUCCESS) { + rc = findDrvPath (databuf,dllpath,MAXPATHLEN); + if (rc == 0) { + lpdrv = new ASIODRVSTRUCT[1]; + if (lpdrv) { + memset(lpdrv,0,sizeof(ASIODRVSTRUCT)); + lpdrv->drvID = drvID; + MultiByteToWideChar(CP_ACP,0,(LPCSTR)databuf,-1,(LPWSTR)wData,100); + if ((cr = CLSIDFromString((LPOLESTR)wData,(LPCLSID)&clsid)) == S_OK) { + memcpy(&lpdrv->clsid,&clsid,sizeof(CLSID)); + } + + datatype = REG_SZ; datasize = 256; + cr = RegQueryValueEx(hksub,ASIODRV_DESC,0,&datatype,(LPBYTE)databuf,&datasize); + if (cr == ERROR_SUCCESS) { + strcpy(lpdrv->drvname,databuf); + } + else strcpy(lpdrv->drvname,keyname); + } + } + } + RegCloseKey(hksub); + } + } + else lpdrv->next = newDrvStruct(hkey,keyname,drvID+1,lpdrv->next); + + return lpdrv; +} + +static void deleteDrvStruct (LPASIODRVSTRUCT lpdrv) +{ + IASIO *iasio; + + if (lpdrv != 0) { + deleteDrvStruct(lpdrv->next); + if (lpdrv->asiodrv) { + iasio = (IASIO *)lpdrv->asiodrv; + iasio->Release(); + } + delete lpdrv; + } +} + + +static LPASIODRVSTRUCT getDrvStruct (int drvID,LPASIODRVSTRUCT lpdrv) +{ + while (lpdrv) { + if (lpdrv->drvID == drvID) return lpdrv; + lpdrv = lpdrv->next; + } + return 0; +} +// ****************************************************************** + + +// ****************************************************************** +// AsioDriverList +// ****************************************************************** +AsioDriverList::AsioDriverList () +{ + HKEY hkEnum = 0; + char keyname[MAXDRVNAMELEN]; + LPASIODRVSTRUCT pdl; + LONG cr; + DWORD index = 0; + BOOL fin = FALSE; + + numdrv = 0; + lpdrvlist = 0; + + cr = RegOpenKey(HKEY_LOCAL_MACHINE,ASIO_PATH,&hkEnum); + while (cr == ERROR_SUCCESS) { + if ((cr = RegEnumKey(hkEnum,index++,(LPTSTR)keyname,MAXDRVNAMELEN))== ERROR_SUCCESS) { + lpdrvlist = newDrvStruct (hkEnum,keyname,0,lpdrvlist); + } + else fin = TRUE; + } + if (hkEnum) RegCloseKey(hkEnum); + + pdl = lpdrvlist; + while (pdl) { + numdrv++; + pdl = pdl->next; + } + + if (numdrv) CoInitialize(0); // initialize COM +} + +AsioDriverList::~AsioDriverList () +{ + if (numdrv) { + deleteDrvStruct(lpdrvlist); + CoUninitialize(); + } +} + + +LONG AsioDriverList::asioGetNumDev (VOID) +{ + return (LONG)numdrv; +} + + +LONG AsioDriverList::asioOpenDriver (int drvID,LPVOID *asiodrv) +{ + LPASIODRVSTRUCT lpdrv = 0; + long rc; + + if (!asiodrv) return DRVERR_INVALID_PARAM; + + if ((lpdrv = getDrvStruct(drvID,lpdrvlist)) != 0) { + if (!lpdrv->asiodrv) { + rc = CoCreateInstance(lpdrv->clsid,0,CLSCTX_INPROC_SERVER,lpdrv->clsid,asiodrv); + if (rc == S_OK) { + lpdrv->asiodrv = *asiodrv; + return 0; + } + // else if (rc == REGDB_E_CLASSNOTREG) + // strcpy (info->messageText, "Driver not registered in the Registration Database!"); + } + else rc = DRVERR_DEVICE_ALREADY_OPEN; + } + else rc = DRVERR_DEVICE_NOT_FOUND; + + return rc; +} + + +LONG AsioDriverList::asioCloseDriver (int drvID) +{ + LPASIODRVSTRUCT lpdrv = 0; + IASIO *iasio; + + if ((lpdrv = getDrvStruct(drvID,lpdrvlist)) != 0) { + if (lpdrv->asiodrv) { + iasio = (IASIO *)lpdrv->asiodrv; + iasio->Release(); + lpdrv->asiodrv = 0; + } + } + + return 0; +} + +LONG AsioDriverList::asioGetDriverName (int drvID,char *drvname,int drvnamesize) +{ + LPASIODRVSTRUCT lpdrv = 0; + + if (!drvname) return DRVERR_INVALID_PARAM; + + if ((lpdrv = getDrvStruct(drvID,lpdrvlist)) != 0) { + if (strlen(lpdrv->drvname) < (unsigned int)drvnamesize) { + strcpy(drvname,lpdrv->drvname); + } + else { + memcpy(drvname,lpdrv->drvname,drvnamesize-4); + drvname[drvnamesize-4] = '.'; + drvname[drvnamesize-3] = '.'; + drvname[drvnamesize-2] = '.'; + drvname[drvnamesize-1] = 0; + } + return 0; + } + return DRVERR_DEVICE_NOT_FOUND; +} + +LONG AsioDriverList::asioGetDriverPath (int drvID,char *dllpath,int dllpathsize) +{ + LPASIODRVSTRUCT lpdrv = 0; + + if (!dllpath) return DRVERR_INVALID_PARAM; + + if ((lpdrv = getDrvStruct(drvID,lpdrvlist)) != 0) { + if (strlen(lpdrv->dllpath) < (unsigned int)dllpathsize) { + strcpy(dllpath,lpdrv->dllpath); + return 0; + } + dllpath[0] = 0; + return DRVERR_INVALID_PARAM; + } + return DRVERR_DEVICE_NOT_FOUND; +} + +LONG AsioDriverList::asioGetDriverCLSID (int drvID,CLSID *clsid) +{ + LPASIODRVSTRUCT lpdrv = 0; + + if (!clsid) return DRVERR_INVALID_PARAM; + + if ((lpdrv = getDrvStruct(drvID,lpdrvlist)) != 0) { + memcpy(clsid,&lpdrv->clsid,sizeof(CLSID)); + return 0; + } + return DRVERR_DEVICE_NOT_FOUND; +} + + diff --git a/Libraries/ASIO/host/pc/asiolist.h b/Libraries/ASIO/host/pc/asiolist.h new file mode 100644 index 0000000000..a5025ef83b --- /dev/null +++ b/Libraries/ASIO/host/pc/asiolist.h @@ -0,0 +1,84 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Helpers +// Filename : host/pc/asiolist.h +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O Helpers +// a simple Windows ASIO host example +// **** Windows specific **** +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2025, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +//----------------------------------------------------------------------------- + +#ifndef __asiolist__ +#define __asiolist__ + +#define DRVERR -5000 +#define DRVERR_INVALID_PARAM DRVERR-1 +#define DRVERR_DEVICE_ALREADY_OPEN DRVERR-2 +#define DRVERR_DEVICE_NOT_FOUND DRVERR-3 + +#define MAXPATHLEN 512 +#define MAXDRVNAMELEN 128 + +struct asiodrvstruct +{ + int drvID; + CLSID clsid; + char dllpath[MAXPATHLEN]; + char drvname[MAXDRVNAMELEN]; + LPVOID asiodrv; + struct asiodrvstruct *next; +}; + +typedef struct asiodrvstruct ASIODRVSTRUCT; +typedef ASIODRVSTRUCT *LPASIODRVSTRUCT; + +class AsioDriverList { +public: + AsioDriverList(); + ~AsioDriverList(); + + LONG asioOpenDriver (int,VOID **); + LONG asioCloseDriver (int); + + // nice to have + LONG asioGetNumDev (VOID); + LONG asioGetDriverName (int,char *,int); + LONG asioGetDriverPath (int,char *,int); + LONG asioGetDriverCLSID (int,CLSID *); + + // or use directly access + LPASIODRVSTRUCT lpdrvlist; + int numdrv; +}; + +typedef class AsioDriverList *LPASIODRIVERLIST; + +#endif diff --git a/Libraries/ASIO/host/sample/hostsample.cpp b/Libraries/ASIO/host/sample/hostsample.cpp new file mode 100644 index 0000000000..1d04e3256c --- /dev/null +++ b/Libraries/ASIO/host/sample/hostsample.cpp @@ -0,0 +1,561 @@ +//------------------------------------------------------------------------ +// Project : ASIO SDK +// +// Category : Helpers +// Filename : host/sample/hostsample.cpp +// Created by : Steinberg, 05/1996 +// Description : Steinberg Audio Stream I/O Helpers +// a simple ASIO host example +// - instantiates the driver +// - get the information from the driver +// - built up some audio channels +// - plays silence for 20 seconds +// - destruct the driver +// Note: This sample cannot work with the "ASIO DirectX Driver" as it does +// not have a valid Application Window handle, which is used as sysRef +// on the Windows platform. +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2025, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +//----------------------------------------------------------------------------- + +#include +#include +#include "asiosys.h" +#include "asio.h" +#include "asiodrivers.h" + +// name of the ASIO device to be used +#if WINDOWS +// #define ASIO_DRIVER_NAME "ASIO Multimedia Driver" + #define ASIO_DRIVER_NAME "ASIO Sample" +#elif MAC +// #define ASIO_DRIVER_NAME "Apple Sound Manager" + #define ASIO_DRIVER_NAME "ASIO Sample" +#endif + +#define TEST_RUN_TIME 20.0 // run for 20 seconds + + +enum { + // number of input and outputs supported by the host application + // you can change these to higher or lower values + kMaxInputChannels = 32, + kMaxOutputChannels = 32 +}; + + +// internal data storage +typedef struct DriverInfo +{ + // ASIOInit() + ASIODriverInfo driverInfo; + + // ASIOGetChannels() + long inputChannels; + long outputChannels; + + // ASIOGetBufferSize() + long minSize; + long maxSize; + long preferredSize; + long granularity; + + // ASIOGetSampleRate() + ASIOSampleRate sampleRate; + + // ASIOOutputReady() + bool postOutput; + + // ASIOGetLatencies () + long inputLatency; + long outputLatency; + + // ASIOCreateBuffers () + long inputBuffers; // becomes number of actual created input buffers + long outputBuffers; // becomes number of actual created output buffers + ASIOBufferInfo bufferInfos[kMaxInputChannels + kMaxOutputChannels]; // buffer info's + + // ASIOGetChannelInfo() + ASIOChannelInfo channelInfos[kMaxInputChannels + kMaxOutputChannels]; // channel info's + // The above two arrays share the same indexing, as the data in them are linked together + + // Information from ASIOGetSamplePosition() + // data is converted to double floats for easier use, however 64 bit integer can be used, too + double nanoSeconds; + double samples; + double tcSamples; // time code samples + + // bufferSwitchTimeInfo() + ASIOTime tInfo; // time info state + unsigned long sysRefTime; // system reference time, when bufferSwitch() was called + + // Signal the end of processing in this example + bool stopped; +} DriverInfo; + + +DriverInfo asioDriverInfo = {0}; +ASIOCallbacks asioCallbacks; + +//---------------------------------------------------------------------------------- +// some external references +extern AsioDrivers* asioDrivers; +bool loadAsioDriver(char *name); + +// internal prototypes (required for the Metrowerks CodeWarrior compiler) +int main(int argc, char* argv[]); +long init_asio_static_data (DriverInfo *asioDriverInfo); +ASIOError create_asio_buffers (DriverInfo *asioDriverInfo); +unsigned long get_sys_reference_time(); + + +// callback prototypes +void bufferSwitch(long index, ASIOBool processNow); +ASIOTime *bufferSwitchTimeInfo(ASIOTime *timeInfo, long index, ASIOBool processNow); +void sampleRateChanged(ASIOSampleRate sRate); +long asioMessages(long selector, long value, void* message, double* opt); + + + +//---------------------------------------------------------------------------------- +long init_asio_static_data (DriverInfo *asioDriverInfo) +{ // collect the informational data of the driver + // get the number of available channels + if(ASIOGetChannels(&asioDriverInfo->inputChannels, &asioDriverInfo->outputChannels) == ASE_OK) + { + printf ("ASIOGetChannels (inputs: %d, outputs: %d);\n", asioDriverInfo->inputChannels, asioDriverInfo->outputChannels); + + // get the usable buffer sizes + if(ASIOGetBufferSize(&asioDriverInfo->minSize, &asioDriverInfo->maxSize, &asioDriverInfo->preferredSize, &asioDriverInfo->granularity) == ASE_OK) + { + printf ("ASIOGetBufferSize (min: %d, max: %d, preferred: %d, granularity: %d);\n", + asioDriverInfo->minSize, asioDriverInfo->maxSize, + asioDriverInfo->preferredSize, asioDriverInfo->granularity); + + // get the currently selected sample rate + if(ASIOGetSampleRate(&asioDriverInfo->sampleRate) == ASE_OK) + { + printf ("ASIOGetSampleRate (sampleRate: %f);\n", asioDriverInfo->sampleRate); + if (asioDriverInfo->sampleRate <= 0.0 || asioDriverInfo->sampleRate > 96000.0) + { + // Driver does not store it's internal sample rate, so set it to a know one. + // Usually you should check beforehand, that the selected sample rate is valid + // with ASIOCanSampleRate(). + if(ASIOSetSampleRate(44100.0) == ASE_OK) + { + if(ASIOGetSampleRate(&asioDriverInfo->sampleRate) == ASE_OK) + printf ("ASIOGetSampleRate (sampleRate: %f);\n", asioDriverInfo->sampleRate); + else + return -6; + } + else + return -5; + } + + // check wether the driver requires the ASIOOutputReady() optimization + // (can be used by the driver to reduce output latency by one block) + if(ASIOOutputReady() == ASE_OK) + asioDriverInfo->postOutput = true; + else + asioDriverInfo->postOutput = false; + printf ("ASIOOutputReady(); - %s\n", asioDriverInfo->postOutput ? "Supported" : "Not supported"); + + return 0; + } + return -3; + } + return -2; + } + return -1; +} + + +//---------------------------------------------------------------------------------- +// conversion from 64 bit ASIOSample/ASIOTimeStamp to double float +#if NATIVE_INT64 + #define ASIO64toDouble(a) (a) +#else + const double twoRaisedTo32 = 4294967296.; + #define ASIO64toDouble(a) ((a).lo + (a).hi * twoRaisedTo32) +#endif + +ASIOTime *bufferSwitchTimeInfo(ASIOTime *timeInfo, long index, ASIOBool processNow) +{ // the actual processing callback. + // Beware that this is normally in a seperate thread, hence be sure that you take care + // about thread synchronization. This is omitted here for simplicity. + static long processedSamples = 0; + + // store the timeInfo for later use + asioDriverInfo.tInfo = *timeInfo; + + // get the time stamp of the buffer, not necessary if no + // synchronization to other media is required + if (timeInfo->timeInfo.flags & kSystemTimeValid) + asioDriverInfo.nanoSeconds = ASIO64toDouble(timeInfo->timeInfo.systemTime); + else + asioDriverInfo.nanoSeconds = 0; + + if (timeInfo->timeInfo.flags & kSamplePositionValid) + asioDriverInfo.samples = ASIO64toDouble(timeInfo->timeInfo.samplePosition); + else + asioDriverInfo.samples = 0; + + if (timeInfo->timeCode.flags & kTcValid) + asioDriverInfo.tcSamples = ASIO64toDouble(timeInfo->timeCode.timeCodeSamples); + else + asioDriverInfo.tcSamples = 0; + + // get the system reference time + asioDriverInfo.sysRefTime = get_sys_reference_time(); + +#if WINDOWS && _DEBUG + // a few debug messages for the Windows device driver developer + // tells you the time when driver got its interrupt and the delay until the app receives + // the event notification. + static double last_samples = 0; + char tmp[128]; + sprintf (tmp, "diff: %d / %d ms / %d ms / %d samples \n", asioDriverInfo.sysRefTime - (long)(asioDriverInfo.nanoSeconds / 1000000.0), asioDriverInfo.sysRefTime, (long)(asioDriverInfo.nanoSeconds / 1000000.0), (long)(asioDriverInfo.samples - last_samples)); + OutputDebugString (tmp); + last_samples = asioDriverInfo.samples; +#endif + + // buffer size in samples + long buffSize = asioDriverInfo.preferredSize; + + // perform the processing + for (int i = 0; i < asioDriverInfo.inputBuffers + asioDriverInfo.outputBuffers; i++) + { + if (asioDriverInfo.bufferInfos[i].isInput == false) + { + // OK do processing for the outputs only + switch (asioDriverInfo.channelInfos[i].type) + { + case ASIOSTInt16LSB: + memset (asioDriverInfo.bufferInfos[i].buffers[index], 0, buffSize * 2); + break; + case ASIOSTInt24LSB: // used for 20 bits as well + memset (asioDriverInfo.bufferInfos[i].buffers[index], 0, buffSize * 3); + break; + case ASIOSTInt32LSB: + memset (asioDriverInfo.bufferInfos[i].buffers[index], 0, buffSize * 4); + break; + case ASIOSTFloat32LSB: // IEEE 754 32 bit float, as found on Intel x86 architecture + memset (asioDriverInfo.bufferInfos[i].buffers[index], 0, buffSize * 4); + break; + case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + memset (asioDriverInfo.bufferInfos[i].buffers[index], 0, buffSize * 8); + break; + + // these are used for 32 bit data buffer, with different alignment of the data inside + // 32 bit PCI bus systems can more easily used with these + case ASIOSTInt32LSB16: // 32 bit data with 18 bit alignment + case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment + memset (asioDriverInfo.bufferInfos[i].buffers[index], 0, buffSize * 4); + break; + + case ASIOSTInt16MSB: + memset (asioDriverInfo.bufferInfos[i].buffers[index], 0, buffSize * 2); + break; + case ASIOSTInt24MSB: // used for 20 bits as well + memset (asioDriverInfo.bufferInfos[i].buffers[index], 0, buffSize * 3); + break; + case ASIOSTInt32MSB: + memset (asioDriverInfo.bufferInfos[i].buffers[index], 0, buffSize * 4); + break; + case ASIOSTFloat32MSB: // IEEE 754 32 bit float, as found on Intel x86 architecture + memset (asioDriverInfo.bufferInfos[i].buffers[index], 0, buffSize * 4); + break; + case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + memset (asioDriverInfo.bufferInfos[i].buffers[index], 0, buffSize * 8); + break; + + // these are used for 32 bit data buffer, with different alignment of the data inside + // 32 bit PCI bus systems can more easily used with these + case ASIOSTInt32MSB16: // 32 bit data with 18 bit alignment + case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment + memset (asioDriverInfo.bufferInfos[i].buffers[index], 0, buffSize * 4); + break; + } + } + } + + // finally if the driver supports the ASIOOutputReady() optimization, do it here, all data are in place + if (asioDriverInfo.postOutput) + ASIOOutputReady(); + + if (processedSamples >= asioDriverInfo.sampleRate * TEST_RUN_TIME) // roughly measured + asioDriverInfo.stopped = true; + else + processedSamples += buffSize; + + return 0L; +} + +//---------------------------------------------------------------------------------- +void bufferSwitch(long index, ASIOBool processNow) +{ // the actual processing callback. + // Beware that this is normally in a seperate thread, hence be sure that you take care + // about thread synchronization. This is omitted here for simplicity. + + // as this is a "back door" into the bufferSwitchTimeInfo a timeInfo needs to be created + // though it will only set the timeInfo.samplePosition and timeInfo.systemTime fields and the according flags + ASIOTime timeInfo; + memset (&timeInfo, 0, sizeof (timeInfo)); + + // get the time stamp of the buffer, not necessary if no + // synchronization to other media is required + if(ASIOGetSamplePosition(&timeInfo.timeInfo.samplePosition, &timeInfo.timeInfo.systemTime) == ASE_OK) + timeInfo.timeInfo.flags = kSystemTimeValid | kSamplePositionValid; + + bufferSwitchTimeInfo (&timeInfo, index, processNow); +} + + +//---------------------------------------------------------------------------------- +void sampleRateChanged(ASIOSampleRate sRate) +{ + // do whatever you need to do if the sample rate changed + // usually this only happens during external sync. + // Audio processing is not stopped by the driver, actual sample rate + // might not have even changed, maybe only the sample rate status of an + // AES/EBU or S/PDIF digital input at the audio device. + // You might have to update time/sample related conversion routines, etc. +} + +//---------------------------------------------------------------------------------- +long asioMessages(long selector, long value, void* message, double* opt) +{ + // currently the parameters "value", "message" and "opt" are not used. + long ret = 0; + switch(selector) + { + case kAsioSelectorSupported: + if(value == kAsioResetRequest + || value == kAsioEngineVersion + || value == kAsioResyncRequest + || value == kAsioLatenciesChanged + // the following three were added for ASIO 2.0, you don't necessarily have to support them + || value == kAsioSupportsTimeInfo + || value == kAsioSupportsTimeCode + || value == kAsioSupportsInputMonitor) + ret = 1L; + break; + case kAsioResetRequest: + // defer the task and perform the reset of the driver during the next "safe" situation + // You cannot reset the driver right now, as this code is called from the driver. + // Reset the driver is done by completely destruct is. I.e. ASIOStop(), ASIODisposeBuffers(), Destruction + // Afterwards you initialize the driver again. + asioDriverInfo.stopped; // In this sample the processing will just stop + ret = 1L; + break; + case kAsioResyncRequest: + // This informs the application, that the driver encountered some non fatal data loss. + // It is used for synchronization purposes of different media. + // Added mainly to work around the Win16Mutex problems in Windows 95/98 with the + // Windows Multimedia system, which could loose data because the Mutex was hold too long + // by another thread. + // However a driver can issue it in other situations, too. + ret = 1L; + break; + case kAsioLatenciesChanged: + // This will inform the host application that the drivers were latencies changed. + // Beware, it this does not mean that the buffer sizes have changed! + // You might need to update internal delay data. + ret = 1L; + break; + case kAsioEngineVersion: + // return the supported ASIO version of the host application + // If a host applications does not implement this selector, ASIO 1.0 is assumed + // by the driver + ret = 2L; + break; + case kAsioSupportsTimeInfo: + // informs the driver wether the asioCallbacks.bufferSwitchTimeInfo() callback + // is supported. + // For compatibility with ASIO 1.0 drivers the host application should always support + // the "old" bufferSwitch method, too. + ret = 1; + break; + case kAsioSupportsTimeCode: + // informs the driver wether application is interested in time code info. + // If an application does not need to know about time code, the driver has less work + // to do. + ret = 0; + break; + } + return ret; +} + + +//---------------------------------------------------------------------------------- +ASIOError create_asio_buffers (DriverInfo *asioDriverInfo) +{ // create buffers for all inputs and outputs of the card with the + // preferredSize from ASIOGetBufferSize() as buffer size + long i; + ASIOError result; + + // fill the bufferInfos from the start without a gap + ASIOBufferInfo *info = asioDriverInfo->bufferInfos; + + // prepare inputs (Though this is not necessaily required, no opened inputs will work, too + if (asioDriverInfo->inputChannels > kMaxInputChannels) + asioDriverInfo->inputBuffers = kMaxInputChannels; + else + asioDriverInfo->inputBuffers = asioDriverInfo->inputChannels; + for(i = 0; i < asioDriverInfo->inputBuffers; i++, info++) + { + info->isInput = ASIOTrue; + info->channelNum = i; + info->buffers[0] = info->buffers[1] = 0; + } + + // prepare outputs + if (asioDriverInfo->outputChannels > kMaxOutputChannels) + asioDriverInfo->outputBuffers = kMaxOutputChannels; + else + asioDriverInfo->outputBuffers = asioDriverInfo->outputChannels; + for(i = 0; i < asioDriverInfo->outputBuffers; i++, info++) + { + info->isInput = ASIOFalse; + info->channelNum = i; + info->buffers[0] = info->buffers[1] = 0; + } + + // create and activate buffers + result = ASIOCreateBuffers(asioDriverInfo->bufferInfos, + asioDriverInfo->inputBuffers + asioDriverInfo->outputBuffers, + asioDriverInfo->preferredSize, &asioCallbacks); + if (result == ASE_OK) + { + // now get all the buffer details, sample word length, name, word clock group and activation + for (i = 0; i < asioDriverInfo->inputBuffers + asioDriverInfo->outputBuffers; i++) + { + asioDriverInfo->channelInfos[i].channel = asioDriverInfo->bufferInfos[i].channelNum; + asioDriverInfo->channelInfos[i].isInput = asioDriverInfo->bufferInfos[i].isInput; + result = ASIOGetChannelInfo(&asioDriverInfo->channelInfos[i]); + if (result != ASE_OK) + break; + } + + if (result == ASE_OK) + { + // get the input and output latencies + // Latencies often are only valid after ASIOCreateBuffers() + // (input latency is the age of the first sample in the currently returned audio block) + // (output latency is the time the first sample in the currently returned audio block requires to get to the output) + result = ASIOGetLatencies(&asioDriverInfo->inputLatency, &asioDriverInfo->outputLatency); + if (result == ASE_OK) + printf ("ASIOGetLatencies (input: %d, output: %d);\n", asioDriverInfo->inputLatency, asioDriverInfo->outputLatency); + } + } + return result; +} + +int main(int argc, char* argv[]) +{ + // load the driver, this will setup all the necessary internal data structures + if (loadAsioDriver (ASIO_DRIVER_NAME)) + { + // initialize the driver + if (ASIOInit (&asioDriverInfo.driverInfo) == ASE_OK) + { + printf ("asioVersion: %d\n" + "driverVersion: %d\n" + "Name: %s\n" + "ErrorMessage: %s\n", + asioDriverInfo.driverInfo.asioVersion, asioDriverInfo.driverInfo.driverVersion, + asioDriverInfo.driverInfo.name, asioDriverInfo.driverInfo.errorMessage); + if (init_asio_static_data (&asioDriverInfo) == 0) + { + // ASIOControlPanel(); you might want to check wether the ASIOControlPanel() can open + + // set up the asioCallback structure and create the ASIO data buffer + asioCallbacks.bufferSwitch = &bufferSwitch; + asioCallbacks.sampleRateDidChange = &sampleRateChanged; + asioCallbacks.asioMessage = &asioMessages; + asioCallbacks.bufferSwitchTimeInfo = &bufferSwitchTimeInfo; + if (create_asio_buffers (&asioDriverInfo) == ASE_OK) + { + if (ASIOStart() == ASE_OK) + { + // Now all is up and running + fprintf (stdout, "\nASIO Driver started succefully.\n\n"); + while (!asioDriverInfo.stopped) + { +#if WINDOWS + Sleep(100); // goto sleep for 100 milliseconds +#elif MAC + unsigned long dummy; + Delay (6, &dummy); +#endif + fprintf (stdout, "%d ms / %d ms / %d samples", asioDriverInfo.sysRefTime, (long)(asioDriverInfo.nanoSeconds / 1000000.0), (long)asioDriverInfo.samples); + + // create a more readable time code format (the quick and dirty way) + double remainder = asioDriverInfo.tcSamples; + long hours = (long)(remainder / (asioDriverInfo.sampleRate * 3600)); + remainder -= hours * asioDriverInfo.sampleRate * 3600; + long minutes = (long)(remainder / (asioDriverInfo.sampleRate * 60)); + remainder -= minutes * asioDriverInfo.sampleRate * 60; + long seconds = (long)(remainder / asioDriverInfo.sampleRate); + remainder -= seconds * asioDriverInfo.sampleRate; + fprintf (stdout, " / TC: %2.2d:%2.2d:%2.2d:%5.5d", (long)hours, (long)minutes, (long)seconds, (long)remainder); + + fprintf (stdout, " \r"); + #if !MAC + fflush (stdout); + #endif + } + ASIOStop(); + } + ASIODisposeBuffers(); + } + } + ASIOExit(); + } + asioDrivers->removeCurrentDriver(); + } + return 0; +} + + +unsigned long get_sys_reference_time() +{ // get the system reference time +#if WINDOWS + return timeGetTime(); +#elif MAC +static const double twoRaisedTo32 = 4294967296.; + UnsignedWide ys; + Microseconds(&ys); + double r = ((double)ys.hi * twoRaisedTo32 + (double)ys.lo); + return (unsigned long)(r / 1000.); +#endif +} diff --git a/Libraries/ASIO/host/sample/hostsample.dsp b/Libraries/ASIO/host/sample/hostsample.dsp new file mode 100644 index 0000000000..c635952202 --- /dev/null +++ b/Libraries/ASIO/host/sample/hostsample.dsp @@ -0,0 +1,134 @@ +# Microsoft Developer Studio Project File - Name="hostsample" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=hostsample - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "hostsample.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "hostsample.mak" CFG="hostsample - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "hostsample - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "hostsample - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "hostsample - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /Yu"stdafx.h" /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "../../common" /I "../../host" /I "../../host/pc" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x809 /d "NDEBUG" +# ADD RSC /l 0x809 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "hostsample - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /Yu"stdafx.h" /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "../../common" /I "../../host" /I "../../host/pc" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FR /YX /FD /GZ /c +# ADD BASE RSC /l 0x809 /d "_DEBUG" +# ADD RSC /l 0x809 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "hostsample - Win32 Release" +# Name "hostsample - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\..\common\asio.cpp +# End Source File +# Begin Source File + +SOURCE=..\asiodrivers.cpp +# End Source File +# Begin Source File + +SOURCE=..\pc\asiolist.cpp +# End Source File +# Begin Source File + +SOURCE=.\hostsample.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\..\common\asio.h +# End Source File +# Begin Source File + +SOURCE=..\asiodrivers.h +# End Source File +# Begin Source File + +SOURCE=..\pc\asiolist.h +# End Source File +# Begin Source File + +SOURCE=..\ginclude.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# Begin Source File + +SOURCE=.\ReadMe.txt +# End Source File +# End Target +# End Project diff --git a/Libraries/ASIO/host/sample/hostsample.vcproj b/Libraries/ASIO/host/sample/hostsample.vcproj new file mode 100644 index 0000000000..0e825e6542 --- /dev/null +++ b/Libraries/ASIO/host/sample/hostsample.vcproj @@ -0,0 +1,571 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index d852a69c4d..64f5b5ad8f 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -362,7 +362,9 @@ if(CMAKE_C_COMPILER_ID MATCHES "Clang|GNU") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-int-conversion -Wno-pointer-sign -Wno-pointer-to-int-cast -Wno-incompatible-pointer-types") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS} -fno-finite-math-only -ffast-math -funroll-loops -fomit-frame-pointer -O3") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} -g -O0") - endif() +elseif(MSVC) + add_compile_options(/wd4244 /wd4311 /wd4003 /wd4047 /wd4477 /wd4068 /wd4133 /wd4311) +endif() # ------------------------------------------------------------------------------# # SFONT~ @@ -447,6 +449,15 @@ unset(MESSAGE_QUIET) add_library(externals STATIC ${ELSE_SOURCES} ${CYCLONE_SOURCES} ${PDLUA_SOURCES}) add_library(externals-multi STATIC ${ELSE_SOURCES} ${CYCLONE_SOURCES} ${PDLUA_SOURCES} ${}) +# Hide deprecation warnings on externals, it's noise and are outside of our control +if(MSVC) + target_compile_options(externals PRIVATE /wd4996) + target_compile_options(externals-multi PRIVATE /wd4996) +else() + target_compile_options(externals PRIVATE -Wno-deprecated-declarations) + target_compile_options(externals-multi PRIVATE -Wno-deprecated-declarations) +endif() + list(APPEND ELSE_INCLUDES pd-else/Source/Control/ pd-else/Source/Audio/ diff --git a/Libraries/JUCE b/Libraries/JUCE index 760afc4960..db1ff7263b 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit 760afc49601588fd60d47a3bbc70b32f47c7f1fe +Subproject commit db1ff7263be13b67ba4cbc6269c999d75d2c9f43 diff --git a/Libraries/fftw3/CMakeLists.txt b/Libraries/fftw3/CMakeLists.txt index 3b3a9ad543..f667eda4a7 100644 --- a/Libraries/fftw3/CMakeLists.txt +++ b/Libraries/fftw3/CMakeLists.txt @@ -313,7 +313,7 @@ set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") add_library (${fftw3_lib} ${SOURCEFILES}) target_include_directories (${fftw3_lib} INTERFACE $) if (MSVC AND NOT (CMAKE_C_COMPILER_ID STREQUAL "Intel")) - target_compile_definitions (${fftw3_lib} PRIVATE /bigobj HAVE_UINTPTR_T=1) + target_compile_definitions (${fftw3_lib} PRIVATE $<$:/bigobj> HAVE_UINTPTR_T=1) endif () if (HAVE_SSE) target_compile_options (${fftw3_lib} PRIVATE ${SSE_FLAG}) diff --git a/Libraries/heavylib b/Libraries/heavylib index 6a73fb493a..56e46b3a1e 160000 --- a/Libraries/heavylib +++ b/Libraries/heavylib @@ -1 +1 @@ -Subproject commit 6a73fb493a19da1152f42a2848835af62d8a08eb +Subproject commit 56e46b3a1e62e77431336a90be45158effd616ff diff --git a/Libraries/nanovg b/Libraries/nanovg index 2f74d5c21b..0770eeeddb 160000 --- a/Libraries/nanovg +++ b/Libraries/nanovg @@ -1 +1 @@ -Subproject commit 2f74d5c21b6768c2ab5f0f9532135d80b09bead9 +Subproject commit 0770eeeddbeb373c7017bee59490e462e60a060a diff --git a/Libraries/pd-else b/Libraries/pd-else index 27f148979f..049f4cf4d3 160000 --- a/Libraries/pd-else +++ b/Libraries/pd-else @@ -1 +1 @@ -Subproject commit 27f148979f145b2a381343a7ecc4c6f796ad5922 +Subproject commit 049f4cf4d3838d97f7adcb0f7857bf12cc5307d6 diff --git a/Libraries/pd-lua b/Libraries/pd-lua index 2bd1efb695..dc74542815 160000 --- a/Libraries/pd-lua +++ b/Libraries/pd-lua @@ -1 +1 @@ -Subproject commit 2bd1efb69532cca3dc7a7e2b465adf4e8ce2c695 +Subproject commit dc7454281572d6f2d8b6da30c4040c189b366cea diff --git a/Libraries/pure-data b/Libraries/pure-data index 768af7db75..5a2151ea6c 160000 --- a/Libraries/pure-data +++ b/Libraries/pure-data @@ -1 +1 @@ -Subproject commit 768af7db75d237fdc489663b75b97e1c07575ddd +Subproject commit 5a2151ea6cac9154e4e1f890784804807cf9d24d diff --git a/README.md b/README.md index 23208272cc..ac034ff914 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ You can use externals inside plugdata's plugin version by recompiling the extern - [Pure Data](https://puredata.info) by Miller Puckette and others - [libpd](https://github.com/libpd/libpd) by the Peter Brinkmann, Dan Wilcox and others - [Heavy/hvcc](https://github.com/Wasted-Audio/hvcc) originally by Enzien Audio, maintained and modernised by Wasted Audio -- [Juce](https://github.com/WeAreROLI/JUCE) by ROLI Ltd. +- [Juce](https://github.com/juce-framework/JUCE) by Raw Material Software Ltd. - [MoodyCamel](https://github.com/cameron314/concurrentqueue) by Cameron Desrochers - [Inter](https://rsms.me/inter/) font by Rasmus Andersson - [Kiwi](https://github.com/Musicoll/Kiwi) by Eliott Paris, Pierre Guillot and Jean Millot diff --git a/Resources/Fonts/InterTabular.ttf b/Resources/Fonts/InterTabular.ttf index a5fc737b1f..0d1d35b501 100644 Binary files a/Resources/Fonts/InterTabular.ttf and b/Resources/Fonts/InterTabular.ttf differ diff --git a/Resources/Fonts/InterUnicode.ttf b/Resources/Fonts/InterUnicode.ttf index 34df2b2bab..ac0dfbde64 100644 Binary files a/Resources/Fonts/InterUnicode.ttf and b/Resources/Fonts/InterUnicode.ttf differ diff --git a/Resources/Fonts/RobotoMono-Bold.ttf b/Resources/Fonts/RobotoMono-Bold.ttf new file mode 100644 index 0000000000..bef439f3b9 Binary files /dev/null and b/Resources/Fonts/RobotoMono-Bold.ttf differ diff --git a/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/Contents.json b/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..e8d3f0385b --- /dev/null +++ b/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,89 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "icon-120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "icon-180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "icon-152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "icon-167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "icon-1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/icon-1024.png b/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/icon-1024.png new file mode 100644 index 0000000000..66837b5372 Binary files /dev/null and b/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/icon-1024.png differ diff --git a/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/icon-120.png b/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/icon-120.png new file mode 100644 index 0000000000..a843fc473e Binary files /dev/null and b/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/icon-120.png differ diff --git a/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/icon-152.png b/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/icon-152.png new file mode 100644 index 0000000000..b3519d969e Binary files /dev/null and b/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/icon-152.png differ diff --git a/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/icon-167.png b/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/icon-167.png new file mode 100644 index 0000000000..cf8b60b464 Binary files /dev/null and b/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/icon-167.png differ diff --git a/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/icon-180.png b/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/icon-180.png new file mode 100644 index 0000000000..00c2a1b44f Binary files /dev/null and b/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/icon-180.png differ diff --git a/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/icon-76.png b/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/icon-76.png new file mode 100644 index 0000000000..4ef1993d08 Binary files /dev/null and b/Resources/Icons/plugdata_icon_ios.xcassets/AppIcon.appiconset/icon-76.png differ diff --git a/Resources/Patches/param-help.pd b/Resources/Patches/param-help.pd index 7f96914c6d..ad3b1c9842 100644 --- a/Resources/Patches/param-help.pd +++ b/Resources/Patches/param-help.pd @@ -16,7 +16,7 @@ #X obj 162 150 tgl 25 0 empty empty empty 17 7 0 10 #e4e4e4 #5a5a5a #5a5a5a 0 1; #X obj 377 187 param param2 1; #X obj 495 186 nbx 5 18 -1e+37 1e+37 0 0 param2-gui-s param2-gui-r empty 0 -8 0 10 #e4e4e4 #5a5a5a #5a5a5a 0 256; -#X text 49 93 [param] sends and receives automation parameters to the DAW. You'll have to create an automation parameter in the sidebar. You, f 82; +#X text 49 93 [param] sends and receives automation parameters to the DAW. You'll have to create an automation parameter in the sidebar., f 82; #X text 377 217 Use the paramname-gui-s and paramname-gui-r pair to send/receive values directly to GUI objects, f 55; #X obj 5 293 cnv 3 550 4 empty empty inlets 8 12 0 13 #dcdcdc #000000 0; #X obj 81 304 cnv 17 4 17 empty empty 0 5 9 0 16 #dcdcdc #9c9c9c 0; @@ -26,7 +26,6 @@ #X msg 63 150 range 0 127; #X text 201 303 float; #X text 185 359 destroy; -#X text 145 321 create ; #X text 149 379 mode ; #X text 202 432 float; #X text 119 398 range; @@ -39,8 +38,9 @@ #X text 245 303 - set DAW parameter value, f 45; #X text 189 525 1) float; #X text 246 525 - automatically handle parameter change state when GUI objects are interacted with \, 1 is enabled (default 0); -#X text 245 321 - create parameter \, optional argument sets default value \;, f 60; #X text 256 339 this makes the parameter active in the sidebar and the DAW; +#X text 63 321 create ; +#X text 245 321 - create parameter \, optional arguments set: default value \, minimum \, maximum, f 77; #X connect 8 0 9 0; #X connect 9 0 8 0; #X connect 10 0 8 1; diff --git a/Resources/Patches/param.pd b/Resources/Patches/param.pd index 46c7296ce0..62a6d164cd 100644 --- a/Resources/Patches/param.pd +++ b/Resources/Patches/param.pd @@ -1,50 +1,60 @@ #N canvas 536 141 844 548 12; #X obj 109 199 outlet; #X obj 125 49 inlet; -#X obj 311 304 inlet; +#X obj 298 303 inlet; #X obj 109 136 r \$1; #X text 319 273 Receive param changing state from pd and send to DAW; #X obj 164 199 s \$1-gui-r; #X obj 35 96 r \$1-gui-s; -#X obj 373 103 loadmess \$2; -#X obj 373 196 gate; -#X obj 413 129 r gui; -#X obj 413 159 route mouse; +#X obj 373 124 r gui; +#X obj 373 154 route mouse; #X obj 373 231 s \$0-param-change; -#X obj 365 304 r \$0-param-change; +#X obj 164 389 r \$0-param-change; #X text 366 34 If the first argument is 1 \, automatically send DAW parameter state changes when GUI objects are interacted with, f 45; -#X obj 35 289 router; -#X obj 35 160 t f f, f 5; +#X obj 35 160 t f f; #X text 116 253 Prevent feedback; #X obj 109 162 t f f f; #X obj 56 199 -; #X obj 56 228 abs; -#X obj 56 254 < 0.001; -#X obj 269 374 list prepend \$1; -#X obj 269 409 s __param; -#X obj 311 341 list prepend change; +#X obj 211 651 list prepend \$1; +#X obj 211 686 s __param; +#X obj 298 585 list prepend change; #X obj 35 322 list prepend float; -#X obj 125 84 routetype float, f 23; -#X connect 1 0 25 0; -#X connect 2 0 23 0; -#X connect 3 0 17 0; -#X connect 6 0 15 0; +#X msg 84 431 1; +#X obj 82 473 change; +#X obj 164 431 spigot; +#X obj 82 389 spigot \$2; +#X obj 35 353 t a b, f 9; +#X obj 125 84 route float, f 16; +#X obj 373 189 spigot \$2; +#X obj 56 254 >= 0.001; +#X obj 35 289 spigot; +#X connect 1 0 26 0; +#X connect 2 0 19 0; +#X connect 3 0 14 0; +#X connect 6 0 12 0; #X connect 7 0 8 0; -#X connect 8 0 11 0; -#X connect 9 0 10 0; -#X connect 10 0 8 1; -#X connect 12 0 23 0; -#X connect 14 0 24 0; -#X connect 15 0 14 0; -#X connect 15 1 18 0; -#X connect 17 0 0 0; -#X connect 17 1 5 0; -#X connect 17 2 18 1; -#X connect 18 0 19 0; -#X connect 19 0 20 0; -#X connect 20 0 14 1; +#X connect 8 0 27 0; +#X connect 10 0 23 0; +#X connect 12 0 29 0; +#X connect 12 1 15 0; +#X connect 14 0 0 0; +#X connect 14 1 5 0; +#X connect 14 2 15 1; +#X connect 15 0 16 0; +#X connect 16 0 28 0; +#X connect 17 0 18 0; +#X connect 19 0 17 0; +#X connect 20 0 25 0; #X connect 21 0 22 0; -#X connect 23 0 21 0; +#X connect 22 0 23 1; +#X connect 22 0 19 0; +#X connect 23 0 22 0; #X connect 24 0 21 0; -#X connect 25 0 15 0; -#X connect 25 1 21 0; +#X connect 25 0 17 0; +#X connect 25 1 24 0; +#X connect 26 0 12 0; +#X connect 26 1 17 0; +#X connect 27 0 9 0; +#X connect 28 0 29 1; +#X connect 29 0 20 0; diff --git a/Resources/Scripts/convert_merda.py b/Resources/Scripts/convert_merda.py index 5b3ba89089..3daa213ceb 100644 --- a/Resources/Scripts/convert_merda.py +++ b/Resources/Scripts/convert_merda.py @@ -6,8 +6,7 @@ def find_restore_and_coords(lines): restore_pattern = re.compile(r'#X restore (\d+) (\d+) graph;?') - coords_pattern = re.compile( - r'#X coords [-\d]+ [-\d]+ [-\d]+ [-\d]+ (\d+) (\d+) .+') + coords_pattern = re.compile(r'#X coords [-\d]+ [-\d]+ [-\d]+ [-\d]+ (\d+) (\d+) .+') restore_x, restore_y = None, None size_w, size_h = None, None @@ -32,29 +31,29 @@ def update_coords(lines, restore_x, restore_y, size_w, size_h): lines[coords_line] = f'#X coords 0 0 1 1 {size_w} {size_h} 1 {restore_x} {restore_y};\n' -def process_patch(file_path): +def process_patch(file_path, out_path): with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: lines = f.readlines() - lines[-1].rstrip(); + if file_path.endswith(".m~.pd") and "brane" not in file_path: + lines[-1].rstrip() + restore_x, restore_y, size_w, size_h = find_restore_and_coords(lines) + if restore_x is not None and size_w is not None: + update_coords(lines, restore_x, restore_y, size_w, size_h) - restore_x, restore_y, size_w, size_h = find_restore_and_coords(lines) - if restore_x is not None and size_w is not None: - update_coords(lines, restore_x, restore_y, size_w, size_h) - - with open(file_path, 'w') as f: + with open(out_path, 'w') as f: f.writelines(lines) -def process(folder_path): +def process(folder_path, out_path): + os.mkdir(out_path) for root, _, files in os.walk(folder_path): for file in files: - if file.endswith(".m~.pd") and "brane" not in file: - process_patch(os.path.join(root, file)) + process_patch(os.path.join(root, file), os.path.join(out_path, file)) if __name__ == "__main__": - if len(sys.argv) < 2: - print("Usage: python update_pd_patch.py ") + if len(sys.argv) < 3: + print("Usage: python update_pd_patch.py ") else: - process_folder(sys.argv[1]) + process(sys.argv[1], sys.argv[2]) diff --git a/Resources/Scripts/package_resources.py b/Resources/Scripts/package_resources.py index 187ee5fa0e..1497707907 100644 --- a/Resources/Scripts/package_resources.py +++ b/Resources/Scripts/package_resources.py @@ -280,9 +280,13 @@ def generate_binary_data(output_dir, file_list): copyFile(project_root + "/Libraries/pd-else/Documentation/README.pdf", "Extra/else") copyDir(project_root + "/Libraries/pd-else/Source/Audio/sfz~/sfz", "Extra/else/sfz") copyDir(project_root + "/Resources/Patches/Presets", "./Extra/Presets") -convert_merda.process(project_root + "/Libraries/pd-else/Abstractions/Merda/Modules/") -globCopy(project_root + "/Libraries/pd-else/Abstractions/Merda/Modules/*.pd", "./Extra/else") + +convert_merda.process(project_root + "/Libraries/pd-else/Abstractions/Merda/Modules/", output_dir + "/Merda_temp") + +globCopy(output_dir + "/Merda_temp/*", "./Extra/else") copyDir(project_root + "/Libraries/pd-else/Abstractions/Merda/Modules/brane-presets", "./Extra/else/brane-presets") +removeDir(output_dir + "/Merda_temp") + globCopy(project_root + "/Libraries/pure-data/doc/sound/*", "Extra/else") # Our folder is called "Documentation" instead of "doc", which makes some file paths in default helpfiles invalid @@ -342,6 +346,7 @@ def generate_binary_data(output_dir, file_list): project_root + "/Resources/Fonts/InterVariable.ttf", project_root + "/Resources/Fonts/InterRegular.ttf", project_root + "/Resources/Fonts/RobotoMono-Regular.ttf", + project_root + "/Resources/Fonts/RobotoMono-Bold.ttf", project_root + "/Resources/Icons/plugdata_large_logo.png", project_root + "/Resources/Icons/plugdata_logo.png", "Documentation.bin", diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 006e70ebc7..5fe0b994bf 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -92,9 +92,9 @@ class ObjectsResizer final : public Component addAndMakeVisible(border); setBounds(selectedObjectBounds.expanded(tabMargin)); - }; + } - ~ObjectsResizer() + ~ObjectsResizer() override { for (auto* obj : cnv->getSelectionOfType()) { obj->hideHandles(false); @@ -206,7 +206,7 @@ class ObjectsResizer final : public Component nvgFontSize(nvg, 20.0f); nvgTextAlign(nvg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); nvgFillColor(nvg, nvgRGBA(240, 240, 240, 255)); - nvgText(nvg, textPos.x, textPos.y, String("Spacer size: " + String(spacer + 1.0f)).toStdString().c_str(), nullptr); + nvgText(nvg, textPos.x, textPos.y, String("Spacer size: " + String(spacer + 1.0f)).toRawUTF8(), nullptr); #endif } @@ -332,8 +332,8 @@ Canvas::Canvas(PluginEditor* parent, pd::Patch::Ptr p, Component* parentGraph) addAndMakeVisible(*graphArea); graphArea->setAlwaysOnTop(true); } - - if(!isGraph) { + + if (!isGraph) { editor->nvgSurface.addBufferedObject(this); } @@ -384,8 +384,8 @@ Canvas::Canvas(PluginEditor* parent, pd::Patch::Ptr p, Component* parentGraph) repaint(); }; - parameters.addParamInt("Width", cDimensions, &patchWidth, 527, true, 0, 1<<30, onInteractionFn); - parameters.addParamInt("Height", cDimensions, &patchHeight, 327, true, 0, 1<<30, onInteractionFn); + parameters.addParamInt("Width", cDimensions, &patchWidth, 527, true, 0, 1 << 30, onInteractionFn); + parameters.addParamInt("Height", cDimensions, &patchHeight, 327, true, 0, 1 << 30, onInteractionFn); if (!isGraph) { patch.setVisible(true); @@ -399,8 +399,8 @@ Canvas::~Canvas() for (auto* object : objects) { object->hideEditor(); } - - if(!isGraph) { + + if (!isGraph) { editor->nvgSurface.removeBufferedObject(this); } @@ -485,7 +485,7 @@ void Canvas::lookAndFeelChanged() sigColBrighter = convertColour(sigColJuce.brighter()); gemColBrigher = convertColour(gemColJuce.brighter()); baseColBrigher = convertColour(baseColJuce.brighter()); - + dotsLargeImage.setDirty(); // Make sure bg colour actually gets updated } @@ -525,7 +525,6 @@ void Canvas::updateFramebuffers(NVGcontext* nvg) g.setColour(Colours::white); // For alpha image colour isn't important g.fillRoundedRectangle(0.0f, 0.0f, 9.0f, 9.0f, Corners::resizeHanleCornerRadius); }, NVGImage::AlphaImage); - editor->nvgSurface.invalidateAll(); } auto gridLogicalSize = objectGrid.gridSize ? objectGrid.gridSize : 25; @@ -567,6 +566,7 @@ void Canvas::updateFramebuffers(NVGcontext* nvg) if (zoom < 0.5f) decim = 12; break; + default: break; } auto const majorDotColour = canvasMarkingsColJuce.withAlpha(std::min(zoom * 0.8f, 1.0f)); @@ -591,13 +591,12 @@ void Canvas::updateFramebuffers(NVGcontext* nvg) g.fillEllipse(centerX - ellipseRadius, centerY - ellipseRadius, ellipseRadius * 2.0f, ellipseRadius * 2.0f); } } }, NVGImage::RepeatImage, canvasBackgroundColJuce); - editor->nvgSurface.invalidateAll(); } } // Callback from canvasViewport to perform actual rendering void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) -{ +{ constexpr auto halfSize = infiniteCanvasSize / 2; auto const zoom = getValue(zoomScale); bool const isLocked = getValue(locked); @@ -608,13 +607,11 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) nvgScale(nvg, zoom, zoom); invalidRegion = invalidRegion.translated(viewport->getViewPositionX(), viewport->getViewPositionY()); invalidRegion /= zoom; - - if(isLocked) - { + + if (isLocked) { nvgFillColor(nvg, canvasBackgroundCol); nvgFillRect(nvg, invalidRegion.getX(), invalidRegion.getY(), invalidRegion.getWidth(), invalidRegion.getHeight()); - } - else { + } else { nvgBeginPath(nvg); nvgRect(nvg, 0, 0, infiniteCanvasSize, infiniteCanvasSize); @@ -633,7 +630,7 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) } } } - + currentRenderArea = invalidRegion; auto drawBorder = [this, nvg, zoom](bool const bg, bool const fg) { @@ -797,8 +794,12 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) if (canvasSearchHighlight) canvasSearchHighlight->render(nvg); - if (dimensionsAreBeingEdited) + if (dimensionsAreBeingEdited) { + bool borderWasShown = showBorder; + showBorder = true; drawBorder(false, true); + showBorder = borderWasShown; + } if (objectsDistributeResizer) objectsDistributeResizer->render(nvg); @@ -867,7 +868,7 @@ void Canvas::renderAllConnections(NVGcontext* nvg, Rectangle const area) } if (connectionsToDraw.not_empty()) { - for (auto* connection : connectionsToDraw) { + for (auto const* connection : connectionsToDraw) { NVGScopedState scopedState(nvg); connection->renderConnectionOrder(nvg); } @@ -892,6 +893,8 @@ void Canvas::settingsChanged(String const& name, var const& value) updateOverlays(); break; } + default: + break; } } @@ -1049,7 +1052,7 @@ void Canvas::save(std::function const& nestedCallback) if (canvasToSave->patch.getCurrentFile().existsAsFile()) { canvasToSave->patch.savePatch(); - SettingsFile::getInstance()->addToRecentlyOpened(canvasToSave->patch.getCurrentFile()); + SettingsFile::getInstance()->addToRecentlyOpened(canvasToSave->patch.getCurrentURL()); pd->titleChanged(); nestedCallback(); } else { @@ -1064,12 +1067,9 @@ void Canvas::saveAs(std::function const& nestedCallback) if (result.getFullPathName().isNotEmpty()) { if (result.exists()) result.deleteFile(); - - if (!result.hasFileExtension("pd")) - result = result.getFullPathName() + ".pd"; - + patch.savePatch(resultURL); - SettingsFile::getInstance()->addToRecentlyOpened(result); + SettingsFile::getInstance()->addToRecentlyOpened(resultURL); pd->titleChanged(); } @@ -1111,7 +1111,7 @@ void Canvas::synchroniseSplitCanvas() void Canvas::performSynchronise() { static bool alreadyFlushed = false; - bool needsFlush = !alreadyFlushed; + bool const needsFlush = !alreadyFlushed; ScopedValueSetter flushGuard(alreadyFlushed, true); // By flushing twice, we can make sure that any message sent before this point will be dequeued if (needsFlush && !isGraph) @@ -1126,10 +1126,8 @@ void Canvas::performSynchronise() // Remove deleted objects for (int n = objects.size() - 1; n >= 0; n--) { - auto* object = objects[n]; - // If the object is showing it's initial editor, meaning no object was assigned yet, allow it to exist without pointing to an object - if (!object->getPointer() && !object->isInitialEditorShown()) { + if (auto* object = objects[n]; !object->getPointer() && !object->isInitialEditorShown()) { setSelected(object, false, false); objects.remove_at(n); } @@ -1166,14 +1164,15 @@ void Canvas::performSynchronise() object->toFront(false); if (object->gui && object->gui->getLabel()) object->gui->getLabel()->toFront(false); + if (object->gui) - object->gui->update(); + object->gui->updateProperties(); } } // Make sure objects have the same order std::ranges::sort(objects, - [&pdObjects](Object* first, Object* second) mutable { + [&pdObjects](Object const* first, Object const* second) mutable { return pdObjects.index_of(first->getPointer()) < pdObjects.index_of(second->getPointer()); }); @@ -1221,11 +1220,9 @@ void Canvas::performSynchronise() if (it == connections.end()) { connections.add(this, inlet, outlet, ptr); } else { - auto& c = **it; - // This is necessary to make resorting a subpatchers iolets work // And it can't hurt to check if the connection is valid anyway - if (c.inlet != inlet || c.outlet != outlet) { + if (auto& c = **it; c.inlet != inlet || c.outlet != outlet) { int const idx = connections.index_of(*it); connections.remove_one(*it); connections.insert(idx, this, inlet, outlet, ptr); @@ -1263,11 +1260,11 @@ void Canvas::updateDrawables() void Canvas::shiftKeyChanged(bool const isHeld) { shiftDown = isHeld; - + if (!isGraph) { SettingsFile::getInstance()->getValueTree().getChildWithName("Overlays").setProperty("alt_mode", altDown && shiftDown, nullptr); } - + if (!isHeld) return; @@ -1305,16 +1302,15 @@ void Canvas::shiftKeyChanged(bool const isHeld) if (inverted && selectedObjects.size() > 1) return; - t_outconnect* connection = nullptr; - auto selectedConnections = getSelectionOfType(); - if (selectedConnections.size() == 1) { + t_outconnect const* connection = nullptr; + if (auto selectedConnections = getSelectionOfType(); selectedConnections.size() == 1) { connection = selectedConnections[0]->getPointer(); } pd::Interface::shiftAutopatch(x.get(), inObj, inletIndex, outObj, outletIndex, selectedObjects, connection); } } - + synchronise(); } } @@ -1332,7 +1328,7 @@ void Canvas::middleMouseChanged(bool isHeld) void Canvas::altKeyChanged(bool const isHeld) { altDown = isHeld; - + if (!isGraph) { SettingsFile::getInstance()->getValueTree().getChildWithName("Overlays").setProperty("alt_mode", altDown && shiftDown, nullptr); } @@ -1428,10 +1424,8 @@ void Canvas::mouseDrag(MouseEvent const& e) } } - bool const objectIsBeingEdited = ObjectBase::isBeingEdited(); - // Ignore on graphs or when locked - if ((isGraph || locked == var(true) || commandLocked == var(true)) && !objectIsBeingEdited) { + if ((isGraph || locked == var(true) || commandLocked == var(true)) && !ObjectBase::isBeingEdited()) { bool hasToggled = false; // Behaviour for dragging over toggles, bang and radiogroup to toggle them @@ -1733,7 +1727,7 @@ void Canvas::focusLost(FocusChangeType cause) }); } -void Canvas::dragAndDropPaste(String const& patchString, Point mousePos, int const patchWidth, int const patchHeight, String const& name) +void Canvas::dragAndDropPaste(String const& patchString, Point const mousePos, int const patchWidth, int const patchHeight, String const& name) { locked = false; presentationMode = false; @@ -2017,9 +2011,7 @@ void Canvas::cycleSelection() return; } // Get the selected objects - auto selectedObjects = getSelectionOfType(); - - if (selectedObjects.size() == 1) { + if (auto selectedObjects = getSelectionOfType(); selectedObjects.size() == 1) { // Find the index of the currently selected object auto const currentIdx = objects.index_of(selectedObjects[0]); setSelected(selectedObjects[0], false); @@ -2032,9 +2024,7 @@ void Canvas::cycleSelection() } // Get the selected connections if no objects are selected - auto selectedConnections = getSelectionOfType(); - - if (selectedConnections.size() == 1) { + if (auto selectedConnections = getSelectionOfType(); selectedConnections.size() == 1) { // Find the index of the currently selected connection auto const currentIdx = connections.index_of(selectedConnections[0]); setSelected(selectedConnections[0], false); @@ -2070,7 +2060,7 @@ void Canvas::triggerizeSelection() } } - t_outconnect* connection = nullptr; + t_outconnect const* connection = nullptr; auto selectedConnections = getSelectionOfType(); if (selectedConnections.size() == 1) { connection = selectedConnections[0]->getPointer(); @@ -2236,9 +2226,8 @@ void Canvas::connectSelection() } } - t_outconnect* connection = nullptr; - auto selectedConnections = getSelectionOfType(); - if (selectedConnections.size() == 1) { + t_outconnect const* connection = nullptr; + if (auto selectedConnections = getSelectionOfType(); selectedConnections.size() == 1) { connection = selectedConnections[0]->getPointer(); } @@ -2316,7 +2305,7 @@ void Canvas::alignObjects(Align const alignment) // get the bounding box of all selected objects auto const selectedBounds = getBoundingBox(selectedObjects); - auto onMove = [this, selectedObjects](Point position) { + auto onMove = [this, selectedObjects](Point const position) { // Calculate the bounding box of all selected objects Rectangle totalSize; @@ -2389,7 +2378,7 @@ void Canvas::alignObjects(Align const alignment) case Align::HDistribute: { sortByXPos(selectedObjects); - auto onResize = [this, selectedObjects](Rectangle newBounds) { + auto onResize = [this, selectedObjects](Rectangle const newBounds) { int totalObjectsWidth = 0; for (auto const* obj : selectedObjects) { totalObjectsWidth += obj->getBounds().getWidth() - Object::doubleMargin; @@ -2419,7 +2408,7 @@ void Canvas::alignObjects(Align const alignment) case Align::VDistribute: { sortByYPos(selectedObjects); - auto onResize = [this, selectedObjects](Rectangle newBounds) { + auto onResize = [this, selectedObjects](Rectangle const newBounds) { int totalObjectsHeight = 0; for (auto const* obj : selectedObjects) { totalObjectsHeight += obj->getBounds().getHeight() - Object::doubleMargin; @@ -2516,7 +2505,7 @@ void Canvas::valueChanged(Value& v) auto y2 = static_cast(cnv->gl_screeny2); char buf[MAXPDSTRING]; - snprintf(buf, MAXPDSTRING - 1, ".x%lx", (unsigned long)cnv.get()); + snprintf(buf, MAXPDSTRING - 1, ".x%lx", reinterpret_cast(cnv.get())); pd->sendMessage(buf, "setbounds", { x1, y1, x2, y2 }); } repaint(); @@ -2529,7 +2518,7 @@ void Canvas::valueChanged(Value& v) auto y2 = static_cast(getValue(patchHeight) + y1); char buf[MAXPDSTRING]; - snprintf(buf, MAXPDSTRING - 1, ".x%lx", (unsigned long)cnv.get()); + snprintf(buf, MAXPDSTRING - 1, ".x%lx", reinterpret_cast(cnv.get())); pd->sendMessage(buf, "setbounds", { x1, y1, x2, y2 }); } repaint(); @@ -2676,7 +2665,7 @@ bool Canvas::setPanDragMode(bool const shouldPan) return false; } -bool Canvas::isPointOutsidePluginArea(Point point) const +bool Canvas::isPointOutsidePluginArea(Point const point) const { auto const borderWidth = getValue(patchWidth); auto const borderHeight = getValue(patchHeight); @@ -2692,7 +2681,7 @@ void Canvas::findLassoItemsInArea(Array>& itemsFound, R { auto const lassoBounds = area.withWidth(jmax(2, area.getWidth())).withHeight(jmax(2, area.getHeight())); - if(!altDown) { // Alt enable connection only mode + if (!altDown) { // Alt enable connection only mode for (auto* object : objects) { if (lassoBounds.intersects(object->getSelectableBounds())) { itemsFound.add(object); @@ -2729,9 +2718,13 @@ ObjectParameters& Canvas::getInspectorParameters() bool Canvas::panningModifierDown() const { + if(isGraph) + return false; + #if JUCE_IOS return OSUtils::ScrollTracker::isScrolling(); #endif + auto const& commandManager = editor->commandManager; // check the command manager for the keycode that is assigned to pan drag key auto const panDragKeycode = commandManager.getKeyMappings()->getKeyPressesAssignedToCommand(CommandIDs::PanDragKey).getFirst().getKeyCode(); @@ -2786,8 +2779,7 @@ void Canvas::receiveMessage(t_symbol* symbol, SmallArray const& atoms) return; if (atoms.size() >= 1) { - int const flag = atoms[0].getFloat(); - if (flag % 2 == 0) { + if (int const flag = atoms[0].getFloat(); flag % 2 == 0) { locked = true; } else { locked = false; @@ -2800,8 +2792,8 @@ void Canvas::receiveMessage(t_symbol* symbol, SmallArray const& atoms) if (atoms.size() >= 4) { auto const width = atoms[2].getFloat() - atoms[0].getFloat(); auto const height = atoms[3].getFloat() - atoms[1].getFloat(); - setValueExcludingListener(patchWidth, width, this); - setValueExcludingListener(patchHeight, height, this); + setValueExcludingListener(patchWidth, static_cast(width), this); + setValueExcludingListener(patchHeight, static_cast(height), this); repaint(); } @@ -2812,6 +2804,8 @@ void Canvas::receiveMessage(t_symbol* symbol, SmallArray const& atoms) synchroniseSplitCanvas(); break; } + default: + break; } } diff --git a/Source/Canvas.h b/Source/Canvas.h index 599273ab4e..c38039034e 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -234,9 +234,9 @@ class Canvas final : public Component bool needsSearchUpdate : 1 = false; bool altDown : 1 = false; bool shiftDown : 1 = false; - + Rectangle currentRenderArea; - + Value isGraphChild = SynchronousValue(var(false)); Value hideNameAndArgs = SynchronousValue(var(false)); Value xRange = SynchronousValue(); diff --git a/Source/CanvasViewport.h b/Source/CanvasViewport.h index c1e100efb8..873be2c454 100644 --- a/Source/CanvasViewport.h +++ b/Source/CanvasViewport.h @@ -22,27 +22,27 @@ using namespace gl; #include "Utility/SettingsFile.h" -class Minimap : public Component +class Minimap final : public Component , public Timer , public AsyncUpdater { public: - Minimap(Canvas* canvas) + explicit Minimap(Canvas* canvas) : cnv(canvas) { } void handleAsyncUpdate() override { - auto area = visibleArea / getValue(cnv->zoomScale); + auto const area = visibleArea / getValue(cnv->zoomScale); bool renderMinimap = cnv->objects.not_empty(); - for (auto* obj : cnv->objects) { + for (auto const* obj : cnv->objects) { if (obj->getBounds().intersects(area)) { renderMinimap = false; break; } } - auto showMinimap = SettingsFile::getInstance()->getProperty("show_minimap"); + auto const showMinimap = SettingsFile::getInstance()->getProperty("show_minimap"); float fadedIn = 0.0f; float fadedOut = 0.0f; if (showMinimap == 1) { @@ -71,7 +71,7 @@ class Minimap : public Component } } - void updateMinimap(Rectangle area) + void updateMinimap(Rectangle const area) { if (isMouseDown || area.isEmpty()) return; @@ -91,8 +91,8 @@ class Minimap : public Component auto const zoom = getValue(cnv->zoomScale); b.viewBounds = cnv->viewport->getViewArea() / zoom; - Rectangle allObjectBounds = Rectangle(cnv->canvasOrigin.x, cnv->canvasOrigin.y, b.viewBounds.getWidth(), b.viewBounds.getHeight()); - for (auto* object : cnv->objects) { + auto allObjectBounds = Rectangle(cnv->canvasOrigin.x, cnv->canvasOrigin.y, b.viewBounds.getWidth(), b.viewBounds.getHeight()); + for (auto const* object : cnv->objects) { allObjectBounds = allObjectBounds.getUnion(object->getBounds()); } @@ -118,8 +118,8 @@ class Minimap : public Component float const x = cnv->viewport->getViewWidth() - (width + 10); float const y = cnv->viewport->getViewHeight() - (height + 10); - auto canvasBackground = cnv->findColour(PlugDataColour::canvasBackgroundColourId); - auto mapBackground = canvasBackground.contrasting(0.5f); + auto const canvasBackground = cnv->findColour(PlugDataColour::canvasBackgroundColourId); + auto const mapBackground = canvasBackground.contrasting(0.5f); // draw background nvgFillColor(nvg, NVGComponent::convertColour(mapBackground.withAlpha(0.4f))); @@ -128,7 +128,7 @@ class Minimap : public Component nvgFillColor(nvg, NVGComponent::convertColour(mapBackground.withAlpha(0.8f))); // draw objects - for (auto* object : cnv->objects) { + for (auto const* object : cnv->objects) { auto b = (object->getBounds().reduced(Object::margin).translated(map.offsetX, map.offsetY) - cnv->canvasOrigin).toFloat() * map.scale; nvgFillRoundedRect(nvg, x + b.getX(), y + b.getY(), b.getWidth(), b.getHeight(), Corners::objectCornerRadius * map.scale); } @@ -155,13 +155,13 @@ class Minimap : public Component downPosition = cnv->viewport->getViewPosition(); auto map = getMapBounds(); - auto realViewBounds = Rectangle((map.offsetX + map.viewBounds.getX() - cnv->canvasOrigin.x) * map.scale, (map.offsetY + map.viewBounds.getY() - cnv->canvasOrigin.y) * map.scale, map.viewBounds.getWidth() * map.scale, map.viewBounds.getHeight() * map.scale); + auto const realViewBounds = Rectangle((map.offsetX + map.viewBounds.getX() - cnv->canvasOrigin.x) * map.scale, (map.offsetY + map.viewBounds.getY() - cnv->canvasOrigin.y) * map.scale, map.viewBounds.getWidth() * map.scale, map.viewBounds.getHeight() * map.scale); isMouseDown = realViewBounds.contains(e.getMouseDownPosition()); } void mouseUp(MouseEvent const& e) override { - auto viewBounds = cnv->viewport->getViewArea(); + auto const viewBounds = cnv->viewport->getViewArea(); downPosition = viewBounds.getPosition(); isMouseDown = false; updateMinimap(viewBounds); @@ -173,7 +173,7 @@ class Minimap : public Component auto map = getMapBounds(); if (isMouseDown) { - cnv->viewport->setViewPosition(downPosition + (e.getOffsetFromDragStart() / map.scale)); + cnv->viewport->setViewPosition(downPosition + e.getOffsetFromDragStart() / map.scale); } } @@ -488,7 +488,9 @@ class CanvasViewport final : public Viewport addAndMakeVisible(vbar); addAndMakeVisible(hbar); - setCachedComponentImage(new NVGSurface::InvalidationListener(editor->nvgSurface, this)); + setCachedComponentImage(new NVGSurface::InvalidationListener(editor->nvgSurface, this, [this]{ + return editor->getTabComponent().getVisibleCanvases().contains(this->cnv); + })); lookAndFeelChanged(); } @@ -545,6 +547,7 @@ class CanvasViewport final : public Viewport lerpAnimation += animationSpeed; break; } + default: break; } } @@ -593,7 +596,7 @@ class CanvasViewport final : public Viewport } void mouseWheelMove(MouseEvent const& e, MouseWheelDetails const& wheel) override - { + { // Check event time to filter out duplicate events // This is a workaround for a bug in JUCE that can cause mouse events to be duplicated when an object has a MouseListener on its parent if (e.eventTime == lastScrollTime) @@ -735,13 +738,6 @@ class CanvasViewport final : public Viewport auto const offset = currentCentre - newCentre; setViewPosition(getViewPosition() + offset); - - // This fixes some graphical glitches on macOS, but causes terrible glitches anywhere else -#if JUCE_MAC - if(!scaleChanged) { - editor->nvgSurface.renderAll(); - } -#endif } // Never respond to arrow keys, they have a different meaning diff --git a/Source/Components/BouncingViewport.h b/Source/Components/BouncingViewport.h index 86a1beb31b..26468b8946 100644 --- a/Source/Components/BouncingViewport.h +++ b/Source/Components/BouncingViewport.h @@ -18,16 +18,30 @@ class BouncingViewportAttachment final : public MouseListener } ~BouncingViewportAttachment() override = default; + +#if JUCE_IOS + void mouseDown(MouseEvent const& e) override + { + OSUtils::ScrollTracker::setAllowOneFingerScroll(true); + } + + void mouseUp(MouseEvent const& e) override + { + if(!OSUtils::ScrollTracker::isScrolling()) + { + OSUtils::ScrollTracker::setAllowOneFingerScroll(false); + } + } +#endif void mouseWheelMove(MouseEvent const& e, MouseWheelDetails const& wheel) override { if (!isBounceable) return; - // Protect against receiving the same even twice if (e.eventTime == lastScrollTime) return; - + lastScrollTime = e.eventTime; auto const deltaY = rescaleMouseWheelDistance(wheel.deltaY); diff --git a/Source/Components/Buttons.cpp b/Source/Components/Buttons.cpp index cc8b725390..2ba4499dc9 100644 --- a/Source/Components/Buttons.cpp +++ b/Source/Components/Buttons.cpp @@ -29,24 +29,23 @@ String MainToolbarButton::getTooltip() return setTooltip; } - void MainToolbarButton::paint(Graphics& g) { bool const active = isOver() || isDown() || getToggleState(); - + auto constexpr cornerSize = Corners::defaultCornerRadius; auto const backgroundColour = active ? findColour(PlugDataColour::toolbarHoverColourId) : Colours::transparentBlack; auto bounds = getLocalBounds().reduced(3, 4).toFloat(); - + g.setColour(backgroundColour); g.fillRoundedRectangle(bounds, cornerSize); - + auto const textColour = findColour(PlugDataColour::toolbarTextColourId).withMultipliedAlpha(isEnabled() ? 1.0f : 0.5f); - + #if JUCE_MAC bounds = bounds.withTrimmedBottom(2); #endif - + g.setFont(Fonts::getIconFont().withHeight(getHeight() / 2.7)); g.setColour(textColour); g.drawText(getButtonText(), bounds, Justification::centred); @@ -88,28 +87,28 @@ void ToolbarRadioButton::paint(Graphics& g) { bool const mouseOver = isOver(); bool const active = mouseOver || isDown() || getToggleState(); - + auto const flatOnLeft = isConnectedOnLeft(); auto const flatOnRight = isConnectedOnRight(); auto const flatOnTop = isConnectedOnTop(); auto const flatOnBottom = isConnectedOnBottom(); - + auto const backgroundColour = findColour(active ? PlugDataColour::toolbarHoverColourId : PlugDataColour::toolbarBackgroundColourId).contrasting(mouseOver && !getToggleState() ? 0.0f : 0.035f); - + auto bounds = getLocalBounds().toFloat(); bounds = bounds.reduced(0.0f, bounds.proportionOfHeight(0.17f)); - + g.setColour(backgroundColour); Path p; p.addRoundedRectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), Corners::defaultCornerRadius, Corners::defaultCornerRadius, - !(flatOnLeft || flatOnTop), - !(flatOnRight || flatOnTop), - !(flatOnLeft || flatOnBottom), - !(flatOnRight || flatOnBottom)); + !(flatOnLeft || flatOnTop), + !(flatOnRight || flatOnTop), + !(flatOnLeft || flatOnBottom), + !(flatOnRight || flatOnBottom)); g.fillPath(p); - + auto const textColour = findColour(PlugDataColour::toolbarTextColourId).withMultipliedAlpha(isEnabled() ? 1.0f : 0.5f); - + g.setFont(Fonts::getIconFont().withHeight(getHeight() / 2.8)); g.setColour(textColour); g.drawText(getButtonText(), getLocalBounds(), Justification::centred); @@ -138,13 +137,11 @@ void ToolbarRadioButton::mouseExit(MouseEvent const& e) } #endif - - -bool SmallIconButton::hitTest(int x, int y) +bool SmallIconButton::hitTest(int const x, int const y) { if (getLocalBounds().reduced(2).contains(x, y)) return true; - + return false; } @@ -161,7 +158,7 @@ void SmallIconButton::mouseExit(MouseEvent const& e) void SmallIconButton::paint(Graphics& g) { auto colour = findColour(PlugDataColour::toolbarTextColourId); - + if (!isEnabled()) { colour = Colours::grey; } else if (getToggleState()) { @@ -169,14 +166,15 @@ void SmallIconButton::paint(Graphics& g) } else if (isMouseOver()) { colour = findColour(PlugDataColour::toolbarTextColourId).brighter(0.8f); } - + Fonts::drawIcon(g, getButtonText(), getLocalBounds(), colour, 12); } - WidePanelButton::WidePanelButton(String const& icon, int const iconSize) -: icon(icon) -, iconSize(iconSize) { }; + : icon(icon) + , iconSize(iconSize) +{ +} void WidePanelButton::mouseEnter(MouseEvent const& e) { @@ -194,31 +192,31 @@ void WidePanelButton::paint(Graphics& g) bool const flatOnRight = isConnectedOnRight(); bool const flatOnTop = isConnectedOnTop(); bool const flatOnBottom = isConnectedOnBottom(); - + float const width = getWidth() - 1.0f; float const height = getHeight() - 1.0f; - + constexpr float cornerSize = Corners::largeCornerRadius; Path outline; outline.addRoundedRectangle(0.5f, 0.5f, width, height, cornerSize, cornerSize, - !(flatOnLeft || flatOnTop), - !(flatOnRight || flatOnTop), - !(flatOnLeft || flatOnBottom), - !(flatOnRight || flatOnBottom)); - + !(flatOnLeft || flatOnTop), + !(flatOnRight || flatOnTop), + !(flatOnLeft || flatOnBottom), + !(flatOnRight || flatOnBottom)); + g.setColour(findColour(isMouseOver() ? PlugDataColour::panelActiveBackgroundColourId : PlugDataColour::panelForegroundColourId)); g.fillPath(outline); - + g.setColour(findColour(PlugDataColour::outlineColourId)); g.strokePath(outline, PathStrokeType(1)); - + Fonts::drawText(g, getButtonText(), getLocalBounds().reduced(12, 2), findColour(PlugDataColour::panelTextColourId), 15); Fonts::drawIcon(g, icon, getLocalBounds().reduced(12, 2).removeFromRight(24), findColour(PlugDataColour::panelTextColourId), iconSize); } SettingsToolbarButton::SettingsToolbarButton(String iconToUse, String textToShow) -: icon(std::move(iconToUse)) -, text(std::move(textToShow)) + : icon(std::move(iconToUse)) + , text(std::move(textToShow)) { setClickingTogglesState(true); setConnectedEdges(12); @@ -227,20 +225,20 @@ SettingsToolbarButton::SettingsToolbarButton(String iconToUse, String textToShow void SettingsToolbarButton::paint(Graphics& g) { auto const b = getLocalBounds().reduced(2.0f, 4.0f); - + if (isMouseOver() || getToggleState()) { auto background = findColour(PlugDataColour::toolbarHoverColourId); if (getToggleState()) background = background.darker(0.025f); - + g.setColour(background); g.fillRoundedRectangle(b.toFloat(), Corners::defaultCornerRadius); } - + auto const textColour = findColour(PlugDataColour::toolbarTextColourId); auto const boldFont = Fonts::getBoldFont().withHeight(13.5f); auto const iconFont = Fonts::getIconFont().withHeight(13.5f); - + AttributedString attrStr; attrStr.setJustification(Justification::centred); attrStr.append(icon, iconFont, textColour); @@ -248,9 +246,8 @@ void SettingsToolbarButton::paint(Graphics& g) attrStr.draw(g, b.toFloat()); } - ReorderButton::ReorderButton() -: SmallIconButton(Icons::Reorder) + : SmallIconButton(Icons::Reorder) { setSize(25, 25); } diff --git a/Source/Components/Buttons.h b/Source/Components/Buttons.h index d8cd3250ad..3199a3d77c 100644 --- a/Source/Components/Buttons.h +++ b/Source/Components/Buttons.h @@ -13,8 +13,8 @@ class MainToolbarButton final : public TextButton { public: using TextButton::TextButton; - bool isUndo:1 = false; - bool isRedo:1 = false; + bool isUndo : 1 = false; + bool isRedo : 1 = false; String getTooltip() override; @@ -22,7 +22,7 @@ class MainToolbarButton final : public TextButton { // On macOS, we need to make sure that dragging any of these buttons doesn't drag the whole titlebar #if JUCE_MAC - ~MainToolbarButton(); + ~MainToolbarButton() override; void mouseEnter(MouseEvent const& e) override; void mouseExit(MouseEvent const& e) override; #endif @@ -57,7 +57,7 @@ class WidePanelButton final : public TextButton { int iconSize; public: - explicit WidePanelButton(String const& icon, int const iconSize = 13); + explicit WidePanelButton(String const& icon, int iconSize = 13); void mouseEnter(MouseEvent const& e) override; diff --git a/Source/Components/CheckedTooltip.h b/Source/Components/CheckedTooltip.h index 5548507545..8cfd90d861 100644 --- a/Source/Components/CheckedTooltip.h +++ b/Source/Components/CheckedTooltip.h @@ -12,11 +12,10 @@ class CheckedTooltip final : public TooltipWindow { public: - explicit CheckedTooltip( - Component* target, std::function getScaleFactor, + explicit CheckedTooltip(std::function getScaleFactor, std::function checkTooltip, int const timeout = 500) - : TooltipWindow(target, timeout) + : TooltipWindow(nullptr, timeout) , checker(std::move(checkTooltip)) , getScaleFactor(getScaleFactor) { @@ -31,7 +30,7 @@ class CheckedTooltip final : public TooltipWindow { } TooltipWindow::setVisible(shouldBeVisible); } - + float getDesktopScaleFactor() const override { return getScaleFactor(); diff --git a/Source/Components/ColourPicker.h b/Source/Components/ColourPicker.h index 9b435f8038..0985bee4e3 100644 --- a/Source/Components/ColourPicker.h +++ b/Source/Components/ColourPicker.h @@ -51,7 +51,7 @@ class Eyedropper final : public Timer return false; } - void setROI(Image& image, Point position) + void setROI(Image const& image, Point const position) { pixelImage = image.getClippedImage(Rectangle(0, 0, 11, 11).withCentre(position)).rescaled(getWidth() - 8 * 2, getHeight() - 8 * 2, Graphics::ResamplingQuality::lowResamplingQuality); colour = image.getPixelAt(position.x, position.y); @@ -192,7 +192,8 @@ class ColourPicker final : public Component { ColourPicker* colourPicker; }; - struct ColourComponentSlider final : public Slider { + class ColourComponentSlider final : public Slider { + public: explicit ColourComponentSlider(String const& name) : Slider(name) { @@ -212,7 +213,7 @@ class ColourPicker final : public Component { }; public: - void show(PluginEditor* editorComponent, Component* topLevelComponent, bool const onlySendCallbackOnClose, Colour const currentColour, Rectangle bounds, std::function const& colourCallback) + void show(PluginEditor* editorComponent, Component* topLevelComponent, bool const onlySendCallbackOnClose, Colour const currentColour, Rectangle const bounds, std::function const& colourCallback) { callback = colourCallback; onlyCallBackOnClose = onlySendCallbackOnClose; @@ -225,11 +226,10 @@ class ColourPicker final : public Component { // we need to put the selector into a holder, as launchAsynchronously will delete the component when its done auto selectorHolder = std::make_unique(this); - if(calloutBox) - { + if (calloutBox) { calloutBox->dismiss(); } - + calloutBox = &CallOutBox::launchAsynchronously(std::move(selectorHolder), bounds, nullptr); } @@ -376,14 +376,13 @@ class ColourPicker final : public Component { } } - auto getHS() + auto getHS() const { - struct HS - { + struct HS { float hue, sat; }; - - return HS{ h, s }; + + return HS { h, s }; } void setHS(float newH, float newS) @@ -617,9 +616,9 @@ class ColourPicker final : public Component { Image colourWheelHSV; Rectangle imageBounds; - struct ColourSpaceMarker final : public Component { + class ColourSpaceMarker final : public Component { ColourPicker& owner; - + public: explicit ColourSpaceMarker(ColourPicker& parent) : owner(parent) { @@ -722,9 +721,9 @@ class ColourPicker final : public Component { float& v; int const edge; - struct BrightnessSelectorMarker final : public Component { + class BrightnessSelectorMarker final : public Component { ColourPicker& owner; - + public: explicit BrightnessSelectorMarker(ColourPicker& parent) : owner(parent) { @@ -767,7 +766,7 @@ class ColourPicker final : public Component { Eyedropper::EyedropperButton showEyedropper; Eyedropper eyedropper; - + SafePointer calloutBox = nullptr; Component* _topLevelComponent; diff --git a/Source/Components/ConnectionMessageDisplay.h b/Source/Components/ConnectionMessageDisplay.h index 3a69f27f3a..0c15f91f11 100644 --- a/Source/Components/ConnectionMessageDisplay.h +++ b/Source/Components/ConnectionMessageDisplay.h @@ -24,7 +24,9 @@ class ConnectionMessageDisplay final public: explicit ConnectionMessageDisplay(PluginEditor* parentEditor) - : editor(parentEditor), pd(parentEditor->pd), connectionPtr(nullptr) + : editor(parentEditor) + , pd(parentEditor->pd) + , connectionPtr(nullptr) { setSize(36, 36); setVisible(false); @@ -33,7 +35,7 @@ class ConnectionMessageDisplay final ~ConnectionMessageDisplay() override { - if(connectionPtr) { + if (connectionPtr) { pd->unregisterWeakReference(connectionPtr, &weakRef); } editor->pd->connectionListener = nullptr; @@ -44,8 +46,13 @@ class ConnectionMessageDisplay final return false; } + float getDesktopScaleFactor() const override + { + return getApproximateScaleFactorForComponent(editor) * Desktop::getInstance().getGlobalScaleFactor(); + } + // Activate the current connection info display overlay, to hide give it a nullptr - void setConnection(Connection* connection, Point screenPosition = { 0, 0 }) + void setConnection(Connection* connection, Point const screenPosition = { 0, 0 }) { // multiple events can hide the display, so we don't need to do anything // if this object has already been set to null @@ -88,7 +95,7 @@ class ConnectionMessageDisplay final updateTextString(true); } } else { - if(connectionPtr) { + if (connectionPtr) { pd->unregisterWeakReference(connectionPtr, &weakRef); } hideDisplay(); @@ -103,27 +110,25 @@ class ConnectionMessageDisplay final void updateSignalData() { - const ScopedTryLock sl (pd->audioLock); - + ScopedTryLock const sl(pd->audioLock); + if (sl.isLocked() && connectionPtr != nullptr && weakRef) { if (auto const* signal = outconnect_get_signal(connectionPtr.load())) { auto const numChannels = std::min(signal->s_nchans, 8); auto const numSamples = signal->s_n; auto const blockSize = std::min(pdBlockSize, numSamples); auto const blocks = numSamples / blockSize; - - if(numChannels > 0) { + + if (numChannels > 0) { auto* samples = signal->s_vec; if (!samples) return; - - for(int block = 0; block < blocks; block++) - { - StackArray output; - for(int ch = 0; ch < numChannels; ch++) - { + + for (int block = 0; block < blocks; block++) { + StackArray output = {}; + for (int ch = 0; ch < numChannels; ch++) { auto* start = samples + (ch * numSamples + block * blockSize); - auto* destination = output.data() + (ch * blockSize); + auto* destination = output.data() + ch * blockSize; std::copy_n(start, blockSize, destination); } sampleQueue.try_enqueue(SignalBlock(std::move(output), numChannels, blockSize)); @@ -140,7 +145,7 @@ class ConnectionMessageDisplay final if (connectionPtr == nullptr || !activeConnection) return; - + auto haveMessage = true; auto textString = activeConnection->getMessageFormated(); @@ -203,8 +208,13 @@ class ConnectionMessageDisplay final // make sure the proposed position is inside the editor area proposedPosition.setPosition(mousePosition.translated(0, -getHeight())); constrainedBounds = proposedPosition.constrainedWithin(editor->getScreenBounds()); - if (getBounds() != constrainedBounds) + if (getBounds() != constrainedBounds) { +#if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD + setBounds(constrainedBounds.withPosition(constrainedBounds.getPosition() / getApproximateScaleFactorForComponent(editor))); +#else setBounds(constrainedBounds); +#endif + } } void updateSignalGraph() @@ -317,14 +327,12 @@ class ConnectionMessageDisplay final g.drawText("0.000", textBounds.toNearestInt(), Justification::centred); continue; } - - - if(!std::isfinite(peakAmplitude) || !std::isfinite(valleyAmplitude)) - { + + if (!std::isfinite(peakAmplitude) || !std::isfinite(valleyAmplitude)) { peakAmplitude = 0; valleyAmplitude = 1; } - + while (peakAmplitude < valleyAmplitude || approximatelyEqual(peakAmplitude, valleyAmplitude)) { peakAmplitude += 0.001f; valleyAmplitude -= 0.001f; @@ -415,11 +423,11 @@ class ConnectionMessageDisplay final SmallArray messageItemsWithFormat; pd::Instance* pd; - + pd_weak_reference weakRef = false; AtomicValue connectionPtr; SafePointer activeConnection; - + int mouseDelay = 500; Point mousePosition; StringArray lastTextString; @@ -430,12 +438,15 @@ class ConnectionMessageDisplay final struct SignalBlock { SignalBlock() - : numChannels(0), numSamples(0) + : numChannels(0) + , numSamples(0) { } SignalBlock(StackArray&& input, int const channels, int const samples) - : samples(input), numChannels(channels), numSamples(samples) + : samples(input) + , numChannels(channels) + , numSamples(samples) { } diff --git a/Source/Components/DraggableNumber.cpp b/Source/Components/DraggableNumber.cpp index 1e3e635ae8..681e2690b4 100644 --- a/Source/Components/DraggableNumber.cpp +++ b/Source/Components/DraggableNumber.cpp @@ -13,7 +13,6 @@ #include "DraggableNumber.h" - DraggableNumber::DraggableNumber(bool const integerDrag) : dragMode(integerDrag ? Integer : Regular) { @@ -47,16 +46,15 @@ void DraggableNumber::editorShown(TextEditor& editor) onTextChange(); }; // Limits of our tabular numbers font, to keep it portable - editor.setInputRestrictions(0, "!\"#$%&'()*+,-./0123456789:;<=>[\\]^_`{|}~ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); + editor.setInputRestrictions(0, "?@!\"#$%&'()*+,-./0123456789:;<=>[\\]^_`{|}~ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); editor.setJustification(Justification::centredLeft); } void DraggableNumber::focusGained(FocusChangeType const cause) { if (editableOnSingleClick - && isEnabled() - && cause == focusChangedByTabKey) - { + && isEnabled() + && cause == focusChangedByTabKey) { showEditor(); } @@ -65,27 +63,27 @@ void DraggableNumber::focusGained(FocusChangeType const cause) void DraggableNumber::focusLost(FocusChangeType const cause) { - if(editor) textEditorTextChanged (*editor); - onInteraction(false); + if (editor) + textEditorTextChanged(*editor); } -void DraggableNumber::setMinimumHorizontalScale(float newScale) +void DraggableNumber::setMinimumHorizontalScale(float const newScale) { minimumHorizontalScale = newScale; } -void DraggableNumber::setText(String const& newText, NotificationType notification) +void DraggableNumber::setText(String const& newText, NotificationType const notification) { hideEditor(true); currentValue = newText; - - if(!currentValue.contains(".") && dragMode != Integer) + + if (!currentValue.contains(".") && dragMode != Integer) currentValue += "."; - + repaint(); - if(notification == sendNotification) { + if (notification == sendNotification) { onTextChange(); } } @@ -95,20 +93,19 @@ TextEditor* DraggableNumber::getCurrentTextEditor() return editor.get(); } -bool DraggableNumber::isBeingEdited() +bool DraggableNumber::isBeingEdited() const { return editor != nullptr; } -void DraggableNumber::setBorderSize(BorderSize newBorder) +void DraggableNumber::setBorderSize(BorderSize const newBorder) { border = newBorder; } String DraggableNumber::getText() { - if(editor) - { + if (editor) { return editor->getText(); } @@ -150,7 +147,7 @@ void DraggableNumber::setLogarithmicHeight(double const logHeight) logarithmicHeight = logHeight; } -void DraggableNumber::setPrecision(int precision) +void DraggableNumber::setPrecision(int const precision) { maxPrecision = precision; } @@ -161,35 +158,34 @@ void DraggableNumber::setShowEllipsesIfTooLong(bool const shouldShowEllipses) showEllipses = shouldShowEllipses; } -static void copyColourIfSpecified (DraggableNumber& l, TextEditor& ed, int colourID, int targetColourID) +static void copyColourIfSpecified(DraggableNumber const& l, TextEditor& ed, int const colourID, int const targetColourID) { - if (l.isColourSpecified (colourID) || l.getLookAndFeel().isColourSpecified (colourID)) - ed.setColour (targetColourID, l.findColour (colourID)); + if (l.isColourSpecified(colourID) || l.getLookAndFeel().isColourSpecified(colourID)) + ed.setColour(targetColourID, l.findColour(colourID)); } void DraggableNumber::showEditor() { - if (!editor) - { - editor.reset (new TextEditor()); - copyAllExplicitColoursTo (*editor); - copyColourIfSpecified (*this, *editor, Label::textWhenEditingColourId, TextEditor::textColourId); - copyColourIfSpecified (*this, *editor, Label::backgroundWhenEditingColourId, TextEditor::backgroundColourId); - copyColourIfSpecified (*this, *editor, Label::outlineWhenEditingColourId, TextEditor::focusedOutlineColourId); + if (!editor) { + editor.reset(new TextEditor()); + copyAllExplicitColoursTo(*editor); + copyColourIfSpecified(*this, *editor, Label::textWhenEditingColourId, TextEditor::textColourId); + copyColourIfSpecified(*this, *editor, Label::backgroundWhenEditingColourId, TextEditor::backgroundColourId); + copyColourIfSpecified(*this, *editor, Label::outlineWhenEditingColourId, TextEditor::focusedOutlineColourId); - editor->setSize (10, 10); + editor->setSize(10, 10); editor->setBorder(border); editor->setFont(font); - addAndMakeVisible (editor.get()); - editor->setText (currentValue, false); - editor->addListener (this); + addAndMakeVisible(editor.get()); + editor->setText(currentValue, false); + editor->addListener(this); editor->grabKeyboardFocus(); if (editor == nullptr) // may be deleted by a callback return; editor->setColour(TextEditor::backgroundColourId, Colours::transparentBlack); - editor->setHighlightedRegion (Range (0, currentValue.length())); + editor->setHighlightedRegion(Range(0, currentValue.length())); resized(); repaint(); @@ -197,7 +193,7 @@ void DraggableNumber::showEditor() editorShown(*editor); onEditorShow(); - enterModalState (false); + enterModalState(false); editor->grabKeyboardFocus(); } } @@ -205,15 +201,14 @@ void DraggableNumber::showEditor() void DraggableNumber::resized() { if (editor != nullptr) - editor->setBounds (getLocalBounds()); + editor->setBounds(getLocalBounds()); } -bool DraggableNumber::updateFromTextEditorContents (TextEditor& ed) +bool DraggableNumber::updateFromTextEditorContents(TextEditor const& ed) { auto newText = ed.getText(); - if (currentValue != newText) - { + if (currentValue != newText) { currentValue = newText; repaint(); return true; @@ -222,17 +217,16 @@ bool DraggableNumber::updateFromTextEditorContents (TextEditor& ed) return false; } -void DraggableNumber::hideEditor (bool discardCurrentEditorContents) +void DraggableNumber::hideEditor(bool const discardCurrentEditorContents) { - if (editor != nullptr) - { - WeakReference deletionChecker (this); + if (editor != nullptr) { + WeakReference const deletionChecker(this); std::unique_ptr outgoingEditor; - std::swap (outgoingEditor, editor); + std::swap(outgoingEditor, editor); - if(!discardCurrentEditorContents) { + if (!discardCurrentEditorContents) { decimalDrag = 0; - updateFromTextEditorContents (*outgoingEditor); + updateFromTextEditorContents(*outgoingEditor); } outgoingEditor.reset(); @@ -240,6 +234,7 @@ void DraggableNumber::hideEditor (bool discardCurrentEditorContents) if (deletionChecker != nullptr) { repaint(); onEditorHide(); + onInteraction(false); exitModalState(0); } } @@ -247,12 +242,11 @@ void DraggableNumber::hideEditor (bool discardCurrentEditorContents) void DraggableNumber::inputAttemptWhenModal() { - if (editor != nullptr) - { + if (editor != nullptr) { if (handleFocusLossManually) - textEditorEscapeKeyPressed (*editor); + textEditorEscapeKeyPressed(*editor); else - textEditorReturnKeyPressed (*editor); + textEditorReturnKeyPressed(*editor); } } @@ -287,7 +281,7 @@ bool DraggableNumber::keyPressed(KeyPress const& key) return false; } -void DraggableNumber::setValue(double newValue, NotificationType const notification, bool clip) +void DraggableNumber::setValue(double newValue, NotificationType const notification, bool const clip) { wasReset = false; @@ -375,25 +369,23 @@ void DraggableNumber::setDragMode(DragMode const newDragMode) dragMode = newDragMode; } -Rectangle DraggableNumber::getDraggedNumberBounds(int dragPosition) +Rectangle DraggableNumber::getDraggedNumberBounds(int const dragPosition) const { auto const textArea = border.subtractedFrom(getLocalBounds()); auto const text = dragMode == Integer ? currentValue.upToFirstOccurrenceOf(".", false, false) : String(currentValue.getDoubleValue(), maxPrecision); - auto value = currentValue.contains(".") ? currentValue : currentValue + "."; + auto const value = currentValue.contains(".") ? currentValue : currentValue + "."; auto const numZeroes = maxPrecision - (value.length() - value.indexOf(".")) + 1; - auto fullNumber = value + String::repeatedString("0", numZeroes); - + auto const fullNumber = value + String::repeatedString("0", numZeroes); + GlyphArrangement glyphs; glyphs.addFittedText(font, fullNumber, textArea.getX(), 0., 99999, getHeight(), 1, 1.0f); - if(dragPosition == 0) - { + if (dragPosition == 0) { return glyphs.getBoundingBox(0, fullNumber.indexOf("."), true); } - else { - return glyphs.getGlyph(fullNumber.indexOf(".") + dragPosition).getBounds(); - } + + return glyphs.getGlyph(fullNumber.indexOf(".") + dragPosition).getBounds(); } int DraggableNumber::getDecimalAtPosition(int const x, Rectangle* position) const @@ -417,10 +409,10 @@ int DraggableNumber::getDecimalAtPosition(int const x, Rectangle* positio return -1; } - auto value = currentValue.contains(".") ? currentValue : currentValue + "."; + auto const value = currentValue.contains(".") ? currentValue : currentValue + "."; auto const numZeroes = maxPrecision - (value.length() - value.indexOf(".")) + 1; - auto fullNumber = value + String::repeatedString("0", numZeroes); - + auto const fullNumber = value + String::repeatedString("0", numZeroes); + GlyphArrangement glyphs; glyphs.addFittedText(font, fullNumber, textArea.getX(), 0., 99999, getHeight(), 1, 1.0f); int draggedDecimal = -1; @@ -482,7 +474,7 @@ void DraggableNumber::render(NVGcontext* nvg) auto const numDecimals = numberText.fromFirstOccurrenceOf(".", false, false).length(); auto numberTextLength = CachedFontStringWidth::get()->calculateSingleLineWidth(font, numberText); - for (int i = 0; i < std::min(hoveredDecimal - numDecimals, (maxPrecision + 1) - numDecimals); ++i) + for (int i = 0; i < std::min(hoveredDecimal - numDecimals, maxPrecision + 1 - numDecimals); ++i) extraNumberText += "0"; // If show ellipses is false, only show ">" when integers are too large to fit @@ -546,7 +538,7 @@ void DraggableNumber::paint(Graphics& g) auto const numDecimals = numberText.fromFirstOccurrenceOf(".", false, false).length(); auto numberTextLength = CachedFontStringWidth::get()->calculateSingleLineWidth(font, numberText); - for (int i = 0; i < std::min(hoveredDecimal - numDecimals, (maxPrecision + 1) - numDecimals); ++i) + for (int i = 0; i < std::min(hoveredDecimal - numDecimals, maxPrecision + 1 - numDecimals); ++i) extraNumberText += "0"; // If show ellipses is false, only show ">" when integers are too large to fit @@ -630,7 +622,6 @@ void DraggableNumber::mouseDrag(MouseEvent const& e) setValue(newValue); } - hoveredDecimalPosition = getDraggedNumberBounds(decimalDrag); repaint(); } @@ -653,7 +644,7 @@ void DraggableNumber::mouseUp(MouseEvent const& e) if (editor) return; - onInteraction(hasKeyboardFocus(false)); + onInteraction(false); repaint(); @@ -670,9 +661,8 @@ void DraggableNumber::mouseUp(MouseEvent const& e) dragEnd(); if (!e.mouseWasDraggedSinceMouseDown()) { - if (editableOnSingleClick && isEnabled() && contains (e.getPosition()) - && ! (e.mouseWasDraggedSinceMouseDown() || e.mods.isPopupMenu())) - { + if (editableOnSingleClick && isEnabled() && contains(e.getPosition()) + && !(e.mouseWasDraggedSinceMouseDown() || e.mods.isPopupMenu())) { showEditor(); } } diff --git a/Source/Components/DraggableNumber.h b/Source/Components/DraggableNumber.h index edb320c7af..92bde5df1e 100644 --- a/Source/Components/DraggableNumber.h +++ b/Source/Components/DraggableNumber.h @@ -7,8 +7,8 @@ struct NVGcontext; class NVGGraphicsContext; -class DraggableNumber : public Component, public TextEditor::Listener -{ +class DraggableNumber : public Component + , public TextEditor::Listener { public: enum DragMode { Regular, @@ -50,9 +50,9 @@ class DraggableNumber : public Component, public TextEditor::Listener std::unique_ptr nvgCtx; public: - std::function onTextChange = [](){}; - std::function onEditorShow = [](){}; - std::function onEditorHide = [](){}; + std::function onTextChange = [] { }; + std::function onEditorShow = [] { }; + std::function onEditorHide = [] { }; std::function onValueChange = [](double) { }; std::function onReturnKey = [](double) { }; @@ -61,9 +61,9 @@ class DraggableNumber : public Component, public TextEditor::Listener std::function onInteraction = [](bool) { }; - explicit DraggableNumber(bool const integerDrag); - - ~DraggableNumber(); + explicit DraggableNumber(bool integerDrag); + + ~DraggableNumber() override; void colourChanged() override; @@ -71,9 +71,9 @@ class DraggableNumber : public Component, public TextEditor::Listener void editorShown(TextEditor& editor); - void focusGained(FocusChangeType const cause) override; + void focusGained(FocusChangeType cause) override; - void focusLost(FocusChangeType const cause) override; + void focusLost(FocusChangeType cause) override; void setMinimumHorizontalScale(float newScale); @@ -81,7 +81,7 @@ class DraggableNumber : public Component, public TextEditor::Listener TextEditor* getCurrentTextEditor(); - bool isBeingEdited(); + bool isBeingEdited() const; void setBorderSize(BorderSize newBorder); @@ -91,38 +91,38 @@ class DraggableNumber : public Component, public TextEditor::Listener Font getFont(); - void setEditableOnClick(bool const editableOnClick, bool const editableOnDoubleClick = false, bool const handleFocusLossManually = false); + void setEditableOnClick(bool editableOnClick, bool editableOnDoubleClick = false, bool handleFocusLossManually = false); - void setMaximum(double const maximum); + void setMaximum(double maximum); - void setMinimum(double const minimum); + void setMinimum(double minimum); + + void setLogarithmicHeight(double logHeight); - void setLogarithmicHeight(double const logHeight); - void setPrecision(int precision); // Toggle between showing ellipses or ">" if number is too large to fit - void setShowEllipsesIfTooLong(bool const shouldShowEllipses); + void setShowEllipsesIfTooLong(bool shouldShowEllipses); void showEditor(); void resized() override; - bool updateFromTextEditorContents (TextEditor& ed); + bool updateFromTextEditorContents(TextEditor const& ed); - void hideEditor (bool discardCurrentEditorContents); + void hideEditor(bool discardCurrentEditorContents); void inputAttemptWhenModal() override; bool keyPressed(KeyPress const& key) override; - void setValue(double newValue, NotificationType const notification = sendNotification, bool clip = true); + void setValue(double newValue, NotificationType notification = sendNotification, bool clip = true); double getValue() const; - void setResetEnabled(bool const enableReset); + void setResetEnabled(bool enableReset); - void setResetValue(double const resetValue); + void setResetValue(double resetValue); // Make sure mouse cursor gets reset, sometimes this doesn't happen automatically void mouseEnter(MouseEvent const& e) override; @@ -132,11 +132,11 @@ class DraggableNumber : public Component, public TextEditor::Listener void mouseDrag(MouseEvent const& e) override; void mouseUp(MouseEvent const& e) override; - void setDragMode(DragMode const newDragMode); + void setDragMode(DragMode newDragMode); - Rectangle getDraggedNumberBounds(int dragPosition); + Rectangle getDraggedNumberBounds(int dragPosition) const; - int getDecimalAtPosition(int const x, Rectangle* position = nullptr) const; + int getDecimalAtPosition(int x, Rectangle* position = nullptr) const; virtual void render(NVGcontext* nvg); @@ -144,7 +144,7 @@ class DraggableNumber : public Component, public TextEditor::Listener double limitValue(double valueToLimit) const; - String formatNumber(double const value, int const precision = -1) const; + String formatNumber(double value, int precision = -1) const; static double parseExpression(String const& expression); @@ -155,11 +155,12 @@ class DraggableNumber : public Component, public TextEditor::Listener void textEditorReturnKeyPressed(TextEditor& editor) override; }; -struct DraggableListNumber final : public DraggableNumber { +class DraggableListNumber final : public DraggableNumber { int numberStartIdx = 0; int numberEndIdx = 0; bool targetFound = false; - + +public: explicit DraggableListNumber(); void mouseDown(MouseEvent const& e) override; @@ -173,5 +174,5 @@ struct DraggableListNumber final : public DraggableNumber { void textEditorReturnKeyPressed(TextEditor& editor) override; - std::tuple getListItemAtPosition(int const x, Rectangle* position = nullptr) const; + std::tuple getListItemAtPosition(int x, Rectangle* position = nullptr) const; }; diff --git a/Source/Components/MarkupDisplay.h b/Source/Components/MarkupDisplay.h index 1946182fa6..31414517a6 100644 --- a/Source/Components/MarkupDisplay.h +++ b/Source/Components/MarkupDisplay.h @@ -136,7 +136,7 @@ class URLHandler { class Block : public Component { public: - Block(URLHandler*& urlHandler) + explicit Block(URLHandler*& urlHandler) : urlHandler(urlHandler) { colours = nullptr; @@ -189,14 +189,14 @@ class Block : public Component { return line; } - virtual void parseMarkup(StringArray const& lines, Font font) { }; + virtual void parseMarkup(StringArray const& lines, Font font) { } virtual float getHeightRequired(float width) = 0; void setColours(StringPairArray* c) { colours = c; defaultColour = parseHexColour((*colours)["default"]); } - virtual bool canExtendBeyondMargin() { return false; }; // for tables + virtual bool canExtendBeyondMargin() { return false; } // for tables void mouseMove(MouseEvent const& event) override { @@ -564,7 +564,7 @@ class AdmonitionBlock final : public Block { class TableBlock final : public Block { public: - TableBlock(URLHandler*& handler) + explicit TableBlock(URLHandler*& handler) : Block(handler) { addAndMakeVisible(viewport); @@ -584,7 +584,7 @@ class TableBlock final : public Block { table.cells.clear(); for (auto line : lines) { // find all cells in this line - OwnedArray* row = new OwnedArray(); + auto const row = new OwnedArray(); while (line.containsAnyOf("^|")) { bool const isHeader = line.startsWith("^"); line = line.substring(1); // remove left delimiter @@ -662,7 +662,7 @@ class TableBlock final : public Block { table.cellgap = gap; table.leftmargin = leftmargin; } - bool canExtendBeyondMargin() override { return true; }; + bool canExtendBeyondMargin() override { return true; } private: typedef struct { @@ -676,24 +676,24 @@ class TableBlock final : public Block { // Override the mouse event methods to forward them to the parent Viewport void mouseDown(MouseEvent const& e) override { - if (Viewport* parent = findParentComponentOfClass()) { - MouseEvent const ep = MouseEvent(e.source, e.position, e.mods, e.pressure, e.orientation, e.rotation, e.tiltX, e.tiltY, parent, e.originalComponent, e.eventTime, e.mouseDownPosition, e.mouseDownTime, e.getNumberOfClicks(), e.mouseWasDraggedSinceMouseDown()); + if (auto const parent = findParentComponentOfClass()) { + auto const ep = MouseEvent(e.source, e.position, e.mods, e.pressure, e.orientation, e.rotation, e.tiltX, e.tiltY, parent, e.originalComponent, e.eventTime, e.mouseDownPosition, e.mouseDownTime, e.getNumberOfClicks(), e.mouseWasDraggedSinceMouseDown()); parent->mouseDown(ep); } Viewport::mouseDown(e); } void mouseUp(MouseEvent const& e) override { - if (Viewport* parent = findParentComponentOfClass()) { - MouseEvent const ep = MouseEvent(e.source, e.position, e.mods, e.pressure, e.orientation, e.rotation, e.tiltX, e.tiltY, parent, e.originalComponent, e.eventTime, e.mouseDownPosition, e.mouseDownTime, e.getNumberOfClicks(), e.mouseWasDraggedSinceMouseDown()); + if (auto* parent = findParentComponentOfClass()) { + auto const ep = MouseEvent(e.source, e.position, e.mods, e.pressure, e.orientation, e.rotation, e.tiltX, e.tiltY, parent, e.originalComponent, e.eventTime, e.mouseDownPosition, e.mouseDownTime, e.getNumberOfClicks(), e.mouseWasDraggedSinceMouseDown()); parent->mouseUp(ep); } Viewport::mouseUp(e); } void mouseDrag(MouseEvent const& e) override { - if (Viewport* parent = findParentComponentOfClass()) { - MouseEvent const ep = MouseEvent(e.source, e.position, e.mods, e.pressure, e.orientation, e.rotation, e.tiltX, e.tiltY, parent, e.originalComponent, e.eventTime, e.mouseDownPosition, e.mouseDownTime, e.getNumberOfClicks(), e.mouseWasDraggedSinceMouseDown()); + if (auto* parent = findParentComponentOfClass()) { + auto const ep = MouseEvent(e.source, e.position, e.mods, e.pressure, e.orientation, e.rotation, e.tiltX, e.tiltY, parent, e.originalComponent, e.eventTime, e.mouseDownPosition, e.mouseDownTime, e.getNumberOfClicks(), e.mouseWasDraggedSinceMouseDown()); parent->mouseDrag(ep); } Viewport::mouseDrag(e); @@ -701,9 +701,9 @@ class TableBlock final : public Block { // Override mouseWheelMove to forward events to the parent Viewport void mouseWheelMove(MouseEvent const& e, MouseWheelDetails const& wheel) override { - Viewport* parent = findParentComponentOfClass(); + auto* parent = findParentComponentOfClass(); if (parent != nullptr) { - MouseEvent const ep = MouseEvent(e.source, e.position, e.mods, e.pressure, e.orientation, e.rotation, e.tiltX, e.tiltY, parent, e.originalComponent, e.eventTime, e.mouseDownPosition, e.mouseDownTime, e.getNumberOfClicks(), e.mouseWasDraggedSinceMouseDown()); + auto const ep = MouseEvent(e.source, e.position, e.mods, e.pressure, e.orientation, e.rotation, e.tiltX, e.tiltY, parent, e.originalComponent, e.eventTime, e.mouseDownPosition, e.mouseDownTime, e.getNumberOfClicks(), e.mouseWasDraggedSinceMouseDown()); parent->mouseWheelMove(ep, wheel); } Viewport::mouseWheelMove(e, wheel); @@ -999,9 +999,9 @@ class MarkupDisplayComponent final : public Component { viewport.setViewPosition(0, newScrollY); } - void setFont(Font const& font) { this->font = font; }; - void setMargin(int const m) { margin = m; }; - void setColours(StringPairArray const& c) { colours = c; }; + void setFont(Font const& font) { this->font = font; } + void setMargin(int const m) { margin = m; } + void setColours(StringPairArray const& c) { colours = c; } void setTableColours(Colour const bg, Colour const bgHeader) { tableBG = bg; @@ -1121,13 +1121,13 @@ class MarkupDisplayComponent final : public Component { li++; lines.insert(li, line.substring(line.lastIndexOf("```") + 3)); } - CodeBlock* b = new CodeBlock(urlHandler); + auto* b = new CodeBlock(urlHandler); b->setColours(&colours); b->parseMarkup(clines, font); content.addAndMakeVisible(b); blocks.add(b); } else if (ListItem::isListItem(line)) { // if we find a list item... - ListItem* b = new ListItem(urlHandler); // ...create a new object... + auto* b = new ListItem(urlHandler); // ...create a new object... b->setColours(&colours); // ...set its colour palette... if (Block::containsLink(line)) { // ...and, if there's a link... line = b->consumeLink(line); // ...preprocess line... @@ -1137,7 +1137,7 @@ class MarkupDisplayComponent final : public Component { blocks.add(b); // ...and the block list... li++; // ...and go to next line. } else if (AdmonitionBlock::isAdmonitionLine(line)) { // if we find an admonition... - AdmonitionBlock* b = new AdmonitionBlock(urlHandler); // ...create a new object... + auto* b = new AdmonitionBlock(urlHandler); // ...create a new object... b->setColours(&colours); // ...set its colour palette... if (Block::containsLink(line)) { // ...and, if there's a link... line = b->consumeLink(line); // ...preprocess line... @@ -1147,7 +1147,7 @@ class MarkupDisplayComponent final : public Component { blocks.add(b); // ...and the block list... li++; // ...and go to next line. } else if (ImageBlock::isImageLine(line)) { // if we find an image... - ImageBlock* b = new ImageBlock(urlHandler); // ...create a new object... + auto* b = new ImageBlock(urlHandler); // ...create a new object... if (Block::containsLink(line)) { // ...and, if there's a link... line = b->consumeLink(line); // ...preprocess line... } @@ -1156,7 +1156,7 @@ class MarkupDisplayComponent final : public Component { blocks.add(b); // ...and the block list... li++; // ...and go to next line. } else if (ImageBlock::isHTMLImageLine(line)) { // if we find an image... - ImageBlock* b = new ImageBlock(urlHandler); // ...create a new object... + auto* b = new ImageBlock(urlHandler); // ...create a new object... if (Block::containsLink(line)) { // ...and, if there's a link... line = b->consumeLink(line); // ...preprocess line... } @@ -1165,7 +1165,7 @@ class MarkupDisplayComponent final : public Component { blocks.add(b); // ...and the block list... li++; // ...and go to next line. } else if (TableBlock::isTableLine(line)) { // if we find a table... - TableBlock* b = new TableBlock(urlHandler); // ...create a new object... + auto* b = new TableBlock(urlHandler); // ...create a new object... b->setColours(&colours); // ...set its colour palette... b->setBGColours(tableBG, tableBGHeader); // ...its background colours... b->setMargins(tableMargin, tableGap, margin); // ...and its margins. @@ -1178,7 +1178,7 @@ class MarkupDisplayComponent final : public Component { content.addAndMakeVisible(b); // ...add the object to content component... blocks.add(b); // ...and the block list. } else if (Block::containsLink(line)) { // ...if we got here and there's a link... - TextBlock* b = new TextBlock(urlHandler); // ...set up a new text block object... + auto* b = new TextBlock(urlHandler); // ...set up a new text block object... b->setColours(&colours); // ...set its colours... line = b->consumeLink(line); // ...preprocess line... b->parseMarkup(line, font); // ...parse markup... @@ -1199,7 +1199,7 @@ class MarkupDisplayComponent final : public Component { line = lines[++li]; // ...read next line... blockEnd &= line.isNotEmpty(); // ...and finish shouldEndBloc... } - TextBlock* b = new TextBlock(urlHandler); // set up a new text block object... + auto* b = new TextBlock(urlHandler); // set up a new text block object... b->setColours(&colours); // ...set its colours... b->parseMarkup(blines, font); // ...parse markup... content.addAndMakeVisible(b); // ...add the object to content component... diff --git a/Source/Components/ObjectDragAndDrop.h b/Source/Components/ObjectDragAndDrop.h index ab1c0c757c..2c393f0f01 100644 --- a/Source/Components/ObjectDragAndDrop.h +++ b/Source/Components/ObjectDragAndDrop.h @@ -8,7 +8,7 @@ class ObjectDragAndDrop : public Component { public: - ObjectDragAndDrop(PluginEditor* e) + explicit ObjectDragAndDrop(PluginEditor* e) : editor(e) { } @@ -45,6 +45,9 @@ class ObjectDragAndDrop : public Component { void mouseDrag(MouseEvent const& e) override { +#if JUCE_IOS + OSUtils::ScrollTracker::setAllowOneFingerScroll(false); +#endif if (reordering || e.getDistanceFromDragStart() < 5) return; @@ -94,7 +97,7 @@ class ObjectClickAndDrop final : public Component Canvas* canvas = nullptr; public: - ObjectClickAndDrop(ObjectDragAndDrop* target) + explicit ObjectClickAndDrop(ObjectDragAndDrop* target) : editor(target->editor) { setWantsKeyboardFocus(true); @@ -103,7 +106,7 @@ class ObjectClickAndDrop final : public Component objectName = target->getPatchStringName(); addToDesktop(ComponentPeer::windowIsTemporary, OSUtils::getDesktopParentPeer(editor)); - + setAlwaysOnTop(true); // FIXME: we should only ask a new mask image when the theme has changed so it's the correct colour diff --git a/Source/Components/PropertiesPanel.cpp b/Source/Components/PropertiesPanel.cpp index c7f4295696..4e03a0061c 100644 --- a/Source/Components/PropertiesPanel.cpp +++ b/Source/Components/PropertiesPanel.cpp @@ -17,28 +17,28 @@ #include "Dialogs/Dialogs.h" PropertiesPanel::SectionComponent::SectionComponent(PropertiesPanel& propertiesPanel, String const& sectionTitle, - PropertiesArray const& newProperties, int const extraPadding) -: Component(sectionTitle) -, parent(propertiesPanel) -, padding(extraPadding) + PropertiesArray const& newProperties, int const extraPadding) + : Component(sectionTitle) + , parent(propertiesPanel) + , padding(extraPadding) { lookAndFeelChanged(); - + propertyComponents.addArray(newProperties); - + for (auto* propertyComponent : propertyComponents) { addAndMakeVisible(propertyComponent); propertyComponent->refresh(); } - + if (propertyComponents.size() == 1) { propertyComponents[0]->setRoundedCorners(true, true); } else if (propertyComponents.size() > 1) { propertyComponents.getFirst()->setRoundedCorners(true, false); propertyComponents.getLast()->setRoundedCorners(false, true); } - + if (parent.drawShadowAndOutline) { dropShadow = std::make_unique(); dropShadow->setColor(Colour(0, 0, 0).withAlpha(0.4f)); @@ -55,39 +55,39 @@ PropertiesPanel::SectionComponent::~SectionComponent() void PropertiesPanel::SectionComponent::paint(Graphics& g) { auto [x, width] = parent.getContentXAndWidth(); - + auto titleX = x; if (parent.titleAlignment == AlignWithPropertyName) { titleX += 11; } - + auto const title = getName(); auto const titleHeight = title.isEmpty() ? 0 : parent.titleHeight; - + if (titleHeight != 0) { Fonts::drawStyledText(g, title, titleX, 0, width - 4, titleHeight, findColour(PlugDataColour::panelTextColourId), Semibold, 14.5f); } - + auto const propertyBounds = Rectangle(x, titleHeight + 8.0f, width, getHeight() - (titleHeight + 16.0f)); - + if (parent.drawShadowAndOutline) { Path p; p.addRoundedRectangle(propertyBounds.reduced(3.0f), Corners::largeCornerRadius); dropShadow->render(g, p); } - + g.setColour(findColour(parent.panelColour)); g.fillRoundedRectangle(propertyBounds, Corners::largeCornerRadius); - + if (parent.drawShadowAndOutline) { g.setColour(findColour(parent.separatorColour)); g.drawRoundedRectangle(propertyBounds, Corners::largeCornerRadius, 1.0f); } - + if (!propertyComponents.isEmpty() && !extraHeaderNames.isEmpty()) { auto propertyBounds = Rectangle(x + width / 2, 0, width / 2, parent.titleHeight); auto const extraHeaderWidth = propertyBounds.getWidth() / static_cast(extraHeaderNames.size()); - + for (auto& extraHeader : extraHeaderNames) { auto const colour = findColour(PlugDataColour::panelTextColourId).withAlpha(0.75f); Fonts::drawText(g, extraHeader, propertyBounds.removeFromLeft(extraHeaderWidth), colour, 15, Justification::centred); @@ -104,9 +104,9 @@ void PropertiesPanel::SectionComponent::setExtraHeaderNames(StringArray headerNa void PropertiesPanel::SectionComponent::paintOverChildren(Graphics& g) { auto [x, width] = parent.getContentXAndWidth(); - + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); - + for (int i = 0; i < propertyComponents.size() - 1; i++) { auto const y = propertyComponents[i]->getBottom() + padding; g.drawHorizontalLine(y, x + 10, x + width - 10); @@ -118,7 +118,7 @@ void PropertiesPanel::SectionComponent::resized() auto const title = getName(); auto y = title.isNotEmpty() ? parent.titleHeight + 8 : 0; auto [x, width] = parent.getContentXAndWidth(); - + for (auto* propertyComponent : propertyComponents) { propertyComponent->setBounds(x, y, width, propertyComponent->getPreferredHeight()); y = propertyComponent->getBottom() + padding; @@ -135,16 +135,16 @@ int PropertiesPanel::SectionComponent::getPreferredHeight() const { auto const title = getName(); auto y = title.isNotEmpty() ? parent.titleHeight : 0; - + auto const numComponents = propertyComponents.size(); - + if (numComponents > 0) { for (auto const* propertyComponent : propertyComponents) y += propertyComponent->getPreferredHeight(); - + y += (numComponents - 1) * padding; } - + return y + (title.isNotEmpty() ? 16 : 0); } @@ -159,12 +159,12 @@ void PropertiesPanel::SectionComponent::mouseUp(MouseEvent const& e) void PropertiesPanel::PropertyHolderComponent::updateLayout(int const width, int const viewHeight) { auto y = 4; - + for (auto* section : sections) { section->setBounds(0, y, width, section->getPreferredHeight()); y = section->getBottom(); } - + setSize(width, std::max(viewHeight, y)); repaint(); } @@ -183,25 +183,24 @@ PropertiesPanel::SectionComponent* PropertiesPanel::PropertyHolderComponent::get if (index++ == targetIndex) return section; } - + return nullptr; } - -PropertiesPanel::ComboComponent::ComboComponent(String const& propertyName, Value& value, StringArray const& options) -: PropertiesPanelProperty(propertyName) -, items(options) +PropertiesPanel::ComboComponent::ComboComponent(String const& propertyName, Value const& value, StringArray const& options) + : PropertiesPanelProperty(propertyName) + , items(options) { comboBox.addItemList(options, 1); comboBox.getProperties().set("Style", "Inspector"); comboBox.getSelectedIdAsValue().referTo(value); - + addAndMakeVisible(comboBox); } PropertiesPanel::ComboComponent::ComboComponent(String const& propertyName, StringArray const& options) -: PropertiesPanelProperty(propertyName) -, items(options) + : PropertiesPanelProperty(propertyName) + , items(options) { comboBox.addItemList(options, 1); comboBox.getProperties().set("Style", "Inspector"); @@ -218,9 +217,10 @@ PropertiesPanelProperty* PropertiesPanel::ComboComponent::createCopy() return new ComboComponent(getName(), comboBox.getSelectedIdAsValue(), items); } - -struct FontEntry final : public PopupMenu::CustomComponent { +class FontEntry final : public PopupMenu::CustomComponent { String fontName; + +public: explicit FontEntry(String name) : fontName(std::move(name)) { @@ -241,11 +241,11 @@ struct FontEntry final : public PopupMenu::CustomComponent { } }; -PropertiesPanel::FontComponent::FontComponent(String const& propertyName, Value& value, File const& extraFontsDir) -: PropertiesPanelProperty(propertyName) +PropertiesPanel::FontComponent::FontComponent(String const& propertyName, Value const& value, File const& extraFontsDir) + : PropertiesPanelProperty(propertyName) { StringArray extraFontOptions; - + if (extraFontsDir.isDirectory() && !extraFontsDir.isRoot()) { auto const patchFonts = Fonts::getFontsInFolder(extraFontsDir); for (int n = 0; n < patchFonts.size(); n++) { @@ -253,24 +253,24 @@ PropertiesPanel::FontComponent::FontComponent(String const& propertyName, Value& } } extraFontOptions.addIfNotAlreadyThere("Inter"); - + auto const offset = extraFontOptions.size(); extraFontOptions.addArray(options); - + for (int n = 0; n < extraFontOptions.size(); n++) { if (n == offset) comboBox.getRootMenu()->addSeparator(); - + comboBox.getRootMenu()->addCustomItem(n + 1, std::make_unique(extraFontOptions[n]), nullptr, extraFontOptions[n]); } - + comboBox.setText(value.toString()); comboBox.getProperties().set("Style", "Inspector"); fontValue.referTo(value); - + comboBox.onChange = [this, extraFontOptions, propertyName] { auto fontName = extraFontOptions[comboBox.getSelectedItemIndex()]; - + if (fontName.isEmpty()) { isFontMissing = true; fontName = fontValue.toString(); @@ -279,16 +279,16 @@ PropertiesPanel::FontComponent::FontComponent(String const& propertyName, Value& isFontMissing = false; PropertiesPanelProperty::setName(propertyName); } - + lookAndFeelChanged(); getParentComponent()->repaint(); fontValue.setValue(fontName); }; - + setLookAndFeel(&LookAndFeel::getDefaultLookAndFeel()); - + addAndMakeVisible(comboBox); - + lookAndFeelChanged(); } @@ -312,18 +312,18 @@ void PropertiesPanel::FontComponent::resized() comboBox.setBounds(getLocalBounds().removeFromRight(getWidth() / (2 - hideLabel))); } -PropertiesPanel::BoolComponent::BoolComponent(String const& propertyName, Value& value, StringArray options) -: PropertiesPanelProperty(propertyName) -, textOptions(std::move(options)) -, toggleStateValue(value) +PropertiesPanel::BoolComponent::BoolComponent(String const& propertyName, Value const& value, StringArray options) + : PropertiesPanelProperty(propertyName) + , textOptions(std::move(options)) + , toggleStateValue(value) { init(); } // Also allow creating it without passing in a Value, makes it easier to derive from this class for custom bool components PropertiesPanel::BoolComponent::BoolComponent(String const& propertyName, StringArray options) -: PropertiesPanelProperty(propertyName) -, textOptions(std::move(options)) + : PropertiesPanelProperty(propertyName) + , textOptions(std::move(options)) { init(); } @@ -331,8 +331,8 @@ PropertiesPanel::BoolComponent::BoolComponent(String const& propertyName, String // Allow creation without an attached juce::Value, but with an initial value // We need this constructor sometimes to prevent feedback caused by the initial value being set after the listener is attached PropertiesPanel::BoolComponent::BoolComponent(String const& propertyName, bool const initialValue, StringArray options) -: PropertiesPanelProperty(propertyName) -, textOptions(std::move(options)) + : PropertiesPanelProperty(propertyName) + , textOptions(std::move(options)) { toggleStateValue = initialValue; init(); @@ -350,8 +350,6 @@ void PropertiesPanel::BoolComponent::init() lookAndFeelChanged(); } - - void PropertiesPanel::BoolComponent::lookAndFeelChanged() { repaint(); @@ -366,7 +364,7 @@ bool PropertiesPanel::BoolComponent::hitTest(int const x, int const y) { if (!isEnabled()) return false; - + auto const bounds = getLocalBounds().removeFromRight(getWidth() / (2 - hideLabel)); return bounds.contains(x, y); } @@ -375,22 +373,22 @@ void PropertiesPanel::BoolComponent::paint(Graphics& g) { bool const isDown = getValue(toggleStateValue); bool const isOver = isMouseOver(); - + auto const bounds = getLocalBounds().toFloat().removeFromRight(getWidth() / (2.0f - hideLabel)); auto const buttonBounds = bounds.reduced(4); - + if (isDown || isOver) { // Add some alpha to make it look good on any background... g.setColour(findColour(PlugDataColour::sidebarActiveBackgroundColourId).contrasting(isOver ? 0.125f : 0.2f).withAlpha(0.25f)); g.fillRoundedRectangle(buttonBounds, Corners::defaultCornerRadius); } auto textColour = findColour(PlugDataColour::panelTextColourId); - + if (!isEnabled()) { textColour = findColour(PlugDataColour::panelTextColourId).withAlpha(0.5f); } Fonts::drawText(g, textOptions[isDown], bounds, textColour, 14.5f, Justification::centred); - + // Paint label PropertiesPanelProperty::paint(g); } @@ -407,6 +405,9 @@ void PropertiesPanel::BoolComponent::mouseExit(MouseEvent const& e) void PropertiesPanel::BoolComponent::mouseUp(MouseEvent const& e) { + if(!e.mods.isLeftButtonDown()) + return; + toggleStateValue.setValue(!getValue(toggleStateValue)); repaint(); } @@ -417,20 +418,20 @@ void PropertiesPanel::BoolComponent::valueChanged(Value& v) repaint(); } -PropertiesPanel::InspectorColourComponent::InspectorColourComponent(String const& propertyName, Value& value) -: PropertiesPanelProperty(propertyName) +PropertiesPanel::InspectorColourComponent::InspectorColourComponent(String const& propertyName, Value const& value) + : PropertiesPanelProperty(propertyName) { - + currentColour.referTo(value); setWantsKeyboardFocus(true); - + currentColour.addListener(this); - + addAndMakeVisible(hexValueEditor); hexValueEditor.setJustificationType(Justification::centred); hexValueEditor.setInterceptsMouseClicks(false, true); hexValueEditor.setFont(Fonts::getCurrentFont().withHeight(13.5f)); - + hexValueEditor.onEditorShow = [this] { auto* editor = hexValueEditor.getCurrentTextEditor(); editor->setBorder(BorderSize(0, 0, 4, 1)); @@ -438,20 +439,20 @@ PropertiesPanel::InspectorColourComponent::InspectorColourComponent(String const editor->setInputRestrictions(7, "#0123456789ABCDEFabcdef"); editor->applyColourToAllText(Colour::fromString(currentColour.toString()).contrasting(0.95f)); }; - + hexValueEditor.onEditorHide = [this] { colour = String("ff") + hexValueEditor.getText().substring(1).toLowerCase(); currentColour.setValue(colour); }; - + hexValueEditor.onTextChange = [this] { colour = String("ff") + hexValueEditor.getText().substring(1).toLowerCase(); }; - + updateHexValue(); - + setLookAndFeel(&LookAndFeel::getDefaultLookAndFeel()); - + repaint(); } @@ -485,22 +486,22 @@ void PropertiesPanel::InspectorColourComponent::paint(Graphics& g) { auto const colour = Colour::fromString(currentColour.toString()); auto const hoverColour = isMouseOver ? colour.brighter(0.4f) : colour; - + auto swatchBounds = getLocalBounds().removeFromRight(getWidth() / 2).toFloat().reduced(4.5f); g.setColour(hoverColour); g.fillRoundedRectangle(swatchBounds, Corners::defaultCornerRadius); g.setColour(colour.darker(0.15f)); g.drawRoundedRectangle(swatchBounds, Corners::defaultCornerRadius, 0.8f); - + if (isMouseOver) { g.setColour(hoverColour.contrasting(0.85f)); g.setFont(Fonts::getIconFont().withHeight(11.5f)); g.drawText(Icons::Eyedropper, swatchBounds.removeFromRight(24), Justification::centred); - + g.setColour(colour.darker(0.15f)); g.drawLine(getWidth() - 28, 4, getWidth() - 28, getHeight() - 4); } - + PropertiesPanelProperty::paint(g); } @@ -516,14 +517,14 @@ void PropertiesPanel::InspectorColourComponent::mouseDown(MouseEvent const& e) { if (hexValueEditor.isBeingEdited() && e.getNumberOfClicks() > 1) return; - + if (e.x > getWidth() - 28) { auto const pickerBounds = getScreenBounds().withTrimmedLeft(getWidth() / 2).expanded(5); - + ColourPicker::getInstance().show(findParentComponentOfClass(), getTopLevelComponent(), false, Colour::fromString(currentColour.toString()), pickerBounds, [_this = SafePointer(this)](Colour const c) { if (!_this) return; - + _this->currentColour = c.toString(); _this->repaint(); }); @@ -547,83 +548,81 @@ void PropertiesPanel::InspectorColourComponent::mouseExit(MouseEvent const& e) } class SwatchComponent final : public Component { - + public: explicit SwatchComponent(Value const& colour) { colourValue.referTo(colour); } - - void paint(Graphics& g) + + void paint(Graphics& g) override { auto const colour = Colour::fromString(colourValue.toString()); - + g.setColour(isMouseOver() ? colour.brighter(0.4f) : colour); g.fillEllipse(getLocalBounds().reduced(1).toFloat()); g.setColour(colour.darker(0.2f)); g.drawEllipse(getLocalBounds().reduced(1).toFloat(), 0.8f); } - - void mouseEnter(MouseEvent const& e) + + void mouseEnter(MouseEvent const& e) override { repaint(); } - - void mouseExit(MouseEvent const& e) + + void mouseExit(MouseEvent const& e) override { repaint(); } - - void mouseDown(MouseEvent const& e) + + void mouseDown(MouseEvent const& e) override { auto const pickerBounds = getScreenBounds().expanded(5); ColourPicker::getInstance().show(findParentComponentOfClass(), getTopLevelComponent(), false, Colour::fromString(colourValue.toString()), pickerBounds, [_this = SafePointer(this)](Colour const c) { if (!_this) return; - + _this->colourValue = c.toString(); _this->repaint(); }); } - + Value colourValue; }; - - PropertiesPanel::ColourComponent::ColourComponent(String const& propertyName, Value& value) -: PropertiesPanelProperty(propertyName) -, swatchComponent(std::make_unique(value)) + : PropertiesPanelProperty(propertyName) + , swatchComponent(std::make_unique(value)) { - + currentColour.referTo(value); currentColour.addListener(this); setWantsKeyboardFocus(false); - + addAndMakeVisible(hexValueEditor); hexValueEditor.getProperties().set("NoOutline", true); hexValueEditor.getProperties().set("NoBackground", true); hexValueEditor.setInputRestrictions(7, "#0123456789ABCDEFabcdef"); hexValueEditor.setColour(outlineColourId, Colour()); hexValueEditor.setJustification(Justification::centred); - + hexValueEditor.onReturnKey = [this] { grabKeyboardFocus(); }; - + hexValueEditor.onTextChange = [this] { colour = String("ff") + hexValueEditor.getText().substring(1).toLowerCase(); }; - + hexValueEditor.onFocusLost = [this] { currentColour.setValue(colour); }; - + addAndMakeVisible(*swatchComponent); updateHexValue(); - + setLookAndFeel(&LookAndFeel::getDefaultLookAndFeel()); - + repaint(); } @@ -652,7 +651,7 @@ void PropertiesPanel::ColourComponent::resized() { auto bounds = getLocalBounds().removeFromRight(getWidth() / (2 - hideLabel)); auto const colourSwatchBounds = bounds.removeFromLeft(getHeight()).reduced(4).translated(12, 0); - + swatchComponent->setBounds(colourSwatchBounds); hexValueEditor.setBounds(bounds.translated(0, -3)); } @@ -665,40 +664,43 @@ void PropertiesPanel::ColourComponent::valueChanged(Value& v) } } -PropertiesPanel::RangeComponent::RangeComponent(String const& propertyName, Value& value, bool const integerMode) -: PropertiesPanelProperty(propertyName), property(value), minLabel(integerMode), maxLabel(integerMode) +PropertiesPanel::RangeComponent::RangeComponent(String const& propertyName, Value const& value, bool const integerMode) + : PropertiesPanelProperty(propertyName) + , property(value) + , minLabel(integerMode) + , maxLabel(integerMode) { property.addListener(this); - + min = value.getValue().getArray()->getReference(0); max = value.getValue().getArray()->getReference(1); - + addAndMakeVisible(minLabel); minLabel.setEditableOnClick(true); minLabel.addMouseListener(this, true); minLabel.setText(String(min), dontSendNotification); minLabel.setPrecision(3); - + addAndMakeVisible(maxLabel); maxLabel.setEditableOnClick(true); maxLabel.addMouseListener(this, true); maxLabel.setText(String(max), dontSendNotification); maxLabel.setPrecision(3); - + auto setMinimum = [this](float const value) { min = value; VarArray const arr = { min, max }; // maxLabel.setMinimum(min + 1e-5f); property = var(arr); }; - + auto setMaximum = [this](float const value) { max = value; VarArray const arr = { min, max }; // minLabel.setMaximum(max - 1e-5f); property = var(arr); }; - + minLabel.onValueChange = setMinimum; minLabel.onReturnKey = setMinimum; maxLabel.onValueChange = setMaximum; @@ -743,12 +745,10 @@ void PropertiesPanel::RangeComponent::valueChanged(Value& v) if (v.refersToSameSourceAs(property)) { min = v.getValue().getArray()->getReference(0); max = v.getValue().getArray()->getReference(1); - if(minLabel.getValue() != min) - { + if (minLabel.getValue() != min) { minLabel.setText(String(min), dontSendNotification); } - if(maxLabel.getValue() != max) - { + if (maxLabel.getValue() != max) { maxLabel.setText(String(max), dontSendNotification); } } @@ -758,18 +758,18 @@ template class PropertiesPanel::EditableComponent; template class PropertiesPanel::EditableComponent; template class PropertiesPanel::EditableComponent; -PropertiesPanel::FilePathComponent::FilePathComponent(String const& propertyName, Value& value) -: PropertiesPanelProperty(propertyName) -, property(value) +PropertiesPanel::FilePathComponent::FilePathComponent(String const& propertyName, Value const& value) + : PropertiesPanelProperty(propertyName) + , property(value) { label.setEditable(true, false); label.getTextValue().referTo(property); label.addMouseListener(this, true); label.setFont(Font(14)); - + addAndMakeVisible(label); addAndMakeVisible(browseButton); - + browseButton.onClick = [this] { Dialogs::showSaveDialog([this](URL const& url) { auto const result = url.getLocalFile(); @@ -777,7 +777,7 @@ PropertiesPanel::FilePathComponent::FilePathComponent(String const& propertyName label.setText(result.getFullPathName(), sendNotification); } }, - "", "", getTopLevelComponent()); + "", "", getTopLevelComponent()); }; } @@ -789,7 +789,7 @@ PropertiesPanelProperty* PropertiesPanel::FilePathComponent::createCopy() void PropertiesPanel::FilePathComponent::paint(Graphics& g) { PropertiesPanelProperty::paint(g); - + g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); g.fillRect(getLocalBounds().removeFromRight(getHeight())); } @@ -801,14 +801,14 @@ void PropertiesPanel::FilePathComponent::resized() browseButton.setBounds(labelBounds.removeFromRight(getHeight())); } -PropertiesPanel::DirectoryPathComponent::DirectoryPathComponent(String const& propertyName, Value& value) -: PropertiesPanelProperty(propertyName) -, property(value) +PropertiesPanel::DirectoryPathComponent::DirectoryPathComponent(String const& propertyName, Value const& value) + : PropertiesPanelProperty(propertyName) + , property(value) { setPath(value.toString()); addAndMakeVisible(browseButton); property.addListener(this); - + browseButton.onClick = [this] { Dialogs::showOpenDialog([this](URL const& url) { auto const result = url.getLocalFile(); @@ -816,7 +816,7 @@ PropertiesPanel::DirectoryPathComponent::DirectoryPathComponent(String const& pr setPath(result.getFullPathName()); } }, - false, true, "", "", getTopLevelComponent()); + false, true, "", "", getTopLevelComponent()); }; } @@ -829,8 +829,7 @@ void PropertiesPanel::DirectoryPathComponent::valueChanged(Value& v) void PropertiesPanel::DirectoryPathComponent::setPath(String path) { property = path; - if(path.length() > 46) - { + if (path.length() > 46) { path = "..." + path.substring(path.length() - 46, path.length()).fromFirstOccurrenceOf("/", true, false); } label = path; @@ -845,7 +844,7 @@ PropertiesPanelProperty* PropertiesPanel::DirectoryPathComponent::createCopy() void PropertiesPanel::DirectoryPathComponent::paint(Graphics& g) { PropertiesPanelProperty::paint(g); - + g.setColour(findColour(PlugDataColour::panelTextColourId).withAlpha(0.8f)); g.setFont(Fonts::getDefaultFont().withHeight(14)); g.drawText(label, 90, 2, getWidth() - 120, getHeight() - 4, Justification::centredLeft); @@ -876,16 +875,16 @@ void PropertiesPanel::ActionComponent::paint(Graphics& g) auto const bounds = getLocalBounds(); auto textBounds = bounds; auto const iconBounds = textBounds.removeFromLeft(textBounds.getHeight()); - + auto const colour = findColour(PlugDataColour::panelTextColourId); if (mouseIsOver) { g.setColour(findColour(PlugDataColour::panelActiveBackgroundColourId)); - + Path p; p.addRoundedRectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), Corners::largeCornerRadius, Corners::largeCornerRadius, roundTop, roundTop, roundBottom, roundBottom); g.fillPath(p); } - + Fonts::drawIcon(g, icon, iconBounds, colour, 12); Fonts::drawText(g, getName(), textBounds, colour, 15); } @@ -910,13 +909,13 @@ void PropertiesPanel::ActionComponent::mouseUp(MouseEvent const& e) PropertiesPanel::PropertiesPanel() { messageWhenEmpty = "(nothing settable)"; - + addAndMakeVisible(viewport); viewport.setViewedComponent(propertyHolderComponent = new PropertyHolderComponent()); viewport.setFocusContainerType(FocusContainerType::focusContainer); - + viewport.addMouseListener(this, true); - + panelColour = PlugDataColour::panelForegroundColourId; separatorColour = PlugDataColour::toolbarOutlineColourId; } @@ -938,11 +937,11 @@ void PropertiesPanel::clear() const } // Adds a set of properties to the panel -void PropertiesPanel::addSection(String const& sectionTitle, PropertiesArray const& newProperties, int const indexToInsertAt, int const extraPaddingBetweenComponents ) +void PropertiesPanel::addSection(String const& sectionTitle, PropertiesArray const& newProperties, int const indexToInsertAt, int const extraPaddingBetweenComponents) { if (isEmpty()) repaint(); - + propertyHolderComponent->insertSection(indexToInsertAt, new SectionComponent(*this, sectionTitle, newProperties, extraPaddingBetweenComponents)); updatePropHolderLayout(); } @@ -968,7 +967,7 @@ Component* PropertiesPanel::getSectionByName(String const& name) const noexcept return section; } } - + return nullptr; } @@ -982,12 +981,12 @@ std::pair PropertiesPanel::getContentXAndWidth() StringArray PropertiesPanel::getSectionNames() const { StringArray s; - + for (auto const* section : propertyHolderComponent->sections) { if (section->getName().isNotEmpty()) s.add(section->getName()); } - + return s; } @@ -997,7 +996,7 @@ void PropertiesPanel::paint(Graphics& g) g.setColour(findColour(PlugDataColour::panelTextColourId).withAlpha(0.5f)); g.setFont(14.0f); g.drawText(messageWhenEmpty, getLocalBounds().withHeight(30), - Justification::centred, true); + Justification::centred, true); } } @@ -1047,7 +1046,7 @@ void PropertiesPanel::updatePropHolderLayout() const auto const maxWidth = viewport.getMaximumVisibleWidth(); auto const maxHeight = viewport.getMaximumVisibleHeight(); propertyHolderComponent->updateLayout(maxWidth, maxHeight); - + auto const newMaxWidth = viewport.getMaximumVisibleWidth(); if (maxWidth != newMaxWidth) { // need to do this twice because of vertical scrollbar changing the size, etc. @@ -1055,13 +1054,12 @@ void PropertiesPanel::updatePropHolderLayout() const } } - PropertiesSearchPanel::PropertiesSearchPanel(SmallArray const& searchedPanels) -: panelsToSearch(searchedPanels) + : panelsToSearch(searchedPanels) { addAndMakeVisible(resultsPanel); resultsPanel.messageWhenEmpty = ""; - + addAndMakeVisible(input); input.setTextToShowWhenEmpty("Type to search for settings", findColour(TextEditor::textColourId).withAlpha(0.5f)); input.setColour(TextEditor::backgroundColourId, Colours::transparentBlack); @@ -1083,16 +1081,16 @@ void PropertiesSearchPanel::resized() void PropertiesSearchPanel::updateResults() { resultsPanel.clear(); - + auto const query = input.getText().toLowerCase(); if (query.isEmpty()) return; - + for (auto const* propertiesPanel : panelsToSearch) { for (auto* section : propertiesPanel->propertyHolderComponent->sections) { PropertiesArray properties; auto sectionTitle = section->getName(); - + for (auto* property : section->propertyComponents) { if (property->getName().toLowerCase().contains(query) || sectionTitle.toLowerCase().contains(query)) { if (auto* propertyCopy = property->createCopy()) @@ -1110,15 +1108,15 @@ void PropertiesSearchPanel::paint(Graphics& g) { g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); g.fillRoundedRectangle(getLocalBounds().reduced(1).toFloat(), Corners::windowCornerRadius); - + auto const titlebarBounds = getLocalBounds().removeFromTop(40).toFloat(); - + Path p; p.addRoundedRectangle(titlebarBounds.getX(), titlebarBounds.getY(), titlebarBounds.getWidth(), titlebarBounds.getHeight(), Corners::windowCornerRadius, Corners::windowCornerRadius, true, true, false, false); - + g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); g.fillPath(p); - + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); g.drawHorizontalLine(40, 0.0f, getWidth()); } diff --git a/Source/Components/PropertiesPanel.h b/Source/Components/PropertiesPanel.h index b1a4f04175..586b503767 100644 --- a/Source/Components/PropertiesPanel.h +++ b/Source/Components/PropertiesPanel.h @@ -11,14 +11,13 @@ #include "SearchEditor.h" #include "PluginEditor.h" - class SwatchComponent; class PropertiesPanelProperty : public PropertyComponent { protected: - bool hideLabel:1 = false; - bool roundTopCorner:1 = false; - bool roundBottomCorner:1 = false; + bool hideLabel : 1 = false; + bool roundTopCorner : 1 = false; + bool roundBottomCorner : 1 = false; public: explicit PropertiesPanelProperty(String const& propertyName) @@ -62,11 +61,10 @@ class PropertiesPanel : public Component { }; private: - - struct SectionComponent final : public Component { - + class SectionComponent final : public Component { + public: SectionComponent(PropertiesPanel& propertiesPanel, String const& sectionTitle, - PropertiesArray const& newProperties, int const extraPadding); + PropertiesArray const& newProperties, int extraPadding); ~SectionComponent() override; @@ -92,16 +90,17 @@ class PropertiesPanel : public Component { JUCE_DECLARE_NON_COPYABLE(SectionComponent) }; - struct PropertyHolderComponent final : public Component { + class PropertyHolderComponent final : public Component { + public: PropertyHolderComponent() = default; void paint(Graphics&) override { } - void updateLayout(int const width, int const viewHeight); + void updateLayout(int width, int viewHeight); - void insertSection(int const indexToInsertAt, SectionComponent* newSection); + void insertSection(int indexToInsertAt, SectionComponent* newSection); - SectionComponent* getSectionWithNonEmptyName(int const targetIndex) const noexcept; + SectionComponent* getSectionWithNonEmptyName(int targetIndex) const noexcept; OwnedArray sections; @@ -109,8 +108,9 @@ class PropertiesPanel : public Component { }; public: - struct ComboComponent : public PropertiesPanelProperty { - ComboComponent(String const& propertyName, Value& value, StringArray const& options); + class ComboComponent : public PropertiesPanelProperty { + public: + ComboComponent(String const& propertyName, Value const& value, StringArray const& options); ComboComponent(String const& propertyName, StringArray const& options); @@ -122,13 +122,13 @@ class PropertiesPanel : public Component { ComboBox comboBox; }; - - struct FontComponent final : public PropertiesPanelProperty { + class FontComponent final : public PropertiesPanelProperty { + public: Value fontValue; StringArray options = Font::findAllTypefaceNames(); bool isFontMissing = false; - FontComponent(String const& propertyName, Value& value, File const& extraFontsDir = File()); + FontComponent(String const& propertyName, Value const& value, File const& extraFontsDir = File()); PropertiesPanelProperty* createCopy() override; @@ -142,17 +142,17 @@ class PropertiesPanel : public Component { ComboBox comboBox; }; - struct BoolComponent : public PropertiesPanelProperty + class BoolComponent : public PropertiesPanelProperty , public Value::Listener { - - BoolComponent(String const& propertyName, Value& value, StringArray options); + public: + BoolComponent(String const& propertyName, Value const& value, StringArray options); // Also allow creating it without passing in a Value, makes it easier to derive from this class for custom bool components BoolComponent(String const& propertyName, StringArray options); // Allow creation without an attached juce::Value, but with an initial value // We need this constructor sometimes to prevent feedback caused by the initial value being set after the listener is attached - BoolComponent(String const& propertyName, bool const initialValue, StringArray options); + BoolComponent(String const& propertyName, bool initialValue, StringArray options); void init(); @@ -162,7 +162,7 @@ class PropertiesPanel : public Component { PropertiesPanelProperty* createCopy() override; - bool hitTest(int const x, int const y) override; + bool hitTest(int x, int y) override; void paint(Graphics& g) override; @@ -171,15 +171,16 @@ class PropertiesPanel : public Component { void mouseUp(MouseEvent const& e) override; void valueChanged(Value& v) override; + protected: StringArray textOptions; Value toggleStateValue; }; - struct InspectorColourComponent final : public PropertiesPanelProperty + class InspectorColourComponent final : public PropertiesPanelProperty , public Value::Listener { - - InspectorColourComponent(String const& propertyName, Value& value); + public: + InspectorColourComponent(String const& propertyName, Value const& value); ~InspectorColourComponent() override; @@ -204,9 +205,9 @@ class PropertiesPanel : public Component { bool isMouseOver = false; }; - struct ColourComponent final : public PropertiesPanelProperty + class ColourComponent final : public PropertiesPanelProperty , public Value::Listener { - + public: ColourComponent(String const& propertyName, Value& value); ~ColourComponent() override; @@ -228,15 +229,15 @@ class PropertiesPanel : public Component { TextEditor hexValueEditor; }; - struct RangeComponent final : public PropertiesPanelProperty + class RangeComponent final : public PropertiesPanelProperty , public Value::Listener { Value property; DraggableNumber minLabel, maxLabel; float min, max; - - RangeComponent(String const& propertyName, Value& value, bool const integerMode); + public: + RangeComponent(String const& propertyName, Value const& value, bool integerMode); ~RangeComponent() override; @@ -246,19 +247,19 @@ class PropertiesPanel : public Component { DraggableNumber& getMaximumComponent(); - void setIntegerMode(bool const integerMode); + void setIntegerMode(bool integerMode); void resized() override; void valueChanged(Value& v) override; }; - struct FilePathComponent final : public PropertiesPanelProperty { + class FilePathComponent final : public PropertiesPanelProperty { Label label; SmallIconButton browseButton = SmallIconButton(Icons::File); Value property; - - FilePathComponent(String const& propertyName, Value& value); + public: + FilePathComponent(String const& propertyName, Value const& value); PropertiesPanelProperty* createCopy() override; @@ -267,15 +268,16 @@ class PropertiesPanel : public Component { void resized() override; }; - struct DirectoryPathComponent final : public PropertiesPanelProperty, public Value::Listener { + class DirectoryPathComponent final : public PropertiesPanelProperty + , public Value::Listener { String label; SmallIconButton browseButton = SmallIconButton(Icons::Folder); Value property; + public: + DirectoryPathComponent(String const& propertyName, Value const& value); - DirectoryPathComponent(String const& propertyName, Value& value); - void valueChanged(Value& v) override; - + void setPath(String path); PropertiesPanelProperty* createCopy() override; @@ -285,13 +287,12 @@ class PropertiesPanel : public Component { void resized() override; }; - class ActionComponent final : public PropertiesPanelProperty { - bool mouseIsOver:1 = false; - bool roundTop:1, roundBottom:1; + bool mouseIsOver : 1 = false; + bool roundTop : 1, roundBottom : 1; public: - ActionComponent(std::function callback, String iconToShow, String const& textToShow, bool const roundOnTop = false, bool const roundOnBottom = false); + ActionComponent(std::function callback, String iconToShow, String const& textToShow, bool roundOnTop = false, bool roundOnBottom = false); PropertiesPanelProperty* createCopy() override; @@ -304,20 +305,24 @@ class PropertiesPanel : public Component { std::function onClick = [] { }; String icon; }; - + template - class EditableComponent final : public PropertiesPanelProperty, public Value::Listener { + class EditableComponent final : public PropertiesPanelProperty + , public Value::Listener { Value property; String allowedCharacters = ""; double min, max; + bool limit; + public: std::unique_ptr label; - - EditableComponent(String const& propertyName, Value& value, double minimum = 0.0, double maximum = 0.0, std::function onInteractionFn = nullptr) + + EditableComponent(String const& propertyName, Value const& value, bool clip = false, double minimum = 0.0, double maximum = 1<<30, std::function onInteractionFn = nullptr) : PropertiesPanelProperty(propertyName) , property(value) , min(minimum) , max(maximum) + , limit(clip) { if constexpr (std::is_arithmetic_v) { auto* draggableNumber = new DraggableNumber(std::is_integral_v); @@ -327,32 +332,31 @@ class PropertiesPanel : public Component { draggableNumber->setText(property.toString(), dontSendNotification); draggableNumber->setFont(draggableNumber->getFont().withHeight(14.5f)); draggableNumber->setEditableOnClick(true); - - if (minimum != 0.0f) + + if(clip) + { draggableNumber->setMinimum(minimum); - if (maximum != 0.0f) draggableNumber->setMaximum(maximum); + } if (onInteractionFn) draggableNumber->onInteraction = onInteractionFn; draggableNumber->setPrecision(3); - draggableNumber->onValueChange = [this](double const newValue){ - if(min != 0.0f || max != 0.0f) { - property = std::clamp(newValue, min, max); - } - else { - property = newValue; + draggableNumber->onValueChange = [this](double const newValue) { + if (limit) { + property = clampValue(newValue); + } else { + property = static_cast(newValue); } }; - + draggableNumber->onReturnKey = [this](double const newValue) { - if(min != 0.0f || max != 0.0f) { - property = std::clamp(newValue, min, max); - } - else { - property = newValue; + if (limit) { + property = clampValue(newValue); + } else { + property = static_cast(newValue); } }; @@ -378,27 +382,36 @@ class PropertiesPanel : public Component { editor->setInputRestrictions(0, allowedCharacters); } }; - - labelComp->onEditorHide = [this] { - // synchronise the value to the canvas when updated - if (PluginEditor* pluginEditor = findParentComponentOfClass()) { - if (auto const cnv = pluginEditor->getCurrentCanvas()) - cnv->synchronise(); - } - }; } addAndMakeVisible(label.get()); label->addMouseListener(this, true); } - + + T clampValue(T value) + { + if constexpr (std::is_arithmetic_v) { + if(!limit) + return value; + + if(min == max) + return value; + else if(min > max) + return std::clamp(value, max, min); + else + return std::clamp(value, min, max); + } + + return value; + } + void valueChanged(Value& v) override { if constexpr (std::is_arithmetic_v) { - auto draggableNumber = reinterpret_cast(label.get()); - auto value = getValue(v); - if(value != draggableNumber->getValue()) { + auto const draggableNumber = reinterpret_cast(label.get()); + auto const value = getValue(v); + if (value != static_cast(draggableNumber->getValue())) { draggableNumber->setValue(value, dontSendNotification); } } @@ -414,22 +427,6 @@ class PropertiesPanel : public Component { allowedCharacters = newAllowedCharacters; } - void setRangeMin(float const minimum) - { - min = minimum; - if constexpr (std::is_arithmetic_v) { - dynamic_cast(label.get())->setMinimum(minimum); - } - } - - void setRangeMax(float const maximum) - { - max = maximum; - if constexpr (std::is_arithmetic_v) { - dynamic_cast(label.get())->setMaximum(maximum); - } - } - void resized() override { label->setBounds(getLocalBounds().removeFromRight(getWidth() / (2 - hideLabel))); @@ -440,14 +437,14 @@ class PropertiesPanel : public Component { dynamic_cast(label.get())->setEditableOnClick(editable); } }; - + template - struct MultiPropertyComponent final : public PropertiesPanelProperty { + class MultiPropertyComponent final : public PropertiesPanelProperty { OwnedArray properties; SmallArray propertyValues; StringArray propertyOptions; - + public: MultiPropertyComponent(String const& propertyName, SmallArray values) : PropertiesPanelProperty(propertyName) , propertyValues(values) @@ -526,12 +523,12 @@ class PropertiesPanel : public Component { void clear() const; // Adds a set of properties to the panel - void addSection(String const& sectionTitle, PropertiesArray const& newProperties, int const indexToInsertAt = -1, int const extraPaddingBetweenComponents = 0); + void addSection(String const& sectionTitle, PropertiesArray const& newProperties, int indexToInsertAt = -1, int extraPaddingBetweenComponents = 0); // Returns true if the panel contains no properties bool isEmpty() const; - void setContentWidth(int const newContentWidth); + void setContentWidth(int newContentWidth); Component* getSectionByName(String const& name) const noexcept; @@ -545,20 +542,20 @@ class PropertiesPanel : public Component { void paint(Graphics& g) override; - void setTitleAlignment(TitleAlignment const newTitleAlignment); + void setTitleAlignment(TitleAlignment newTitleAlignment); - void setPanelColour(int const newPanelColourId); + void setPanelColour(int newPanelColourId); - void setSeparatorColour(int const newSeparatorColourId); + void setSeparatorColour(int newSeparatorColourId); - void setDrawShadowAndOutline(bool const shouldDrawShadowAndOutline); + void setDrawShadowAndOutline(bool shouldDrawShadowAndOutline); - void setTitleHeight(int const newTitleHeight); + void setTitleHeight(int newTitleHeight); // Sets extra section header text // All lines passed in here will be divided equally across the non-label area of the property // Useful for naming rows when using a MultiPropertyComponent - void setExtraHeaderNames(int const sectionIndex, StringArray headerNames) const; + void setExtraHeaderNames(int sectionIndex, StringArray headerNames) const; void resized() override; @@ -580,14 +577,14 @@ class PropertiesPanel : public Component { class PropertiesSearchPanel final : public Component { public: - PropertiesSearchPanel(SmallArray const& searchedPanels); + explicit PropertiesSearchPanel(SmallArray const& searchedPanels); void resized() override; void updateResults(); void paint(Graphics& g) override; - + void startSearching(); void stopSearching(); diff --git a/Source/Components/SuggestionComponent.h b/Source/Components/SuggestionComponent.h index a279b35035..b28a073526 100644 --- a/Source/Components/SuggestionComponent.h +++ b/Source/Components/SuggestionComponent.h @@ -333,8 +333,7 @@ class SuggestionComponent final : public Component editor->grabKeyboardFocus(); }; } - - + addToDesktop(ComponentPeer::windowIsTemporary | ComponentPeer::windowIgnoresKeyPresses, OSUtils::getDesktopParentPeer(object->editor)); if (canBeTransparent()) { diff --git a/Source/Components/WelcomePanel.h b/Source/Components/WelcomePanel.h index 7978d0a4b7..c2d0be4520 100644 --- a/Source/Components/WelcomePanel.h +++ b/Source/Components/WelcomePanel.h @@ -74,6 +74,9 @@ class WelcomePanel final : public Component void mouseUp(MouseEvent const& e) override { + if (!e.mods.isLeftButtonDown()) + return; + if (clearButtonBounds.contains(e.getPosition())) { auto const settingsTree = SettingsFile::getInstance()->getValueTree(); settingsTree.getChildWithName("RecentlyOpened").removeAllChildren(nullptr); @@ -159,7 +162,6 @@ class WelcomePanel final : public Component nvgText(nvg, 92, 63, "Create a new empty patch", nullptr); break; } - case Open: { nvgFontFace(nvg, "icon_font-Regular"); nvgFillColor(nvg, bgCol); @@ -219,7 +221,7 @@ class WelcomePanel final : public Component { if (!getScreenBounds().reduced(12).contains(e.getScreenPosition())) return; - + if (!e.mods.isLeftButtonDown()) return; @@ -276,7 +278,7 @@ class WelcomePanel final : public Component resized(); } - WelcomePanelTile(WelcomePanel& welcomePanel, ValueTree subTree, String svgImage, Colour iconColour, float const scale, bool const favourited, Image const& thumbImage = Image()) + WelcomePanelTile(WelcomePanel& welcomePanel, ValueTree subTree, String const& svgImage, float const scale, bool const favourited, Image const& thumbImage = Image()) : isFavourited(favourited) , parent(welcomePanel) , snapshotScale(scale) @@ -310,7 +312,7 @@ class WelcomePanel final : public Component return (dateOrDay.isNotEmpty() ? dateOrDay : openTime.toString(true, false)) + ", " + time; }; - auto const accessedInPlugdata = Time(static_cast(subTree.getProperty("Time"))); + auto const accessedInPlugdata = Time(subTree.getProperty("Time")); tileSubtitle = formatTimeDescription(accessedInPlugdata); @@ -335,7 +337,7 @@ class WelcomePanel final : public Component updateGeneratedThumbnailIfNeeded(thumbImage, svgImage); } - WelcomePanelTile(WelcomePanel& welcomePanel, String const& name, String const& subtitle, String const& svgImage, Colour iconColour, float const scale, bool const favourited, Image const& thumbImage = Image()) + WelcomePanelTile(WelcomePanel& welcomePanel, String const& name, String const& subtitle, String const& svgImage, float const scale, bool const favourited, Image const& thumbImage = Image()) : isFavourited(favourited) , parent(welcomePanel) , snapshotScale(scale) @@ -349,9 +351,9 @@ class WelcomePanel final : public Component void updateGeneratedThumbnailIfNeeded(Image const& thumbnailImage, String const& svgImage) { if (!thumbnailImage.isValid()) { - auto const snapshotColour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId).withAlpha(0.3f); snapshot = Drawable::createFromImageData(svgImage.toRawUTF8(), svgImage.getNumBytesAsUTF8()); if (snapshot) { + auto const snapshotColour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId).withAlpha(0.3f); snapshot->replaceColour(Colours::black, snapshotColour); } } @@ -359,19 +361,16 @@ class WelcomePanel final : public Component resized(); } - void setPreviousVersions(Array> versions) + void setPreviousVersions(HeapArray> const& versions) { previousVersions.clear(); - for (auto& [time, file] : versions) { - auto metaFile = file.getParentDirectory().getChildFile("meta.json"); - if (metaFile.existsAsFile()) { - auto const json = JSON::fromString(metaFile.loadFileAsString()); - if (json.hasProperty("Version")) { - previousVersions[json["Version"].toString()] = file; - } else { - previousVersions["Added " + Time(time).toString(true, false)] = file; - } + for (auto& [file, json] : versions) { + if (json.hasProperty("Version")) { + previousVersions[json["Version"].toString()] = file; + } + else { + previousVersions["Added " + file.getCreationTime().toString(true, false)] = file; } } } @@ -398,10 +397,11 @@ class WelcomePanel final : public Component tileMenu.addSeparator(); auto metaFile = patchFile.getParentDirectory().getChildFile("meta.json"); + auto currentVersion = String("Latest"); if (metaFile.existsAsFile()) { - auto const json = JSON::fromString(metaFile.loadFileAsString()); auto const patchInfo = PatchInfo(json); + currentVersion = patchInfo.version; PopupMenu patchInfoSubMenu; patchInfoSubMenu.addItem("Title: " + patchInfo.title, false, false, nullptr); @@ -429,14 +429,51 @@ class WelcomePanel final : public Component tileMenu.addSeparator(); - // Put this at the bottom, so it's not accidentally clicked on - tileMenu.addItem("Delete from library...", [this] { - Dialogs::showMultiChoiceDialog(&parent.confirmationDialog, parent.getParentComponent(), "Are you sure you want to delete: " + patchFile.getFileNameWithoutExtension(), [this](int const choice) { + + auto allVersions = previousVersions; + allVersions[currentVersion] = patchFile; + + PopupMenu versionsToDeleteSubMenu; + versionsToDeleteSubMenu.addItem("Delete All", [this, allVersions](){ + Dialogs::showMultiChoiceDialog(&parent.editor->openedDialog, parent.editor, "Are you sure you want to delete all versions?", [this, allVersions](int const choice) { if (choice == 0) { - patchFile.getParentDirectory().deleteRecursively(true); + for (auto& [name, patchFile] : allVersions) { + auto patchesDir = ProjectInfo::appDataDir.getChildFile("Patches"); + auto trashDir = ProjectInfo::appDataDir.getChildFile("Patches").getChildFile(".trash"); + trashDir.createDirectory(); + auto patchDir = patchFile.getParentDirectory() == patchesDir ? patchFile : patchFile.getParentDirectory(); + auto targetLocation = trashDir.getChildFile(patchDir.getFileName()); + if(targetLocation.exists()) + { + targetLocation.deleteRecursively(); + } + patchDir.moveFileTo(trashDir.getChildFile(patchDir.getFileName())); + } parent.triggerAsyncUpdate(); } }, { "Yes", "No" }, Icons::Warning); - }); + }); + + versionsToDeleteSubMenu.addSeparator(); + + for (auto& [name, file] : allVersions) { + versionsToDeleteSubMenu.addItem("Version " + name, [this, patchFile = file] { + Dialogs::showMultiChoiceDialog(&parent.editor->openedDialog, parent.editor, "Are you sure you want to delete: " + patchFile.getFileNameWithoutExtension(), [this, patchFile](int const choice) { + if (choice == 0) { + auto patchesDir = ProjectInfo::appDataDir.getChildFile("Patches"); + auto trashDir = ProjectInfo::appDataDir.getChildFile("Patches").getChildFile(".trash"); + trashDir.createDirectory(); + auto patchDir = patchFile.getParentDirectory() == patchesDir ? patchFile : patchFile.getParentDirectory(); + auto targetLocation = trashDir.getChildFile(patchDir.getFileName()); + if(targetLocation.exists()) + { + targetLocation.deleteRecursively(); + } + patchDir.moveFileTo(trashDir.getChildFile(patchDir.getFileName())); + parent.triggerAsyncUpdate(); + } }, { "Yes", "No" }, Icons::Warning); + }); + } + tileMenu.addSubMenu("Delete from library", versionsToDeleteSubMenu, true); } else { if (tileType == Patch) { tileMenu.addItem(PlatformStrings::getBrowserTip(), [this] { @@ -493,7 +530,7 @@ class WelcomePanel final : public Component auto const imageWidth = thumbnailImageData.getWidth(); auto const imageHeight = thumbnailImageData.getHeight(); auto const componentWidth = bounds.getWidth(); - auto const componentHeight = (bounds.getHeight() - 32); + auto const componentHeight = bounds.getHeight() - 32; float const imageAspect = static_cast(imageWidth) / imageHeight; float const componentAspect = static_cast(componentWidth) / componentHeight; @@ -553,7 +590,7 @@ class WelcomePanel final : public Component nvgFontFace(nvg, "icon_font-Regular"); nvgFontSize(nvg, 68.0f); nvgTextAlign(nvg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); - nvgText(nvg, bounds.getCentreX(), (bounds.getHeight() - 30) * 0.5f, tileType == LibraryPatch ? Icons::PlugdataIconStandard.toRawUTF8() : Icons::Error.toRawUTF8(), nullptr); + nvgText(nvg, bounds.getCentreX(), (bounds.getHeight() - 30) * 0.5f, Icons::PlugdataIconStandard.toRawUTF8(), nullptr); } nvgRestore(nvg); @@ -685,8 +722,14 @@ class WelcomePanel final : public Component newPatchTile->onClick = [this] { editor->getTabComponent().newPatch(); }; openPatchTile->onClick = [this] { editor->getTabComponent().openPatch(); }; storeTile->onClick = [this] { Dialogs::showStore(editor); }; - - triggerAsyncUpdate(); + } + + void visibilityChanged() override + { + if(isVisible()) + { + triggerAsyncUpdate(); + } } void drawShadow(NVGcontext* nvg, int width, int height, float scale) @@ -804,10 +847,12 @@ class WelcomePanel final : public Component viewport.setViewPosition(viewPos); } - void setShownTab(WelcomePanel::Tab tab) + void setShownTab(WelcomePanel::Tab const tab) { currentTab = tab; - triggerAsyncUpdate(); + if(isVisible()) { + triggerAsyncUpdate(); + } } void handleAsyncUpdate() override @@ -816,7 +861,7 @@ class WelcomePanel final : public Component recentlyOpenedTiles.clear(); - auto settingsTree = SettingsFile::getInstance()->getValueTree(); + auto const settingsTree = SettingsFile::getInstance()->getValueTree(); auto recentlyOpenedTree = settingsTree.getChildWithName("RecentlyOpened"); if (currentTab == Home) { @@ -825,7 +870,7 @@ class WelcomePanel final : public Component } else { contentComponent.addAndMakeVisible(*storeTile); } - + if (recentlyOpenedTree.isValid()) { for (int i = recentlyOpenedTree.getNumChildren() - 1; i >= 0; i--) { auto subTree = recentlyOpenedTree.getChild(i); @@ -837,14 +882,12 @@ class WelcomePanel final : public Component // Place favourited patches at the top for (int i = 0; i < recentlyOpenedTree.getNumChildren(); i++) { - auto subTree = recentlyOpenedTree.getChild(i); auto patchFile = File(subTree.getProperty("Path").toString()); auto patchThumbnailBase = File(patchFile.getParentDirectory().getChildFile(patchFile.getFileNameWithoutExtension()).getFullPathName() + "_thumb"); - auto favourited = subTree.hasProperty("Pinned") && static_cast(subTree.getProperty("Pinned")); - auto snapshotColour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId).withAlpha(0.3f); + auto const favourited = subTree.hasProperty("Pinned") && static_cast(subTree.getProperty("Pinned")); String silhoutteSvg; Image thumbImage; @@ -867,24 +910,33 @@ class WelcomePanel final : public Component if (cachedSilhouette != patchSvgCache.end()) { silhoutteSvg = cachedSilhouette->second; } else { +#if JUCE_IOS + // Recover file permission bookmark from valuetree if possible + auto url = URL(patchFile); + auto bookmarkData = subTree.getProperty("Bookmark").toString(); + std::unique_ptr scopedStream; + if(bookmarkData.isNotEmpty()) + { + url.setBookmarkData(bookmarkData); + scopedStream = url.createInputStream(URL::InputStreamOptions(URL::ParameterHandling::inAddress)); + } +#endif silhoutteSvg = OfflineObjectRenderer::patchToSVG(patchFile.loadFileAsString()); patchSvgCache[patchFile.getFullPathName()] = silhoutteSvg; } } } - auto* tile = recentlyOpenedTiles.add(new WelcomePanelTile(*this, subTree, silhoutteSvg, snapshotColour, 1.0f, favourited, thumbImage)); + auto* tile = recentlyOpenedTiles.add(new WelcomePanelTile(*this, subTree, silhoutteSvg, 1.0f, favourited, thumbImage)); - tile->onClick = [this, patchFile]() mutable { + tile->onClick = [this, patchFile, subTree]() mutable { + auto patchURL = URL(patchFile); +#if JUCE_IOS + // Load bookmark to keep file access permissions from last open + patchURL.setBookmarkData(subTree.getProperty("Bookmark").toString()); +#endif if (patchFile.existsAsFile()) { - editor->pd->autosave->checkForMoreRecentAutosave(URL(patchFile), editor, [this](URL const& patchFile, URL const& patchPath) { - auto* cnv = editor->getTabComponent().openPatch(patchFile); - if(cnv) - { - cnv->patch.setCurrentFile(patchPath); - } - SettingsFile::getInstance()->addToRecentlyOpened(patchPath.getLocalFile()); - }); + editor->getTabComponent().openPatch(patchURL); } else { editor->pd->logError("Patch not found"); } @@ -923,21 +975,35 @@ class WelcomePanel final : public Component } resized(); } - + void findLibraryPatches() { libraryTiles.clear(); - auto addTile = [this](Array> patches) { - auto patchFile = patches[0].second; + auto addTile = [this](HeapArray> patches) { + patches.sort([](auto const& versionA, auto const& versionB) -> int { + auto& jsonA = versionA.second; + auto& jsonB = versionB.second; + auto versionTokensA = StringArray::fromTokens(jsonA["Version"].toString(), ".", ""); + auto versionTokensB = StringArray::fromTokens(jsonB["Version"].toString(), ".", ""); + + for(int i = 0; i < std::max(versionTokensA.size(), versionTokensB.size()); i++) + { + int v1 = i < versionTokensA.size() && versionTokensA[i].containsOnly("0123456789") ? versionTokensA[i].getIntValue() : 0; + int v2 = i < versionTokensB.size() && versionTokensB[i].containsOnly("0123456789") ? versionTokensB[i].getIntValue() : 0; + + if(v1 != v2) + return v1 > v2; + } + + return false; + }); + + auto patchFile = patches[0].first; auto const pName = patchFile.getFileNameWithoutExtension(); auto foundThumbs = patchFile.getParentDirectory().findChildFiles(File::findFiles, true, pName + "_thumb.png;" + pName + "_thumb.jpg;" + pName + "_thumb.jpeg;" + pName + "_thumb.gif"); - std::ranges::sort(patches, [](std::pair const& first, std::pair const& second) { - return first.first > second.first; - }); - - patches.remove(0); + patches.remove_at(0); constexpr float scale = 1.0f; Image thumbImage; @@ -959,7 +1025,6 @@ class WelcomePanel final : public Component tile->onClick = [this, patchFile]() mutable { if (patchFile.existsAsFile()) { editor->getTabComponent().openPatch(URL(patchFile)); - SettingsFile::getInstance()->addToRecentlyOpened(patchFile); } else { editor->pd->logError("Patch not found"); } @@ -968,32 +1033,27 @@ class WelcomePanel final : public Component contentComponent.addAndMakeVisible(tile); }; - Array> allPatches; + HeapArray> allPatches; auto const patchesFolder = ProjectInfo::appDataDir.getChildFile("Patches"); for (auto& file : OSUtils::iterateDirectory(patchesFolder, false, false)) { + if(file.getFileName() == ".trash") continue; + if (OSUtils::isDirectoryFast(file.getFullPathName())) { auto const metaFile = file.getChildFile("meta.json"); auto metaFileExists = metaFile.existsAsFile(); String author; String title; - String patchName; - int64 installTime; if(metaFile.existsAsFile()) { auto const json = JSON::fromString(metaFile.loadFileAsString()); if(!json.isVoid()) { author = json["Author"].toString(); title = json["Title"].toString(); - if (json.hasProperty("InstallTime")) { - installTime = static_cast(json["InstallTime"]); - } else { - installTime = metaFile.getCreationTime().toMilliseconds(); - } if (json.hasProperty("Patch")) { - patchName = json["Patch"].toString(); + auto patchName = json["Patch"].toString(); auto patchFile = file.getChildFile(patchName); if(patchFile.existsAsFile()) { - allPatches.add({ patchFile, hash(title + author), installTime }); + allPatches.add({ patchFile, hash(title + author), json }); continue; } } @@ -1006,23 +1066,22 @@ class WelcomePanel final : public Component if (subfile.hasFileExtension("pd")) { if (!metaFileExists) { title = subfile.getFileNameWithoutExtension(); - installTime = 0; } - allPatches.add({ subfile, hash(title + author), installTime }); + allPatches.add({ subfile, hash(title + author), var() }); break; } } } else { if (file.hasFileExtension("pd")) { - allPatches.add({ file, 0, 0 }); + allPatches.add({ file, 0, var() }); } } } // Combine different versions of the same patch into one tile - UnorderedMap>> versions; - for (auto& [file, hash, time] : allPatches) { - versions[hash].add({ time, file }); + UnorderedMap>> versions; + for (auto& [file, hash, json] : allPatches) { + versions[hash].add({file, json}); } for (auto& [hash, patches] : versions) { addTile(patches); @@ -1060,7 +1119,9 @@ class WelcomePanel final : public Component void lookAndFeelChanged() override { - triggerAsyncUpdate(); + if(isVisible()) { + triggerAsyncUpdate(); + } } std::unique_ptr newPatchTile, openPatchTile, storeTile; @@ -1078,8 +1139,6 @@ class WelcomePanel final : public Component Tab currentTab = Home; UnorderedMap patchSvgCache; - std::unique_ptr confirmationDialog; - // To make the library panel update automatically class LibraryFSListener final : public FileSystemWatcher::Listener { FileSystemWatcher libraryFsWatcher; diff --git a/Source/Connection.cpp b/Source/Connection.cpp index e06a5c1eb7..89dc5f085f 100644 --- a/Source/Connection.cpp +++ b/Source/Connection.cpp @@ -9,10 +9,9 @@ using namespace juce::gl; #include #include "Utility/Config.h" -#include "Utility/Fonts.h" #include "Utility/NVGUtils.h" +#include "Utility/SettingsFile.h" -#include "NVGSurface.h" #include "Connection.h" #include "Canvas.h" @@ -240,7 +239,7 @@ void Connection::render(NVGcontext* nvg) constexpr float arrowWidth = 8.0f; constexpr float arrowLength = 12.0f; - auto renderArrow = [this, nvg, connectionColour](Path& path, float const connectionLength) { + auto renderArrow = [this, nvg, connectionColour](Path const& path, float const connectionLength) { // get the center point of the connection path auto const arrowCenter = connectionLength * 0.5f; auto const arrowBase = path.getPointAlongPath(arrowCenter - arrowLength * 0.5f); @@ -288,7 +287,7 @@ void Connection::render(NVGcontext* nvg) } } -void Connection::renderConnectionOrder(NVGcontext* nvg) +void Connection::renderConnectionOrder(NVGcontext* nvg) const { if (cableType == DataCable && getNumberOfConnections() > 1) { auto connectionPath = getPath(); @@ -313,7 +312,7 @@ void Connection::renderConnectionOrder(NVGcontext* nvg) } } -void Connection::pushPathState(bool force) +void Connection::pushPathState(bool const force) { if (!inlet || !outlet) return; @@ -761,17 +760,14 @@ float Connection::getPathWidth() const switch (connectionStyle) { case PlugDataLook::ConnectionStyleVanilla: return cableType == SignalCable ? 4.5f : 2.5f; - break; case PlugDataLook::ConnectionStyleThin: return 3.0f; - break; default: return 4.5f; - break; } } -void Connection::reconnect(Iolet* target) +void Connection::reconnect(Iolet const* target) { if (!reconnecting.empty() || !target) return; @@ -887,9 +883,8 @@ Point Connection::getStartPoint() const auto const outletBounds = outlet->getCanvasBounds().toFloat(); if (PlugDataLook::isFixedIoletPosition()) { - return Point(outletBounds.getX() + PlugDataLook::ioletSize * 0.5f, outletBounds.getCentreY()); + return {outletBounds.getX() + PlugDataLook::ioletSize * 0.5f, outletBounds.getCentreY()}; } - return outletBounds.getCentre(); } @@ -1185,10 +1180,10 @@ void Connection::findPath() pushPathState(); } -int Connection::findLatticePaths(PathPlan& bestPath, PathPlan& pathStack, Point pstart, Point pend, Point increment) +int Connection::findLatticePaths(PathPlan& bestPath, PathPlan& pathStack, Point pend, Point pstart, Point increment) { auto obstacles = SmallArray(); - auto const searchBounds = Rectangle(pstart, pend); + auto const searchBounds = Rectangle(pend, pstart); for (auto* object : cnv->objects) { if (object->getBounds().toFloat().intersects(searchBounds)) { @@ -1201,17 +1196,17 @@ int Connection::findLatticePaths(PathPlan& bestPath, PathPlan& pathStack, Point< return 0; // Add point to path - pathStack.add(pstart); + pathStack.add(pend); // Check if it intersects any object if (pathStack.size() > 1 && straightLineIntersectsObject(Line(pathStack.back(), *(pathStack.end() - 2)), obstacles)) { return 0; } - bool const endVertically = pathStack[0].y > pend.y; + bool const endVertically = pathStack[0].y > pstart.y; // Check if we've reached the destination - if (std::abs(pstart.x - pend.x) < increment.x * 0.5 && std::abs(pstart.y - pend.y) < increment.y * 0.5) { + if (std::abs(pend.x - pstart.x) < increment.x * 0.5 && std::abs(pend.y - pstart.y) < increment.y * 0.5) { bestPath = pathStack; return 1; } @@ -1222,7 +1217,7 @@ int Connection::findLatticePaths(PathPlan& bestPath, PathPlan& pathStack, Point< // Get current stack to revert to after each trial auto pathCopy = pathStack; - auto followLine = [this, &count, &pathCopy, &bestPath, &pathStack, &increment](Point currentOutlet, Point currentInlet, bool const isX) { + auto followLine = [this, &count, &pathCopy, &bestPath, &pathStack, &increment](Point currentOutlet, Point const currentInlet, bool const isX) { auto& coord1 = isX ? currentOutlet.x : currentOutlet.y; auto const& coord2 = isX ? currentInlet.x : currentInlet.y; auto const& incr = isX ? increment.x : increment.y; @@ -1237,20 +1232,20 @@ int Connection::findLatticePaths(PathPlan& bestPath, PathPlan& pathStack, Point< // If we're halfway on the axis, change preferred direction by inverting search order // This will make it do a staircase effect if (endVertically) { - if (std::abs(pstart.y - pend.y) >= std::abs(pathStack[0].y - pend.y) * 0.5) { - followLine(pstart, pend, false); - followLine(pstart, pend, true); + if (std::abs(pend.y - pstart.y) >= std::abs(pathStack[0].y - pstart.y) * 0.5) { + followLine(pend, pstart, false); + followLine(pend, pstart, true); } else { - followLine(pstart, pend, true); - followLine(pstart, pend, false); + followLine(pend, pstart, true); + followLine(pend, pstart, false); } } else { - if (std::abs(pstart.x - pend.x) >= std::abs(pathStack[0].x - pend.x) * 0.5) { - followLine(pstart, pend, true); - followLine(pstart, pend, false); + if (std::abs(pend.x - pstart.x) >= std::abs(pathStack[0].x - pstart.x) * 0.5) { + followLine(pend, pstart, true); + followLine(pend, pstart, false); } else { - followLine(pstart, pend, false); - followLine(pstart, pend, true); + followLine(pend, pstart, false); + followLine(pend, pstart, true); } } diff --git a/Source/Connection.h b/Source/Connection.h index 3609817753..bb10b7daec 100644 --- a/Source/Connection.h +++ b/Source/Connection.h @@ -50,7 +50,7 @@ class Connection final : public DrawablePath bool intersectsRectangle(Rectangle rectToIntersect) const; void render(NVGcontext* nvg) override; - void renderConnectionOrder(NVGcontext* nvg); + void renderConnectionOrder(NVGcontext* nvg) const; void updatePath(); @@ -74,7 +74,7 @@ class Connection final : public DrawablePath Point getStartPoint() const; Point getEndPoint() const; - void reconnect(Iolet* target); + void reconnect(Iolet const* target); bool intersects(Rectangle toCheck, int accuracy = 4) const; int getClosestLineIdx(Point const& position, PathPlan const& plan) const; @@ -236,7 +236,7 @@ class ConnectionBeingCreated final : public DrawablePath scrollViewport(cnv, e); } - void updatePosition(Point cursorPoint) + void updatePosition(Point const cursorPoint) { if (!iolet) return; diff --git a/Source/Constants.h b/Source/Constants.h index 5d056b13c3..b83a873b64 100644 --- a/Source/Constants.h +++ b/Source/Constants.h @@ -10,272 +10,272 @@ #include struct Icons { - inline static String const Open = "b"; - inline static String const Save = "c"; - inline static String const SaveAs = "d"; - inline static String const Undo = "e"; - inline static String const Redo = "f"; - inline static String const Add = "g"; - inline static String const AddObject = ";"; - inline static String const Settings = "h"; - inline static String const Sparkle = "i"; - inline static String const CPU = "j"; - inline static String const Clear = "k"; - inline static String const ClearText = "l"; - inline static String const Lock = "m"; - inline static String const Unlock = "n"; - inline static String const ConnectionStyle = "o"; - inline static String const Power = "p"; - inline static String const Audio = "q"; - inline static String const Search = "r"; - inline static String const Wand = "s"; - inline static String const Pencil = "t"; - inline static String const Grid = "u"; - inline static String const Pin = "v"; - inline static String const Keyboard = "w"; - inline static String const Folder = "x"; - inline static String const OpenedFolder = "y"; - inline static String const File = "z"; - inline static String const New = "z"; - inline static String const AutoScroll = "A"; - inline static String const Restore = "B"; - inline static String const Error = "C"; - inline static String const Message = "D"; - inline static String const Parameters = "E"; - inline static String const Presentation = "F"; - inline static String const Externals = "G"; - inline static String const Refresh = "H"; - inline static String const Up = "I"; - inline static String const Down = "J"; - inline static String const Edit = "K"; - inline static String const ThinDown = "L"; - inline static String const Sine = "M"; - inline static String const Documentation = "N"; - inline static String const AddCircled = "O"; - inline static String const Console = "P"; - inline static String const OpenLink = "Q"; - inline static String const Wrench = "R"; - inline static String const Back = "S"; - inline static String const Forward = "T"; - inline static String const Library = "U"; - inline static String const Menu = "V"; - inline static String const Info = "W"; - inline static String const Warning = "\""; - inline static String const History = "X"; - inline static String const Protection = "Y"; - inline static String const DevTools = "{"; - inline static String const Help = "\\"; - inline static String const Checkmark = "_"; - - inline static String const SavePatch = "Z"; - inline static String const ClosePatch = "["; - inline static String const CloseAllPatches = "]"; - inline static String const Centre = "}"; - inline static String const FitAll = ">"; - inline static String const Eye = "|"; - inline static String const Magnet = "%"; - inline static String const SnapEdges = "#"; - inline static String const SnapCenters = "$"; - inline static String const ExportState = "^"; - inline static String const Trash = "~"; - inline static String const CanvasSettings = "&"; - inline static String const Eyedropper = "@"; - inline static String const HeartFilled = "?"; - inline static String const HeartStroked = ">"; - - inline static String const Reset = "'"; - inline static String const More = "."; - inline static String const MIDI = "`"; - inline static String const PluginMode = "="; - inline static String const CommandInput = "+"; - - inline static String const Reorder = "("; - inline static String const Object = ":"; - inline static String const ObjectMulti = CharPointer_UTF8("\xc2\xb9"); - - inline static String const List = "!"; - inline static String const Graph = "<"; - - inline static String const Heart = ","; - inline static String const Download = "-"; - - inline static String const Copy = "0"; - inline static String const Paste = "1"; - inline static String const Duplicate = "2"; - inline static String const Cut = "3"; - - inline static String const Storage = CharPointer_UTF8("\xc3\x90"); - inline static String const Money = CharPointer_UTF8("\xc3\x91"); - inline static String const Time = CharPointer_UTF8("\xc3\x92"); - inline static String const Store = CharPointer_UTF8("\xc3\x8f"); - inline static String const PanelExpand = CharPointer_UTF8("\xc3\x8d"); - inline static String const PanelContract = CharPointer_UTF8("\xc3\x8c"); - inline static String const ItemGrid = " "; - - inline static String const AlignLeft = "4"; - inline static String const AlignRight = "5"; - inline static String const AlignHCentre = "6"; - inline static String const AlignHDistribute = "/"; - inline static String const AlignTop = "7"; - inline static String const AlignBottom = "8"; - inline static String const AlignVCentre = "9"; - inline static String const AlignVDistribute = "*"; - - inline static String const Home = CharPointer_UTF8("\xc3\x8e"); - - inline static String const ShowIndex = CharPointer_UTF8("\xc2\xbA"); - inline static String const ShowXY = CharPointer_UTF8("\xc2\xbb"); + static inline String const Open = "b"; + static inline String const Save = "c"; + static inline String const SaveAs = "d"; + static inline String const Undo = "e"; + static inline String const Redo = "f"; + static inline String const Add = "g"; + static inline String const AddObject = ";"; + static inline String const Settings = "h"; + static inline String const Sparkle = "i"; + static inline String const CPU = "j"; + static inline String const Clear = "k"; + static inline String const ClearText = "l"; + static inline String const Lock = "m"; + static inline String const Unlock = "n"; + static inline String const ConnectionStyle = "o"; + static inline String const Power = "p"; + static inline String const Audio = "q"; + static inline String const Search = "r"; + static inline String const Wand = "s"; + static inline String const Pencil = "t"; + static inline String const Grid = "u"; + static inline String const Pin = "v"; + static inline String const Keyboard = "w"; + static inline String const Folder = "x"; + static inline String const OpenedFolder = "y"; + static inline String const File = "z"; + static inline String const New = "z"; + static inline String const AutoScroll = "A"; + static inline String const Restore = "B"; + static inline String const Error = "C"; + static inline String const Message = "D"; + static inline String const Parameters = "E"; + static inline String const Presentation = "F"; + static inline String const Externals = "G"; + static inline String const Refresh = "H"; + static inline String const Up = "I"; + static inline String const Down = "J"; + static inline String const Edit = "K"; + static inline String const ThinDown = "L"; + static inline String const Sine = "M"; + static inline String const Documentation = "N"; + static inline String const AddCircled = "O"; + static inline String const Console = "P"; + static inline String const OpenLink = "Q"; + static inline String const Wrench = "R"; + static inline String const Back = "S"; + static inline String const Forward = "T"; + static inline String const Library = "U"; + static inline String const Menu = "V"; + static inline String const Info = "W"; + static inline String const Warning = "\""; + static inline String const History = "X"; + static inline String const Protection = "Y"; + static inline String const DevTools = "{"; + static inline String const Help = "\\"; + static inline String const Checkmark = "_"; + + static inline String const SavePatch = "Z"; + static inline String const ClosePatch = "["; + static inline String const CloseAllPatches = "]"; + static inline String const Centre = "}"; + static inline String const FitAll = ">"; + static inline String const Eye = "|"; + static inline String const Magnet = "%"; + static inline String const SnapEdges = "#"; + static inline String const SnapCenters = "$"; + static inline String const ExportState = "^"; + static inline String const Trash = "~"; + static inline String const CanvasSettings = "&"; + static inline String const Eyedropper = "@"; + static inline String const HeartFilled = "?"; + static inline String const HeartStroked = ">"; + + static inline String const Reset = "'"; + static inline String const More = "."; + static inline String const MIDI = "`"; + static inline String const PluginMode = "="; + static inline String const CommandInput = "+"; + + static inline String const Reorder = "("; + static inline String const Object = ":"; + static inline String const ObjectMulti = CharPointer_UTF8("\xc2\xb9"); + + static inline String const List = "!"; + static inline String const Graph = "<"; + + static inline String const Heart = ","; + static inline String const Download = "-"; + + static inline String const Copy = "0"; + static inline String const Paste = "1"; + static inline String const Duplicate = "2"; + static inline String const Cut = "3"; + + static inline String const Storage = CharPointer_UTF8("\xc3\x90"); + static inline String const Money = CharPointer_UTF8("\xc3\x91"); + static inline String const Time = CharPointer_UTF8("\xc3\x92"); + static inline String const Store = CharPointer_UTF8("\xc3\x8f"); + static inline String const PanelExpand = CharPointer_UTF8("\xc3\x8d"); + static inline String const PanelContract = CharPointer_UTF8("\xc3\x8c"); + static inline String const ItemGrid = " "; + + static inline String const AlignLeft = "4"; + static inline String const AlignRight = "5"; + static inline String const AlignHCentre = "6"; + static inline String const AlignHDistribute = "/"; + static inline String const AlignTop = "7"; + static inline String const AlignBottom = "8"; + static inline String const AlignVCentre = "9"; + static inline String const AlignVDistribute = "*"; + + static inline String const Home = CharPointer_UTF8("\xc3\x8e"); + + static inline String const ShowIndex = CharPointer_UTF8("\xc2\xbA"); + static inline String const ShowXY = CharPointer_UTF8("\xc2\xbb"); // ================== OBJECT ICONS ================== // generic - inline static String const GlyphGenericSignal = CharPointer_UTF8("\xc3\x80"); - inline static String const GlyphGeneric = CharPointer_UTF8("\xc3\x81"); + static inline String const GlyphGenericSignal = CharPointer_UTF8("\xc3\x80"); + static inline String const GlyphGeneric = CharPointer_UTF8("\xc3\x81"); // default - inline static String const GlyphEmptyObject = CharPointer_UTF8("\xc3\x82"); - inline static String const GlyphMessage = CharPointer_UTF8("\xc3\x84"); - inline static String const GlyphFloatBox = CharPointer_UTF8("\xc3\x83"); - inline static String const GlyphSymbolBox = CharPointer_UTF8("\xc3\x85"); - inline static String const GlyphListBox = CharPointer_UTF8("\xc3\x86"); - inline static String const GlyphComment = CharPointer_UTF8("\xc3\x87"); + static inline String const GlyphEmptyObject = CharPointer_UTF8("\xc3\x82"); + static inline String const GlyphMessage = CharPointer_UTF8("\xc3\x84"); + static inline String const GlyphFloatBox = CharPointer_UTF8("\xc3\x83"); + static inline String const GlyphSymbolBox = CharPointer_UTF8("\xc3\x85"); + static inline String const GlyphListBox = CharPointer_UTF8("\xc3\x86"); + static inline String const GlyphComment = CharPointer_UTF8("\xc3\x87"); // ui - inline static String const GlyphBang = CharPointer_UTF8("\xc2\xa1"); - inline static String const GlyphToggle = CharPointer_UTF8("\xc2\xa2"); - inline static String const GlyphButton = CharPointer_UTF8("\xc2\xa3"); - inline static String const GlyphKnob = CharPointer_UTF8("\xc2\xa4"); - inline static String const GlyphNumber = CharPointer_UTF8("\xc2\xa5"); - inline static String const GlyphHSlider = CharPointer_UTF8("\xc2\xa8"); - inline static String const GlyphVSlider = CharPointer_UTF8("\xc2\xa9"); - inline static String const GlyphHRadio = CharPointer_UTF8("\xc2\xa6"); - inline static String const GlyphVRadio = CharPointer_UTF8("\xc2\xa7"); - inline static String const GlyphCanvas = CharPointer_UTF8("\xc2\xaa"); - inline static String const GlyphKeyboard = CharPointer_UTF8("\xc2\xab"); - inline static String const GlyphVUMeter = CharPointer_UTF8("\xc2\xac"); - inline static String const GlyphArray = CharPointer_UTF8("\xc2\xae"); - inline static String const GlyphGOP = CharPointer_UTF8("\xc2\xaf"); - inline static String const GlyphOscilloscope = CharPointer_UTF8("\xc2\xb0"); - inline static String const GlyphFunction = CharPointer_UTF8("\xc2\xb1"); - inline static String const GlyphMessbox = CharPointer_UTF8("\xc2\xb5"); - inline static String const GlyphBicoeff = CharPointer_UTF8("\xc2\xb3"); + static inline String const GlyphBang = CharPointer_UTF8("\xc2\xa1"); + static inline String const GlyphToggle = CharPointer_UTF8("\xc2\xa2"); + static inline String const GlyphButton = CharPointer_UTF8("\xc2\xa3"); + static inline String const GlyphKnob = CharPointer_UTF8("\xc2\xa4"); + static inline String const GlyphNumber = CharPointer_UTF8("\xc2\xa5"); + static inline String const GlyphHSlider = CharPointer_UTF8("\xc2\xa8"); + static inline String const GlyphVSlider = CharPointer_UTF8("\xc2\xa9"); + static inline String const GlyphHRadio = CharPointer_UTF8("\xc2\xa6"); + static inline String const GlyphVRadio = CharPointer_UTF8("\xc2\xa7"); + static inline String const GlyphCanvas = CharPointer_UTF8("\xc2\xaa"); + static inline String const GlyphKeyboard = CharPointer_UTF8("\xc2\xab"); + static inline String const GlyphVUMeter = CharPointer_UTF8("\xc2\xac"); + static inline String const GlyphArray = CharPointer_UTF8("\xc2\xae"); + static inline String const GlyphGOP = CharPointer_UTF8("\xc2\xaf"); + static inline String const GlyphOscilloscope = CharPointer_UTF8("\xc2\xb0"); + static inline String const GlyphFunction = CharPointer_UTF8("\xc2\xb1"); + static inline String const GlyphMessbox = CharPointer_UTF8("\xc2\xb5"); + static inline String const GlyphBicoeff = CharPointer_UTF8("\xc2\xb3"); // general - inline static String const GlyphMetro = CharPointer_UTF8("\xc3\xa4"); - inline static String const GlyphCounter = CharPointer_UTF8("\xc3\xa6"); - inline static String const GlyphSelect = CharPointer_UTF8("\xc3\xa7"); - inline static String const GlyphRoute = CharPointer_UTF8("\xc3\xa8"); - inline static String const GlyphExpr = CharPointer_UTF8("\xc3\xb5"); - inline static String const GlyphLoadbang = CharPointer_UTF8("\xc3\xa9"); - inline static String const GlyphPack = CharPointer_UTF8("\xc3\xaa"); - inline static String const GlyphUnpack = CharPointer_UTF8("\xc3\xab"); - inline static String const GlyphPrint = CharPointer_UTF8("\xc3\xac"); - inline static String const GlyphNetsend = CharPointer_UTF8("\xc3\xae"); - inline static String const GlyphNetreceive = CharPointer_UTF8("\xc3\xad"); - inline static String const GlyphOSCsend = CharPointer_UTF8("\xc4\xb5"); - inline static String const GlyphOSCreceive = CharPointer_UTF8("\xc4\xb4"); - inline static String const GlyphTimer = CharPointer_UTF8("\xc3\xb6"); - inline static String const GlyphDelay = CharPointer_UTF8("\xc3\xb7"); - inline static String const GlyphTrigger = CharPointer_UTF8("\xc3\xb1"); - inline static String const GlyphMoses = CharPointer_UTF8("\xc3\xb2"); - inline static String const GlyphSpigot = CharPointer_UTF8("\xc3\xb3"); - inline static String const GlyphBondo = CharPointer_UTF8("\xc3\xb4"); - inline static String const GlyphSfz = CharPointer_UTF8("\xc3\xb8"); + static inline String const GlyphMetro = CharPointer_UTF8("\xc3\xa4"); + static inline String const GlyphCounter = CharPointer_UTF8("\xc3\xa6"); + static inline String const GlyphSelect = CharPointer_UTF8("\xc3\xa7"); + static inline String const GlyphRoute = CharPointer_UTF8("\xc3\xa8"); + static inline String const GlyphExpr = CharPointer_UTF8("\xc3\xb5"); + static inline String const GlyphLoadbang = CharPointer_UTF8("\xc3\xa9"); + static inline String const GlyphPack = CharPointer_UTF8("\xc3\xaa"); + static inline String const GlyphUnpack = CharPointer_UTF8("\xc3\xab"); + static inline String const GlyphPrint = CharPointer_UTF8("\xc3\xac"); + static inline String const GlyphNetsend = CharPointer_UTF8("\xc3\xae"); + static inline String const GlyphNetreceive = CharPointer_UTF8("\xc3\xad"); + static inline String const GlyphOSCsend = CharPointer_UTF8("\xc4\xb5"); + static inline String const GlyphOSCreceive = CharPointer_UTF8("\xc4\xb4"); + static inline String const GlyphTimer = CharPointer_UTF8("\xc3\xb6"); + static inline String const GlyphDelay = CharPointer_UTF8("\xc3\xb7"); + static inline String const GlyphTrigger = CharPointer_UTF8("\xc3\xb1"); + static inline String const GlyphMoses = CharPointer_UTF8("\xc3\xb2"); + static inline String const GlyphSpigot = CharPointer_UTF8("\xc3\xb3"); + static inline String const GlyphBondo = CharPointer_UTF8("\xc3\xb4"); + static inline String const GlyphSfz = CharPointer_UTF8("\xc3\xb8"); // MIDI - inline static String const GlyphMidiIn = CharPointer_UTF8("\xc4\x87"); - inline static String const GlyphMidiOut = CharPointer_UTF8("\xc4\x88"); - inline static String const GlyphNoteIn = CharPointer_UTF8("\xc4\x89"); - inline static String const GlyphNoteOut = CharPointer_UTF8("\xc4\x8a"); - inline static String const GlyphCtlIn = CharPointer_UTF8("\xc4\x8b"); - inline static String const GlyphCtlOut = CharPointer_UTF8("\xc4\x8c"); - inline static String const GlyphPgmIn = CharPointer_UTF8("\xc4\x8d"); - inline static String const GlyphPgmOut = CharPointer_UTF8("\xc4\x8e"); - inline static String const GlyphSysexIn = CharPointer_UTF8("\xc4\x8f"); - inline static String const GlyphSysexOut = CharPointer_UTF8("\xc4\x90"); - inline static String const GlyphMtof = CharPointer_UTF8("\xc4\x91"); - inline static String const GlyphFtom = CharPointer_UTF8("\xc4\x92"); - inline static String const GlyphAutotune = CharPointer_UTF8("\xc4\x93"); + static inline String const GlyphMidiIn = CharPointer_UTF8("\xc4\x87"); + static inline String const GlyphMidiOut = CharPointer_UTF8("\xc4\x88"); + static inline String const GlyphNoteIn = CharPointer_UTF8("\xc4\x89"); + static inline String const GlyphNoteOut = CharPointer_UTF8("\xc4\x8a"); + static inline String const GlyphCtlIn = CharPointer_UTF8("\xc4\x8b"); + static inline String const GlyphCtlOut = CharPointer_UTF8("\xc4\x8c"); + static inline String const GlyphPgmIn = CharPointer_UTF8("\xc4\x8d"); + static inline String const GlyphPgmOut = CharPointer_UTF8("\xc4\x8e"); + static inline String const GlyphSysexIn = CharPointer_UTF8("\xc4\x8f"); + static inline String const GlyphSysexOut = CharPointer_UTF8("\xc4\x90"); + static inline String const GlyphMtof = CharPointer_UTF8("\xc4\x91"); + static inline String const GlyphFtom = CharPointer_UTF8("\xc4\x92"); + static inline String const GlyphAutotune = CharPointer_UTF8("\xc4\x93"); // Multi~ - inline static String const GlyphMultiSnake = CharPointer_UTF8("\xc4\xbf"); - inline static String const GlyphMultiGet = CharPointer_UTF8("\xc5\x82"); - inline static String const GlyphMultiPick = CharPointer_UTF8("\xc5\x81"); - inline static String const GlyphMultiSig = CharPointer_UTF8("\xc5\x83"); - inline static String const GlyphMultiMerge = CharPointer_UTF8("\xc5\x84"); - inline static String const GlyphMultiUnmerge = CharPointer_UTF8("\xc5\x85"); + static inline String const GlyphMultiSnake = CharPointer_UTF8("\xc4\xbf"); + static inline String const GlyphMultiGet = CharPointer_UTF8("\xc5\x82"); + static inline String const GlyphMultiPick = CharPointer_UTF8("\xc5\x81"); + static inline String const GlyphMultiSig = CharPointer_UTF8("\xc5\x83"); + static inline String const GlyphMultiMerge = CharPointer_UTF8("\xc5\x84"); + static inline String const GlyphMultiUnmerge = CharPointer_UTF8("\xc5\x85"); // IO~ - inline static String const GlyphAdc = CharPointer_UTF8("\xc4\xaa"); - inline static String const GlyphDac = CharPointer_UTF8("\xc4\xab"); - inline static String const GlyphOut = CharPointer_UTF8("\xc4\xac"); - inline static String const GlyphBlocksize = CharPointer_UTF8("\xc4\xad"); - inline static String const GlyphSamplerate = CharPointer_UTF8("\xc4\xae"); - inline static String const GlyphSetDsp = CharPointer_UTF8("\xc4\xaf"); - inline static String const GlyphSend = CharPointer_UTF8("\xc4\xb0"); - inline static String const GlyphReceive = CharPointer_UTF8("\xc4\xb1"); - inline static String const GlyphSignalSend = CharPointer_UTF8("\xc4\xb2"); - inline static String const GlyphSignalReceive = CharPointer_UTF8("\xc4\xb3"); + static inline String const GlyphAdc = CharPointer_UTF8("\xc4\xaa"); + static inline String const GlyphDac = CharPointer_UTF8("\xc4\xab"); + static inline String const GlyphOut = CharPointer_UTF8("\xc4\xac"); + static inline String const GlyphBlocksize = CharPointer_UTF8("\xc4\xad"); + static inline String const GlyphSamplerate = CharPointer_UTF8("\xc4\xae"); + static inline String const GlyphSetDsp = CharPointer_UTF8("\xc4\xaf"); + static inline String const GlyphSend = CharPointer_UTF8("\xc4\xb0"); + static inline String const GlyphReceive = CharPointer_UTF8("\xc4\xb1"); + static inline String const GlyphSignalSend = CharPointer_UTF8("\xc4\xb2"); + static inline String const GlyphSignalReceive = CharPointer_UTF8("\xc4\xb3"); // OSC~ - inline static String const GlyphOsc = CharPointer_UTF8("\xc5\x8d"); - inline static String const GlyphPhasor = CharPointer_UTF8("\xc5\x8e"); - inline static String const GlyphSaw = CharPointer_UTF8("\xc5\x8f"); - inline static String const GlyphSaw2 = CharPointer_UTF8("\xc5\x90"); - inline static String const GlyphSquare = CharPointer_UTF8("\xc5\x91"); - inline static String const GlyphTriangle = CharPointer_UTF8("\xc5\x92"); - inline static String const GlyphImp = CharPointer_UTF8("\xc5\x93"); - inline static String const GlyphImp2 = CharPointer_UTF8("\xc5\x94"); - inline static String const GlyphWavetable = CharPointer_UTF8("\xc5\x95"); - inline static String const GlyphPlaits = CharPointer_UTF8("\xc5\x96"); - - inline static String const GlyphOscBL = CharPointer_UTF8("\xc5\x97"); - inline static String const GlyphSawBL = CharPointer_UTF8("\xc5\x98"); - inline static String const GlyphSawBL2 = CharPointer_UTF8("\xc5\x99"); - inline static String const GlyphSquareBL = CharPointer_UTF8("\xc5\x9a"); - inline static String const GlyphTriBL = CharPointer_UTF8("\xc5\x9b"); - inline static String const GlyphImpBL = CharPointer_UTF8("\xc5\x9c"); - inline static String const GlyphImpBL2 = CharPointer_UTF8("\xc5\x9d"); - inline static String const GlyphWavetableBL = CharPointer_UTF8("\xc5\x9e"); - inline static String const GlyphLFORamp = CharPointer_UTF8("\xc5\x8f"); - inline static String const GlyphLFOSaw = CharPointer_UTF8("\xc5\x9f"); - inline static String const GlyphLFOSquare = CharPointer_UTF8("\xc5\xa0"); - inline static String const GlyphPulse = CharPointer_UTF8("\xc5\xa1"); - inline static String const GlyphPinknoise = CharPointer_UTF8("\xc5\xa2"); + static inline String const GlyphOsc = CharPointer_UTF8("\xc5\x8d"); + static inline String const GlyphPhasor = CharPointer_UTF8("\xc5\x8e"); + static inline String const GlyphSaw = CharPointer_UTF8("\xc5\x8f"); + static inline String const GlyphSaw2 = CharPointer_UTF8("\xc5\x90"); + static inline String const GlyphSquare = CharPointer_UTF8("\xc5\x91"); + static inline String const GlyphTriangle = CharPointer_UTF8("\xc5\x92"); + static inline String const GlyphImp = CharPointer_UTF8("\xc5\x93"); + static inline String const GlyphImp2 = CharPointer_UTF8("\xc5\x94"); + static inline String const GlyphWavetable = CharPointer_UTF8("\xc5\x95"); + static inline String const GlyphPlaits = CharPointer_UTF8("\xc5\x96"); + + static inline String const GlyphOscBL = CharPointer_UTF8("\xc5\x97"); + static inline String const GlyphSawBL = CharPointer_UTF8("\xc5\x98"); + static inline String const GlyphSawBL2 = CharPointer_UTF8("\xc5\x99"); + static inline String const GlyphSquareBL = CharPointer_UTF8("\xc5\x9a"); + static inline String const GlyphTriBL = CharPointer_UTF8("\xc5\x9b"); + static inline String const GlyphImpBL = CharPointer_UTF8("\xc5\x9c"); + static inline String const GlyphImpBL2 = CharPointer_UTF8("\xc5\x9d"); + static inline String const GlyphWavetableBL = CharPointer_UTF8("\xc5\x9e"); + static inline String const GlyphLFORamp = CharPointer_UTF8("\xc5\x8f"); + static inline String const GlyphLFOSaw = CharPointer_UTF8("\xc5\x9f"); + static inline String const GlyphLFOSquare = CharPointer_UTF8("\xc5\xa0"); + static inline String const GlyphPulse = CharPointer_UTF8("\xc5\xa1"); + static inline String const GlyphPinknoise = CharPointer_UTF8("\xc5\xa2"); // effects~ - inline static String const GlyphCrusher = CharPointer_UTF8("\xc6\x99"); - inline static String const GlyphDelayEffect = CharPointer_UTF8("\xc6\x9a"); - inline static String const GlyphDrive = CharPointer_UTF8("\xc6\x9b"); - inline static String const GlyphFlanger = CharPointer_UTF8("\xc6\x9c"); - inline static String const GlyphReverb = CharPointer_UTF8("\xc6\x9d"); - inline static String const GlyphFreeze = CharPointer_UTF8("\xc6\x9e"); - inline static String const GlyphRingmod = CharPointer_UTF8("\xc6\xa2"); - inline static String const GlyphSVFilter = CharPointer_UTF8("\xc6\xa6"); - inline static String const GlyphClip = CharPointer_UTF8("\xc6\xa3"); - inline static String const GlyphFold = CharPointer_UTF8("\xc6\xa4"); - inline static String const GlyphWrap = CharPointer_UTF8("\xc6\xa5"); - inline static String const GlyphCombRev = CharPointer_UTF8("\xc6\x9f"); - inline static String const GlyphComp = CharPointer_UTF8("\xc6\xa0"); - inline static String const GlyphBallance = CharPointer_UTF8("\xc6\xa7"); - inline static String const GlyphPan = CharPointer_UTF8("\xc6\xa8"); + static inline String const GlyphCrusher = CharPointer_UTF8("\xc6\x99"); + static inline String const GlyphDelayEffect = CharPointer_UTF8("\xc6\x9a"); + static inline String const GlyphDrive = CharPointer_UTF8("\xc6\x9b"); + static inline String const GlyphFlanger = CharPointer_UTF8("\xc6\x9c"); + static inline String const GlyphReverb = CharPointer_UTF8("\xc6\x9d"); + static inline String const GlyphFreeze = CharPointer_UTF8("\xc6\x9e"); + static inline String const GlyphRingmod = CharPointer_UTF8("\xc6\xa2"); + static inline String const GlyphSVFilter = CharPointer_UTF8("\xc6\xa6"); + static inline String const GlyphClip = CharPointer_UTF8("\xc6\xa3"); + static inline String const GlyphFold = CharPointer_UTF8("\xc6\xa4"); + static inline String const GlyphWrap = CharPointer_UTF8("\xc6\xa5"); + static inline String const GlyphCombRev = CharPointer_UTF8("\xc6\x9f"); + static inline String const GlyphComp = CharPointer_UTF8("\xc6\xa0"); + static inline String const GlyphBallance = CharPointer_UTF8("\xc6\xa7"); + static inline String const GlyphPan = CharPointer_UTF8("\xc6\xa8"); // filters~ - inline static String const GlyphLowpass = CharPointer_UTF8("\xc7\x8b"); - inline static String const GlyphHighpass = CharPointer_UTF8("\xc7\x8c"); - inline static String const GlyphBandpass = CharPointer_UTF8("\xc7\x8d"); - inline static String const GlyphNotch = CharPointer_UTF8("\xc7\x8e"); - inline static String const GlyphRezLowpass = CharPointer_UTF8("\xc7\x8f"); - inline static String const GlyphRezHighpass = CharPointer_UTF8("\xc7\x90"); - inline static String const GlyphLowShelf = CharPointer_UTF8("\xc7\x91"); - inline static String const GlyphHighShelf = CharPointer_UTF8("\xc7\x92"); - inline static String const GlyphAllPass = CharPointer_UTF8("\xc7\x93"); - inline static String const GlyphFreqShift = CharPointer_UTF8("\xc6\x9a"); + static inline String const GlyphLowpass = CharPointer_UTF8("\xc7\x8b"); + static inline String const GlyphHighpass = CharPointer_UTF8("\xc7\x8c"); + static inline String const GlyphBandpass = CharPointer_UTF8("\xc7\x8d"); + static inline String const GlyphNotch = CharPointer_UTF8("\xc7\x8e"); + static inline String const GlyphRezLowpass = CharPointer_UTF8("\xc7\x8f"); + static inline String const GlyphRezHighpass = CharPointer_UTF8("\xc7\x90"); + static inline String const GlyphLowShelf = CharPointer_UTF8("\xc7\x91"); + static inline String const GlyphHighShelf = CharPointer_UTF8("\xc7\x92"); + static inline String const GlyphAllPass = CharPointer_UTF8("\xc7\x93"); + static inline String const GlyphFreqShift = CharPointer_UTF8("\xc6\x9a"); // plugdata icon with three styles - inline static String const PlugdataIconStandard = CharPointer_UTF8("\xc2\xbc"); - inline static String const PlugdataIconFilled = CharPointer_UTF8("\xc2\xbd"); - inline static String const PlugdataIconSilhouette = CharPointer_UTF8("\xc2\xbe"); + static inline String const PlugdataIconStandard = CharPointer_UTF8("\xc2\xbc"); + static inline String const PlugdataIconFilled = CharPointer_UTF8("\xc2\xbd"); + static inline String const PlugdataIconSilhouette = CharPointer_UTF8("\xc2\xbe"); }; enum PlugDataColour { @@ -430,7 +430,7 @@ struct Corners { static constexpr float largeCornerRadius = 8.0f; static constexpr float defaultCornerRadius = 5.0f; static constexpr float resizeHanleCornerRadius = 2.75f; - inline static float objectCornerRadius = 2.75f; + static inline float objectCornerRadius = 2.75f; }; enum Overlay { diff --git a/Source/Dialogs/AboutPanel.h b/Source/Dialogs/AboutPanel.h index 29b552a09e..a091b2126d 100644 --- a/Source/Dialogs/AboutPanel.h +++ b/Source/Dialogs/AboutPanel.h @@ -187,9 +187,7 @@ class AboutPanel final : public Component { "This application comes with absolutely no warranty."; public: - LicensePanel() - { - } + LicensePanel() = default; void visibilityChanged() override { diff --git a/Source/Dialogs/AddObjectMenu.h b/Source/Dialogs/AddObjectMenu.h index 8d7c3fdf96..7b1809a12e 100644 --- a/Source/Dialogs/AddObjectMenu.h +++ b/Source/Dialogs/AddObjectMenu.h @@ -36,7 +36,7 @@ class ObjectItem final : public ObjectDragAndDrop return "(" + keyPresses.getReference(0).getTextDescription() + ") "; } - return String(); + return {}; } void paint(Graphics& g) override @@ -277,7 +277,7 @@ class ObjectList final : public Component { { Icons::GlyphTriangle, "#X obj 0 0 tri~ 440", "Triangle", "Triangle", OtherObject }, { Icons::GlyphTriBL, "#X obj 0 0 bl.tri~ 100", "Triangle band limited", "Bl. Tri", OtherObject }, { Icons::GlyphSquare, "#X obj 0 0 square~", "Square", "Square", OtherObject }, - { Icons::GlyphSquareBL, "#X obj 0 0 bl.tri~ 440", "Square band limited", "Bl. Square", OtherObject }, + { Icons::GlyphSquareBL, "#X obj 0 0 bl.square~ 440", "Square band limited", "Bl. Square", OtherObject }, { Icons::GlyphSaw, "#X obj 0 0 saw~ 440", "Saw", "Saw", OtherObject }, { Icons::GlyphSawBL, "#X obj 0 0 bl.saw~ 440", "Saw band limited", "Bl. Saw", OtherObject }, { Icons::GlyphImp, "#X obj 0 0 imp~ 100", "Impulse", "Impulse", OtherObject }, @@ -438,8 +438,8 @@ class ObjectList final : public Component { { Icons::GlyphCombRev, "#X obj 0 0 hv.comb~", "Comb filter", "Comb. Filt", OtherObject }, { Icons::GlyphReverb, "#X obj 0 0 hv.reverb~", "Reverb", "Reverb", OtherObject }, { Icons::GlyphRezLowpass, "#X obj 0 0 hv.filter~ lowpass", "Resonant Lowpass Filter", "Res Lp Filt", OtherObject }, - { Icons::GlyphRezHighpass, "#X obj 0 0 hv.filter~ bandpass1", "Resonant Bandpass Filter", "Res Bp Filt", OtherObject }, - { Icons::GlyphBandpass, "#X obj 0 0 hv.filter~ highpass", "Resonant Highpass Filter", "Res Hp Filt", OtherObject }, + { Icons::GlyphBandpass, "#X obj 0 0 hv.filter~ bandpass1", "Resonant Bandpass Filter", "Res Bp Filt", OtherObject }, + { Icons::GlyphRezHighpass, "#X obj 0 0 hv.filter~ highpass", "Resonant Highpass Filter", "Res Hp Filt", OtherObject }, { Icons::GlyphAllPass, "#X obj 0 0 hv.filter~ allpass", "Resonant Allpass Filter", "Res Ap Filt", OtherObject }, { Icons::GlyphLowpass, "#X obj 0 0 hv.lop~", "One-pole Lowpass", "1p Lp Filt", OtherObject }, { Icons::GlyphHighpass, "#X obj 0 0 hv.hip~", "One-pole Highpass", "1p Hp Filt", OtherObject }, diff --git a/Source/Dialogs/AdvancedSettingsPanel.h b/Source/Dialogs/AdvancedSettingsPanel.h index a3512b5eae..7427fe62f6 100644 --- a/Source/Dialogs/AdvancedSettingsPanel.h +++ b/Source/Dialogs/AdvancedSettingsPanel.h @@ -26,7 +26,7 @@ class AdvancedSettingsPanel final : public SettingsDialogPanel if (ProjectInfo::isStandalone) { nativeTitlebar.referTo(settingsFile->getPropertyAsValue("native_window")); nativeTitlebar.addListener(this); - + PropertiesArray windowProperties; windowProperties.add(new PropertiesPanel::BoolComponent("Use system titlebar", nativeTitlebar, { "No", "Yes" })); propertiesPanel.addSection("Window", windowProperties); @@ -67,7 +67,7 @@ class AdvancedSettingsPanel final : public SettingsDialogPanel otherProperties.add(new PropertiesPanel::BoolComponent("Enable auto patching", autoPatchingValue, { "No", "Yes" })); autosaveInterval.referTo(settingsFile->getPropertyAsValue("autosave_interval")); - autosaveProperties.add(new PropertiesPanel::EditableComponent("Auto-save interval (minutes)", autosaveInterval, 1, 60)); + autosaveProperties.add(new PropertiesPanel::EditableComponent("Auto-save interval (minutes)", autosaveInterval, true, 1, 60)); autosaveEnabled.referTo(settingsFile->getPropertyAsValue("autosave_enabled")); autosaveProperties.add(new PropertiesPanel::BoolComponent("Enable autosave", autosaveEnabled, { "No", "Yes" })); @@ -80,7 +80,8 @@ class AdvancedSettingsPanel final : public SettingsDialogPanel }, Icons::Save, "Show autosave history")); - struct ScaleComponent : public PropertiesPanelProperty { + class ScaleComponent : public PropertiesPanelProperty { + public: ScaleComponent(String const& propertyName, Value& value) : PropertiesPanelProperty(propertyName) , scaleValue(value) @@ -127,7 +128,7 @@ class AdvancedSettingsPanel final : public SettingsDialogPanel defaultZoom = settingsFile->getProperty("default_zoom"); defaultZoom.addListener(this); - interfaceProperties.add(new PropertiesPanel::EditableComponent("Default zoom %", defaultZoom)); + interfaceProperties.add(new PropertiesPanel::EditableComponent("Default zoom %", defaultZoom, true, 25, 300)); centreResized = settingsFile->getPropertyAsValue("centre_resized_canvas"); centreResized.addListener(this); diff --git a/Source/Dialogs/AudioOutputSettings.h b/Source/Dialogs/AudioOutputSettings.h index 674fe177ff..ad0311ee27 100644 --- a/Source/Dialogs/AudioOutputSettings.h +++ b/Source/Dialogs/AudioOutputSettings.h @@ -185,7 +185,7 @@ class AudioOutputSettings final : public Component { g.drawLine(4, 24, getWidth() - 8, 24); } - static void show(PluginEditor* editor, Rectangle bounds, AudioOutputSettings::Type typeToShow, std::function changeCallback = [] { }) + static void show(PluginEditor* editor, Rectangle const bounds, AudioOutputSettings::Type typeToShow, std::function changeCallback = [] { }) { if (isShowing) return; diff --git a/Source/Dialogs/AudioSettingsPanel.h b/Source/Dialogs/AudioSettingsPanel.h index 49d9c35204..04195f4763 100644 --- a/Source/Dialogs/AudioSettingsPanel.h +++ b/Source/Dialogs/AudioSettingsPanel.h @@ -320,12 +320,12 @@ class StandaloneAudioSettingsPanel final : public SettingsDialogPanel Icons::Down, "Show more input channels")); inputProperties.add(new PropertiesPanel::ActionComponent([this, numChannels = currentDevice->getInputChannelNames().size()] { setup.useDefaultInputChannels = false; - for(int ch = 0; ch < numChannels; ch++) - { + for (int ch = 0; ch < numChannels; ch++) { setup.inputChannels.setBit(ch, true); } updateConfig(); - }, Icons::Checkmark, "Enable all input channels")); + }, + Icons::Checkmark, "Enable all input channels")); break; } @@ -347,12 +347,12 @@ class StandaloneAudioSettingsPanel final : public SettingsDialogPanel Icons::Up, "Show fewer input channels")); inputProperties.add(new PropertiesPanel::ActionComponent([this, numChannels = idx] { setup.useDefaultInputChannels = false; - for(int ch = 0; ch < numChannels; ch++) - { + for (int ch = 0; ch < numChannels; ch++) { setup.inputChannels.setBit(ch, true); } updateConfig(); - }, Icons::Checkmark, "Enable all input channels")); + }, + Icons::Checkmark, "Enable all input channels")); } idx = 0; @@ -365,12 +365,12 @@ class StandaloneAudioSettingsPanel final : public SettingsDialogPanel Icons::Down, "Show more output channels")); outputProperties.add(new PropertiesPanel::ActionComponent([this, numChannels = currentDevice->getOutputChannelNames().size()] { setup.useDefaultOutputChannels = false; - for(int ch = 0; ch < numChannels; ch++) - { + for (int ch = 0; ch < numChannels; ch++) { setup.outputChannels.setBit(ch, true); } updateConfig(); - }, Icons::Checkmark, "Enable all output channels")); + }, + Icons::Checkmark, "Enable all output channels")); break; } @@ -392,12 +392,12 @@ class StandaloneAudioSettingsPanel final : public SettingsDialogPanel Icons::Up, "Show fewer output channels")); outputProperties.add(new PropertiesPanel::ActionComponent([this, numChannels = idx] { setup.useDefaultOutputChannels = false; - for(int ch = 0; ch < numChannels; ch++) - { + for (int ch = 0; ch < numChannels; ch++) { setup.outputChannels.setBit(ch, true); } updateConfig(); - }, Icons::Checkmark, "Enable all output channels")); + }, + Icons::Checkmark, "Enable all output channels")); } } @@ -431,9 +431,7 @@ class StandaloneAudioSettingsPanel final : public SettingsDialogPanel // Updates the configuration, called when we change any settings void updateConfig() { - String const error = deviceManager.setAudioDeviceSetup(setup, true); - if (error.isNotEmpty()) { std::cerr << error << std::endl; } @@ -495,14 +493,12 @@ class DAWAudioSettingsPanel final : public SettingsDialogPanel latencyValue = processor->getLatencySamples() - pd::Instance::getBlockSize(); - latencyNumberBox = new PropertiesPanel::EditableComponent("Latency (samples)", latencyValue); + latencyNumberBox = new PropertiesPanel::EditableComponent("Latency (samples)", latencyValue, true, 0, 1<<30); tailLengthNumberBox = new PropertiesPanel::EditableComponent("Tail length (seconds)", tailLengthValue); dawSettingsPanel.addSection("Audio", { latencyNumberBox, tailLengthNumberBox }); addAndMakeVisible(dawSettingsPanel); - - latencyNumberBox->setRangeMin(64); } PropertiesPanel* getPropertiesPanel() override diff --git a/Source/Dialogs/Deken.h b/Source/Dialogs/Deken.h index 4cc4766a52..4594fd63f9 100644 --- a/Source/Dialogs/Deken.h +++ b/Source/Dialogs/Deken.h @@ -78,13 +78,15 @@ class PackageManager final : public Thread , public DeletedAtShutdown { public: - struct DownloadTask final : public Thread { + struct DownloadTask final : public Thread + , public AsyncUpdater { PackageManager& manager; PackageInfo packageInfo; + Result taskResult = Result::fail("Failed to start download"); std::unique_ptr instream; - DownloadTask(PackageManager& m, PackageInfo& info) + DownloadTask(PackageManager& m, PackageInfo const& info) : Thread("Download Thread") , manager(m) , packageInfo(info) @@ -161,19 +163,25 @@ class PackageManager final : public Thread void finish(Result result) { - MessageManager::callAsync( - [this, result, finishCopy = onFinish]() mutable { - waitForThreadToExit(-1); + taskResult = result; + triggerAsyncUpdate(); + } - // Self-destruct - manager.downloads.removeObject(this); + void handleAsyncUpdate() override + { + auto const finishCopy = onFinish; + auto const result = taskResult; - finishCopy(result); - }); + waitForThreadToExit(-1); + + // Self-destruct + manager.downloads.removeObject(this); + + finishCopy(result); } - std::function onProgress; - std::function onFinish; + std::function onProgress = [](float) { }; + std::function onFinish = [](Result) { }; }; PackageManager() @@ -283,7 +291,7 @@ class PackageManager final : public Thread pkgInfo.replaceWithText(packageState.toXmlString()); } - void uninstall(PackageInfo& packageInfo) + void uninstall(PackageInfo const& packageInfo) { auto const toRemove = packageState.getChildWithProperty("ID", packageInfo.packageId); if (toRemove.isValid()) { @@ -324,7 +332,7 @@ class PackageManager final : public Thread } // Checks if the current package is already being downloaded - DownloadTask* getDownloadForPackage(PackageInfo& info) + DownloadTask* getDownloadForPackage(PackageInfo const& info) { for (auto* download : downloads) { if (download->packageInfo == info) { @@ -350,7 +358,7 @@ class PackageManager final : public Thread std::unique_ptr webstream; - static inline String const floatsize = String(PD_FLOATSIZE); + static inline auto const floatsize = String(PD_FLOATSIZE); static inline String const os = #if JUCE_LINUX "Linux" @@ -532,10 +540,6 @@ class Deken final : public Component g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); g.fillPath(p); - if (errorMessage.isNotEmpty()) { - Fonts::drawText(g, errorMessage, getLocalBounds().removeFromBottom(28).withTrimmedLeft(8).translated(0, 2), Colours::red); - } - if (searchResult.empty()) { auto const message = installedButton.getToggleState() ? "No externals installed" : "Couldn't find any externals"; Fonts::drawText(g, message, getLocalBounds(), findColour(PlugDataColour::panelTextColourId), 14, Justification::centred); @@ -544,6 +548,10 @@ class Deken final : public Component void paintOverChildren(Graphics& g) override { + if (errorMessage.isNotEmpty()) { + Fonts::drawText(g, errorMessage, getLocalBounds().removeFromTop(100).withTrimmedLeft(28).translated(0, 2), Colours::red); + } + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); g.drawLine(0, 40, getWidth(), 40); } @@ -741,10 +749,10 @@ class Deken final : public Component addAndMakeVisible(listBox); - if(!ProjectInfo::isStandalone) { + if (!ProjectInfo::isStandalone) { headerWarning = std::make_unique("Externals available in standalone only", - "Click to see more info online...", - [] { URL("https://github.com/plugdata-team/plugdata/issues/34").launchInDefaultBrowser(); }); + "Click to see more info online...", + [] { URL("https://github.com/plugdata-team/plugdata/issues/34").launchInDefaultBrowser(); }); addAndMakeVisible(headerWarning.get()); } } @@ -765,14 +773,14 @@ class Deken final : public Component auto bounds = getLocalBounds(); bounds.removeFromTop(10); - if(!ProjectInfo::isStandalone) { + if (!ProjectInfo::isStandalone) { auto const headerBounds = bounds.removeFromTop(headerHeight); headerWarning->setBounds(headerBounds); } listBox.setBounds(bounds.reduced(20, 18).withHeight(listHeight)); // Update the overall size - setSize(getWidth(), totalHeight + 26); + setSize(getWidth(), totalHeight + 32); } void setModel(ListBoxModel* model) @@ -843,7 +851,7 @@ class Deken final : public Component bool isFirst, isLast; - DekenRowComponent(Deken& parent, PackageInfo& info, bool const first, bool const last) + DekenRowComponent(Deken& parent, PackageInfo const& info, bool const first, bool const last) : deken(parent) , packageInfo(info) , packageState(deken.packageManager->packageState) @@ -916,7 +924,7 @@ class Deken final : public Component return; if (result.wasOk()) { - _this->setInstalled(result); + _this->setInstalled(true); _this->deken.filterResults(); } else { _this->deken.showError(result.getErrorMessage()); diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index ddba798fdb..2e2eb5b3a0 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -91,7 +91,7 @@ bool Dialog::wantsRoundedCorners() const return true; } -Component* Dialogs::showTextEditorDialog(String const& text, String filename, std::function closeCallback, std::function saveCallback, const float desktopScale, bool const enableSyntaxHighlighting) +Component* Dialogs::showTextEditorDialog(String const& text, String filename, std::function closeCallback, std::function saveCallback, float const desktopScale, bool const enableSyntaxHighlighting) { #if ENABLE_TESTING return nullptr; @@ -131,6 +131,7 @@ void Dialogs::showAskToSaveDialog(std::unique_ptr* target, Component* ce target->reset(dialog); #if !JUCE_IOS + Process::makeForegroundProcess(); centre->getTopLevelComponent()->toFront(true); #endif } @@ -160,31 +161,35 @@ void Dialogs::showMainMenu(PluginEditor* editor, Component* centre) break; } case 3: { + editor->getTabComponent().openPatchFolder(); + break; + } + case 4: { if (auto* cnv = editor->getCurrentCanvas()) cnv->save(); break; } - case 4: { + case 5: { if (auto* cnv = editor->getCurrentCanvas()) cnv->saveAs(); break; } - case 5: { + case 6: { Dialogs::showSettingsDialog(editor); break; } - case 6: { + case 7: { auto* dialog = new Dialog(&editor->openedDialog, editor, 360, 490, true); auto* aboutPanel = new AboutPanel(); dialog->setViewedComponent(aboutPanel); editor->openedDialog.reset(dialog); break; } - case 7: { + case 8: { SettingsFile::getInstance()->setProperty("theme", PlugDataLook::selectedThemes[0]); break; } - case 8: { + case 9: { SettingsFile::getInstance()->setProperty("theme", PlugDataLook::selectedThemes[1]); break; } @@ -339,6 +344,11 @@ void Dialogs::showMultiChoiceDialog(std::unique_ptr* target, Component* dialog->height = dialogContent->getBestHeight(); dialog->setViewedComponent(dialogContent); target->reset(dialog); + +#if !JUCE_IOS + Process::makeForegroundProcess(); + parent->getTopLevelComponent()->toFront(true); +#endif } void Dialogs::showHeavyExportDialog(std::unique_ptr* target, Component* parent) @@ -354,7 +364,7 @@ void Dialogs::showObjectBrowserDialog(std::unique_ptr* target, Component { auto* dialog = new Dialog(target, parent, 750, 480, true); - auto* dialogContent = new ObjectBrowserDialog(parent, dialog); + auto* dialogContent = new ObjectBrowserDialog(parent); dialog->setViewedComponent(dialogContent); target->reset(dialog); @@ -402,7 +412,7 @@ StringArray DekenInterface::getExternalPaths() return searchPaths; } -void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent, Point position) +void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent, Point const position) { #if JUCE_IOS // OSUtils::showMobileCanvasMenu(cnv->getPeer()); @@ -830,7 +840,7 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent popupMenu.showMenuAsync(PopupMenu::Options().withMinimumWidth(100).withMaximumNumColumns(1).withParentComponent(parent).withTargetScreenArea(Rectangle(position, position.translated(1, 1))), ModalCallbackFunction::create(callback)); } -void Dialogs::showObjectMenu(PluginEditor* editor, Component* target) +void Dialogs::showObjectMenu(PluginEditor* editor, Component const* target) { AddObjectMenu::show(editor, target->getScreenBounds()); } @@ -868,11 +878,16 @@ void Dialogs::showOpenDialog(std::function const& callback, bool cons fileChooser->launchAsync(openChooserFlags, [callback, lastFileId](FileChooser const& fileChooser) { auto const result = fileChooser.getResult(); - - auto lastDir = result.isDirectory() ? result : result.getParentDirectory(); + auto const resultURL = fileChooser.getURLResult(); + + auto const lastDir = result.isDirectory() ? result : result.getParentDirectory(); if (result.exists()) { +#if JUCE_IOS + // Create an input stream to access the security scoped resource + auto scopedAccessStream = resultURL.createInputStream(URL::InputStreamOptions(URL::ParameterHandling::inAddress)); +#endif SettingsFile::getInstance()->setLastBrowserPathForId(lastFileId, lastDir); - callback(fileChooser.getURLResult()); + callback(resultURL); } Dialogs::fileChooser = nullptr; }); diff --git a/Source/Dialogs/Dialogs.h b/Source/Dialogs/Dialogs.h index d0e138a6d9..769a5a561a 100644 --- a/Source/Dialogs/Dialogs.h +++ b/Source/Dialogs/Dialogs.h @@ -67,7 +67,7 @@ class Dialog final : public Component { } } - bool isIphone() + static bool isIphone() { #if JUCE_IOS return !OSUtils::isIPad(); @@ -108,7 +108,16 @@ class Dialog final : public Component { if (isPositiveAndBelow(e.getEventRelativeTo(viewedComponent.get()).getMouseDownY(), 40) && ProjectInfo::isStandalone) { dragger.startDraggingWindow(parentComponent->getTopLevelComponent(), e); dragging = true; - } else if (!viewedComponent->getBounds().contains(e.getPosition())) { + } + else if(!Process::isForegroundProcess()) + { + Process::makeForegroundProcess(); + if(auto* topLevel = getTopLevelComponent()) + { + topLevel->toFront(true); + } + } + else if (!viewedComponent->getBounds().contains(e.getPosition())) { closeDialog(); } } @@ -116,7 +125,7 @@ class Dialog final : public Component { void mouseDrag(MouseEvent const& e) override { if (dragging) { - dragger.dragWindow(parentComponent->getTopLevelComponent(), e, nullptr); + dragger.dragWindow(parentComponent->getTopLevelComponent(), e); } } @@ -164,7 +173,7 @@ struct Dialogs { static Component* showTextEditorDialog(String const& text, String filename, std::function closeCallback, std::function saveCallback, float desktopScale = 1.0f, bool enableSyntaxHighlighting = false); static void appendTextToTextEditorDialog(Component* dialog, String const& text); static void clearTextEditorDialog(Component* dialog); - + static void showAskToSaveDialog(std::unique_ptr* target, Component* centre, String const& filename, std::function callback, int margin = 0, bool withLogo = true); static void showSettingsDialog(PluginEditor* editor); @@ -180,7 +189,7 @@ struct Dialogs { static void showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent, Point position); - static void showObjectMenu(PluginEditor* parent, Component* target); + static void showObjectMenu(PluginEditor* parent, Component const* target); static void showDeken(PluginEditor* editor); static void showStore(PluginEditor* editor); diff --git a/Source/Dialogs/KeyMappingSettingsPanel.h b/Source/Dialogs/KeyMappingSettingsPanel.h index c9aa4ed638..ddfe341db2 100644 --- a/Source/Dialogs/KeyMappingSettingsPanel.h +++ b/Source/Dialogs/KeyMappingSettingsPanel.h @@ -75,7 +75,7 @@ class KeyMappingSettingsPanel final : public SettingsDialogPanel updateMappings(); } - static void resetKeyMappingsToPdCallback(int const result, KeyMappingSettingsPanel* owner) + static void resetKeyMappingsToPdCallback(int const result, KeyMappingSettingsPanel const* owner) { if (result == 1 || owner == nullptr) return; @@ -84,7 +84,7 @@ class KeyMappingSettingsPanel final : public SettingsDialogPanel owner->getMappings().sendChangeMessage(); } - static void resetKeyMappingsToMaxCallback(int const result, KeyMappingSettingsPanel* owner) + static void resetKeyMappingsToMaxCallback(int const result, KeyMappingSettingsPanel const* owner) { if (result == 1 || owner == nullptr) return; @@ -257,7 +257,7 @@ class KeyMappingSettingsPanel final : public SettingsDialogPanel JUCE_DECLARE_NON_COPYABLE(KeyEntryWindow) }; - static void assignNewKeyCallback(int result, ChangeKeyButton* button, KeyPress newKey) + static void assignNewKeyCallback(int const result, ChangeKeyButton* button, KeyPress newKey) { if (result != 0 && button != nullptr) button->setNewKey(newKey, true); diff --git a/Source/Dialogs/MainMenu.h b/Source/Dialogs/MainMenu.h index d2f0c762b7..c1e049edc2 100644 --- a/Source/Dialogs/MainMenu.h +++ b/Source/Dialogs/MainMenu.h @@ -31,14 +31,7 @@ class MainMenu : public PopupMenu { auto path = File(recentlyOpenedTree.getChild(i).getProperty("Path").toString()); recentlyOpened->addItem(path.getFileName(), [path, editor]() mutable { if (path.existsAsFile()) { - editor->pd->autosave->checkForMoreRecentAutosave(URL(path), editor, [editor](URL const& patchFile, URL const& patchPath) { - auto* cnv = editor->getTabComponent().openPatch(patchFile); - if(cnv) - { - cnv->patch.setCurrentFile(patchPath); - } - SettingsFile::getInstance()->addToRecentlyOpened(patchPath.getLocalFile()); - }); + editor->getTabComponent().openPatch(URL(path)); } else { editor->pd->logError("Patch not found"); } @@ -77,7 +70,7 @@ class MainMenu : public PopupMenu { static auto saveChooser = std::make_unique("Choose save location", File(SettingsFile::getInstance()->getProperty("last_filechooser_path")), "*.pdproj", SettingsFile::getInstance()->wantsNativeDialog()); saveChooser->launchAsync(FileBrowserComponent::saveMode | FileBrowserComponent::canSelectFiles, [editor](FileChooser const& f) { - auto const file = f.getResult(); + auto const file = f.getResult().withFileExtension(".pdproj"); if (file.getParentDirectory().exists()) { MemoryBlock destData; editor->processor.getStateInformation(destData); diff --git a/Source/Dialogs/MidiSettingsPanel.h b/Source/Dialogs/MidiSettingsPanel.h index 27c8f96ddf..e56eabe580 100644 --- a/Source/Dialogs/MidiSettingsPanel.h +++ b/Source/Dialogs/MidiSettingsPanel.h @@ -30,7 +30,7 @@ class MidiSettingsComboBox final : public PropertiesPanel::ComboComponent { repaint(); auto const port = getValue(comboValue); - processor->getMidiDeviceManager().setMidiDevicePort(isInput, deviceInfo.name, deviceInfo.identifier, port - 2); + processor->getMidiDeviceManager().setMidiDevicePort(isInput, deviceInfo.identifier, port - 2); } bool isInput; @@ -126,7 +126,9 @@ class MidiSettingsPanel final : public SettingsDialogPanel midiOutputProperties.add(new MidiSettingsComboBox(false, processor, deviceInfo)); } - midiOutputProperties.add(new InternalSynthToggle(processor)); + if (ProjectInfo::isStandalone) { + midiOutputProperties.add(new InternalSynthToggle(processor)); + } midiProperties.addSection("MIDI Inputs", midiInputProperties); midiProperties.addSection("MIDI Outputs", midiOutputProperties); diff --git a/Source/Dialogs/ObjectBrowserDialog.h b/Source/Dialogs/ObjectBrowserDialog.h index 2a2d856296..4b4fa9c7b2 100644 --- a/Source/Dialogs/ObjectBrowserDialog.h +++ b/Source/Dialogs/ObjectBrowserDialog.h @@ -349,11 +349,7 @@ class ObjectViewer final : public Component { openHelp.onClick = [this, editor, dismissMenu] { auto const file = pd::Library::findHelpfile(objectName); - if (auto const* helpCanvas = editor->getTabComponent().openPatch(URL(file))) { - if (auto patch = helpCanvas->patch.getPointer()) { - patch->gl_edit = 0; - } - } + editor->getTabComponent().openHelpPatch(URL(file)); dismissMenu(false); }; @@ -418,7 +414,7 @@ class ObjectViewer final : public Component { } } - void drawObject(Graphics& g, Rectangle objectRect) + void drawObject(Graphics& g, Rectangle const objectRect) { constexpr int ioletSize = 8; int const ioletWidth = (ioletSize + 4) * std::max(inlets.size(), outlets.size()); @@ -431,7 +427,7 @@ class ObjectViewer final : public Component { auto squareIolets = PlugDataLook::getUseSquareIolets(); - auto drawIolet = [this, squareIolets](Graphics& g, Rectangle bounds, bool const type) mutable { + auto drawIolet = [this, squareIolets](Graphics& g, Rectangle const bounds, bool const type) mutable { g.setColour(type ? findColour(PlugDataColour::signalColourId) : findColour(PlugDataColour::dataColourId)); if (squareIolets) { @@ -791,7 +787,7 @@ class ObjectSearchComponent final : public Component class ObjectBrowserDialog final : public Component { public: - ObjectBrowserDialog(Component* pluginEditor, Dialog* parent) + ObjectBrowserDialog(Component* pluginEditor) : editor(dynamic_cast(pluginEditor)) , objectsList(editor, *editor->pd->objectLibrary, [this](bool const shouldFade) { dismiss(shouldFade); }) , objectReference(editor, true) diff --git a/Source/Dialogs/ObjectReferenceDialog.h b/Source/Dialogs/ObjectReferenceDialog.h index 6dfd837689..6a3bd6a8e4 100644 --- a/Source/Dialogs/ObjectReferenceDialog.h +++ b/Source/Dialogs/ObjectReferenceDialog.h @@ -197,7 +197,7 @@ class ObjectInfoPanel final : public Component { class ObjectReferenceDialog final : public Component { public: - ObjectReferenceDialog(PluginEditor* editor, bool const showBackButton) + ObjectReferenceDialog(PluginEditor const* editor, bool const showBackButton) : library(*editor->pd->objectLibrary) { // We only need to respond to explicit repaints anyway! @@ -282,7 +282,7 @@ class ObjectReferenceDialog final : public Component { } } - void drawObject(Graphics& g, Rectangle objectRect) + void drawObject(Graphics& g, Rectangle const objectRect) { constexpr int ioletSize = 8; int const ioletWidth = (ioletSize + 4) * std::max(inlets.size(), outlets.size()); @@ -300,7 +300,7 @@ class ObjectReferenceDialog final : public Component { auto squareIolets = static_cast(themeTree.getProperty("square_iolets")); - auto drawIolet = [this, squareIolets](Graphics& g, Rectangle bounds, bool const type) mutable { + auto drawIolet = [this, squareIolets](Graphics& g, Rectangle const bounds, bool const type) mutable { g.setColour(type ? findColour(PlugDataColour::signalColourId) : findColour(PlugDataColour::dataColourId)); if (squareIolets) { diff --git a/Source/Dialogs/OverlayDisplaySettings.h b/Source/Dialogs/OverlayDisplaySettings.h index 5167d94bef..80b756511a 100644 --- a/Source/Dialogs/OverlayDisplaySettings.h +++ b/Source/Dialogs/OverlayDisplaySettings.h @@ -109,7 +109,7 @@ class OverlayDisplaySettings final : public Component } }; - OverlayDisplaySettings(pd::Instance* pd) + explicit OverlayDisplaySettings(pd::Instance* pd) : pd(pd) { auto const settingsTree = SettingsFile::getInstance()->getValueTree(); @@ -228,7 +228,7 @@ class OverlayDisplaySettings final : public Component } } - static void show(PluginEditor* editor, Rectangle bounds) + static void show(PluginEditor* editor, Rectangle const bounds) { if (isShowing) return; diff --git a/Source/Dialogs/PatchStore.h b/Source/Dialogs/PatchStore.h index 699c214700..a2a2b9ec8e 100644 --- a/Source/Dialogs/PatchStore.h +++ b/Source/Dialogs/PatchStore.h @@ -70,9 +70,7 @@ class DownloadPool final : public DeletedAtShutdown { break; } - MemoryInputStream jsonStream(jsonData, false); - - auto const parsedData = JSON::parse(jsonStream); + auto const parsedData = JSON::parse(jsonData.toString()); // Converting to string is important on Windows to get correct character encoding auto patchData = parsedData["Patches"]; if (patchData.isArray()) { for (int i = 0; i < patchData.size(); ++i) { @@ -115,7 +113,7 @@ class DownloadPool final : public DeletedAtShutdown { void downloadImage(hash32 hash, URL location) { - imagePool.addJob([this, hash, location] (){ + imagePool.addJob([this, hash, location]{ static UnorderedMap downloadImageCache; static CriticalSection cacheMutex; // Prevent threadpool jobs from touching cache at the same time @@ -125,7 +123,7 @@ class DownloadPool final : public DeletedAtShutdown { ScopedLock lock(cacheMutex); if (downloadImageCache.contains(hash)) { - if (auto blockIter = downloadImageCache.find(hash); blockIter != downloadImageCache.end()) { + if (auto const blockIter = downloadImageCache.find(hash); blockIter != downloadImageCache.end()) { imageData = blockIter->second; } } @@ -212,29 +210,34 @@ class DownloadPool final : public DeletedAtShutdown { ZipFile zip(input); auto const patchesDir = ProjectInfo::appDataDir.getChildFile("Patches"); - auto result = zip.uncompressTo(patchesDir, true); - auto const downloadedPatch = patchesDir.getChildFile(zip.getEntry(0)->filename); - - auto const targetLocation = downloadedPatch.getParentDirectory().getChildFile(info.getNameInPatchFolder()); - targetLocation.deleteRecursively(true); + + auto extractedDir = File::createTempFile(""); + auto result = zip.uncompressTo(extractedDir, true); - downloadedPatch.moveFileTo(targetLocation); + for(auto downloadedPatch : OSUtils::iterateDirectory(extractedDir, false, false)) + { + if(!downloadedPatch.isDirectory() || downloadedPatch.getFileName() == "__MACOSX") continue; + + PatchInfo currentInfo; + auto metaFile = downloadedPatch.getChildFile("meta.json"); + if(metaFile.existsAsFile()) + { + auto json = JSON::fromString(metaFile.loadFileAsString()); + currentInfo = json.isVoid() ? info : PatchInfo(json); + } + else { + currentInfo = info; + } + + auto const targetLocation = patchesDir.getChildFile(currentInfo.getNameInPatchFolder()); + targetLocation.deleteRecursively(true); - auto const metaFile = targetLocation.getChildFile("meta.json"); - if (!metaFile.existsAsFile()) { - info.setInstallTime(Time::currentTimeMillis()); - auto json = info.json; - metaFile.replaceWithText(info.json); - } else { - info = PatchInfo(JSON::fromString(metaFile.loadFileAsString())); - info.setInstallTime(Time::currentTimeMillis()); - auto json = info.json; - metaFile.replaceWithText(info.json); - } + downloadedPatch.moveFileTo(targetLocation); - auto const macOSTrash = ProjectInfo::appDataDir.getChildFile("Patches").getChildFile("__MACOSX"); - if (macOSTrash.isDirectory()) { - macOSTrash.deleteRecursively(); + metaFile = targetLocation.getChildFile("meta.json"); + if (!metaFile.existsAsFile()) { + metaFile.replaceWithText(currentInfo.json); + } } MessageManager::callAsync([this, downloadHash, result] { @@ -292,20 +295,20 @@ class OnlineImage final : public Component if (hash == imageHash && width && height) { Image webpImage; - const uint8_t* webpData = static_cast(imageData.getData()); - size_t dataSize = imageData.getSize(); + const auto* webpData = static_cast(imageData.getData()); + size_t const dataSize = imageData.getSize(); WebPDecoderConfig config; if (WebPInitDecoderConfig(&config)) { if (WebPGetFeatures(webpData, dataSize, &config.input) == VP8_STATUS_OK) { - float srcWidth = config.input.width; - float srcHeight = config.input.height; - int targetWidth = getWidth(); - int targetHeight = getHeight(); + float const srcWidth = config.input.width; + float const srcHeight = config.input.height; + int const targetWidth = getWidth(); + int const targetHeight = getHeight(); // Calculate the aspect ratios - float srcAspect = static_cast(srcWidth) / static_cast(srcHeight); - float targetAspect = static_cast(targetWidth) / static_cast(targetHeight); + float const srcAspect = srcWidth / srcHeight; + float const targetAspect = static_cast(targetWidth) / static_cast(targetHeight); // Crop the image to match the target aspect ratio int cropX = 0, cropY = 0, cropWidth = srcWidth, cropHeight = srcHeight; @@ -333,22 +336,22 @@ class OnlineImage final : public Component config.output.colorspace = MODE_rgbA; // or MODE_bgra for JUCE if (WebPDecode(webpData, dataSize, &config) == VP8_STATUS_OK) { - uint8_t* decodedData = config.output.u.RGBA.rgba; - int width = config.output.width; - int height = config.output.height; - int stride = config.output.u.RGBA.stride; - + uint8_t const* const decodedData = config.output.u.RGBA.rgba; + int const width = config.output.width; + int const height = config.output.height; + int const stride = config.output.u.RGBA.stride; + // Now copy this into a juce::Image webpImage = juce::Image(juce::Image::PixelFormat::ARGB, width, height, true); - juce::Image::BitmapData bitmapData(webpImage, juce::Image::BitmapData::writeOnly); + juce::Image::BitmapData const bitmapData(webpImage, juce::Image::BitmapData::writeOnly); for (int y = 0; y < targetHeight; ++y) { for (int x = 0; x < targetWidth; ++x) { - int index = y * stride + x * 4; - uint8_t r = decodedData[index + 0]; - uint8_t g = decodedData[index + 1]; - uint8_t b = decodedData[index + 2]; - uint8_t a = decodedData[index + 3]; + int const index = y * stride + x * 4; + uint8_t const r = decodedData[index + 0]; + uint8_t const g = decodedData[index + 1]; + uint8_t const b = decodedData[index + 2]; + uint8_t const a = decodedData[index + 3]; bitmapData.setPixelColour(x, y, juce::Colour(r, g, b, a)); } } @@ -531,8 +534,11 @@ class PatchDisplay final : public Component { repaint(); } - void mouseDown(MouseEvent const& e) override + void mouseUp(MouseEvent const& e) override { + if (!e.mods.isLeftButtonDown()) + return; + callback(info); } @@ -593,8 +599,8 @@ class PatchContainer final : public Component { patchDisplays.clear(); - for (auto& patch : patches) { - auto* display = patchDisplays.add(new PatchDisplay(patch.first, patchClicked, patch.second)); + for (auto& [info, flags] : patches) { + auto* display = patchDisplays.add(new PatchDisplay(info, patchClicked, flags)); addAndMakeVisible(display); } @@ -660,7 +666,7 @@ class PatchFullDisplay final : public Component }; Type type; - LinkButton(Type const type) + explicit LinkButton(Type const type) : type(type) { setClickingTogglesState(true); diff --git a/Source/Dialogs/PathsSettingsPanel.h b/Source/Dialogs/PathsSettingsPanel.h index 7d9cd1e3b7..c9841f5521 100644 --- a/Source/Dialogs/PathsSettingsPanel.h +++ b/Source/Dialogs/PathsSettingsPanel.h @@ -55,6 +55,9 @@ class ActionButton final : public Component { void mouseUp(MouseEvent const& e) override { + if (!e.mods.isLeftButtonDown()) + return; + onClick(); } diff --git a/Source/Dialogs/SnapSettings.h b/Source/Dialogs/SnapSettings.h index c2ef33c04e..fd786cced7 100644 --- a/Source/Dialogs/SnapSettings.h +++ b/Source/Dialogs/SnapSettings.h @@ -60,7 +60,7 @@ class SnapSettings final : public Component { CentersBit = 4 }; - static void show(PluginEditor* editor, Rectangle bounds) + static void show(PluginEditor* editor, Rectangle const bounds) { if (isShowing) return; @@ -194,6 +194,9 @@ class SnapSettings final : public Component { void mouseUp(MouseEvent const& e) override { + if (!e.mods.isLeftButtonDown()) + return; + for (auto* group : buttonGroups) { group->dragToggledInteraction = false; group->repaint(); diff --git a/Source/Dialogs/TextEditorDialog.h b/Source/Dialogs/TextEditorDialog.h index e8172626cb..fb677a78a4 100644 --- a/Source/Dialogs/TextEditorDialog.h +++ b/Source/Dialogs/TextEditorDialog.h @@ -16,12 +16,6 @@ #include "Utility/Config.h" #include "Constants.h" -#define GUTTER_WIDTH 48.f -#define CURSOR_WIDTH 3.f -#define TEXT_INDENT 4.f -#define TEST_MULTI_CARET_EDITING false -#define ENABLE_CARET_BLINK true - struct LuaTokeniserFunctions { static bool isIdentifierStart(juce_wchar const c) noexcept { @@ -651,27 +645,6 @@ class TextDocument; // stores text data and caret ranges, supplies metr class PlugDataTextEditor; // is a component, issues actions, computes view transform class Transaction; // a text replacement, the document computes the inverse on fulfilling it -template -class Memoizer { -public: - using FunctionType = std::function; - - explicit Memoizer(FunctionType f) - : f(std::move(f)) - { - } - DataType operator()(ArgType argument) const - { - if (map.contains(argument)) { - return map[argument]; - } - map[argument] = f(argument); - return this->operator()(argument); - } - FunctionType f; - mutable UnorderedMap map; -}; - /** A data structure encapsulating a contiguous range within a TextDocument. The head and tail refer to the leading and trailing edges of a selected @@ -689,12 +662,12 @@ struct Selection { }; Selection() = default; - Selection(Point head) + Selection(Point const head) : head(head) , tail(head) { } - Selection(Point head, Point tail) + Selection(Point const head, Point const tail) : head(head) , tail(tail) { @@ -852,10 +825,25 @@ class Transaction { */ class GlyphArrangementArray { public: + struct Entry { + Entry() = default; + Entry(String string, bool newline) + : string(std::move(string)), isNewLine(newline) + { + } + String string; + GlyphArrangement glyphsWithTrailingSpace; + GlyphArrangement glyphs; + SmallArray tokens; + bool glyphsAreDirty = true; + bool tokensAreDirty = true; + bool isNewLine; + }; + int size() const { return lines.size(); } void clear() { lines.clear(); } - void add(String const& string) { lines.add(string); } - void insert(int const index, String const& string) { lines.insert(index, string); } + void add(Entry const& entry) { lines.add(entry); } + void insert(int const index, Entry const& entry) { lines.insert(index, entry); } void removeRange(int const startIndex, int const numberToRemove) { lines.remove_range(startIndex, startIndex + numberToRemove); } String const& operator[](int index) const; @@ -866,6 +854,8 @@ class GlyphArrangementArray { float baseline, int token, bool withTrailingSpace = false) const; + + bool isNewLine(int index) const; private: friend class TextDocument; @@ -875,20 +865,7 @@ class GlyphArrangementArray { void ensureValid(int index) const; void invalidateAll(); - - struct Entry { - Entry() = default; - Entry(String string) - : string(std::move(string)) - { - } - String string; - GlyphArrangement glyphsWithTrailingSpace; - GlyphArrangement glyphs; - SmallArray tokens; - bool glyphsAreDirty = true; - bool tokensAreDirty = true; - }; + mutable SmallArray lines; }; @@ -928,13 +905,14 @@ class TextDocument { struct RowData { int rowNumber = 0; + int lineNumber = 0; bool isRowSelected = false; Rectangle bounds; }; class Iterator { public: - Iterator(TextDocument const& document, Point index) noexcept + Iterator(TextDocument const& document, Point const index) noexcept : document(&document) , index(index) { @@ -990,7 +968,12 @@ class TextDocument { lines.font = fontToUse; } - StringArray getText() const; + void setMaximumLineWidth(int maxWidth, float viewScaleFactor); + + String getText() const; + + /** Break up line to fit window width */ + SmallArray breakLine(String line); /** Replace the whole document content. */ void replaceAll(String const& content); @@ -1091,7 +1074,7 @@ class TextDocument { /** Navigate all selections. */ void navigateSelections(Target target, Direction direction, Selection::Part part); - void search(String const& text); + void search(String const& text, bool clearCurrent); /** Return the character at the given index. */ juce_wchar getCharacter(Point index) const; @@ -1132,21 +1115,93 @@ class TextDocument { int searchNext() { + if(searchSelections.size() == 0) + return 0; + currentSearchSelection++; currentSearchSelection %= searchSelections.size(); return currentSearchSelection; } + + std::pair getCurrentSearchSelection() + { + return {currentSearchSelection, searchSelections.size()}; + } + + void setViewScale(float scale) + { + viewScaleFactor = scale; + } + + std::pair getCaretPosition() + { + auto selections = getSelections(); + if(!selections.size()) return {0, 0}; + auto const& selection = selections[0]; + + + int lineNumber = 1; + int charNumber = selection.tail.y; + for (int n = selection.tail.x - 1; n >= 0; n--) + { + lineNumber += lines.isNewLine(n); + + if(lineNumber == 1) + charNumber += lines[n].length(); + } + + return {lineNumber, charNumber}; + } + + int getCaretOffset() + { + auto selections = getSelections(); + if(!selections.size()) return -1; + auto const& selection = selections[0]; + + int offset = 0; + for (int n = 0; n < selection.tail.x; ++n) + offset += lines[n].length(); + + offset += selection.tail.y; + + return offset; + } + + void setCaretOffset(int offset) + { + if(offset < 0) return; + + int running = 0; + for (int n = 0; n < lines.size(); ++n) + { + int lineLength = lines[n].length(); + if (offset <= running + lineLength) + { + int column = offset - running; + selections = { Selection(n, column, n, column) }; + return; + } + running += lineLength; + } + + int last = lines.size() - 1; + selections = { Selection(last, lines[last].length(), last, lines[last].length()) }; + } private: friend class PlugDataTextEditor; float lineSpacing = 1.25f; + float viewScaleFactor = 1.0f; mutable Rectangle cachedBounds; GlyphArrangementArray lines; Font font; SmallArray selections; SmallArray searchSelections; int currentSearchSelection = 0; + int maxCharWidth = 128; + String lastSearch; }; class Caret final : public Component @@ -1171,24 +1226,24 @@ class Caret final : public Component class GutterComponent final : public Component { public: explicit GutterComponent(TextDocument const& document); - void setViewTransform(AffineTransform const& transformToUse); void updateSelections(); + + void setViewTransform(AffineTransform const& transformToUse) + { + transform = transformToUse; + } void paint(Graphics& g) override; - private: - GlyphArrangement getLineNumberGlyphs(int row) const; - TextDocument const& document; AffineTransform transform; - Memoizer memoizedGlyphArrangements; }; class HighlightComponent final : public Component { public: explicit HighlightComponent(TextDocument const& document); void setViewTransform(AffineTransform const& transformToUse, SmallArray const& selections); - void updateSelections(SmallArray const& selections); + void updateSelections(SmallArray const& selections, int mainSelection = -1); void setHighlightColour(Colour const c) { highlightColour = c; } @@ -1200,16 +1255,14 @@ class HighlightComponent final : public Component { TextDocument const& document; AffineTransform transform; Path outlinePath; + Path mainSelectionPath; Colour highlightColour; + int lastMainSelection = -1; }; class PlugDataTextEditor final : public Component , public Timer { public: - enum class RenderScheme { - usingAttributedString, - usingGlyphArrangement, - }; PlugDataTextEditor(); @@ -1218,8 +1271,8 @@ class PlugDataTextEditor final : public Component void setText(String const& text); String getText() const; - void translateView(float dx, float dy); - void scaleView(float scaleFactor, float verticalCenter); + void translateView(float dy); + bool scaleView(float scaleFactor, float verticalCenter, bool absolute = false); void resized() override; void paint(Graphics& g) override; @@ -1263,9 +1316,15 @@ class PlugDataTextEditor final : public Component enableSyntaxHighlighting = enable; repaint(); } + + std::pair getCurrentSearchSelection() { return document.getCurrentSearchSelection(); } + + float getScale() { return viewScaleFactor; } void setSearchText(String const& searchText); void searchNext(); + + std::pair getCaretPosition() { return document.getCaretPosition(); } private: bool insert(String const& content); @@ -1275,13 +1334,10 @@ class PlugDataTextEditor final : public Component void translateToEnsureSearchIsVisible(int idx); void renderTextUsingAttributedString(Graphics& g); - void renderTextUsingGlyphArrangement(Graphics& g); bool enableSyntaxHighlighting = false; bool allowCoreGraphics = true; - RenderScheme renderScheme = RenderScheme::usingAttributedString; - double lastTransactionTime; bool tabKeyUsed = true; bool changed = false; @@ -1292,6 +1348,7 @@ class PlugDataTextEditor final : public Component HighlightComponent searchHighlight; float viewScaleFactor = 1.f; + float magnifyScaleFactor = 1.f; float mouseDownViewPosition; float scrollbarFadePosition = 0.0f; bool isOverScrollBar = false; @@ -1299,6 +1356,8 @@ class PlugDataTextEditor final : public Component Point translation; AffineTransform transform; UndoManager undo; + + static inline const StackArray zoomLevels = {0.75, 0.875, 1.0f, 1.125f, 1.25f, 1.375, 1.5f}; }; // IMPLEMENTATIONS @@ -1307,9 +1366,7 @@ Caret::Caret(TextDocument const& document) : document(document) { setInterceptsMouseClicks(false, false); -#if ENABLE_CARET_BLINK startTimerHz(20); -#endif } void Caret::setViewTransform(AffineTransform const& transformToUse) @@ -1355,8 +1412,8 @@ SmallArray> Caret::getCaretRectangles() const if (selection.head == selection.tail) { rectangles.add(document .getGlyphBounds(selection.head) - .removeFromLeft(CURSOR_WIDTH) - .translated(selection.head.y == 0 ? 0 : -0.5f * CURSOR_WIDTH, 0.f) + .removeFromLeft(3.f) + .translated(selection.head.y == 0 ? 0 : -0.5f * 3.f, 0.f) .transformedBy(transform) .expanded(0.f, 1.f)); } @@ -1366,17 +1423,10 @@ SmallArray> Caret::getCaretRectangles() const GutterComponent::GutterComponent(TextDocument const& document) : document(document) - , memoizedGlyphArrangements([this](int const row) { return getLineNumberGlyphs(row); }) { setInterceptsMouseClicks(false, false); } -void GutterComponent::setViewTransform(AffineTransform const& transformToUse) -{ - transform = transformToUse; - repaint(); -} - void GutterComponent::updateSelections() { repaint(); @@ -1384,61 +1434,47 @@ void GutterComponent::updateSelections() void GutterComponent::paint(Graphics& g) { - /* - Draw the gutter background, shadow, and outline - ------------------------------------------------------------------ - */ auto const ln = getParentComponent()->findColour(PlugDataColour::sidebarBackgroundColourId); - + auto const scaleFactor = std::sqrt(std::abs(transform.getDeterminant())); + + g.setColour(ln); - g.fillRect(getLocalBounds().removeFromLeft(GUTTER_WIDTH)); - - if (transform.getTranslationX() < GUTTER_WIDTH) { - auto const shadowRect = getLocalBounds().withLeft(GUTTER_WIDTH).withWidth(12); - - auto const gradient = ColourGradient::horizontal(ln.contrasting().withAlpha(0.3f), - Colours::transparentBlack, shadowRect); - g.setFillType(gradient); - g.fillRect(shadowRect); - } else { - g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); - g.drawVerticalLine(GUTTER_WIDTH - 1.f, 0.f, getHeight()); - } + g.fillRect(getLocalBounds().removeFromLeft(48.f * scaleFactor)); + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); + g.drawVerticalLine(47.f * scaleFactor, 0.f, getHeight()); - /* - Draw the line numbers and selected rows - ------------------------------------------------------------------ - */ + auto const area = g.getClipBounds().toFloat().transformedBy(transform.inverted()); auto rowData = document.findRowsIntersecting(area); auto const verticalTransform = transform.withAbsoluteTranslation(0.f, transform.getTranslationY()); g.setColour(findColour(PlugDataColour::sidebarActiveBackgroundColourId)); + for (int i = 0; i < rowData.size(); i++) { + if (rowData[i].isRowSelected) { + auto line = i; + while (line > 0 && rowData[line - 1].lineNumber == rowData[i].lineNumber) { + line--; + } + + auto A = rowData[line].bounds + .transformedBy(transform) + .withX(0) + .withWidth(48.f * scaleFactor); + + g.fillRoundedRectangle(A.reduced(4, 1), Corners::defaultCornerRadius); + } + } + + int lastLineNumber = -1; for (auto const& r : rowData) { - if (r.isRowSelected) { - auto A = r.bounds - .transformedBy(transform) - .withX(0) - .withWidth(GUTTER_WIDTH); - - g.fillRoundedRectangle(A.reduced(4, 1), Corners::defaultCornerRadius); + if (lastLineNumber != r.lineNumber) { + g.setColour(getParentComponent()->findColour(PlugDataColour::panelTextColourId)); + g.setFont(document.getFont().withHeight(12.f * scaleFactor)); + g.drawSingleLineText(String(r.lineNumber), 8.f * scaleFactor, (document.getVerticalPosition(r.rowNumber, TextDocument::Metric::baseline) * scaleFactor) + verticalTransform.getTranslationY()); + lastLineNumber = r.lineNumber; } } - - for (auto const& r : rowData) { - g.setColour(getParentComponent()->findColour(PlugDataColour::panelTextColourId)); - memoizedGlyphArrangements(r.rowNumber).draw(g, verticalTransform); - } -} - -GlyphArrangement GutterComponent::getLineNumberGlyphs(int const row) const -{ - GlyphArrangement glyphs; - glyphs.addLineOfText(document.getFont().withHeight(12.f), - String(row + 1), - 8.f, document.getVerticalPosition(row, TextDocument::Metric::baseline)); - return glyphs; } HighlightComponent::HighlightComponent(TextDocument const& document) @@ -1450,25 +1486,25 @@ HighlightComponent::HighlightComponent(TextDocument const& document) void HighlightComponent::setViewTransform(AffineTransform const& transformToUse, SmallArray const& selections) { transform = transformToUse; - - outlinePath.clear(); - auto const clip = getLocalBounds().toFloat().transformedBy(transform.inverted()); - - for (auto const& s : selections) { - outlinePath.addPath(getOutlinePath(document.getSelectionRegion(s, clip))); - } - repaint(outlinePath.getBounds().getSmallestIntegerContainer()); + updateSelections(selections, lastMainSelection); } -void HighlightComponent::updateSelections(SmallArray const& selections) +void HighlightComponent::updateSelections(SmallArray const& selections, int mainSelection) { + lastMainSelection = mainSelection; outlinePath.clear(); + mainSelectionPath.clear(); + auto const clip = getLocalBounds().toFloat().transformedBy(transform.inverted()); - - for (auto const& s : selections) { + for (int i = 0; i < selections.size(); i++) { + auto const& s = selections[i]; + if(s.head == s.tail) continue; + if(i == mainSelection) + mainSelectionPath.addPath(getOutlinePath(document.getSelectionRegion(s, clip))); + outlinePath.addPath(getOutlinePath(document.getSelectionRegion(s, clip))); } - + repaint(outlinePath.getBounds().getSmallestIntegerContainer()); } @@ -1478,6 +1514,9 @@ void HighlightComponent::paint(Graphics& g) g.setColour(highlightColour); g.fillPath(outlinePath); + + g.setColour(highlightColour.withRotatedHue(0.5)); + g.fillPath(mainSelectionPath); g.setColour(highlightColour.darker()); g.strokePath(outlinePath, PathStrokeType(1.f)); @@ -1571,7 +1610,7 @@ Selection Selection::measuring(String const& content) const return Selection(content).startingFrom(tail).swapped(); } -Selection Selection::startingFrom(Point index) const +Selection Selection::startingFrom(Point const index) const { Selection s = *this; @@ -1671,6 +1710,11 @@ int GlyphArrangementArray::getToken(int const row, int const col, int const defa return lines[row].tokens[col]; } +bool GlyphArrangementArray::isNewLine(int index) const +{ + return lines[index].isNewLine; +} + void GlyphArrangementArray::clearTokens(int const index) { if (!isPositiveAndBelow(index, lines.size())) @@ -1705,11 +1749,12 @@ GlyphArrangement GlyphArrangementArray::getGlyphs(int const index, int const token, bool const withTrailingSpace) const { + constexpr float textIndent = 4.f; if (!isPositiveAndBelow(index, lines.size())) { GlyphArrangement glyphs; if (withTrailingSpace) { - glyphs.addLineOfText(font, " ", TEXT_INDENT, baseline); + glyphs.addLineOfText(font, " ", textIndent, baseline); } return glyphs; } @@ -1722,7 +1767,7 @@ GlyphArrangement GlyphArrangementArray::getGlyphs(int const index, for (int n = 0; n < glyphSource.getNumGlyphs(); ++n) { if (token == -1 || entry.tokens[n] == token) { auto glyph = glyphSource.getGlyph(n); - glyph.moveBy(TEXT_INDENT, baseline); + glyph.moveBy(textIndent, baseline); glyphs.addGlyph(glyph); } } @@ -1752,23 +1797,53 @@ void GlyphArrangementArray::invalidateAll() } } +void TextDocument::setMaximumLineWidth(int maxWidth, float viewScaleFactor) { + maxCharWidth = (maxWidth - 12 - (48.f * viewScaleFactor)) / (7.052f * viewScaleFactor); + auto caretOffset = getCaretOffset(); + replaceAll(getText()); + search(lastSearch, false); + setCaretOffset(caretOffset); +} + +SmallArray TextDocument::breakLine(String line) +{ + SmallArray lines; + + if(line.endsWith("\n")) + line = line.substring(0, line.length() - 1); + + while (line.length() > maxCharWidth) { + lines.add({line.substring(0, maxCharWidth), false}); + line = line.substring(maxCharWidth); + } + lines.add({line, true}); + + return lines; +} + void TextDocument::replaceAll(String const& content) { lines.clear(); for (auto const& line : StringArray::fromLines(content)) { - lines.add(line); + for(auto const& entry : breakLine(line)) + { + lines.add(entry); + } } } -StringArray TextDocument::getText() const +String TextDocument::getText() const { - StringArray text; + String text; for (int i = 0; i < lines.size(); i++) { - text.add(lines[i]); + text += lines[i]; + if(lines.isNewLine(i)) { + text += "\n"; + } } - return text; + return text.trimCharactersAtEnd("\n\r"); } int TextDocument::getNumRows() const @@ -1802,12 +1877,12 @@ float TextDocument::getVerticalPosition(int const row, Metric const metric) cons } } -Point TextDocument::getPosition(Point index, Metric const metric) const +Point TextDocument::getPosition(Point const index, Metric const metric) const { return { getGlyphBounds(index).getX(), getVerticalPosition(index.x, metric) }; } -SmallArray> TextDocument::getSelectionRegion(Selection selection, Rectangle clip) const +SmallArray> TextDocument::getSelectionRegion(Selection const selection, Rectangle const clip) const { SmallArray> patches; Selection const s = selection.oriented(); @@ -1852,7 +1927,7 @@ Rectangle TextDocument::getBounds() const return cachedBounds; } -Rectangle TextDocument::getBoundsOnRow(int const row, Range columns) const +Rectangle TextDocument::getBoundsOnRow(int const row, Range const columns) const { return getGlyphsForRow(row, -1, true) .getBoundingBox(columns.getStart(), columns.getLength(), true) @@ -1874,7 +1949,7 @@ GlyphArrangement TextDocument::getGlyphsForRow(int const row, int const token, b withTrailingSpace); } -GlyphArrangement TextDocument::findGlyphsIntersecting(Rectangle area, int const token) const +GlyphArrangement TextDocument::findGlyphsIntersecting(Rectangle const area, int const token) const { auto const range = getRangeOfRowsIntersecting(area); auto rows = SmallArray(); @@ -1886,7 +1961,7 @@ GlyphArrangement TextDocument::findGlyphsIntersecting(Rectangle area, int return glyphs; } -Range TextDocument::getRangeOfRowsIntersecting(Rectangle area) const +Range TextDocument::getRangeOfRowsIntersecting(Rectangle const area) const { auto const lineHeight = font.getHeight() * lineSpacing; auto row0 = jlimit(0, jmax(getNumRows() - 1, 0), static_cast(area.getY() / lineHeight)); @@ -1894,37 +1969,42 @@ Range TextDocument::getRangeOfRowsIntersecting(Rectangle area) const return { row0, row1 + 1 }; } -SmallArray TextDocument::findRowsIntersecting(Rectangle area, +SmallArray TextDocument::findRowsIntersecting(Rectangle const area, bool const computeHorizontalExtent) const { auto const range = getRangeOfRowsIntersecting(area); auto rows = SmallArray(); - for (int n = range.getStart(); n < range.getEnd(); ++n) { - RowData data; - data.rowNumber = n; + int lineNumber = 1; + for (int n = 0; n < range.getEnd(); ++n) { + if (n >= range.getStart()) { + RowData data; + data.rowNumber = n; + data.lineNumber = lineNumber; - if (computeHorizontalExtent) // slower - { - data.bounds = getBoundsOnRow(n, Range(0, getNumColumns(n))); - } else // faster - { - data.bounds.setY(getVerticalPosition(n, Metric::top)); - data.bounds.setBottom(getVerticalPosition(n, Metric::bottom)); - } + if (computeHorizontalExtent) // slower + { + data.bounds = getBoundsOnRow(n, Range(0, getNumColumns(n))); + } else // faster + { + data.bounds.setY(getVerticalPosition(n, Metric::top)); + data.bounds.setBottom(getVerticalPosition(n, Metric::bottom)); + } - for (auto const& s : selections) { - if (s.intersectsRow(n)) { - data.isRowSelected = true; - break; + for (auto const& s : selections) { + if (s.intersectsRow(n)) { + data.isRowSelected = true; + break; + } } + rows.add(data); } - rows.add(data); + lineNumber += lines.isNewLine(n); } return rows; } -Point TextDocument::findIndexNearestPosition(Point position) const +Point TextDocument::findIndexNearestPosition(Point const position) const { auto const lineHeight = font.getHeight() * lineSpacing; auto row = jlimit(0, jmax(getNumRows() - 1, 0), static_cast(position.y / lineHeight)); @@ -2008,7 +2088,7 @@ void TextDocument::navigate(Point& i, Target const target, Direction const switch (direction) { case Direction::forwardRow: advance = [this](Point& i) { return nextRow(i); }; - get = [this](Point i) { return getCharacter(i); }; + get = [this](Point const i) { return getCharacter(i); }; break; case Direction::backwardRow: advance = [this](Point& i) { return prevRow(i); }; @@ -2016,7 +2096,7 @@ void TextDocument::navigate(Point& i, Target const target, Direction const break; case Direction::forwardCol: advance = [this](Point& i) { return next(i); }; - get = [this](Point i) { return getCharacter(i); }; + get = [this](Point const i) { return getCharacter(i); }; break; case Direction::backwardCol: advance = [this](Point& i) { return prev(i); }; @@ -2067,9 +2147,9 @@ void TextDocument::navigate(Point& i, Target const target, Direction const } } -void TextDocument::navigateSelections(Target const target, Direction const direction, Selection::Part part) +void TextDocument::navigateSelections(Target const target, Direction const direction, Selection::Part const part) { - auto isHeadBeforeTail = [](Point head, Point tail) -> int { + auto isHeadBeforeTail = [](Point const head, Point const tail) -> int { if (head.x == tail.x) return head.y == tail.y ? -1 : head.y < tail.y; return head.x < tail.x; @@ -2099,8 +2179,9 @@ void TextDocument::navigateSelections(Target const target, Direction const direc } } -void TextDocument::search(String const& text) +void TextDocument::search(String const& text, bool clearCurrent) { + lastSearch = text; selections.clear(); searchSelections.clear(); @@ -2110,9 +2191,11 @@ void TextDocument::search(String const& text) searchSelections.add(Selection(Point(i, idx), Point(i, idx + text.length()))); } } + + if(clearCurrent) currentSearchSelection = -1; } -juce_wchar TextDocument::getCharacter(Point index) const +juce_wchar TextDocument::getCharacter(Point const index) const { jassert(0 <= index.x && index.x <= lines.size()); jassert(0 <= index.y && index.y <= lines[index.x].length()); @@ -2145,10 +2228,11 @@ String TextDocument::getSelectionContent(Selection s) const if (s.isSingleLine()) { return lines[s.head.x].substring(s.head.y, s.tail.y); } - String content = lines[s.head.x].substring(s.head.y) + "\n"; + + String content = lines[s.head.x].substring(s.head.y) + (lines.isNewLine(s.head.x) ? "\n" : ""); for (int row = s.head.x + 1; row < s.tail.x; ++row) { - content += lines[row] + "\n"; + content += lines[row] + (lines.isNewLine(row) ? "\n" : ""); } content += lines[s.tail.x].substring(0, s.tail.y); return content; @@ -2174,12 +2258,14 @@ Transaction TextDocument::fulfill(Transaction const& transaction) int row = s.head.x; if (M.isEmpty()) { - lines.insert(row++, String()); + lines.insert(row++, {String(), true}); } for (auto const& line : StringArray::fromLines(M)) { - lines.insert(row++, line); + for(auto const& entry : breakLine(line)) { + lines.insert(row++, entry); + } } - + using D = Transaction::Direction; constexpr auto inf = std::numeric_limits::max(); @@ -2192,14 +2278,14 @@ Transaction TextDocument::fulfill(Transaction const& transaction) return r; } -void TextDocument::clearTokens(Range rows) +void TextDocument::clearTokens(Range const rows) { for (int n = rows.getStart(); n < rows.getEnd(); ++n) { lines.clearTokens(n); } } -void TextDocument::applyTokens(Range rows, SmallArray const& zones) +void TextDocument::applyTokens(Range const rows, SmallArray const& zones) { for (int n = rows.getStart(); n < rows.getEnd(); ++n) { for (auto const& zone : zones) { @@ -2275,7 +2361,7 @@ PlugDataTextEditor::PlugDataTextEditor() setFont(Font(Fonts::getMonospaceFont().withHeight(15.5f))); - translateView(GUTTER_WIDTH, 0); + translateView(0); setWantsKeyboardFocus(true); addAndMakeVisible(highlight); @@ -2313,28 +2399,45 @@ void PlugDataTextEditor::setText(String const& text) String PlugDataTextEditor::getText() const { - return document.getText().joinIntoString("\r"); + return document.getText(); } -void PlugDataTextEditor::translateView(float const dx, float const dy) +void PlugDataTextEditor::translateView(float const dy) { - auto const W = viewScaleFactor * document.getBounds().getWidth(); auto const H = viewScaleFactor * document.getBounds().getHeight(); - translation.x = jlimit(jmin(GUTTER_WIDTH, -W + getWidth()), GUTTER_WIDTH, translation.x + dx); + translation.x = 48.f * viewScaleFactor; translation.y = jlimit(jmin(-0.f, -H + (getHeight() - 10)), 0.0f, translation.y + dy); updateViewTransform(); } -void PlugDataTextEditor::scaleView(float const scaleFactorMultiplier, float const verticalCenter) +bool PlugDataTextEditor::scaleView(float const scaleFactor, float const verticalCenter, bool absolute) { - auto const newS = viewScaleFactor * scaleFactorMultiplier; + auto const oldS = viewScaleFactor; + auto targetScale = absolute ? scaleFactor : viewScaleFactor * scaleFactor; + auto const fixedy = Point(0, verticalCenter).transformedBy(transform.inverted()).y; - - translation.y = -newS * fixedy + verticalCenter; - viewScaleFactor = newS; - updateViewTransform(); + + viewScaleFactor = std::max(0.7f, *std::min_element(zoomLevels.begin(), zoomLevels.end(), + [targetScale](float a, float b) { + return std::abs(a - targetScale) < std::abs(b - targetScale); + })); + + std::cout << scaleFactor << std::endl; + std::cout << viewScaleFactor << std::endl; + std::cout << std::endl; + + if(!approximatelyEqual(viewScaleFactor, oldS)){ + translation.x = 48.f * viewScaleFactor; + translation.y = std::min(0.0f, -viewScaleFactor * fixedy + verticalCenter); + updateViewTransform(); + document.setMaximumLineWidth(getWidth(), viewScaleFactor); + updateSelections(); + return true; + } + + return false; } void PlugDataTextEditor::updateViewTransform() @@ -2350,9 +2453,10 @@ void PlugDataTextEditor::updateViewTransform() void PlugDataTextEditor::updateSelections() { highlight.updateSelections(document.getSelections()); - searchHighlight.updateSelections(document.getSearchSelections()); + searchHighlight.updateSelections(document.getSearchSelections(), document.getCurrentSearchSelection().first); caret.updateSelections(); gutter.updateSelections(); + if(auto* parent = getParentComponent()) parent->repaint(); } void PlugDataTextEditor::translateToEnsureCaretIsVisible() @@ -2362,9 +2466,9 @@ void PlugDataTextEditor::translateToEnsureCaretIsVisible() auto const b = Point(0.f, document.getVerticalPosition(i.x, TextDocument::Metric::bottom)).transformedBy(transform); if (t.y < 0.f) { - translateView(0.f, -t.y); + translateView(-t.y); } else if (b.y > getHeight()) { - translateView(0.f, -b.y + getHeight()); + translateView(-b.y + getHeight()); } } @@ -2379,9 +2483,9 @@ void PlugDataTextEditor::translateToEnsureSearchIsVisible(int const index) auto const b = Point(0.f, document.getVerticalPosition(i.x, TextDocument::Metric::bottom)).transformedBy(transform); if (t.y < 0.f) { - translateView(0.f, -t.y); + translateView(-t.y); } else if (b.y > getHeight()) { - translateView(0.f, -b.y + getHeight()); + translateView(-b.y + getHeight()); } } @@ -2391,23 +2495,80 @@ void PlugDataTextEditor::resized() searchHighlight.setBounds(getLocalBounds()); caret.setBounds(getLocalBounds()); gutter.setBounds(getLocalBounds()); + document.setMaximumLineWidth(getWidth(), viewScaleFactor); + updateSelections(); } void PlugDataTextEditor::paint(Graphics& g) { g.fillAll(findColour(PlugDataColour::canvasBackgroundColourId)); - String renderSchemeString; + auto const colourScheme = getSyntaxColourScheme(); + auto const originalHeight = document.getFont().getHeight(); + auto const scaleFactor = std::sqrt(std::abs(transform.getDeterminant())); + auto const font = document.getFont().withHeight(originalHeight * scaleFactor); + auto rows = document.findRowsIntersecting(g.getClipBounds().toFloat().transformedBy(transform.inverted())); + + for (auto const& r : rows) { + auto line = document.getLine(r.rowNumber); + auto const T = document.getVerticalPosition(r.rowNumber, TextDocument::Metric::ascent); + auto const B = document.getVerticalPosition(r.rowNumber, TextDocument::Metric::bottom); + auto bounds = Rectangle::leftTopRightBottom(0.f, T, 10000.f, B).transformedBy(transform).translated(4, 0); - switch (renderScheme) { - case RenderScheme::usingAttributedString: - renderTextUsingAttributedString(g); - renderSchemeString = "attr. str"; - break; - case RenderScheme::usingGlyphArrangement: - renderTextUsingGlyphArrangement(g); - renderSchemeString = "glyph arr."; - break; + AttributedString s; + if (!enableSyntaxHighlighting) { + s.append(line, font, findColour(PlugDataColour::panelTextColourId)); + } else { + // Build the full logical line by backtracking to the start + String fullLine; + int currentRow = r.rowNumber; + int charsBeforeCurrentLine = 0; + + // Go backwards to find the start of the logical line + // If currentRow - 1 has isNewLine == false, it means currentRow is a continuation + while (currentRow > 0 && !document.lines.isNewLine(currentRow - 1)) { + currentRow--; + } + + for (int i = currentRow; i <= r.rowNumber; i++) { + auto segmentLine = document.getLine(i); + if (i < r.rowNumber) { + charsBeforeCurrentLine += segmentLine.length(); + } + fullLine += segmentLine; + } + + // Tokenize the full logical line + LuaTokeniserFunctions::StringIterator si(fullLine); + auto previous = si.t; + int charCount = 0; + + while (!si.isEOF()) { + auto const tokenType = LuaTokeniserFunctions::readNextToken(si); + auto const colour = colourScheme.types[tokenType].colour; + auto token = String(previous, si.t); + previous = si.t; + + int tokenStart = charCount; + int tokenEnd = charCount + token.length(); + charCount = tokenEnd; + + if (tokenEnd > charsBeforeCurrentLine && tokenStart < charsBeforeCurrentLine + line.length()) { + int startOffset = jmax(0, charsBeforeCurrentLine - tokenStart); + int endOffset = jmin(token.length(), charsBeforeCurrentLine + line.length() - tokenStart); + auto visiblePart = token.substring(startOffset, endOffset); + s.append(visiblePart, font, colour); + } + } + } + + if (allowCoreGraphics) { + s.draw(g, bounds); + } else { + TextLayout layout; + layout.createLayout(s, bounds.getWidth()); + layout.draw(g, bounds); + } } auto const scrollBarBounds = getScrollBarBounds(); @@ -2465,7 +2626,7 @@ void PlugDataTextEditor::mouseDown(MouseEvent const& e) bool const wasOriented = selection.isOriented(); auto orientedSelection = selection.oriented(); - auto isBeforeSelection = [](Point index, Point selection) -> int { + auto isBeforeSelection = [](Point const index, Point const selection) -> int { if (index.x == selection.x) return index.y == selection.y ? -1 : index.y < selection.y; return index.x < selection.x; @@ -2482,9 +2643,7 @@ void PlugDataTextEditor::mouseDown(MouseEvent const& e) updateSelections(); return; } - if (!e.mods.isCommandDown() || !TEST_MULTI_CARET_EDITING) { - selections.clear(); - } + selections.clear(); selections.add(index); document.setSelections(selections); @@ -2541,14 +2700,16 @@ void PlugDataTextEditor::mouseDoubleClick(MouseEvent const& e) void PlugDataTextEditor::mouseWheelMove(MouseEvent const& e, MouseWheelDetails const& d) { - float dx = d.deltaX; - /* - make scrolling away from the gutter just a little "sticky" - */ - if (translation.x == GUTTER_WIDTH && -0.01f < dx && dx < 0.f) { - dx = 0.f; + if(e.mods.isCommandDown()) { + magnifyScaleFactor = std::clamp(magnifyScaleFactor * 1.0f + d.deltaY, 0.8f, 1.2f); + if(scaleView(magnifyScaleFactor, e.position.y)) + { + magnifyScaleFactor = 1.0f; + } + return; } - translateView(dx * 400, d.deltaY * 800); + + translateView(d.deltaY * 800); } void PlugDataTextEditor::timerCallback() @@ -2570,7 +2731,11 @@ void PlugDataTextEditor::timerCallback() void PlugDataTextEditor::mouseMagnify(MouseEvent const& e, float const scaleFactor) { - scaleView(scaleFactor, e.position.y); + magnifyScaleFactor = std::clamp(magnifyScaleFactor * (((scaleFactor - 1.0f) * 0.8f) + 1.0f), 0.8f, 1.2f); + if(scaleView(magnifyScaleFactor, e.position.y)) + { + magnifyScaleFactor = 1.0f; + } } bool PlugDataTextEditor::keyPressed(KeyPress const& key) @@ -2643,6 +2808,10 @@ bool PlugDataTextEditor::keyPressed(KeyPress const& key) if (key == KeyPress('a', ModifierKeys::ctrlModifier, 0) || key == KeyPress('a', ModifierKeys::ctrlModifier | ModifierKeys::shiftModifier, 0)) return nav(Target::line, Direction::backwardCol); } if (mods.isCommandDown()) { + if (key.isKeyCode(61)) // + + return scaleView(1.125, 0); + if (key.isKeyCode(45)) // - + return scaleView(0.8888, 0); if (key.isKeyCode(KeyPress::downKey)) return nav(Target::document, Direction::forwardRow); if (key.isKeyCode(KeyPress::upKey)) @@ -2734,7 +2903,7 @@ MouseCursor PlugDataTextEditor::getMouseCursor() if (isOverScrollBar) return MouseCursor::NormalCursor; - return getMouseXYRelative().x < GUTTER_WIDTH && getMouseXYRelative().x > getWidth() - 10 ? MouseCursor::NormalCursor : MouseCursor::IBeamCursor; + return getMouseXYRelative().x < (48.f * viewScaleFactor) && getMouseXYRelative().x > getWidth() - 10 ? MouseCursor::NormalCursor : MouseCursor::IBeamCursor; } CodeEditorComponent::ColourScheme PlugDataTextEditor::getSyntaxColourScheme() @@ -2784,9 +2953,11 @@ CodeEditorComponent::ColourScheme PlugDataTextEditor::getSyntaxColourScheme() void PlugDataTextEditor::setSearchText(String const& searchText) { - document.search(searchText); + document.search(searchText, true); updateSelections(); - translateToEnsureSearchIsVisible(0); + if(searchText.isNotEmpty()) { + translateToEnsureSearchIsVisible(0); + } } void PlugDataTextEditor::searchNext() @@ -2796,85 +2967,6 @@ void PlugDataTextEditor::searchNext() translateToEnsureSearchIsVisible(next); } -void PlugDataTextEditor::renderTextUsingAttributedString(Graphics& g) -{ - /* - Credit to chrisboy2000 for this - */ - auto const colourScheme = getSyntaxColourScheme(); - auto const originalHeight = document.getFont().getHeight(); - - auto const scaleFactor = std::sqrt(std::abs(transform.getDeterminant())); - auto const font = document.getFont().withHeight(originalHeight * scaleFactor); - auto rows = document.findRowsIntersecting(g.getClipBounds().toFloat().transformedBy(transform.inverted())); - - for (auto const& r : rows) { - auto line = document.getLine(r.rowNumber); - auto const T = document.getVerticalPosition(r.rowNumber, TextDocument::Metric::ascent); - auto const B = document.getVerticalPosition(r.rowNumber, TextDocument::Metric::bottom); - auto bounds = Rectangle::leftTopRightBottom(0.f, T, 1000.f, B).transformedBy(transform).translated(4, 0); - - AttributedString s; - - if (!enableSyntaxHighlighting) { - s.append(line, font, findColour(PlugDataColour::panelTextColourId)); - } else { - LuaTokeniserFunctions::StringIterator si(line); - auto previous = si.t; - - while (!si.isEOF()) { - auto const tokenType = LuaTokeniserFunctions::readNextToken(si); - auto const colour = enableSyntaxHighlighting ? colourScheme.types[tokenType].colour : findColour(PlugDataColour::panelTextColourId); - auto token = String(previous, si.t); - - previous = si.t; - s.append(token, font, colour); - } - } - if (allowCoreGraphics) { - s.draw(g, bounds); - } else { - TextLayout layout; - layout.createLayout(s, bounds.getWidth()); - layout.draw(g, bounds); - } - } -} - -void PlugDataTextEditor::renderTextUsingGlyphArrangement(Graphics& g) -{ - g.saveState(); - g.addTransform(transform); - - if (enableSyntaxHighlighting) { - auto const colourScheme = getSyntaxColourScheme(); - auto const rows = document.getRangeOfRowsIntersecting(g.getClipBounds().toFloat()); - auto index = Point(rows.getStart(), 0); - document.navigate(index, TextDocument::Target::token, TextDocument::Direction::backwardRow); - - auto it = TextDocument::Iterator(document, { 0, 0 }); - auto previous = it.getIndex(); - auto zones = SmallArray(); - - while (it.getIndex().x < rows.getEnd() && !it.isEOF()) { - auto const tokenType = LuaTokeniserFunctions::readNextToken(it); - zones.add(Selection(previous, it.getIndex()).withStyle(tokenType)); - previous = it.getIndex(); - } - document.clearTokens(rows); - document.applyTokens(rows, zones); - - for (int n = 0; n < colourScheme.types.size(); ++n) { - g.setColour(colourScheme.types[n].colour); - document.findGlyphsIntersecting(g.getClipBounds().toFloat(), n).draw(g); - } - } else { - g.setColour(findColour(PlugDataColour::panelTextColourId)); - document.findGlyphsIntersecting(g.getClipBounds().toFloat()).draw(g); - } - g.restoreState(); -} - struct TextEditorDialog final : public Component , public ChangeListener { ResizableBorderComponent resizer; @@ -2887,7 +2979,8 @@ struct TextEditorDialog final : public Component MainToolbarButton undoButton = MainToolbarButton(Icons::Undo); MainToolbarButton redoButton = MainToolbarButton(Icons::Redo); MainToolbarButton searchButton = MainToolbarButton(Icons::Search); - + + SmallIconButton zoomComboButton; SearchEditor searchInput; std::function onClose; @@ -2929,7 +3022,23 @@ struct TextEditorDialog final : public Component addAndMakeVisible(undoButton); addAndMakeVisible(redoButton); addAndMakeVisible(searchButton); - + addAndMakeVisible(zoomComboButton); + + zoomComboButton.setButtonText(Icons::ThinDown); + + zoomComboButton.onClick = [this] { + PopupMenu zoomMenu; + auto zoomOptions = StringArray { "75%", "87.5%", "100%", "112.5%", "125%", "137.5%", "150%",}; + for (auto zoomOption : zoomOptions) { + auto scale = zoomOption.upToFirstOccurrenceOf("%", false, false).getIntValue() / 100.0f; + zoomMenu.addItem(zoomOption, [this, scale] { + editor.scaleView(scale, 0, true); + }); + } + zoomMenu.showMenuAsync(PopupMenu::Options().withMinimumWidth(150).withMaximumNumColumns(1).withTargetComponent(&zoomComboButton)); + }; + + editor.setUndoChangeListener(this); undoButton.onClick = [this] { @@ -2943,7 +3052,7 @@ struct TextEditorDialog final : public Component onSave(editor.getText()); editor.setUnchanged(); }; - + searchButton.onClick = [this] { searchInput.setVisible(searchButton.getToggleState()); editor.setSearchText(""); @@ -2966,6 +3075,7 @@ struct TextEditorDialog final : public Component searchInput.setColour(TextEditor::focusedOutlineColourId, Colours::transparentBlack); searchInput.setJustification(Justification::centredLeft); searchInput.setBorder({ 0, 3, 5, 1 }); + searchInput.setAlwaysOnTop(true); searchInput.onTextChange = [this] { editor.setSearchText(searchInput.getText()); }; @@ -3012,7 +3122,10 @@ struct TextEditorDialog final : public Component undoButton.setBounds(undoButtonBounds); redoButton.setBounds(redoButtonBounds); - editor.setBounds(b.withTrimmedBottom(20)); + auto statusBarBounds = b.removeFromBottom(28); + editor.setBounds(b); + + zoomComboButton.setBounds(statusBarBounds.removeFromRight(26)); } void mouseDown(MouseEvent const& e) override @@ -3024,13 +3137,7 @@ struct TextEditorDialog final : public Component { windowDragger.dragComponent(this, e, nullptr); } - - void paintOverChildren(Graphics& g) override - { - g.setColour(findColour(PlugDataColour::outlineColourId)); - g.drawRoundedRectangle(getLocalBounds().reduced(margin).toFloat(), ProjectInfo::canUseSemiTransparentWindows() ? Corners::windowCornerRadius : 0.0f, 1.0f); - } - + bool keyPressed(KeyPress const& key) override { if (key == KeyPress('s', ModifierKeys::commandModifier, 0)) { @@ -3046,6 +3153,31 @@ struct TextEditorDialog final : public Component return false; } + + void paintOverChildren(Graphics& g) override + { + g.setColour(findColour(PlugDataColour::outlineColourId)); + g.drawRoundedRectangle(getLocalBounds().reduced(margin).toFloat(), ProjectInfo::canUseSemiTransparentWindows() ? Corners::windowCornerRadius : 0.0f, 1.0f); + + if(searchInput.isVisible() && searchInput.getText().isNotEmpty()) { + g.setColour(findColour(PlugDataColour::outlineColourId)); + g.drawRoundedRectangle(getLocalBounds().reduced(margin).toFloat(), ProjectInfo::canUseSemiTransparentWindows() ? Corners::windowCornerRadius : 0.0f, 1.0f); + + auto [selection, total] = editor.getCurrentSearchSelection(); + auto tabularFont = Fonts::getTabularNumbersFont().withHeight(13); + auto searchIndexText = String(selection + 1) + " / " + String(total); + auto searchIndexTextWidth = tabularFont.getStringWidth(searchIndexText) + 8; + + auto searchIndexBounds = searchInput.getBounds().withTrimmedRight(30).removeFromRight(searchIndexTextWidth).reduced(0, 6); + g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); + g.fillRoundedRectangle(searchIndexBounds.toFloat(), Corners::defaultCornerRadius); + + g.setColour(findColour(PlugDataColour::toolbarTextColourId)); + g.setFont(tabularFont); + g.drawFittedText(searchIndexText, searchIndexBounds.reduced(1), Justification::centred, 1); + } + } + void paint(Graphics& g) override { @@ -3064,11 +3196,15 @@ struct TextEditorDialog final : public Component g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); g.drawRoundedRectangle(b.toFloat().reduced(0.5f), radius, 1.0f); - - g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); - // g.drawHorizontalLine(b.getX() + 39, b.getY() + 48, b.getWidth()); - g.drawHorizontalLine(b.getHeight() - 20, b.getY() + 48, b.getWidth()); - + g.drawHorizontalLine(b.getHeight() - 28, b.getY() + 48, b.getWidth()); + + g.setFont(Fonts::getTabularNumbersFont().withHeight(14)); + g.setColour(findColour(PlugDataColour::toolbarTextColourId)); + g.drawFittedText(String(static_cast(editor.getScale() * 100.f)) + "%", zoomComboButton.getX() - 26, b.getHeight() - 14, 30, 28, Justification::centredRight, 1, 0.95f); + + auto caretPos = editor.getCaretPosition(); + g.drawFittedText(String(caretPos.first) + ":" + String(caretPos.second), margin + 8, b.getHeight() - 14, 128, 28, Justification::centredLeft, 1, 0.95f); + if (!title.isEmpty()) { Fonts::drawText(g, title, b.getX(), b.getY(), b.getWidth(), 40, findColour(PlugDataColour::toolbarTextColourId), 15, Justification::centred); } diff --git a/Source/Heavy/CompatibleObjects.h b/Source/Heavy/CompatibleObjects.h index 317e538521..9a517648d4 100644 --- a/Source/Heavy/CompatibleObjects.h +++ b/Source/Heavy/CompatibleObjects.h @@ -33,6 +33,7 @@ class HeavyCompatibleObjects { "bang", "bendin", "bendout", + "block~", // ignored "bng", "canvas", "change", @@ -48,6 +49,7 @@ class HeavyCompatibleObjects { "delay", "div", "exp", + "expr", "f", "float", "ftom", @@ -138,6 +140,7 @@ class HeavyCompatibleObjects { "delread4~", "env~", "exp~", + "expr~", "ftom~", "hip~", "inlet~", @@ -223,6 +226,7 @@ class HeavyCompatibleObjects { "add", "avg", "car2pol", + "nop~", "sysrt.in", "sysrt.out", "trig2bang", @@ -265,7 +269,6 @@ class HeavyCompatibleObjects { static inline StringArray allCompatibleObjects = initialiseCompatibleObjects(); public: - static bool isCompatible(String const& type) { return allCompatibleObjects.contains(type.upToFirstOccurrenceOf(" ", false, false)); diff --git a/Source/Heavy/CppExporter.h b/Source/Heavy/CppExporter.h index 7de7c76e0e..ca2ac0aa2f 100644 --- a/Source/Heavy/CppExporter.h +++ b/Source/Heavy/CppExporter.h @@ -33,11 +33,8 @@ class CppExporter final : public ExporterBase { { exportingView->showState(ExportingProgressView::Exporting); -#if JUCE_WINDOWS - auto const heavyPath = heavyExecutable.getFullPathName().replaceCharacter('\\', '/'); -#else - auto const heavyPath = heavyExecutable.getFullPathName(); -#endif + auto const heavyPath = pathToString(heavyExecutable); + StringArray args = { heavyPath.quoted(), pdPatch.quoted(), "-o", outdir.quoted() }; args.add("-n" + name); @@ -58,8 +55,7 @@ class CppExporter final : public ExporterBase { return true; auto const command = args.joinIntoString(" "); - exportingView->logToConsole("Command: " + command + "\n"); - Toolchain::startShellScript(command, this); + startShellScript(command); waitForProcessToFinish(-1); exportingView->flushConsole(); diff --git a/Source/Heavy/DPFExporter.h b/Source/Heavy/DPFExporter.h index 4c0abcf597..1516b3bfb0 100644 --- a/Source/Heavy/DPFExporter.h +++ b/Source/Heavy/DPFExporter.h @@ -136,11 +136,7 @@ class DPFExporter final : public ExporterBase { { exportingView->showState(ExportingProgressView::Exporting); -#if JUCE_WINDOWS - auto const heavyPath = heavyExecutable.getFullPathName().replaceCharacter('\\', '/'); -#else - auto const heavyPath = heavyExecutable.getFullPathName(); -#endif + auto const heavyPath = pathToString(heavyExecutable); StringArray args = { heavyPath.quoted(), pdPatch.quoted(), "-o", outdir.quoted() }; args.add("-n" + name); @@ -150,20 +146,20 @@ class DPFExporter final : public ExporterBase { args.add(copyright.quoted()); } - auto makerName = getValue(makerNameValue); - auto projectLicense = getValue(projectLicenseValue); + auto const makerName = getValue(makerNameValue); + auto const projectLicense = getValue(projectLicenseValue); - int exportType = getValue(exportTypeValue); - int midiin = getValue(midiinEnableValue); - int midiout = getValue(midioutEnableValue); + auto const exportType = getValue(exportTypeValue); + auto const midiin = getValue(midiinEnableValue); + auto const midiout = getValue(midioutEnableValue); - bool lv2 = getValue(lv2EnableValue); - bool vst2 = getValue(vst2EnableValue); - bool vst3 = getValue(vst3EnableValue); - bool clap = getValue(clapEnableValue); - bool jack = getValue(jackEnableValue); + bool const lv2 = getValue(lv2EnableValue); + bool const vst2 = getValue(vst2EnableValue); + bool const vst3 = getValue(vst3EnableValue); + bool const clap = getValue(clapEnableValue); + bool const jack = getValue(jackEnableValue); - bool nosimd = getValue(disableSIMD); + bool const nosimd = getValue(disableSIMD); StringArray formats; @@ -183,9 +179,9 @@ class DPFExporter final : public ExporterBase { formats.add("jack"); } - DynamicObject::Ptr metaJson(new DynamicObject()); + DynamicObject::Ptr const metaJson(new DynamicObject()); - var metaDPF(new DynamicObject()); + var const metaDPF(new DynamicObject()); metaDPF.getDynamicObject()->setProperty("project", true); metaDPF.getDynamicObject()->setProperty("description", "Rename Me"); if (makerName.isNotEmpty()) { @@ -209,8 +205,8 @@ class DPFExporter final : public ExporterBase { metaJson->setProperty("dpf", metaDPF); metaJson->setProperty("nosimd", nosimd); - auto metaJsonFile = createMetaJson(metaJson); - args.add("-m" + metaJsonFile.getFullPathName()); + auto const metaJsonFile = createMetaJson(metaJson); + args.add("-m" + pathToString(metaJsonFile)); args.add("-v"); args.add("-gdpf"); @@ -224,8 +220,7 @@ class DPFExporter final : public ExporterBase { return true; auto const command = args.joinIntoString(" "); - exportingView->logToConsole("Command: " + command + "\n"); - Toolchain::startShellScript(command, this); + startShellScript(command); waitForProcessToFinish(-1); exportingView->flushConsole(); @@ -238,11 +233,11 @@ class DPFExporter final : public ExporterBase { outputFile.getChildFile("hv").deleteRecursively(); outputFile.getChildFile("c").deleteRecursively(); - auto DPF = Toolchain::dir.getChildFile("lib").getChildFile("dpf"); + auto const DPF = toolchainDir.getChildFile("lib").getChildFile("dpf"); DPF.copyDirectoryTo(outputFile.getChildFile("dpf")); if (exportType == 2 || exportType == 4) { - auto DPFGui = Toolchain::dir.getChildFile("lib").getChildFile("dpf-widgets"); + auto const DPFGui = toolchainDir.getChildFile("lib").getChildFile("dpf-widgets"); DPFGui.copyDirectoryTo(outputFile.getChildFile("dpf-widgets")); } @@ -253,38 +248,36 @@ class DPFExporter final : public ExporterBase { // Delay to get correct exit code Time::waitForMillisecondCounter(Time::getMillisecondCounter() + 300); - bool generationExitCode = getExitCode(); + bool const generationExitCode = getExitCode(); // Check if we need to compile if (!generationExitCode && (exportType == 1 || exportType == 2)) { - auto workingDir = File::getCurrentWorkingDirectory(); + auto const workingDir = File::getCurrentWorkingDirectory(); outputFile.setAsCurrentWorkingDirectory(); - auto bin = Toolchain::dir.getChildFile("bin"); + auto const bin = toolchainDir.getChildFile("bin"); auto make = bin.getChildFile("make" + exeSuffix); auto makefile = outputFile.getChildFile("Makefile"); - + #if JUCE_MAC - Toolchain::startShellScript("make -j4 -f " + makefile.getFullPathName(), this); + startShellScript("make -j4 -f " + makefile.getFullPathName()); #elif JUCE_WINDOWS - auto path = "export PATH=\"$PATH:" + Toolchain::dir.getChildFile("bin").getFullPathName().replaceCharacter('\\', '/') + "\"\n"; - auto cc = "CC=" + Toolchain::dir.getChildFile("bin").getChildFile("gcc.exe").getFullPathName().replaceCharacter('\\', '/') + " "; - auto cxx = "CXX=" + Toolchain::dir.getChildFile("bin").getChildFile("g++.exe").getFullPathName().replaceCharacter('\\', '/') + " "; - auto shell = " SHELL=" + Toolchain::dir.getChildFile("bin").getChildFile("bash.exe").getFullPathName().replaceCharacter('\\', '/').quoted(); - Toolchain::startShellScript(path + cc + cxx + make.getFullPathName().replaceCharacter('\\', '/') + " -j4 -f " + makefile.getFullPathName().replaceCharacter('\\', '/') + shell, this); - + auto path = "export PATH=\"$PATH:" + pathToString(toolchainDir.getChildFile("bin")) + "\"\n"; + auto cc = "CC=" + pathToString(toolchainDir.getChildFile("bin").getChildFile("gcc.exe")) + " "; + auto cxx = "CXX=" + pathToString(toolchainDir.getChildFile("bin").getChildFile("g++.exe")) + " "; + auto shell = " SHELL=" + pathToString(toolchainDir.getChildFile("bin").getChildFile("bash.exe")).quoted(); + startShellScript(path + cc + cxx + pathToString(make) + " -j4 -f " + pathToString(makefile) + shell); #else // Linux or BSD - auto prepareEnvironmentScript = Toolchain::dir.getChildFile("scripts").getChildFile("anywhere-setup.sh").getFullPathName() + "\n"; - + auto prepareEnvironmentScript = pathToString(toolchainDir.getChildFile("scripts").getChildFile("anywhere-setup.sh")) + "\n"; auto buildScript = prepareEnvironmentScript - + make.getFullPathName() - + " -j4 -f " + makefile.getFullPathName(); + + pathToString(make) + + " -j4 -f " + pathToString(makefile); // For some reason we need to do this again outputFile.getChildFile("dpf").getChildFile("utils").getChildFile("generate-ttl.sh").setExecutePermission(true); - Toolchain::dir.getChildFile("scripts").getChildFile("anywhere-setup.sh").getChildFile("generate-ttl.sh").setExecutePermission(true); + toolchainDir.getChildFile("scripts").getChildFile("anywhere-setup.sh").getChildFile("generate-ttl.sh").setExecutePermission(true); - Toolchain::startShellScript(buildScript, this); + startShellScript(buildScript); #endif waitForProcessToFinish(-1); @@ -324,7 +317,7 @@ class DPFExporter final : public ExporterBase { #endif } - bool compilationExitCode = getExitCode(); + bool const compilationExitCode = getExitCode(); // Clean up if successful if (!compilationExitCode) { diff --git a/Source/Heavy/DaisyExporter.h b/Source/Heavy/DaisyExporter.h index 4e1731f9a5..592c486fea 100644 --- a/Source/Heavy/DaisyExporter.h +++ b/Source/Heavy/DaisyExporter.h @@ -35,9 +35,7 @@ class DaisyExporter final : public ExporterBase { usbMidiProperty = new PropertiesPanel::BoolComponent("USB MIDI", usbMidiValue, { "No", "Yes" }); properties.add(usbMidiProperty); properties.add(new PropertiesPanel::BoolComponent("Debug printing", debugPrintValue, { "No", "Yes" })); - auto const blocksizeProperty = new PropertiesPanel::EditableComponent("Blocksize", blocksizeValue); - blocksizeProperty->setRangeMin(1); - blocksizeProperty->setRangeMax(256); + auto const blocksizeProperty = new PropertiesPanel::EditableComponent("Blocksize", blocksizeValue, true, 1, 256); blocksizeProperty->editableOnClick(false); properties.add(blocksizeProperty); properties.add(new PropertiesPanel::ComboComponent("Samplerate", samplerateValue, { "8000", "16000", "32000", "48000", "96000" })); @@ -75,7 +73,7 @@ class DaisyExporter final : public ExporterBase { flashButton.onClick = [this] { auto const tempFolder = File::getSpecialLocation(File::tempDirectory).getChildFile("Heavy-" + Uuid().toString().substring(10)); - Toolchain::deleteTempFileLater(tempFolder); + deleteTempFileLater(tempFolder); startExport(tempFolder); }; @@ -84,10 +82,10 @@ class DaisyExporter final : public ExporterBase { exportingView->monitorProcessOutput(this); exportingView->showState(ExportingProgressView::Flashing); - auto const bin = Toolchain::dir.getChildFile("bin"); + auto const bin = toolchainDir.getChildFile("bin"); auto const make = bin.getChildFile("make" + exeSuffix); auto const& gccPath = bin.getFullPathName(); - auto const sourceDir = Toolchain::dir.getChildFile("lib").getChildFile("libdaisy").getChildFile("core"); + auto const sourceDir = toolchainDir.getChildFile("lib").getChildFile("libdaisy").getChildFile("core"); int const result = flashBootloader(bin, sourceDir, make, gccPath); @@ -215,19 +213,12 @@ class DaisyExporter final : public ExporterBase { { exportingView->logToConsole("Flashing bootloader...\n"); -#if JUCE_WINDOWS - String bootloaderScript = "export PATH=\"" + bin.getFullPathName().replaceCharacter('\\', '/') + ":$PATH\"\n" - + "cd " + sourceDir.getFullPathName().replaceCharacter('\\', '/') + "\n" - + make.getFullPathName().replaceCharacter('\\', '/') + " program-boot" - + " GCC_PATH=" + gccPath.replaceCharacter('\\', '/'); -#else - String bootloaderScript = "export PATH=\"" + bin.getFullPathName() + ":$PATH\"\n" - + "cd " + sourceDir.getFullPathName() + "\n" - + make.getFullPathName() + " program-boot" + String bootloaderScript = "export PATH=\"" + pathToString(bin) + ":$PATH\"\n" + + "cd " + pathToString(sourceDir) + "\n" + + pathToString(make) + " program-boot" + " GCC_PATH=" + gccPath; -#endif - Toolchain::startShellScript(bootloaderScript, this); + startShellScript(bootloaderScript); waitForProcessToFinish(-1); exportingView->flushConsole(); @@ -248,12 +239,8 @@ class DaisyExporter final : public ExporterBase { auto rate = getValue(samplerateValue) - 1; auto size = getValue(patchSizeValue); auto appType = getValue(appTypeValue); - -#if JUCE_WINDOWS - auto const heavyPath = heavyExecutable.getFullPathName().replaceCharacter('\\', '/'); -#else - auto const heavyPath = heavyExecutable.getFullPathName(); -#endif + + auto const heavyPath = pathToString(heavyExecutable); StringArray args = { heavyPath.quoted(), pdPatch.quoted(), "-o", outdir.quoted() }; args.add("-n" + name); @@ -275,7 +262,7 @@ class DaisyExporter final : public ExporterBase { if (board == "custom") { metaDaisy.getDynamicObject()->setProperty("board_file", customBoardDefinition.getFullPathName()); } else if (extra_boards.contains(board)) { - metaDaisy.getDynamicObject()->setProperty("board_file", Toolchain::dir.getChildFile("etc").getChildFile(board + ".json").getFullPathName()); + metaDaisy.getDynamicObject()->setProperty("board_file", toolchainDir.getChildFile("etc").getChildFile(board + ".json").getFullPathName()); } else { metaDaisy.getDynamicObject()->setProperty("board", board); } @@ -314,7 +301,7 @@ class DaisyExporter final : public ExporterBase { } else if (size == 3) { metaDaisy.getDynamicObject()->setProperty( "linker_script", - Toolchain::dir.getChildFile("etc").getChildFile("linkers").getChildFile("sram_linker_sdram.lds").getFullPathName()); + toolchainDir.getChildFile("etc").getChildFile("linkers").getChildFile("sram_linker_sdram.lds").getFullPathName()); metaDaisy.getDynamicObject()->setProperty("bootloader", "BOOT_SRAM"); } else if (size == 4) { metaDaisy.getDynamicObject()->setProperty("linker_script", "../../libdaisy/core/STM32H750IB_qspi.lds"); @@ -322,7 +309,7 @@ class DaisyExporter final : public ExporterBase { } else if (size == 5) { metaDaisy.getDynamicObject()->setProperty( "linker_script", - Toolchain::dir.getChildFile("etc").getChildFile("linkers").getChildFile("qspi_linker_sdram.lds").getFullPathName()); + toolchainDir.getChildFile("etc").getChildFile("linkers").getChildFile("qspi_linker_sdram.lds").getFullPathName()); metaDaisy.getDynamicObject()->setProperty("bootloader", "BOOT_QSPI"); } else if (size == 6) { metaDaisy.getDynamicObject()->setProperty("linker_script", customLinker.getFullPathName()); @@ -346,8 +333,8 @@ class DaisyExporter final : public ExporterBase { } auto const command = args.joinIntoString(" "); - exportingView->logToConsole("Command: " + command + "\n"); - Toolchain::startShellScript(command, this); + startShellScript(command); + waitForProcessToFinish(-1); exportingView->flushConsole(); @@ -366,8 +353,8 @@ class DaisyExporter final : public ExporterBase { metaJsonFile.copyFileTo(outputFile.getChildFile("meta.json")); if (compile) { - auto bin = Toolchain::dir.getChildFile("bin"); - auto libDaisy = Toolchain::dir.getChildFile("lib").getChildFile("libdaisy"); + auto bin = toolchainDir.getChildFile("bin"); + auto libDaisy = toolchainDir.getChildFile("lib").getChildFile("libdaisy"); auto make = bin.getChildFile("make" + exeSuffix); auto compiler = bin.getChildFile("arm-none-eabi-gcc" + exeSuffix); @@ -382,26 +369,24 @@ class DaisyExporter final : public ExporterBase { sourceDir.setAsCurrentWorkingDirectory(); sourceDir.getChildFile("build").createDirectory(); - auto const& gccPath = bin.getFullPathName(); + auto const& gccPath = pathToString(bin); + // Bit hacky, but the only way to get colour coding on daisy builds for Windows #if JUCE_WINDOWS - auto buildScript = make.getFullPathName().replaceCharacter('\\', '/') + sourceDir.getChildFile("Makefile").appendText("\nCFLAGS += -fdiagnostics-color=always"); +#endif + + auto buildScript = pathToString(make) + " -j4 -f " - + sourceDir.getChildFile("Makefile").getFullPathName().replaceCharacter('\\', '/').quoted() - + " SHELL=" + Toolchain::dir.getChildFile("bin").getChildFile("bash.exe").getFullPathName().replaceCharacter('\\', '/').quoted() + + pathToString(sourceDir.getChildFile("Makefile")).quoted() +#if JUCE_WINDOWS + + " SHELL=" + pathToString(toolchainDir.getChildFile("bin").getChildFile("bash.exe")).quoted() +#endif + " GCC_PATH=" - + gccPath.replaceCharacter('\\', '/') - + " PROJECT_NAME=" + name; - - Toolchain::startShellScript(buildScript, this); -#else - String buildScript = make.getFullPathName() - + " -j4 -f " + sourceDir.getChildFile("Makefile").getFullPathName().quoted() - + " GCC_PATH=" + gccPath + + gccPath + " PROJECT_NAME=" + name; - Toolchain::startShellScript(buildScript, this); -#endif + startShellScript(buildScript); waitForProcessToFinish(-1); exportingView->flushConsole(); @@ -436,8 +421,7 @@ class DaisyExporter final : public ExporterBase { String testBootloaderScript = "export PATH=\"" + bin.getFullPathName() + ":$PATH\"\n" + dfuUtil.getFullPathName() + " -l "; - Toolchain runTest; - auto output = runTest.startShellScriptWithOutput(testBootloaderScript); + auto output = startShellScriptWithOutput(testBootloaderScript); if (output.contains("alt=1")) { exportingView->logToConsole("Bootloader not found...\n"); bootloaderExitCode = flashBootloader(bin, sourceDir, make, gccPath); @@ -448,21 +432,13 @@ class DaisyExporter final : public ExporterBase { exportingView->logToConsole("Flashing...\n"); -#if JUCE_WINDOWS - String flashScript = "export PATH=\"" + bin.getFullPathName().replaceCharacter('\\', '/') + ":$PATH\"\n" - + "cd " + sourceDir.getFullPathName().replaceCharacter('\\', '/') + "\n" - + make.getFullPathName().replaceCharacter('\\', '/') + " program-dfu" - + " GCC_PATH=" + gccPath.replaceCharacter('\\', '/') - + " PROJECT_NAME=" + name; -#else - String flashScript = "export PATH=\"" + bin.getFullPathName() + ":$PATH\"\n" - + "cd " + sourceDir.getFullPathName() + "\n" - + make.getFullPathName() + " program-dfu" + String flashScript = "export PATH=\"" + pathToString(bin) + ":$PATH\"\n" + + "cd " + pathToString(sourceDir) + "\n" + + pathToString(make) + " program-dfu" + " GCC_PATH=" + gccPath + " PROJECT_NAME=" + name; -#endif - Toolchain::startShellScript(flashScript, this); + startShellScript(flashScript); waitForProcessToFinish(-1); exportingView->flushConsole(); @@ -470,9 +446,36 @@ class DaisyExporter final : public ExporterBase { // Delay to get correct exit code Time::waitForMillisecondCounter(Time::getMillisecondCounter() + 300); - auto flashExitCode = getExitCode(); + // dfu-util will always return 2, even if everything worked + // We just test for the search for any dfu-util errors in the console to decide if the export was successful + StringArray errorMessages = { + "No DFU capable USB device available", + "More than one DFU capable USB device found!", + "Cannot open device", + "unable to initialize libusb:", + "Cannot claim interface", + "Cannot set alt interface zero", + "Cannot set alternate interface", + "error resetting ", + "error clear_status", + "Lost device after RESET?", + "Device still in Runtime Mode!", + "can't send DFU_ABORT", + "USB communication error", + "Transfer size must be specified", + "Cannot open file", + "Error: File ID", + "Unsupported mode:", + "error resetting after download" + }; + auto flashExitCode = exportingView->hasConsoleMessage(errorMessages); + + if(!flashExitCode && exportingView->hasConsoleMessage({"Error 74"})) { + exportingView->logToConsole("\x1b[1;36mnote:\x1b[0m Error 74 is not fatal and may be ignored\n"); + } + - return heavyExitCode && flashExitCode && bootloaderExitCode; + return heavyExitCode || flashExitCode || bootloaderExitCode; } auto binLocation = outputFile.getChildFile(name + ".bin"); sourceDir.getChildFile("build").getChildFile("HeavyDaisy_" + name + ".bin").moveFileTo(binLocation); @@ -480,11 +483,11 @@ class DaisyExporter final : public ExporterBase { outputFile.getChildFile("daisy").deleteRecursively(); outputFile.getChildFile("libdaisy").deleteRecursively(); - return heavyExitCode && compileExitCode; + return heavyExitCode || compileExitCode; } else { auto outputFile = File(outdir); - auto libDaisy = Toolchain::dir.getChildFile("lib").getChildFile("libdaisy"); + auto libDaisy = toolchainDir.getChildFile("lib").getChildFile("libdaisy"); libDaisy.copyDirectoryTo(outputFile.getChildFile("libdaisy")); outputFile.getChildFile("ir").deleteRecursively(); diff --git a/Source/Heavy/ExporterBase.h b/Source/Heavy/ExporterBase.h index 60448c16dc..c0f1df13ce 100644 --- a/Source/Heavy/ExporterBase.h +++ b/Source/Heavy/ExporterBase.h @@ -22,13 +22,16 @@ struct ExporterBase : public Component bool blockDialog = false; #if JUCE_WINDOWS - inline static String const exeSuffix = ".exe"; + static inline File const toolchainDir = ProjectInfo::appDataDir.getChildFile("Toolchain").getChildFile("usr"); + static inline String const exeSuffix = ".exe"; #else - inline static String const exeSuffix = ""; + static inline File const toolchainDir = ProjectInfo::appDataDir.getChildFile("Toolchain"); + static inline String const exeSuffix = ""; #endif - inline static File heavyExecutable = Toolchain::dir.getChildFile("bin").getChildFile("Heavy").getChildFile("Heavy" + exeSuffix); - + static inline File heavyExecutable = toolchainDir.getChildFile("bin").getChildFile("Heavy").getChildFile("Heavy" + exeSuffix); + static inline SmallArray tempFilesToDelete; + bool validPatchSelected = false; bool canvasDirty = false; bool isTempFile = false; @@ -45,6 +48,8 @@ struct ExporterBase : public Component Label unsavedLabel = Label("", "Warning: patch has unsaved changes"); PluginEditor* editor; + + ExporterBase(PluginEditor* pluginEditor, ExportingProgressView* exportView) : ThreadPool(1, Thread::osDefaultStackSize, Thread::Priority::highest) @@ -86,10 +91,9 @@ struct ExporterBase : public Component if (auto const* cnv = editor->getCurrentCanvas()) { openedPatchFile = File::createTempFile(".pd"); - Toolchain::deleteTempFileLater(openedPatchFile); + deleteTempFileLater(openedPatchFile); patchFile = cnv->patch.getCurrentFile(); - if(!patchFile.existsAsFile()) - { + if (!patchFile.existsAsFile()) { openedPatchFile.replaceWithText(cnv->patch.getCanvasContent(), false, false, "\n"); patchChooser->comboBox.setItemEnabled(1, true); patchChooser->comboBox.setSelectedId(1); @@ -97,8 +101,7 @@ struct ExporterBase : public Component patchFile = openedPatchFile; canvasDirty = false; isTempFile = true; - } - else { + } else { canvasDirty = cnv->patch.isDirty(); openedPatchFile = patchFile; realPatchFile = patchFile; @@ -128,6 +131,7 @@ struct ExporterBase : public Component addChildComponent(unsavedLabel); } + // TODO: hides non-virtual destructor from juce::ChildProcess! modify JUCE to make it virtual? ~ExporterBase() override { if (openedPatchFile.existsAsFile() && isTempFile) { @@ -142,16 +146,82 @@ struct ExporterBase : public Component virtual ValueTree getState() = 0; virtual void setState(ValueTree& state) = 0; + + String pathToString(File const& file) + { +#if JUCE_WINDOWS + return file.getFullPathName().replaceCharacter('\\', '/'); +#else + return file.getFullPathName(); +#endif + } + + static void deleteTempFileLater(File const& script) + { + tempFilesToDelete.add(script); + } - void startExport(File const& outDir) + static void deleteTempFiles() { + for (auto& file : tempFilesToDelete) { + if (file.existsAsFile()) + file.deleteFile(); + if (file.isDirectory()) + file.deleteRecursively(); + } + } + + String startShellScriptWithOutput(String const& scriptText) + { + exportingView->logToConsole("\n\x1b[1;34m> " + scriptText + " \x1b[0m \n\n"); + + File scriptFile = File::createTempFile(".sh"); + deleteTempFileLater(scriptFile); + + auto const bash = String("#!/bin/bash\n"); + scriptFile.replaceWithText(bash + scriptText, false, false, "\n"); + + ChildProcess process; + #if JUCE_WINDOWS - auto const patchPath = patchFile.getFullPathName().replaceCharacter('\\', '/'); - auto const& outPath = outDir.getFullPathName().replaceCharacter('\\', '/'); + auto sh = toolchainDir.getChildFile("bin").getChildFile("sh.exe"); + auto arguments = StringArray { sh.getFullPathName(), "--login", scriptFile.getFullPathName().replaceCharacter('\\', '/') }; #else - auto patchPath = patchFile.getFullPathName(); - auto const& outPath = outDir.getFullPathName(); + scriptFile.setExecutePermission(true); + auto arguments = scriptFile.getFullPathName(); #endif + process.start(arguments, ChildProcess::wantStdOut | ChildProcess::wantStdErr); + return process.readAllProcessOutput(); + } + + void startShellScript(String const& scriptText) + { + exportingView->logToConsole("\n\x1b[1;92m> " + scriptText + " \x1b[0m \n\n"); + + File scriptFile = File::createTempFile(".sh"); + deleteTempFileLater(scriptFile); + +#if JUCE_WINDOWS + auto const gccColourFlags = String("export CFLAGS=\"-fdiagnostics-color=always\"\nexport CXXFLAGS=\"-fdiagnostics-color=always\"\n "); +#else + auto const gccColourFlags = String("export TERM=xterm-256color\nexport GCC_URLS=no\n"); +#endif + auto const bash = String("#!/bin/bash\n"); + scriptFile.replaceWithText(bash + gccColourFlags + scriptText, false, false, "\n"); + +#if JUCE_WINDOWS + auto sh = toolchainDir.getChildFile("bin").getChildFile("sh.exe"); + start(StringArray { sh.getFullPathName(), "--login", scriptFile.getFullPathName().replaceCharacter('\\', '/') }); +#else + scriptFile.setExecutePermission(true); + start(scriptFile.getFullPathName(), ChildProcess::wantStdOut | ChildProcess::wantStdErr | ChildProcess::wantTtyOut); +#endif + } + + void startExport(File const& outDir) + { + auto patchPath = pathToString(patchFile); + auto const& outPath = pathToString(outDir); auto projectTitle = projectNameValue.toString(); auto projectCopyright = projectCopyrightValue.toString(); @@ -169,11 +239,7 @@ struct ExporterBase : public Component auto searchPaths = StringArray {}; if (realPatchFile.existsAsFile() && !realPatchFile.isRoot()) // Make sure file actually exists { -#if JUCE_WINDOWS - searchPaths.add(realPatchFile.getParentDirectory().getFullPathName().replaceCharacter('\\', '/').quoted()); -#else - searchPaths.add(realPatchFile.getParentDirectory().getFullPathName().quoted()); -#endif + searchPaths.add(pathToString(realPatchFile.getParentDirectory()).quoted()); } editor->pd->setThis(); @@ -192,6 +258,8 @@ struct ExporterBase : public Component exportingView->monitorProcessOutput(this); exportingView->showState(ExportingProgressView::Exporting); + FileSystemWatcher::addGlobalIgnorePath(outPath); + auto const result = performExport(patchPath, outPath, projectTitle, projectCopyright, searchPaths); if (shouldQuit) @@ -204,6 +272,8 @@ struct ExporterBase : public Component MessageManager::callAsync([this] { repaint(); }); + + FileSystemWatcher::removeGlobalIgnorePath(outPath); }); } @@ -243,10 +313,10 @@ struct ExporterBase : public Component exportButton.setBounds(getLocalBounds().removeFromBottom(23).removeFromRight(80).translated(-10, -10)); } - File createMetaJson(DynamicObject::Ptr const& metaJson) + static File createMetaJson(DynamicObject::Ptr const& metaJson) { auto const metadata = File::createTempFile(".json"); - Toolchain::deleteTempFileLater(metadata); + deleteTempFileLater(metadata); String const metaString = JSON::toString(var(metaJson.get())); metadata.replaceWithText(metaString, false, false, "\n"); return metadata; diff --git a/Source/Heavy/ExportingProgressView.h b/Source/Heavy/ExportingProgressView.h index 7e5f6e90a9..f9ee866c60 100644 --- a/Source/Heavy/ExportingProgressView.h +++ b/Source/Heavy/ExportingProgressView.h @@ -5,11 +5,545 @@ */ #pragma once +class ExporterConsole : public Component +{ +public: + ExporterConsole() + { + viewport.setViewedComponent(this, false); + viewport.setScrollBarsShown(true, false, false, false); + setVisible(true); + setWantsKeyboardFocus(true); + setMouseCursor(MouseCursor::IBeamCursor); + } + + void clear() + { + string.clear(); + plainText.clear(); + glyphPositions.clear(); + + layout.createLayout(string, viewport.getWidth() - 8); + viewport.setViewPositionProportionately(0.0f, 0.0f); + selectionStart = selectionEnd = 0; + + layout.createLayout(string, viewport.getWidth() - 8); + setSize(viewport.getWidth(), layout.getHeight() + 4); + + repaint(); + } + + void append(String const& text) + { + auto shouldAutoScroll = viewport.getViewPositionY() + viewport.getViewHeight() > getHeight() - 10; + + parseAnsiText(text); + layout.createLayout(string, viewport.getWidth() - 8); + setSize(viewport.getWidth(), layout.getHeight() + 4); + + if(shouldAutoScroll) + viewport.setViewPositionProportionately(0.0f, 1.0f); + } + + void paint(Graphics& g) override + { + // Draw selection highlight first + if (hasSelection()) + { + g.setColour(findColour(TextEditor::highlightColourId)); + + auto selStart = jmin(selectionStart, selectionEnd); + auto selEnd = jmax(selectionStart, selectionEnd); + + for (const auto& rect : getSelectionRectangles(selStart, selEnd)) + { + g.fillRect(rect); + } + } + + layout.draw(g, getLocalBounds().reduced(4, 0).translated(0, -12).toFloat()); + } + + void mouseDown(const MouseEvent& e) override + { + updateGlyphPositions(); + + if(e.mods.isRightButtonDown()) + { + copySelectionToClipboard(); + return; + } + if(e.mods.isShiftDown()) + { + selectionEnd = getCharacterIndexAt(e.position); + repaint(); + return; + } + + selectionStart = selectionEnd = getCharacterIndexAt(e.position); + repaint(); + } + + void mouseDrag(const MouseEvent& e) override + { + selectionEnd = getCharacterIndexAt(e.position); + repaint(); + } + + void mouseDoubleClick(const MouseEvent& e) override + { + int index = getCharacterIndexAt(e.position); + selectWord(index); + } + + bool keyPressed(const KeyPress& key) override + { + if (key == KeyPress('c', ModifierKeys::commandModifier, 0)) { + if (hasSelection()) + { + copySelectionToClipboard(); + return true; + } + } + else if (key == KeyPress('a', ModifierKeys::commandModifier, 0)) { + updateGlyphPositions(); + + selectionStart = 0; + selectionEnd = plainText.length(); + repaint(); + return true; + } + + return false; + } + + Viewport& getViewport() + { + return viewport; + } + +private: + struct GlyphInfo + { + Rectangle bounds; + int charIndex; + }; + + void parseAnsiText(const String& text) + { + String fullText = ansiBuffer + text; + ansiBuffer.clear(); + + String currentSegment; + Colour currentColour = findColour(PlugDataColour::panelTextColourId); + Font currentFont = Fonts::getMonospaceFont(); + + for (int i = 0; i < fullText.length(); ++i) + { + // Check for ANSI escape sequence start + if (fullText[i] == '\x1b' && i + 1 < fullText.length() && fullText[i + 1] == '[') + { + // Append any text before this escape sequence + if (currentSegment.isNotEmpty()) + { + appendWithWordWrap(currentSegment, currentFont, currentColour); + currentSegment = ""; + } + + // Find the end of the escape sequence + int sequenceStart = i + 2; + int sequenceEnd = sequenceStart; + + if(sequenceStart >= fullText.length()) + { + ansiBuffer = fullText.substring(i); + break; + } + + while (sequenceEnd < fullText.length() && + fullText[sequenceEnd] >= 0x30 && fullText[sequenceEnd] <= 0x3F) + { + ++sequenceEnd; + } + + if (sequenceEnd < fullText.length() && + fullText[sequenceEnd] >= 0x40 && fullText[sequenceEnd] <= 0x7E) + { + char command = fullText[sequenceEnd]; + + if (command == 'm') // SGR (Select Graphic Rendition) + { + auto params = fullText.substring(sequenceStart, sequenceEnd); + parseAnsiSGR(params, currentColour, currentFont); + } + + i = sequenceEnd; // Skip past the entire sequence + } + else { + ansiBuffer = fullText.substring(i); + break; + } + } + else + { + currentSegment += fullText[i]; + } + } + + if (currentSegment.isNotEmpty()) + { + appendWithWordWrap(currentSegment, currentFont, currentColour); + } + + plainText = string.getText(); + needsGlyphPositionUpdate = true; + } + + void parseAnsiSGR(const String& params, Colour& currentColour, Font& currentFont) + { + auto defaultTextColour = findColour(PlugDataColour::panelTextColourId); + currentColour = defaultTextColour; + currentFont = Fonts::getMonospaceFont(); + + StringArray codes; + codes.addTokens(params, ";", ""); + + for (const auto& code : codes) + { + int value = code.getIntValue(); + switch (value) + { + case 0: + currentColour = findColour(PlugDataColour::panelTextColourId); + break; + case 1: + currentFont = Fonts::getMonospaceBoldFont(); + break; + case 30: + currentColour = defaultTextColour.interpolatedWith(Colours::black, 0.5); + break; + case 31: + currentColour = defaultTextColour.interpolatedWith(Colours::red, 0.5); + break; + case 32: + currentColour = defaultTextColour.interpolatedWith(Colours::green, 0.5); + break; + case 33: + currentColour = defaultTextColour.interpolatedWith(Colours::yellow, 0.5); + break; + case 34: + currentColour = defaultTextColour.interpolatedWith(Colours::blue, 0.5); + break; + case 35: + currentColour = defaultTextColour.interpolatedWith(Colours::magenta, 0.5); + break; + case 36: + currentColour = defaultTextColour.interpolatedWith(Colours::cyan, 0.5); + break; + case 37: + currentColour = defaultTextColour.interpolatedWith(Colours::white, 0.5); + break; + + case 90: + currentColour = defaultTextColour.interpolatedWith(Colours::darkgrey, 0.7); + break; + case 91: + currentColour = defaultTextColour.interpolatedWith(Colours::red, 0.7); + break; + case 92: + currentColour = defaultTextColour.interpolatedWith(Colours::green, 0.7); + break; + case 93: + currentColour = defaultTextColour.interpolatedWith(Colours::yellow, 0.7); + break; + case 94: + currentColour = defaultTextColour.interpolatedWith(Colours::blue, 0.7); + break; + case 95: + currentColour = defaultTextColour.interpolatedWith(Colours::magenta, 0.7); + break; + case 96: + currentColour = defaultTextColour.interpolatedWith(Colours::cyan, 0.7); + break; + case 97: + currentColour = defaultTextColour.interpolatedWith(Colours::white, 0.7); + break; + default: + break; + } + } + } + + void appendWithWordWrap(const String& text, Font const& font, Colour const& colour) + { + StringArray words; + + // Split on spaces while preserving the spaces + int lastPos = 0; + for (int i = 0; i < text.length(); ++i) + { + if (text[i] == ' ' || text[i] == '\t' || text[i] == '\n' || text[i] == '\r') + { + if (i > lastPos) + { + words.add(text.substring(lastPos, i)); + } + + // Check if \r is part of \r\n (Windows line ending) + if (text[i] == '\r' && i + 1 < text.length() && text[i + 1] == '\n') + { + words.add("\r\n"); // Treat as single unit + ++i; // Skip the \n + } + else + { + words.add(String::charToString(text[i])); + } + + lastPos = i + 1; + } + } + + // Add remaining text + if (lastPos < text.length()) + { + words.add(text.substring(lastPos)); + } + + float maxWidth = viewport.getWidth() - 10; + + for (int wordIdx = 0; wordIdx < words.size(); ++wordIdx) + { + const auto& word = words[wordIdx]; + + if (word == "\n" || word == "\r\n") + { + string.append("\n", font, colour); + plainText += "\n"; + currentLineWidth = 0; + continue; + } + + if (word == "\r") + { + // Standalone \r means overwrite current line + int lastNewline = plainText.lastIndexOf("\n"); + if (lastNewline >= 0) + { + plainText = plainText.substring(0, lastNewline + 1); + string.eraseEnd(lastNewline + 1); + } + else + { + plainText.clear(); + string.clear(); + } + currentLineWidth = 0; + continue; + } + + float wordWidth = font.getStringWidthFloat(word); + + // Look ahead: if this is whitespace, check if the next word would fit + if ((word == " " || word == "\t") && wordIdx + 1 < words.size()) + { + float nextWordWidth = font.getStringWidthFloat(words[wordIdx + 1]); + + // If current line + space + next word exceeds width, break NOW + if (currentLineWidth + wordWidth + nextWordWidth > maxWidth && currentLineWidth > 0) + { + string.append("\n", font, colour); + plainText += "\n"; + currentLineWidth = 0; + continue; // Skip the space + } + } + + // If adding this word would exceed the width, insert a newline + if (currentLineWidth + wordWidth > maxWidth && currentLineWidth > 0 && word != " " && word != "\t") + { + string.append("\n", font, colour); + plainText += "\n"; + currentLineWidth = 0; + } + + string.append(word, font, colour); + plainText += word; + currentLineWidth += wordWidth; + } + } + + void updateGlyphPositions() + { + if(!needsGlyphPositionUpdate) return; + + glyphPositions.clear(); + int charIndex = 0; + for (auto& line : layout) { + // TextLayout on macOS/iOS considers whitespace as a character, but on Windows/Linux it does not +#if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD + float lastX = 4.0f; + for (auto* run : line.runs) { + auto const runText = string.getText().substring(run->stringRange.getStart(), run->stringRange.getEnd()); + int glyphIndex = 0; + for (int i = 0; i < runText.length(); ++i) { + if (CharacterFunctions::isWhitespace(runText[i])) { + GlyphInfo info; + info.bounds = line.getLineBounds().withX(lastX).withWidth(run->font.getStringWidthFloat(runText.substring(i, i + 1))).translated(0, -12); + info.charIndex = charIndex++; + glyphPositions.add(info); + lastX = info.bounds.getRight(); + continue; + } + while (glyphIndex < run->glyphs.size() && +#if JUCE_WINDOWS + run->glyphs.getReference(glyphIndex).glyphCode == 1 +#else + run->glyphs.getReference(glyphIndex).glyphCode == 32 +#endif + ) + glyphIndex++; + + if (glyphIndex < run->glyphs.size()) { + auto const& glyph = run->glyphs.getReference(glyphIndex); + auto const position = glyph.anchor + line.lineOrigin; + GlyphInfo info; + info.bounds = Rectangle(position.x + 4, position.y - run->font.getAscent() - 12, glyph.width, run->font.getHeight()); + info.charIndex = charIndex++; + glyphPositions.add(info); + glyphIndex++; + lastX = info.bounds.getRight(); + } + } + } +#else // JUCE_MAC or JUCE_IOS + for (auto* run : line.runs) { + for (auto& glyph : run->glyphs) { + GlyphInfo info; + auto position = glyph.anchor + line.lineOrigin; + info.bounds = Rectangle(position.x + 4, position.y - run->font.getAscent() - 12, glyph.width, run->font.getHeight()); + info.charIndex = charIndex++; + glyphPositions.add(info); + } + } +#endif + } + + needsGlyphPositionUpdate = false; + } + + int getCharacterIndexAt(Point position) + { + int closestIndex = 0; + for (const auto& glyph : glyphPositions) + { + bool pastCharacter = position.x > (glyph.bounds.getX() - 2); + bool sameLine = glyph.bounds.withX(position.x - 1).withWidth(2).contains(position); + + if (sameLine && pastCharacter) + { + closestIndex = glyph.charIndex; + } + } + + return jlimit(0, plainText.length(), closestIndex); + } + + SmallArray> getSelectionRectangles(int start, int end) + { + SmallArray> rectangles; + + if (glyphPositions.empty()) + return rectangles; + + float currentY = -1; + float lineLeft = std::numeric_limits::max(); + float lineRight = 0; + float glyphHeight = 0.0f; + + for (int i = start; i < end && i < glyphPositions.size(); ++i) + { + const auto& glyph = glyphPositions[i]; + auto const glyphY = glyph.bounds.getY(); + glyphHeight = glyph.bounds.getHeight(); + + if (std::abs(glyphY - currentY) > glyphHeight * 0.5f) + { + rectangles.add(Rectangle(lineLeft, currentY, + lineRight - lineLeft, glyphHeight)); + lineLeft = std::numeric_limits::max(); + lineRight = 0; + } + + currentY = glyphY; + lineLeft = jmin(lineLeft, glyph.bounds.getX()); + lineRight = jmax(lineRight, glyph.bounds.getRight()); + } + + if (currentY >= 0) + { + rectangles.add(Rectangle(lineLeft, currentY, + lineRight - lineLeft, glyphHeight)); + } + + return rectangles; + } + + bool hasSelection() const + { + return selectionStart != selectionEnd; + } + + void selectWord(int index) + { + if (index < 0 || index >= plainText.length()) + return; + + int start = index; + int end = index; + + while (start > 0 && !CharacterFunctions::isWhitespace(plainText[start - 1])) + --start; + + while (end < plainText.length() && !CharacterFunctions::isWhitespace(plainText[end])) + ++end; + + selectionStart = start; + selectionEnd = end; + repaint(); + } + + void copySelectionToClipboard() + { + if (!hasSelection()) + return; + + int start = jmin(selectionStart, selectionEnd); + int end = jmax(selectionStart, selectionEnd); + + String selectedText = plainText.substring(start, end); + SystemClipboard::copyTextToClipboard(selectedText); + } + + Viewport viewport; + AttributedString string; + TextLayout layout; + String plainText; + String ansiBuffer; + HeapArray glyphPositions; + + int selectionStart = 0; + int selectionEnd = 0; + float currentLineWidth = 0; + + bool needsGlyphPositionUpdate = false; +}; + class ExportingProgressView final : public Component , public Thread , public Timer { - TextEditor console; - + + ExporterConsole console; ChildProcess* processToMonitor; public: @@ -28,8 +562,11 @@ class ExportingProgressView final : public Component AtomicValue state = NotExporting; String userInteractionMessage; + + String allConsoleOutput; + CriticalSection allConsoleOutputLock; - static constexpr int maxLength = 512; + static constexpr int maxLength = 8192; char processOutput[maxLength]; ExportingProgressView() @@ -37,24 +574,11 @@ class ExportingProgressView final : public Component { setVisible(false); addChildComponent(continueButton); - addAndMakeVisible(console); + addAndMakeVisible(console.getViewport()); continueButton.onClick = [this] { showState(NotExporting); }; - - console.setColour(TextEditor::backgroundColourId, Colours::transparentBlack); - console.setColour(TextEditor::outlineColourId, Colours::transparentBlack); - - console.setScrollbarsShown(true); - console.setMultiLine(true); - console.setReadOnly(true); - console.setWantsKeyboardFocus(true); - - // To ensure custom LnF got assigned... - MessageManager::callAsync([this] { - console.setFont(Fonts::getMonospaceFont()); - }); } // For the spinning animation @@ -62,12 +586,32 @@ class ExportingProgressView final : public Component { repaint(); } - + + bool hasConsoleMessage(StringArray const& messagesToFind) + { + ScopedLock lock(allConsoleOutputLock); + for(auto& message : messagesToFind) + { + if(allConsoleOutput.contains(message)) + { + return true; + } + } + return false; + } + void run() override { while (processToMonitor && !threadShouldExit()) { - if (int const len = processToMonitor->readProcessOutput(processOutput, maxLength)) - logToConsole(String::fromUTF8(processOutput, len)); + if (int const len = processToMonitor->readProcessOutput(processOutput, maxLength)) { + auto newOutput = String::fromUTF8(processOutput, len); + + allConsoleOutputLock.enter(); + allConsoleOutput += newOutput; + allConsoleOutputLock.exit(); + + logToConsole(newOutput); + } Time::waitForMillisecondCounter(Time::getMillisecondCounter() + 100); } @@ -106,7 +650,7 @@ class ExportingProgressView final : public Component setVisible(state < NotExporting); continueButton.setVisible(state >= Success); if (state == Exporting || state == Flashing) - console.setText(""); + console.clear(); if (console.isShowing()) { console.grabKeyboardFocus(); } @@ -123,9 +667,7 @@ class ExportingProgressView final : public Component if (!_this) return; - _this->console.setText(_this->console.getText() + text); - _this->console.moveCaretToEnd(); - _this->console.setScrollToShowCursor(true); + _this->console.append(text); }); } } @@ -144,30 +686,30 @@ class ExportingProgressView final : public Component g.strokePath(background, PathStrokeType(1.0f)); g.setColour(findColour(PlugDataColour::sidebarBackgroundColourId)); - g.fillRoundedRectangle(console.getBounds().expanded(2).toFloat(), Corners::defaultCornerRadius); + g.fillRoundedRectangle(console.getViewport().getBounds().expanded(6).toFloat(), Corners::defaultCornerRadius); if (state == Exporting) { - Fonts::drawStyledText(g, "Exporting...", 0, 25, getWidth(), 40, findColour(PlugDataColour::panelTextColourId), Bold, 32, Justification::centred); + Fonts::drawStyledText(g, "Exporting...", 0, 20, getWidth(), 32, findColour(PlugDataColour::panelTextColourId), Bold, 32, Justification::centred); getLookAndFeel().drawSpinningWaitAnimation(g, findColour(PlugDataColour::panelTextColourId), getWidth() / 2 - 16, getHeight() / 2 + 118, 32, 32); } else if (state == Flashing) { - Fonts::drawStyledText(g, "Flashing...", 0, 25, getWidth(), 40, findColour(PlugDataColour::panelTextColourId), Bold, 32, Justification::centred); + Fonts::drawStyledText(g, "Flashing...", 0, 20, getWidth(), 32, findColour(PlugDataColour::panelTextColourId), Bold, 32, Justification::centred); getLookAndFeel().drawSpinningWaitAnimation(g, findColour(PlugDataColour::panelTextColourId), getWidth() / 2 - 16, getHeight() / 2 + 118, 32, 32); } else if (state == Success) { - Fonts::drawStyledText(g, "Export successful", 0, 25, getWidth(), 40, findColour(PlugDataColour::panelTextColourId), Bold, 32, Justification::centred); + Fonts::drawStyledText(g, "Export successful", 0, 20, getWidth(), 32, findColour(PlugDataColour::panelTextColourId), Bold, 32, Justification::centred); } else if (state == Failure) { - Fonts::drawStyledText(g, "Exporting failed", 0, 25, getWidth(), 40, findColour(PlugDataColour::panelTextColourId), Bold, 32, Justification::centred); + Fonts::drawStyledText(g, "Exporting failed", 0, 20, getWidth(), 32, findColour(PlugDataColour::panelTextColourId), Bold, 32, Justification::centred); } else if (state == BootloaderFlashSuccess) { - Fonts::drawStyledText(g, "Bootloader flashed", 0, 25, getWidth(), 40, findColour(PlugDataColour::panelTextColourId), Bold, 32, Justification::centred); + Fonts::drawStyledText(g, "Bootloader flashed", 0, 20, getWidth(), 32, findColour(PlugDataColour::panelTextColourId), Bold, 32, Justification::centred); } else if (state == BootloaderFlashFailure) { - Fonts::drawStyledText(g, "Bootloader flash failed", 0, 25, getWidth(), 40, findColour(PlugDataColour::panelTextColourId), Bold, 32, Justification::centred); + Fonts::drawStyledText(g, "Bootloader flash failed", 0, 20, getWidth(), 32, findColour(PlugDataColour::panelTextColourId), Bold, 32, Justification::centred); } } void resized() override { - console.setBounds(proportionOfWidth(0.05f), 80, proportionOfWidth(0.9f), getHeight() - 172); + console.getViewport().setBounds(proportionOfWidth(0.05f), 80, proportionOfWidth(0.9f), getHeight() - 172); continueButton.setBounds(proportionOfWidth(0.42f), getHeight() - 60, proportionOfWidth(0.12f), 24); } }; diff --git a/Source/Heavy/HeavyExportDialog.cpp b/Source/Heavy/HeavyExportDialog.cpp index ae93b59ed7..40d2d4345b 100644 --- a/Source/Heavy/HeavyExportDialog.cpp +++ b/Source/Heavy/HeavyExportDialog.cpp @@ -9,13 +9,13 @@ #include "Dialogs/Dialogs.h" #include "HeavyExportDialog.h" -//#include "Dialogs/HelpDialog.h" +// #include "Dialogs/HelpDialog.h" #include "PluginEditor.h" #include "Components/PropertiesPanel.h" #include "Utility/OSUtils.h" -#include "Toolchain.h" +#include "ToolchainInstaller.h" #include "ExportingProgressView.h" #include "ExporterBase.h" #include "CppExporter.h" @@ -78,7 +78,7 @@ class ExporterSettingsPanel final : public Component return stateTree; } - void setState(ValueTree& stateTree) + void setState(ValueTree const& stateTree) { auto const tree = stateTree.getChildWithName("HeavySelect"); listBox.selectRow(tree.getProperty("listBox")); @@ -196,7 +196,7 @@ HeavyExportDialog::HeavyExportDialog(Dialog* dialog) , exporterPanel(new ExporterSettingsPanel(dynamic_cast(dialog->parentComponent), exportingView.get())) , infoButton(new MainToolbarButton(Icons::Help)) { - hasToolchain = Toolchain::dir.exists(); + hasToolchain = ExporterBase::toolchainDir.exists(); // Don't do this relative to toolchain variable, that won't work on Windows auto const versionFile = ProjectInfo::appDataDir.getChildFile("Toolchain").getChildFile("VERSION"); @@ -261,7 +261,7 @@ HeavyExportDialog::~HeavyExportDialog() Dialogs::dismissFileDialog(); // Clean up temp files - Toolchain::deleteTempFiles(); + ExporterBase::deleteTempFiles(); } void HeavyExportDialog::paint(Graphics& g) diff --git a/Source/Heavy/HeavyExportDialog.h b/Source/Heavy/HeavyExportDialog.h index 99f40a60b7..2c680eec77 100644 --- a/Source/Heavy/HeavyExportDialog.h +++ b/Source/Heavy/HeavyExportDialog.h @@ -19,7 +19,7 @@ class HeavyExportDialog final : public Component { std::unique_ptr exporterPanel; std::unique_ptr infoButton; - //std::unique_ptr helpDialog; + // std::unique_ptr helpDialog; public: explicit HeavyExportDialog(Dialog* dialog); diff --git a/Source/Heavy/OWLExporter.h b/Source/Heavy/OWLExporter.h index 788abec126..4bd6448cb8 100644 --- a/Source/Heavy/OWLExporter.h +++ b/Source/Heavy/OWLExporter.h @@ -1,10 +1,11 @@ +#pragma once /* // Copyright (c) 2024 Timothy Schoen and Wasted Audio // For information on usage and redistribution, and for a DISCLAIMER OF ALL // WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ -class OWLExporter : public ExporterBase { +class OWLExporter final : public ExporterBase { public: Value targetBoardValue = Value(var(2)); Value exportTypeValue = SynchronousValue(var(3)); @@ -44,7 +45,7 @@ class OWLExporter : public ExporterBase { flashButton.onClick = [this] { auto const tempFolder = File::getSpecialLocation(File::tempDirectory).getChildFile("Heavy-" + Uuid().toString().substring(10)); - Toolchain::deleteTempFileLater(tempFolder); + deleteTempFileLater(tempFolder); startExport(tempFolder); }; } @@ -63,7 +64,7 @@ class OWLExporter : public ExporterBase { void setState(ValueTree& stateTree) override { - auto tree = stateTree.getChildWithName("OWL"); + auto const tree = stateTree.getChildWithName("OWL"); inputPatchValue = tree.getProperty("inputPatchValue"); projectNameValue = tree.getProperty("projectNameValue"); projectCopyrightValue = tree.getProperty("projectCopyrightValue"); @@ -85,7 +86,7 @@ class OWLExporter : public ExporterBase { flashButton.setEnabled(validPatchSelected); int const exportType = getValue(exportTypeValue); - bool flash = exportType == 3 || exportType == 4; + bool const flash = exportType == 3 || exportType == 4; exportButton.setVisible(!flash); flashButton.setVisible(flash); @@ -94,17 +95,13 @@ class OWLExporter : public ExporterBase { bool performExport(String const& pdPatch, String const& outdir, String const& name, String const& copyright, StringArray const& searchPaths) override { - auto target = getValue(targetBoardValue); - bool compile = getValue(exportTypeValue) - 1; - bool load = getValue(exportTypeValue) == 3; - bool store = getValue(exportTypeValue) == 4; - int slot = getValue(storeSlotValue); + auto const target = getValue(targetBoardValue); + bool const compile = getValue(exportTypeValue) - 1; + bool const load = getValue(exportTypeValue) == 3; + bool const store = getValue(exportTypeValue) == 4; + int const slot = getValue(storeSlotValue); -#if JUCE_WINDOWS - auto const heavyPath = heavyExecutable.getFullPathName().replaceCharacter('\\', '/'); -#else - auto const heavyPath = heavyExecutable.getFullPathName(); -#endif + auto const heavyPath = pathToString(heavyExecutable); StringArray args = { heavyPath.quoted(), pdPatch.quoted(), "-o", outdir.quoted() }; args.add("-n" + name); @@ -123,8 +120,7 @@ class OWLExporter : public ExporterBase { } auto const command = args.joinIntoString(" "); - exportingView->logToConsole("Command: " + command + "\n"); - Toolchain::startShellScript(command, this); + startShellScript(command); waitForProcessToFinish(-1); exportingView->flushConsole(); @@ -137,18 +133,17 @@ class OWLExporter : public ExporterBase { // Delay to get correct exit code Time::waitForMillisecondCounter(Time::getMillisecondCounter() + 300); - auto outputFile = File(outdir); + auto const outputFile = File(outdir); auto sourceDir = outputFile.getChildFile("Source"); - bool heavyExitCode = getExitCode(); + bool const heavyExitCode = getExitCode(); if (compile) { - auto workingDir = File::getCurrentWorkingDirectory(); + auto const workingDir = File::getCurrentWorkingDirectory(); - auto bin = Toolchain::dir.getChildFile("bin"); - auto OWL = Toolchain::dir.getChildFile("lib").getChildFile("OwlProgram"); + auto const bin = toolchainDir.getChildFile("bin"); + auto const OWL = toolchainDir.getChildFile("lib").getChildFile("OwlProgram"); auto make = bin.getChildFile("make" + exeSuffix); - auto compiler = bin.getChildFile("arm-none-eabi-gcc" + exeSuffix); OWL.copyDirectoryTo(outputFile.getChildFile("OwlProgram")); @@ -157,7 +152,7 @@ class OWLExporter : public ExporterBase { outputFile.getChildFile("c").deleteRecursively(); // Run from within OwlProgram directory - auto OwlDir = outputFile.getChildFile("OwlProgram"); + auto const OwlDir = outputFile.getChildFile("OwlProgram"); OwlDir.setAsCurrentWorkingDirectory(); OwlDir.getChildFile("Tools/FirmwareSender" + exeSuffix).setExecutePermission(1); @@ -165,24 +160,17 @@ class OWLExporter : public ExporterBase { String buildScript; -#if JUCE_WINDOWS - buildScript += make.getFullPathName().replaceCharacter('\\', '/') - + " -j4" - + " TOOLROOT=" + gccPath.replaceCharacter('\\', '/') + "/" - + " BUILD=../" - + " PATCHNAME=" + name - + " PATCHCLASS=HeavyPatch" - + " PATCHFILE=HeavyOWL_" + name + ".hpp" - + " SHELL=" + Toolchain::dir.getChildFile("bin").getChildFile("bash.exe").getFullPathName().replaceCharacter('\\', '/').quoted(); -#else - buildScript += make.getFullPathName() + + buildScript += pathToString(make) + " -j4" - + " TOOLROOT=" + gccPath + "/" +#if JUCE_WINDOWS + + " SHELL=" + pathToString(toolchainDir.getChildFile("bin").getChildFile("bash.exe")).quoted() +#endif + + " TOOLROOT=" + pathToString(gccPath) + "/" + " BUILD=../" + " PATCHNAME=" + name + " PATCHCLASS=HeavyPatch" + " PATCHFILE=HeavyOWL_" + name + ".hpp"; -#endif buildScript += " PLATFORM=OWL" + String(target); @@ -198,7 +186,7 @@ class OWLExporter : public ExporterBase { buildScript += " patch"; } - Toolchain::startShellScript(buildScript, this); + startShellScript(buildScript); waitForProcessToFinish(-1); exportingView->flushConsole(); @@ -209,7 +197,7 @@ class OWLExporter : public ExporterBase { // Delay to get correct exit code Time::waitForMillisecondCounter(Time::getMillisecondCounter() + 300); - auto compileExitCode = getExitCode(); + auto const compileExitCode = getExitCode(); // cleanup outputFile.getChildFile("OwlProgram").deleteRecursively(); @@ -227,11 +215,15 @@ class OWLExporter : public ExporterBase { // rename binary outputFile.getChildFile("patch.bin").moveFileTo(outputFile.getChildFile(name + ".bin")); + if(!compileExitCode) { + exportingView->logToConsole("Compilation finished"); + } + return heavyExitCode && compileExitCode; } else { - auto outputFile = File(outdir); + auto const outputFile = File(outdir); - auto OWL = Toolchain::dir.getChildFile("lib").getChildFile("OwlProgram"); + auto const OWL = toolchainDir.getChildFile("lib").getChildFile("OwlProgram"); OWL.copyDirectoryTo(outputFile.getChildFile("OwlProgram")); outputFile.getChildFile("ir").deleteRecursively(); diff --git a/Source/Heavy/PdExporter.h b/Source/Heavy/PdExporter.h index 1eafb6486f..4b24ca3656 100644 --- a/Source/Heavy/PdExporter.h +++ b/Source/Heavy/PdExporter.h @@ -61,15 +61,11 @@ class PdExporter final : public ExporterBase { } } - bool performExport(String const& pdPatch, String const& outdir, String const &name, String const& copyright, StringArray const& searchPaths) override + bool performExport(String const& pdPatch, String const& outdir, String const& name, String const& copyright, StringArray const& searchPaths) override { exportingView->showState(ExportingProgressView::Exporting); -#if JUCE_WINDOWS - auto const heavyPath = heavyExecutable.getFullPathName().replaceCharacter('\\', '/'); -#else - auto const heavyPath = heavyExecutable.getFullPathName(); -#endif + auto const heavyPath = pathToString(heavyExecutable); StringArray args = { heavyPath.quoted(), pdPatch.quoted(), "-o", outdir.quoted() }; args.add("-n" + name); @@ -91,8 +87,7 @@ class PdExporter final : public ExporterBase { return true; auto const command = args.joinIntoString(" "); - exportingView->logToConsole("Command: " + command + "\n"); - Toolchain::startShellScript(command, this); + startShellScript(command); waitForProcessToFinish(-1); exportingView->flushConsole(); @@ -114,12 +109,12 @@ class PdExporter final : public ExporterBase { outputFile.setAsCurrentWorkingDirectory(); - auto const bin = Toolchain::dir.getChildFile("bin"); + auto const bin = toolchainDir.getChildFile("bin"); auto make = bin.getChildFile("make" + exeSuffix); auto makefile = outputFile.getChildFile("Makefile"); #if JUCE_MAC - Toolchain::startShellScript("make -j4", this); + startShellScript("make -j4 suppress-wunused=1"); #elif JUCE_WINDOWS File pdDll; if (ProjectInfo::isStandalone) { @@ -128,22 +123,22 @@ class PdExporter final : public ExporterBase { pdDll = File::getSpecialLocation(File::globalApplicationsDirectory).getChildFile("plugdata"); } - auto path = "export PATH=\"$PATH:" + Toolchain::dir.getChildFile("bin").getFullPathName().replaceCharacter('\\', '/') + "\"\n"; - auto cc = "CC=" + Toolchain::dir.getChildFile("bin").getChildFile("gcc.exe").getFullPathName().replaceCharacter('\\', '/') + " "; - auto cxx = "CXX=" + Toolchain::dir.getChildFile("bin").getChildFile("g++.exe").getFullPathName().replaceCharacter('\\', '/') + " "; - auto pdbindir = "PDBINDIR=\"" + pdDll.getFullPathName().replaceCharacter('\\', '/') + "\" "; - auto shell = " SHELL=" + Toolchain::dir.getChildFile("bin").getChildFile("bash.exe").getFullPathName().replaceCharacter('\\', '/').quoted(); + auto path = "export PATH=\"$PATH:" + pathToString(toolchainDir.getChildFile("bin")) + "\"\n"; + auto cc = "CC=" + pathToString(toolchainDir.getChildFile("bin").getChildFile("gcc.exe")) + " "; + auto cxx = "CXX=" + pathToString(toolchainDir.getChildFile("bin").getChildFile("g++.exe")) + " "; + auto pdbindir = "PDBINDIR=\"" + pathToString(pdDll) + "\" "; + auto shell = " SHELL=" + pathToString(toolchainDir.getChildFile("bin").getChildFile("bash.exe")).quoted(); - Toolchain::startShellScript(path + cc + cxx + pdbindir + make.getFullPathName().replaceCharacter('\\', '/') + " -j4" + shell, this); + startShellScript(path + cc + cxx + pdbindir + pathToString(make) + " -j4 suppress-wunused=1" + shell); #else // Linux or BSD - auto prepareEnvironmentScript = Toolchain::dir.getChildFile("scripts").getChildFile("anywhere-setup.sh").getFullPathName() + "\n"; + auto prepareEnvironmentScript = toolchainDir.getChildFile("scripts").getChildFile("anywhere-setup.sh").getFullPathName() + "\n"; auto buildScript = prepareEnvironmentScript + make.getFullPathName() - + " -j4"; + + " -j4 suppress-wunused=1"; - Toolchain::startShellScript(buildScript, this); + startShellScript(buildScript); #endif waitForProcessToFinish(-1); diff --git a/Source/Heavy/Toolchain.h b/Source/Heavy/ToolchainInstaller.h similarity index 72% rename from Source/Heavy/Toolchain.h rename to Source/Heavy/ToolchainInstaller.h index e34576bb7b..5374fe1526 100644 --- a/Source/Heavy/Toolchain.h +++ b/Source/Heavy/ToolchainInstaller.h @@ -12,83 +12,6 @@ #include "Utility/Decompress.h" #include "Constants.h" -struct Toolchain { -#if JUCE_WINDOWS - static inline File const dir = ProjectInfo::appDataDir.getChildFile("Toolchain").getChildFile("usr"); -#else - static inline File const dir = ProjectInfo::appDataDir.getChildFile("Toolchain"); -#endif - - static void deleteTempFileLater(File const& script) - { - tempFilesToDelete.add(script); - } - - static void deleteTempFiles() - { - for (auto& file : tempFilesToDelete) { - if (file.existsAsFile()) - file.deleteFile(); - if (file.isDirectory()) - file.deleteRecursively(); - } - } - - static void startShellScript(String const& scriptText, ChildProcess* processToUse = nullptr) - { - File scriptFile = File::createTempFile(".sh"); - Toolchain::deleteTempFileLater(scriptFile); - - auto const bash = String("#!/bin/bash\n"); - scriptFile.replaceWithText(bash + scriptText, false, false, "\n"); - -#if JUCE_WINDOWS - auto sh = Toolchain::dir.getChildFile("bin").getChildFile("sh.exe"); - - if (processToUse) { - processToUse->start(StringArray { sh.getFullPathName(), "--login", scriptFile.getFullPathName().replaceCharacter('\\', '/') }); - } else { - ChildProcess process; - process.start(StringArray { sh.getFullPathName(), "--login", scriptFile.getFullPathName().replaceCharacter('\\', '/') }); - process.waitForProcessToFinish(-1); - } -#else - scriptFile.setExecutePermission(true); - - if (processToUse) { - processToUse->start(scriptFile.getFullPathName()); - } else { - ChildProcess process; - process.start(scriptFile.getFullPathName()); - process.waitForProcessToFinish(-1); - } -#endif - } - - static String startShellScriptWithOutput(String const& scriptText) - { - File scriptFile = File::createTempFile(".sh"); - Toolchain::deleteTempFileLater(scriptFile); - - auto const bash = String("#!/bin/bash\n"); - scriptFile.replaceWithText(bash + scriptText, false, false, "\n"); - - ChildProcess process; -#if JUCE_WINDOWS - auto sh = Toolchain::dir.getChildFile("bin").getChildFile("sh.exe"); - auto arguments = StringArray { sh.getFullPathName(), "--login", scriptFile.getFullPathName().replaceCharacter('\\', '/') }; -#else - scriptFile.setExecutePermission(true); - auto arguments = scriptFile.getFullPathName(); -#endif - process.start(arguments, ChildProcess::wantStdOut | ChildProcess::wantStdErr); - return process.readAllProcessOutput(); - } - -private: - inline static SmallArray tempFilesToDelete; -}; - class ToolchainInstaller final : public Component , public Thread , public Timer { @@ -245,7 +168,7 @@ class ToolchainInstaller final : public Component bytesDownloaded += written; - float progress = static_cast(bytesDownloaded) / static_cast(totalBytes); + float const progress = static_cast(bytesDownloaded) / static_cast(totalBytes); if (threadShouldExit()) return; @@ -270,7 +193,7 @@ class ToolchainInstaller final : public Component #else int expectedSize = 500 * 1024 * 1024; #endif - auto success = Decompress::extractTarXz((const uint8_t *)toolchainData.getData(), toolchainData.getSize(), toolchainDir.getParentDirectory(), expectedSize); + auto success = Decompress::extractTarXz((const uint8_t*)toolchainData.getData(), toolchainData.getSize(), toolchainDir.getParentDirectory(), expectedSize); if (!success || statusCode >= 400) { MessageManager::callAsync([this] { @@ -283,8 +206,8 @@ class ToolchainInstaller final : public Component } #if JUCE_WINDOWS - File usbDriverInstaller = Toolchain::dir.getChildFile("etc").getChildFile("usb_driver").getChildFile("install-filter.exe"); - File driverSpec = Toolchain::dir.getChildFile("etc").getChildFile("usb_driver").getChildFile("DFU_in_FS_Mode.inf"); + File usbDriverInstaller = toolchainDir.getChildFile("etc").getChildFile("usb_driver").getChildFile("install-filter.exe"); + File driverSpec = toolchainDir.getChildFile("etc").getChildFile("usb_driver").getChildFile("DFU_in_FS_Mode.inf"); // Since we interact with ComponentPeer, better call it from the message thread MessageManager::callAsync([this, usbDriverInstaller, driverSpec]() mutable { @@ -297,8 +220,8 @@ class ToolchainInstaller final : public Component // This makes sure we can use dfu-util without admin privileges // Kinda sucks that we need to sudo this, but there's no other way AFAIK - auto askpassScript = Toolchain::dir.getChildFile("scripts").getChildFile("askpass.sh"); - auto udevInstallScript = Toolchain::dir.getChildFile("scripts").getChildFile("install_udev_rule.sh"); + auto askpassScript = toolchainDir.getChildFile("scripts").getChildFile("askpass.sh"); + auto udevInstallScript = toolchainDir.getChildFile("scripts").getChildFile("install_udev_rule.sh"); askpassScript.setExecutePermission(true); udevInstallScript.setExecutePermission(true); @@ -308,7 +231,9 @@ class ToolchainInstaller final : public Component } #elif JUCE_MAC - Toolchain::startShellScript("xcode-select --install"); + ChildProcess process; + process.start("xcode-select --install"); + process.waitForProcessToFinish(-1); #endif installProgress = 0.0f; @@ -328,9 +253,9 @@ class ToolchainInstaller final : public Component #if JUCE_WINDOWS String downloadSize = "1.2 GB"; #elif JUCE_MAC - String downloadSize = "490 MB"; + String downloadSize = "426 MB"; #else - String downloadSize = "829 MB"; + String downloadSize = "764 MB"; #endif class ToolchainInstallerButton final : public Component { @@ -366,6 +291,9 @@ class ToolchainInstaller final : public Component void mouseUp(MouseEvent const& e) override { + if (!e.mods.isLeftButtonDown()) + return; + onClick(); } diff --git a/Source/Heavy/WASMExporter.h b/Source/Heavy/WASMExporter.h index b37e488536..0e89f2ee5c 100644 --- a/Source/Heavy/WASMExporter.h +++ b/Source/Heavy/WASMExporter.h @@ -1,12 +1,12 @@ +#pragma once /* // Copyright (c) 2024 Timothy Schoen and Wasted Audio // For information on usage and redistribution, and for a DISCLAIMER OF ALL // WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ -class WASMExporter : public ExporterBase { +class WASMExporter final : public ExporterBase { public: - Value emsdkPathValue; WASMExporter(PluginEditor* editor, ExportingProgressView* exportingView) @@ -38,7 +38,7 @@ class WASMExporter : public ExporterBase { void setState(ValueTree& stateTree) override { - auto tree = stateTree.getChildWithName("WASM"); + auto const tree = stateTree.getChildWithName("WASM"); inputPatchValue = tree.getProperty("inputPatchValue"); projectNameValue = tree.getProperty("projectNameValue"); projectCopyrightValue = tree.getProperty("projectCopyrightValue"); @@ -62,11 +62,7 @@ class WASMExporter : public ExporterBase { { exportingView->showState(ExportingProgressView::Exporting); - #if JUCE_WINDOWS - auto const heavyPath = heavyExecutable.getFullPathName().replaceCharacter('\\', '/'); -#else - auto const heavyPath = heavyExecutable.getFullPathName(); -#endif + auto const heavyPath = pathToString(heavyExecutable); StringArray args = { heavyPath.quoted(), pdPatch.quoted(), "-o", outdir.quoted() }; args.add("-n" + name); @@ -89,7 +85,6 @@ class WASMExporter : public ExporterBase { return true; auto compileString = args.joinIntoString(" "); - exportingView->logToConsole("Command: " + compileString + "\n"); #if JUCE_WINDOWS auto buildScript = "source " + emsdkPath.replaceCharacter('\\', '/') + "/emsdk_env.sh; " + compileString; @@ -97,7 +92,7 @@ class WASMExporter : public ExporterBase { auto buildScript = "source " + emsdkPath + "/emsdk_env.sh; " + compileString; #endif - Toolchain::startShellScript(buildScript, this); + startShellScript(buildScript); waitForProcessToFinish(-1); exportingView->flushConsole(); @@ -105,7 +100,7 @@ class WASMExporter : public ExporterBase { if (shouldQuit) return true; - auto outputFile = File(outdir); + auto const outputFile = File(outdir); outputFile.getChildFile("c").deleteRecursively(); outputFile.getChildFile("ir").deleteRecursively(); outputFile.getChildFile("hv").deleteRecursively(); @@ -113,7 +108,7 @@ class WASMExporter : public ExporterBase { // Delay to get correct exit code Time::waitForMillisecondCounter(Time::getMillisecondCounter() + 300); - bool generationExitCode = getExitCode(); + bool const generationExitCode = getExitCode(); return generationExitCode; } }; diff --git a/Source/Iolet.cpp b/Source/Iolet.cpp index 49d66d445b..cfde04b969 100644 --- a/Source/Iolet.cpp +++ b/Source/Iolet.cpp @@ -326,7 +326,7 @@ SmallArray Iolet::getConnections() const return result; } -Iolet* Iolet::findNearestIolet(Canvas* cnv, Point position, bool const inlet, Object* objectToExclude) +Iolet* Iolet::findNearestIolet(Canvas* cnv, Point const position, bool const inlet, Object const* objectToExclude) { // Find all potential iolets SmallArray allIolets; diff --git a/Source/Iolet.h b/Source/Iolet.h index 5a96c92b15..936176d21f 100644 --- a/Source/Iolet.h +++ b/Source/Iolet.h @@ -36,7 +36,7 @@ class Iolet final : public Component void valueChanged(Value& v) override; void settingsChanged(String const& name, var const& value) override; - static Iolet* findNearestIolet(Canvas* cnv, Point position, bool inlet, Object* boxToExclude = nullptr); + static Iolet* findNearestIolet(Canvas* cnv, Point position, bool inlet, Object const* boxToExclude = nullptr); void createConnection(); diff --git a/Source/LookAndFeel.cpp b/Source/LookAndFeel.cpp index 563e94cfdb..cf35f3b57e 100644 --- a/Source/LookAndFeel.cpp +++ b/Source/LookAndFeel.cpp @@ -167,15 +167,6 @@ Font PlugDataLook::getTextButtonFont(TextButton& but, int const buttonHeight) return { buttonHeight / 1.7f }; } -void PlugDataLook::drawLinearSlider(Graphics& g, int const x, int const y, int const width, int const height, float const sliderPos, float const minSliderPos, float const maxSliderPos, Slider::SliderStyle const style, Slider& slider) -{ - if (slider.getProperties()["Style"] == "SliderObject") { - drawGUIObjectSlider(g, x, y, width, height, sliderPos, minSliderPos, maxSliderPos, slider); - } else { - LookAndFeel_V4::drawLinearSlider(g, x, y, width, height, sliderPos, minSliderPos, maxSliderPos, style, slider); - } -} - Button* PlugDataLook::createDocumentWindowButton(int const buttonType) { if (buttonType == -1) @@ -499,33 +490,6 @@ PopupMenu::Options PlugDataLook::getOptionsForComboBoxPopupMenu(ComboBox& box, L return options; } -void PlugDataLook::drawGUIObjectSlider(Graphics& g, int const x, int const y, int const width, int const height, float sliderPos, float minSliderPos, float maxSliderPos, Slider& slider) -{ - auto const sliderBounds = slider.getLocalBounds().toFloat().reduced(1.0f); - - g.setColour(findColour(Slider::backgroundColourId)); - g.fillRect(sliderBounds); - - constexpr auto thumbSize = 4.0f; - auto const cornerSize = Corners::objectCornerRadius / 2.0f; - - Path toDraw; - if (slider.isHorizontal()) { - sliderPos = jmap(sliderPos, x, width, x, width - thumbSize); - - auto const b = Rectangle(thumbSize, height).translated(sliderPos, y); - - g.setColour(findColour(Slider::trackColourId)); - g.fillRoundedRectangle(b, cornerSize); - } else { - sliderPos = jmap(sliderPos, y, height, y, height - thumbSize); - auto const b = Rectangle(width, thumbSize).translated(x, sliderPos); - - g.setColour(findColour(Slider::trackColourId)); - g.fillRoundedRectangle(b, cornerSize); - } -} - void PlugDataLook::fillTextEditorBackground(Graphics& g, int const width, int const height, TextEditor& textEditor) { if (textEditor.getProperties()["NoBackground"].isVoid()) { @@ -549,20 +513,20 @@ void PlugDataLook::drawTextEditorOutline(Graphics& g, int const width, int const } } -void PlugDataLook::drawSpinningWaitAnimation(Graphics& g, const Colour& colour, int x, int y, int w, int h) +void PlugDataLook::drawSpinningWaitAnimation(Graphics& g, const Colour& colour, int const x, int const y, int const w, int const h) { - const float radius = (float) jmin(w, h) * 0.4f; + const float radius = static_cast(jmin(w, h)) * 0.4f; const float thickness = radius * 0.3f; - const float cx = (float)x + (float)w * 0.5f; - const float cy = (float)y + (float)h * 0.5f; + const float cx = static_cast(x) + static_cast(w) * 0.5f; + const float cy = static_cast(y) + static_cast(h) * 0.5f; // Compute animation progress const double animationTime = Time::getMillisecondCounterHiRes() / 1000.0; const double progress = fmod(animationTime, 2.0); // Loops every 2 seconds // Adwaita-style arc calculation - const float minArcLength = MathConstants::pi * 0.2f; // Shortest segment - const float maxArcLength = MathConstants::pi * 0.8f; // Longest segment + constexpr float minArcLength = MathConstants::pi * 0.2f; // Shortest segment + constexpr float maxArcLength = MathConstants::pi * 0.8f; // Longest segment const float startAngle = MathConstants::twoPi * progress; // Rotating angle const float t = (sinf(progress * MathConstants::pi) + 1.0f) / 2.0f; // Smooth curve const float arcLength = minArcLength + t * (maxArcLength - minArcLength); @@ -708,7 +672,7 @@ void PlugDataLook::drawPropertyPanelSectionHeader(Graphics& g, String const& nam Fonts::drawStyledText(g, name, textX, 0, std::max(width - textX - 4, 0), height, findColour(PropertyComponent::labelTextColourId), Bold, height * 0.6f); } -Rectangle PlugDataLook::getTooltipBounds(String const& tipText, Point screenPos, Rectangle parentArea) +Rectangle PlugDataLook::getTooltipBounds(String const& tipText, Point const screenPos, Rectangle parentArea) { auto const expandTooltip = ProjectInfo::canUseSemiTransparentWindows(); @@ -740,6 +704,18 @@ Rectangle PlugDataLook::getTooltipBounds(String const& tipText, Point auto const w = static_cast(tl.getWidth() + marginX); auto const h = static_cast(tl.getHeight() + marginY); + +#if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD + if (!ProjectInfo::isStandalone) { + auto const mouseSource = Desktop::getInstance().getMainMouseSource(); + auto* newComp = mouseSource.isTouch() ? nullptr : mouseSource.getComponentUnderMouse(); + if(newComp) { + auto globalScale = SettingsFile::getInstance()->getProperty("global_scale"); + auto transformScale = Component::getApproximateScaleFactorForComponent(newComp); + parentArea /= (transformScale / globalScale); + } + } +#endif return Rectangle(screenPos.x > parentArea.getCentreX() ? screenPos.x - (w + 12) : screenPos.x + 24, screenPos.y > parentArea.getCentreY() ? screenPos.y - (h + 6) : screenPos.y + 6, @@ -885,11 +861,11 @@ void PlugDataLook::drawAlertBox(Graphics& g, AlertWindow& alert, Rectangle const& textArea, TextLayout& textLayout) { constexpr auto cornerSize = Corners::largeCornerRadius; - + auto const bounds = alert.getLocalBounds().reduced(1); + g.setColour(alert.findColour(PlugDataColour::outlineColourId)); - g.drawRoundedRectangle(alert.getLocalBounds().toFloat(), cornerSize, 1.0f); + g.drawRoundedRectangle(bounds.toFloat(), cornerSize, 1.0f); - auto const bounds = alert.getLocalBounds().reduced(1); g.reduceClipRegion(bounds); g.setColour(alert.findColour(PlugDataColour::dialogBackgroundColourId)); @@ -929,7 +905,7 @@ void PlugDataLook::drawAlertBox(Graphics& g, AlertWindow& alert, GlyphArrangement ga; ga.addFittedText({ static_cast(iconRect.getHeight()) * 0.9f, Font::bold }, - String::charToString((juce_wchar) static_cast(character)), + String::charToString(static_cast(character)), static_cast(iconRect.getX()), static_cast(iconRect.getY()), static_cast(iconRect.getWidth()), static_cast(iconRect.getHeight()), Justification::centred, false); @@ -1177,12 +1153,12 @@ bool PlugDataLook::getUseStraightConnections() return useStraightConnections; } -bool PlugDataLook::getUseFlagOutline() +bool PlugDataLook::getUseFlagOutline() const { return useFlagOutline; } -bool PlugDataLook::getUseSyntaxHighlighting() +bool PlugDataLook::getUseSyntaxHighlighting() const { return useSyntaxHighlighting; } diff --git a/Source/LookAndFeel.h b/Source/LookAndFeel.h index 88f365615f..a335cb04a2 100644 --- a/Source/LookAndFeel.h +++ b/Source/LookAndFeel.h @@ -81,8 +81,6 @@ struct PlugDataLook final : public LookAndFeel_V4 { Font getTextButtonFont(TextButton& but, int buttonHeight) override; - void drawLinearSlider(Graphics& g, int x, int y, int width, int height, float sliderPos, float minSliderPos, float maxSliderPos, Slider::SliderStyle style, Slider& slider) override; - Button* createDocumentWindowButton(int buttonType) override; void positionDocumentWindowButtons(DocumentWindow& window, @@ -124,14 +122,12 @@ struct PlugDataLook final : public LookAndFeel_V4 { void drawResizableFrame(Graphics& g, int w, int h, BorderSize const& border) override { } - void drawGUIObjectSlider(Graphics& g, int x, int y, int width, int height, float sliderPos, float minSliderPos, float maxSliderPos, Slider& slider); - void fillTextEditorBackground(Graphics& g, int width, int height, TextEditor& textEditor) override; void drawTextEditorOutline(Graphics& g, int width, int height, TextEditor& textEditor) override; - void drawSpinningWaitAnimation(Graphics& g, const Colour& colour, int x, int y, int w, int h) override; - + void drawSpinningWaitAnimation(Graphics& g, Colour const& colour, int x, int y, int w, int h) override; + void drawCornerResizer(Graphics& g, int w, int h, bool isMouseOver, bool isMouseDragging) override; void drawLasso(Graphics& g, Component& lassoComp) override; @@ -174,8 +170,8 @@ struct PlugDataLook final : public LookAndFeel_V4 { static bool getUseStraightConnections(); - bool getUseFlagOutline(); - bool getUseSyntaxHighlighting(); + bool getUseFlagOutline() const; + bool getUseSyntaxHighlighting() const; enum ConnectionStyle { ConnectionStyleDefault = 1, @@ -185,8 +181,8 @@ struct PlugDataLook final : public LookAndFeel_V4 { static inline ConnectionStyle useConnectionStyle = ConnectionStyleDefault; static ConnectionStyle getConnectionStyle(); - bool useFlagOutline; - bool useSyntaxHighlighting; + bool useFlagOutline = false; + bool useSyntaxHighlighting = false; static inline bool useSquareIolets; static inline bool useIoletSpacingEdge; @@ -208,7 +204,7 @@ struct PlugDataLook final : public LookAndFeel_V4 { void setMainComponent(Component* c) { mainComponent = c; } Component::SafePointer mainComponent; #endif - + #if JUCE_IOS static constexpr int ioletSize = 15; #else diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 36abcb42ed..ba3af95b3e 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -127,7 +127,7 @@ void NVGSurface::initialise() setVisible(true); lastRenderScale = calculateRenderScale(); - nvg = nvgCreateContext(view, NVG_ANTIALIAS | NVG_TRIPLE_BUFFER, getWidth() * lastRenderScale, getHeight() * lastRenderScale); + nvg = nvgCreateContext(view, 0, getWidth() * lastRenderScale, getHeight() * lastRenderScale); #else setVisible(true); glContext->attachTo(*this); @@ -137,13 +137,26 @@ void NVGSurface::initialise() nvg = nvgCreateContext(0); #endif if (!nvg) { - std::cerr << "could not initialise nvg" << std::endl; + static bool isShowingMessageBox = false; + if (!isShowingMessageBox) { + isShowingMessageBox = true; + std::cerr << "could not initialise nvg" << std::endl; + AlertWindow::showMessageBoxAsync(MessageBoxIconType::WarningIcon, + "Could not initialize plugdata", + "Please check that you have up-to-date graphics drivers installed. At least OpenGL 3.0 support is required to run plugdata.", + "OK", + nullptr, + nullptr); + } + return; } updateWindowContextVisibility(); surfaces[nvg] = this; + + nvgAtlasTextThreshold(nvg, 32.0f); nvgCreateFontMem(nvg, "Inter", (unsigned char*)BinaryData::InterRegular_ttf, BinaryData::InterRegular_ttfSize, 0); nvgCreateFontMem(nvg, "Inter-Regular", (unsigned char*)BinaryData::InterRegular_ttf, BinaryData::InterRegular_ttfSize, 0); nvgCreateFontMem(nvg, "Inter-Bold", (unsigned char*)BinaryData::InterBold_ttf, BinaryData::InterBold_ttfSize, 0); @@ -154,13 +167,23 @@ void NVGSurface::initialise() void NVGSurface::updateWindowContextVisibility() { + if(renderThroughImage == isRenderingThroughImage) return; + + isRenderingThroughImage = renderThroughImage; + + // Render a frame to the new target before showing/hiding GPU context + renderAll(); + #ifdef NANOVG_GL_IMPLEMENTATION - if(glContext) glContext->setVisible(!renderThroughImage); + if (glContext) + glContext->setVisible(!renderThroughImage); #else - if(auto* view = getView()) { + if (auto* view = getView()) { OSUtils::MTLSetVisible(view, !renderThroughImage); } #endif + + invalidateAll(); } void NVGSurface::detachContext() @@ -188,6 +211,7 @@ void NVGSurface::detachContext() #else glContext->detach(); #endif + isRenderingThroughImage = false; } } @@ -200,7 +224,7 @@ void NVGSurface::updateBufferSize() if (fbWidth != scaledWidth || fbHeight != scaledHeight || !invalidFBO) { if (invalidFBO) nvgDeleteFramebuffer(invalidFBO); - invalidFBO = nvgCreateFramebuffer(nvg, scaledWidth, scaledHeight, NVG_IMAGE_PREMULTIPLIED); + invalidFBO = nvgCreateFramebuffer(nvg, scaledWidth, scaledHeight, 0); fbWidth = scaledWidth; fbHeight = scaledHeight; invalidArea = getLocalBounds(); @@ -246,25 +270,24 @@ float NVGSurface::getRenderScale() const return lastRenderScale; } -void NVGSurface::updateBounds(Rectangle bounds) +void NVGSurface::updateBounds(Rectangle const bounds) { + currentBounds = bounds; + +#if JUCE_LINUX || JUCE_BSD + // Directly setting bounds gives the best result on Linux setBounds(bounds); - +#endif + #ifdef NANOVG_GL_IMPLEMENTATION updateWindowContextVisibility(); #endif + + invalidateAll(); } void NVGSurface::resized() { -#ifdef NANOVG_METAL_IMPLEMENTATION - if (auto* view = getView()) { - auto const renderScale = getRenderScale(); - auto const* topLevel = getTopLevelComponent(); - auto const bounds = topLevel->getLocalArea(this, getLocalBounds()).toFloat() * renderScale; - mnvgSetViewBounds(view, bounds.getWidth(), bounds.getHeight()); - } -#endif backupImageComponent.setBounds(editor->getLocalArea(this, getLocalBounds())); invalidateAll(); } @@ -274,7 +297,7 @@ void NVGSurface::invalidateAll() invalidArea = invalidArea.getUnion(getLocalBounds()); } -void NVGSurface::invalidateArea(Rectangle area) +void NVGSurface::invalidateArea(Rectangle const area) { invalidArea = invalidArea.getUnion(area); } @@ -294,13 +317,35 @@ void NVGSurface::render() } lastRenderTime = startTime; } + if (!getPeer()) { return; } + + // Do this right before rendering, so that it doesn't show a frame with the last rendered content skewed to the new view size + if(getBounds() != currentBounds) + { + setBounds(currentBounds); + } + +#if JUCE_LINUX + if(skipFrame) + { + // On Linux, there is a strange bug where repaints will be executed immediately if the last repaint took too long + // This will then completely occupy the message thread, leaving no space for handling interaction... + // So if our rendering took too long, we need to manually skip a frame + skipFrame = false; + return; + } + auto start = Time::getMillisecondCounter(); +#endif if (!nvg) { initialise(); + if (!nvg) { + return; + } } if (!makeContextActive()) { @@ -322,11 +367,20 @@ void NVGSurface::render() { return; } + auto viewWidth = getWidth() * devicePixelScale; auto viewHeight = getHeight() * devicePixelScale; #else auto viewWidth = getWidth() * pixelScale; auto viewHeight = getHeight() * pixelScale; + + // In case we apply a global scale factor, calculate the necessary fractional offset for glViewport size + if(!approximatelyEqual(desktopScale, 1.0f)) { + auto desktopScaleOffsetX = getWidth() * desktopScale; + auto desktopScaleOffsetY = getHeight() * desktopScale; + viewWidth += (std::ceil(desktopScaleOffsetX) - desktopScaleOffsetX) * devicePixelScale; + viewHeight += (std::ceil(desktopScaleOffsetY) - desktopScaleOffsetY) * devicePixelScale; + } #endif #if ENABLE_FPS_COUNT @@ -337,12 +391,11 @@ void NVGSurface::render() invalidArea = invalidArea.getIntersection(getLocalBounds()); - for(auto bufferedObject : bufferedObjects) - { - if(bufferedObject) + for (auto bufferedObject : bufferedObjects) { + if (bufferedObject) bufferedObject->updateFramebuffers(nvg); } - + if (!invalidArea.isEmpty()) { // Draw only the invalidated region on top of framebuffer nvgBindFramebuffer(invalidFBO); @@ -369,17 +422,49 @@ void NVGSurface::render() } if (needsBufferSwap) { - nvgBindFramebuffer(nullptr); - nvgBlitFramebuffer(nvg, invalidFBO, 0, 0, viewWidth, viewHeight); + blitToScreen(); + needsBufferSwap = false; + } -#ifdef NANOVG_GL_IMPLEMENTATION - glContext->swapBuffers(); +#if JUCE_LINUX + auto* display = Desktop::getInstance().getDisplays().getPrimaryDisplay(); + auto refreshRate = display ? display->verticalFrequencyHz.value_or(60.0) : 60.0; + if(Time::getMillisecondCounter() - start > (1000 / refreshRate)) + { + skipFrame = true; + } #endif - needsBufferSwap = false; +} + +void NVGSurface::blitToScreen() +{ + if (!makeContextActive() || !invalidFBO) { + return; } + + auto pixelScale = calculateRenderScale(); + +#if NANOVG_METAL_IMPLEMENTATION + auto const devicePixelScale = pixelScale / Desktop::getInstance().getGlobalScaleFactor(); + auto viewWidth = getWidth() * devicePixelScale; + auto viewHeight = getHeight() * devicePixelScale; + if (auto* view = getView()) { + mnvgSetViewBounds(view, getWidth() * pixelScale, getHeight() * pixelScale); + } +#else + auto viewWidth = getWidth() * pixelScale; + auto viewHeight = getHeight() * pixelScale; +#endif + + nvgBindFramebuffer(nullptr); + nvgBlitFramebuffer(nvg, invalidFBO, 0, 0, viewWidth, viewHeight); + +#ifdef NANOVG_GL_IMPLEMENTATION + glContext->swapBuffers(); +#endif } -void NVGSurface::renderFrameToImage(Image& image, Rectangle area) +void NVGSurface::renderFrameToImage(Image& image, Rectangle const area) { nvgBindFramebuffer(nullptr); auto const bufferSize = fbHeight * fbWidth; @@ -393,7 +478,7 @@ void NVGSurface::renderFrameToImage(Image& image, Rectangle area) image = Image(Image::PixelFormat::ARGB, fbWidth, fbHeight, true); } - Image::BitmapData imageData(image, Image::BitmapData::writeOnly); + Image::BitmapData const imageData(image, Image::BitmapData::writeOnly); for (int y = 0; y < static_cast(region.getHeight()); y++) { auto* scanLine = reinterpret_cast(imageData.getLinePointer(y + region.getY())); @@ -425,9 +510,8 @@ void NVGSurface::renderFrameToImage(Image& image, Rectangle area) void NVGSurface::setRenderThroughImage(bool const shouldRenderThroughImage) { renderThroughImage = shouldRenderThroughImage; - invalidateAll(); - updateWindowContextVisibility(); backupImageComponent.setVisible(shouldRenderThroughImage); + //if(renderThroughImage) updateWindowContextVisibility(); } NVGSurface* NVGSurface::getSurfaceForContext(NVGcontext* nvg) @@ -449,3 +533,8 @@ void NVGSurface::removeBufferedObject(NVGComponent* component) { bufferedObjects.erase(component); } + +void NVGSurface::handleCommandMessage(int commandID) +{ + needsBufferSwap = true; +} diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index a2f02138a2..ac1abb4a1f 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -33,7 +33,7 @@ class NVGSurface final : #endif { public: - NVGSurface(PluginEditor* editor); + explicit NVGSurface(PluginEditor* editor); ~NVGSurface() override; void initialise(); @@ -42,6 +42,8 @@ class NVGSurface final : void renderAll(); void render(); + void blitToScreen(); + bool makeContextActive(); void detachContext(); @@ -56,10 +58,10 @@ class NVGSurface final : class InvalidationListener final : public CachedComponentImage { public: - InvalidationListener(NVGSurface& s, Component* origin, bool const passRepaintEvents = false) + InvalidationListener(NVGSurface& s, Component* origin, std::function canRepaintCheck = [] { return true; }) : surface(s) , originComponent(origin) - , passEvents(passRepaintEvents) + , canRepaint(canRepaintCheck) { } @@ -71,26 +73,26 @@ class NVGSurface final : auto invalidatedBounds = surface.getLocalArea(originComponent, rect.expanded(2).toFloat()).getSmallestIntegerContainer(); invalidatedBounds = invalidatedBounds.getIntersection(surface.getLocalBounds()); - if (originComponent->isVisible() && !invalidatedBounds.isEmpty()) { + if (originComponent->isVisible() && canRepaint() && !invalidatedBounds.isEmpty()) { surface.invalidateArea(invalidatedBounds); } - return surface.renderThroughImage || passEvents; + return surface.renderThroughImage; } bool invalidateAll() override { - if (originComponent->isVisible()) { + if (originComponent->isVisible() && canRepaint()) { surface.invalidateArea(originComponent->getLocalBounds()); } - return surface.renderThroughImage || passEvents; + return surface.renderThroughImage; } void releaseResources() override { } NVGSurface& surface; Component* originComponent; - bool passEvents; + std::function canRepaint; }; void invalidateArea(Rectangle area); @@ -103,16 +105,18 @@ class NVGSurface final : void renderFrameToImage(Image& image, Rectangle area); void resized() override; - + void addBufferedObject(NVGComponent* component); void removeBufferedObject(NVGComponent* component); - -private: - float calculateRenderScale() const; + void handleCommandMessage(int commandID) override; // Sets the surface context to render through floating window, or inside editor as image void updateWindowContextVisibility(); + +private: + + float calculateRenderScale() const; PluginEditor* editor; NVGcontext* nvg = nullptr; @@ -120,6 +124,7 @@ class NVGSurface final : std::unique_ptr vBlankAttachment; Rectangle invalidArea; + Rectangle currentBounds; NVGframebuffer* invalidFBO = nullptr; int fbWidth = 0, fbHeight = 0; @@ -127,13 +132,17 @@ class NVGSurface final : juce::Image backupRenderImage; bool renderThroughImage = false; + bool isRenderingThroughImage = false; ImageComponent backupImageComponent; HeapArray backupPixelData; - + UnorderedSegmentedSet> bufferedObjects; float lastRenderScale = 0.0f; uint32 lastRenderTime; +#if JUCE_LINUX + bool skipFrame = false; +#endif #if NANOVG_GL_IMPLEMENTATION std::unique_ptr glContext; @@ -141,4 +150,3 @@ class NVGSurface final : std::unique_ptr frameTimer; }; - diff --git a/Source/Object.cpp b/Source/Object.cpp index 7bc117a9fd..a25f2d6604 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -32,7 +32,7 @@ extern "C" { #include } -Object::Object(Canvas* parent, String const& name, Point position) +Object::Object(Canvas* parent, String const& name, Point const position) : NVGComponent(this) , cnv(parent) , editor(parent->editor) @@ -86,7 +86,7 @@ Rectangle Object::getSelectableBounds() const return getBounds().reduced(margin); } -void Object::setObjectBounds(Rectangle bounds) +void Object::setObjectBounds(Rectangle const bounds) { setBounds(bounds.expanded(margin) + cnv->canvasOrigin); } @@ -146,8 +146,11 @@ bool Object::isSelected() const void Object::settingsChanged(String const& name, var const& value) { if (name == "hvcc_mode") { - if (gui && !isHvccCompatible) { - isHvccCompatible = !hvccMode.get() || gui->checkHvccCompatibility(); + if (gui) { + if (value) + isHvccCompatible = gui->checkHvccCompatibility(); + else + isHvccCompatible = true; } repaint(); } @@ -209,7 +212,7 @@ bool Object::hitTest(int const x, int const y) void Object::mouseEnter(MouseEvent const& e) { drawIoletExpanded = true; - if(!getValue(locked)) { + if (!getValue(locked)) { repaint(); } } @@ -221,13 +224,16 @@ void Object::mouseExit(MouseEvent const& e) resizeZone = ResizableBorderComponent::Zone(ResizableBorderComponent::Zone::centre); validResizeZone = false; drawIoletExpanded = false; - if(!getValue(locked)) { + if (!getValue(locked)) { repaint(); } } void Object::mouseMove(MouseEvent const& e) { + using Zone = ResizableBorderComponent::Zone; + using Direction = ObjectBase::ResizeDirection; + if (!selectedFlag || locked == var(true) || commandLocked == var(true)) { setMouseCursor(MouseCursor::NormalCursor); updateMouseCursor(); @@ -243,17 +249,42 @@ void Object::mouseMove(MouseEvent const& e) auto const minH = jmax(b.getHeight() / 10.0f, jmin(10.0f, b.getHeight() / 3.0f)); if (corners[0].contains(e.position) || corners[1].contains(e.position) || (e.position.x < jmax(7.0f, minW) && b.getX() > 0.0f)) - zone |= ResizableBorderComponent::Zone::left; + zone |= Zone::left; else if (corners[2].contains(e.position) || corners[3].contains(e.position) || e.position.x >= b.getWidth() - jmax(7.0f, minW)) - zone |= ResizableBorderComponent::Zone::right; + zone |= Zone::right; if (corners[0].contains(e.position) || corners[3].contains(e.position) || e.position.y < jmax(7.0f, minH)) - zone |= ResizableBorderComponent::Zone::top; + zone |= Zone::top; else if (corners[1].contains(e.position) || corners[2].contains(e.position) || e.position.y >= b.getHeight() - jmax(7.0f, minH)) - zone |= ResizableBorderComponent::Zone::bottom; + zone |= Zone::bottom; + } + + auto allowedDirection = gui ? gui->getAllowedResizeDirections() : Direction::None; + switch(allowedDirection) + { + case Direction::None: { + zone = 0; + break; + } + case Direction::HorizontalOnly: { + zone &= (Zone::left | Zone::right); + break; + } + case Direction::VerticalOnly: { + zone &= (Zone::top | Zone::bottom); + break; + } + case Direction::DiagonalOnly: { + const bool hasHorizontal = zone & (Zone::left | Zone::right); + const bool hasVertical = zone & (Zone::top | Zone::bottom); + if (!(hasHorizontal && hasVertical)) + zone = 0; + break; + } + default: break; // Allow any direction } - resizeZone = static_cast(zone); + resizeZone = static_cast(zone); validResizeZone = resizeZone.getZoneFlags() != ResizableBorderComponent::Zone::centre && e.originalComponent == this && !(gui && gui->isEditorShown()) && !newObjectEditor; setMouseCursor(validResizeZone ? resizeZone.getMouseCursor() : MouseCursor::NormalCursor); @@ -373,7 +404,10 @@ void Object::setType(String const& newType, pd::WeakReference existingObject) gui->lock(cnv->isGraph || locked == var(true) || commandLocked == var(true)); gui->addMouseListener(this, true); addAndMakeVisible(gui.get()); - isHvccCompatible = !hvccMode.get() || gui->checkHvccCompatibility(); + if (hvccMode.get()) + isHvccCompatible = gui->checkHvccCompatibility(); + else + isHvccCompatible = true; } // Update inlets/outlets @@ -612,7 +646,7 @@ void Object::updateTooltips() continue; auto const name = String::fromUTF8((*obj.getRaw())->c_name->s_name); - auto* checkedObject = pd::Interface::checkObject(obj.getRaw()); + auto const* checkedObject = pd::Interface::checkObject(obj.getRaw()); if (name == "inlet" || name == "inlet~") { int size; char* str_ptr; @@ -693,9 +727,9 @@ void Object::updateIolets() for (auto* iolet : iolets) { if (gui && !iolet->isInlet) { - iolet->setHidden(gui->outletIsSymbol()); + iolet->setHidden(gui->hideOutlet()); } else if (gui && iolet->isInlet) { - iolet->setHidden(gui->inletIsSymbol()); + iolet->setHidden(gui->hideInlet()); } } @@ -947,7 +981,7 @@ void Object::mouseDrag(MouseEvent const& e) // Create undo step when we start resizing if (!ds.wasResized) { - auto* objPtr = static_cast(obj->getPointer()); + auto* objPtr = obj->getPointer(); auto const* cnv = obj->cnv; if (auto patchPtr = cnv->patch.getPointer()) { @@ -1142,8 +1176,7 @@ void Object::mouseDrag(MouseEvent const& e) if (object->numInputs && object->numOutputs && !object->iolets.empty()) { bool intersected = false; for (auto* connection : cnv->connections) { - if (connection->intersectsRectangle(object->iolets[0]->getCanvasBounds()) && - !iolets.contains(connection->inlet) && !iolets.contains(connection->outlet)) { + if (connection->intersectsRectangle(object->iolets[0]->getCanvasBounds()) && !iolets.contains(connection->inlet) && !iolets.contains(connection->outlet)) { object->iolets[0]->isTargeted = true; object->iolets[object->numInputs]->isTargeted = true; object->iolets[0]->repaint(); @@ -1493,11 +1526,7 @@ void Object::openHelpPatch() const return; } - if (auto const* helpCanvas = editor->getTabComponent().openPatch(URL(file))) { - if (auto patch = helpCanvas->patch.getPointer()) { - patch->gl_edit = 0; - } - } + editor->getTabComponent().openHelpPatch(URL(file)); return; } diff --git a/Source/ObjectGrid.cpp b/Source/ObjectGrid.cpp index 44777f47ce..4fbcc50f69 100644 --- a/Source/ObjectGrid.cpp +++ b/Source/ObjectGrid.cpp @@ -22,7 +22,7 @@ ObjectGrid::ObjectGrid(Canvas* cnv) gridSize = SettingsFile::getInstance()->getProperty("grid_size"); } -SmallArray ObjectGrid::getSnappableObjects(Object* draggedObject) +SmallArray ObjectGrid::getSnappableObjects(Object const* draggedObject) { auto const& cnv = draggedObject->cnv; if (!cnv->viewport) @@ -42,7 +42,7 @@ SmallArray ObjectGrid::getSnappableObjects(Object* draggedObject) auto centre = draggedObject->getBounds().getCentre(); - snappable.sort([centre](Object* a, Object* b) { + snappable.sort([centre](Object const* a, Object const* b) { auto const distA = a->getBounds().getCentre().getDistanceFrom(centre); auto const distB = b->getBounds().getCentre().getDistanceFrom(centre); @@ -185,9 +185,8 @@ Point ObjectGrid::performMove(Object* toDrag, Point dragOffset) } if (objectSnapped || connectionSnapped) { - auto lineScale = 0.75f / std::max(1.0f, getValue(toDrag->cnv->zoomScale)); - setIndicator(0, verticalIndicator, lineScale); - setIndicator(1, horizontalIndicator, lineScale); + setIndicator(0, verticalIndicator); + setIndicator(1, horizontalIndicator); return dragOffset + distance; } @@ -294,9 +293,8 @@ Point ObjectGrid::performResize(Object* toDrag, Point dragOffset, Rect } if (snapped) { - auto lineScale = 0.75f / std::max(1.0f, getValue(toDrag->cnv->zoomScale)); - setIndicator(0, verticalIndicator, lineScale); - setIndicator(1, horizontalIndicator, lineScale); + setIndicator(0, verticalIndicator); + setIndicator(1, horizontalIndicator); return dragOffset + distance; } } @@ -388,34 +386,36 @@ Line ObjectGrid::getObjectIndicatorLine(Side const side, Rectangle b1, void ObjectGrid::clearIndicators(bool const fast) { - float const lineFadeMs = fast ? 50 : 250; + float const lineFadeMs = fast ? 50 : 300; lineAlphaMultiplier[0] = dsp::FastMathApproximations::exp(-MathConstants::twoPi * 1000.0f / 60.0f / lineFadeMs); lineAlphaMultiplier[1] = lineAlphaMultiplier[0]; if (lineTargetAlpha[0] != 0.0f || lineTargetAlpha[1] != 0.0f) { lineTargetAlpha[0] = 0.0f; lineTargetAlpha[1] = 0.0f; - if(!isTimerRunning()) startTimerHz(60); + if (!isTimerRunning()) + startTimerHz(60); } } -void ObjectGrid::setIndicator(int const idx, Line line, float scale) +void ObjectGrid::setIndicator(int const idx, Line const line) { auto const lineIsEmpty = line.getLength() == 0; if (lineIsEmpty && line != lines[idx]) { lineAlphaMultiplier[idx] = dsp::FastMathApproximations::exp(-MathConstants::twoPi * 1000.0f / 60.0f / 50); lineTargetAlpha[idx] = 0.0f; - if(!isTimerRunning()) startTimerHz(60); - } else if (line != lines[idx]){ + if (!isTimerRunning()) + startTimerHz(60); + } else if (line != lines[idx]) { lineTargetAlpha[idx] = 1.0f; lineAlpha[idx] = 1.0f; auto lineArea = cnv->editor->nvgSurface.getLocalArea(cnv, Rectangle(line.getStart(), line.getEnd()).expanded(2)); - if(lines[idx].getLength() != 0) { + if (lines[idx].getLength() != 0) { auto const oldLineArea = cnv->editor->nvgSurface.getLocalArea(cnv, Rectangle(lines[idx].getStart(), lines[idx].getEnd()).expanded(2)); lineArea = lineArea.getUnion(oldLineArea); } - + cnv->editor->nvgSurface.invalidateArea(lineArea); lines[idx] = line; } diff --git a/Source/ObjectGrid.h b/Source/ObjectGrid.h index b71055a499..f26e7cada3 100644 --- a/Source/ObjectGrid.h +++ b/Source/ObjectGrid.h @@ -10,9 +10,9 @@ class Object; class Canvas; -struct ObjectGrid final : public SettingsFileListener +class ObjectGrid final : public SettingsFileListener , public Timer { - +public: int gridSize = 20; explicit ObjectGrid(Canvas* cnv); @@ -38,9 +38,9 @@ struct ObjectGrid final : public SettingsFileListener void settingsChanged(String const& name, var const& value) override; - static SmallArray getSnappableObjects(Object* draggedObject); + static SmallArray getSnappableObjects(Object const* draggedObject); - void setIndicator(int idx, Line line, float lineScale); + void setIndicator(int idx, Line line); static Line getObjectIndicatorLine(Side side, Rectangle b1, Rectangle b2); diff --git a/Source/Objects/AllGuis.h b/Source/Objects/AllGuis.h index 8218151be9..eb23a3ffd1 100644 --- a/Source/Objects/AllGuis.h +++ b/Source/Objects/AllGuis.h @@ -168,16 +168,16 @@ struct t_fake_keyboard { // [else/knob] struct t_fake_knob { t_object x_obj; - void *x_proxy; - t_glist *x_glist; + void* x_proxy; + t_glist* x_glist; int x_ctrl; int x_size; - double x_pos; // 0-1 normalized position + double x_pos; // 0-1 normalized position t_float x_exp; int x_expmode; int x_log; - t_float x_load; // value when loading patch - t_float x_arcstart; // arc start value + t_float x_load; // value when loading patch + t_float x_arcstart; // arc start value t_float x_radius; int x_arcstart_angle; int x_fill_bg; @@ -202,25 +202,25 @@ struct t_fake_knob { int x_jump; int x_readonly; double x_fval; - t_symbol *x_fg; - t_symbol *x_mg; - t_symbol *x_bg; - t_symbol *x_param; - t_symbol *x_var; - t_symbol *x_var_raw; + t_symbol* x_fg; + t_symbol* x_mg; + t_symbol* x_bg; + t_symbol* x_param; + t_symbol* x_var; + t_symbol* x_var_raw; int x_var_set; int x_savestate; int x_lb; - t_symbol *x_snd; - t_symbol *x_snd_raw; + t_symbol* x_snd; + t_symbol* x_snd_raw; int x_flag; int x_r_flag; int x_s_flag; int x_v_flag; int x_rcv_set; int x_snd_set; - t_symbol *x_rcv; - t_symbol *x_rcv_raw; + t_symbol* x_rcv; + t_symbol* x_rcv_raw; int x_circular; int x_arc; int x_zoom; @@ -240,7 +240,7 @@ struct t_fake_knob { char x_tag_sel[32]; char x_tag_number[32]; char x_buf[32]; // number buffer - t_symbol *x_ignore; + t_symbol* x_ignore; int x_ignore_int; }; diff --git a/Source/Objects/ArrayObject.h b/Source/Objects/ArrayObject.h index 261505d404..62f8e7765c 100644 --- a/Source/Objects/ArrayObject.h +++ b/Source/Objects/ArrayObject.h @@ -60,7 +60,7 @@ class GraphicalArray final : public Component setOpaque(false); } - ~GraphicalArray() + ~GraphicalArray() override { pd->unregisterMessageListener(this); } @@ -80,7 +80,7 @@ class GraphicalArray final : public Component if (mod == 0) result[i] = v[idx]; else { - float const part = float(mod) / float(newSize); + auto const part = static_cast(mod) / static_cast(newSize); result[i] = v[idx] * (1.0 - part) + v[idx + 1] * part; } } @@ -103,8 +103,8 @@ class GraphicalArray final : public Component float const pointOffset = style == Points; float const dh = (height - 2) / (scale[0] - scale[1]); - auto yToCoords = [scale, dh, pointOffset](float y){ - return ((((y - scale[1]) * dh) + 1) - pointOffset); + auto yToCoords = [scale, dh, pointOffset](float const y){ + return (y - scale[1]) * dh + 1 - pointOffset; }; auto const* pointPtr = points.data(); @@ -131,8 +131,8 @@ class GraphicalArray final : public Component for (int i = onset; i < numPoints; i++, pointPtr++) { switch (style) { case Points: { - float nextX = static_cast(i + 1) / numPoints * width; - float y = yToCoords(pointPtr[0]); + float const nextX = static_cast(i + 1) / numPoints * width; + float const y = yToCoords(pointPtr[0]); minY = std::min(y, minY); maxY = std::max(y, maxY); @@ -157,12 +157,12 @@ class GraphicalArray final : public Component break; } case Curve: { - float nextX = static_cast(i) / (numPoints - 1) * width; + float const nextX = static_cast(i) / (numPoints - 1) * width; if(std::abs(nextX - lastX) < 1.0f && i != 0 && i != numPoints-1) continue; - float y1 = yToCoords(pointPtr[0]); - float y2 = yToCoords(pointPtr[1]); + float const y1 = yToCoords(pointPtr[0]); + float const y2 = yToCoords(pointPtr[1]); // Curve logic taken from tcl/tk source code: // https://github.com/tcltk/tk/blob/c9fe293db7a52a34954db92d2bdc5454d4de3897/generic/tkTrig.c#L1363 @@ -222,7 +222,7 @@ class GraphicalArray final : public Component void paintGraph(NVGcontext* nvg) { - auto arrDrawMode = static_cast(getValue(drawMode) - 1); + auto const arrDrawMode = static_cast(getValue(drawMode) - 1); if(arrayNeedsUpdate) { if(vec.not_empty()) { @@ -274,85 +274,55 @@ class GraphicalArray final : public Component case hash("edit"): { if (atoms.size() <= 0) break; - MessageManager::callAsync([_this = SafePointer(this), shouldBeEditable = static_cast(atoms[0].getFloat())] { - if (_this) { - _this->editable = shouldBeEditable; - _this->setInterceptsMouseClicks(shouldBeEditable, false); - } - }); + auto shouldBeEditable = static_cast(atoms[0].getFloat()); + editable = shouldBeEditable; + setInterceptsMouseClicks(shouldBeEditable, false); break; } case hash("rename"): { if (atoms.size() <= 0) break; - MessageManager::callAsync([_this = SafePointer(this), newName = atoms[0].toString()] { - if (_this) { - _this->object->cnv->setSelected(_this->object, false); - _this->object->editor->sidebar->hideParameters(); - _this->name = newName; - } - }); - + + auto newName = atoms[0].toString(); + object->cnv->setSelected(object, false); + object->editor->sidebar->hideParameters(); + name = newName; break; } case hash("color"): { - MessageManager::callAsync([_this = SafePointer(this)] { - if (_this) - _this->repaint(); - }); + repaint(); break; } case hash("width"): { - MessageManager::callAsync([_this = SafePointer(this)] { - if (_this) { - _this->updateArrayPath(); - } - }); + updateArrayPath(); break; } case hash("style"): { - MessageManager::callAsync([_this = SafePointer(this), newDrawMode = static_cast(atoms[0].getFloat())] { - if (_this) { - setValueExcludingListener(_this->drawMode, newDrawMode + 1, _this.getComponent()); - _this->updateArrayPath(); - } - }); + auto newDrawMode = static_cast(atoms[0].getFloat()); + setValueExcludingListener(drawMode, newDrawMode + 1, this); + updateArrayPath(); break; } case hash("xticks"): { - MessageManager::callAsync([_this = SafePointer(this)] { - if (_this) - _this->repaint(); - }); + repaint(); break; } case hash("yticks"): { - MessageManager::callAsync([_this = SafePointer(this)] { - if (_this) - _this->repaint(); - }); + repaint(); break; } case hash("vis"): { - MessageManager::callAsync([_this = SafePointer(this), visible = atoms[0].getFloat()] { - if (_this) { - _this->visible = static_cast(visible); - _this->repaint(); - } - }); - + visible = atoms[0].getFloat(); + repaint(); break; } case hash("resize"): { - MessageManager::callAsync([_this = SafePointer(this), newSize = atoms[0].getFloat()] { - if (_this) { - _this->size = static_cast(newSize); - _this->updateSettings(); - _this->repaint(); - } - }); + size = static_cast(atoms[0].getFloat()); + updateSettings(); + repaint(); break; } + default: break; } } @@ -389,7 +359,7 @@ class GraphicalArray final : public Component void mouseDown(MouseEvent const& e) override { - if (error || !getEditMode() || !e.mods.isLeftButtonDown()) + if (error || !editable || !e.mods.isLeftButtonDown()) return; edited = true; @@ -404,7 +374,7 @@ class GraphicalArray final : public Component void mouseDrag(MouseEvent const& e) override { - if (error || !getEditMode() || !e.mods.isLeftButtonDown()) + if (error || !editable || !e.mods.isLeftButtonDown()) return; auto const s = static_cast(vec.size() - 1); @@ -448,7 +418,7 @@ class GraphicalArray final : public Component void mouseUp(MouseEvent const& e) override { - if (error || !getEditMode() || !e.mods.isLeftButtonDown()) + if (error || !editable || !e.mods.isLeftButtonDown()) return; if (auto ptr = arr.get()) { @@ -467,6 +437,10 @@ class GraphicalArray final : public Component updateArrayPath(); } } + + if (auto ptr = arr.get()) { + editable = ptr->x_edit; + } } bool willSaveContent() const @@ -536,15 +510,6 @@ class GraphicalArray final : public Component return { -1.0f, 1.0f }; } - bool getEditMode() const - { - if (auto ptr = arr.get()) { - return ptr->x_edit; - } - - return true; - } - // Gets the scale of the array. int getArraySize() const { @@ -618,7 +583,7 @@ class GraphicalArray final : public Component auto const arrSaveContents = getValue(saveContents); - int const flags = arrSaveContents + 2 * static_cast(arrDrawMode); + int const flags = arrSaveContents + 2 * arrDrawMode; t_symbol* name = pd->generateSymbol(arrName); if (auto garray = arr.get()) { @@ -642,7 +607,7 @@ class GraphicalArray final : public Component { auto scale = getScale(); range = var(VarArray { var(scale[0]), var(scale[1]) }); - size = var(static_cast(getArraySize())); + size = var(getArraySize()); saveContents = willSaveContent(); name = String(getUnexpandedName()); drawMode = static_cast(getDrawType()) + 1; @@ -746,6 +711,9 @@ struct ArrayPropertiesPanel final : public PropertiesPanelProperty void mouseUp(MouseEvent const& e) override { + if (!e.mods.isLeftButtonDown()) + return; + onClick(); } }; @@ -886,16 +854,13 @@ class ArrayListView final : public PropertiesPanel for (int i = 0; i < numProperties; i++) { auto& value = *arrayValues.add(new Value(vec[i].w_float)); value.addListener(this); - auto* property = new EditableComponent(String(i), value); + auto* property = new EditableComponent(String(i), value, true, ptr->x_glist->gl_y2, ptr->x_glist->gl_y1); auto* label = dynamic_cast(property->label.get()); - property->setRangeMin(ptr->x_glist->gl_y2); - property->setRangeMax(ptr->x_glist->gl_y1); - // Only send this after drag end so it doesn't interrupt the drag action label->dragEnd = [this] { - if (auto ptr = array.get()) { - plugdata_forward_message(ptr->x_glist, gensym("redraw"), 0, nullptr); + if (auto p = array.get()) { + plugdata_forward_message(p->x_glist, gensym("redraw"), 0, nullptr); } }; properties.set(i, property); @@ -1133,6 +1098,23 @@ class ArrayObject final : public ObjectBase { updateLabel(); } + + bool canReceiveMouseEvent(int const x, int const y) override + { + if(!getValue(cnv->locked)) + { + return true; + } + + for(auto* graph : graphs) + { + if(graph->editable) { + return true; + } + } + + return false; + } void onConstrainerCreate() override { @@ -1198,7 +1180,7 @@ class ArrayObject final : public ObjectBase { ticks.render(nvg, b); } - bool isTransparent() override { return true; }; + bool isTransparent() override { return true; } void updateGraphs() { @@ -1378,6 +1360,7 @@ class ArrayObject final : public ObjectBase { repaint(); break; } + default: break; } } diff --git a/Source/Objects/AtomHelper.h b/Source/Objects/AtomHelper.h index be2154af08..145baa0050 100644 --- a/Source/Objects/AtomHelper.h +++ b/Source/Objects/AtomHelper.h @@ -5,7 +5,7 @@ */ #pragma once -static t_atom* fake_gatom_getatom(t_fake_gatom* x) +static t_atom* fake_gatom_getatom(t_fake_gatom const* x) { int const ac = binbuf_getnatom(x->a_text.te_binbuf); t_atom const* av = binbuf_getvec(x->a_text.te_binbuf); @@ -91,7 +91,7 @@ class AtomHelper { } } - Rectangle getPdBounds(int const textLength) + Rectangle getPdBounds(int const textLength) const { if (auto atom = ptr.get()) { auto* patchPtr = cnv->patch.getRawPointer(); @@ -148,10 +148,10 @@ class AtomHelper { void checkBounds(Rectangle& bounds, Rectangle const& old, Rectangle const& limits, - bool isStretchingTop, + bool const isStretchingTop, bool const isStretchingLeft, - bool isStretchingBottom, - bool isStretchingRight) override + bool const isStretchingBottom, + bool const isStretchingRight) override { auto const oldBounds = old.reduced(Object::margin); @@ -174,14 +174,14 @@ class AtomHelper { helper->setFontHeight(atomSizes[heightIdx]); object->gui->setParameterExcludingListener(helper->fontSize, heightIdx + 1); - + if (isStretchingTop || isStretchingLeft) { auto const x = oldBounds.getRight() - (bounds.getWidth() - Object::doubleMargin); auto const y = oldBounds.getBottom() - (bounds.getHeight() - Object::doubleMargin); if (auto atom = helper->ptr.get()) { auto* patch = object->cnv->patch.getRawPointer(); - + pd::Interface::moveObject(patch, atom.get(), x - object->cnv->canvasOrigin.x, y - object->cnv->canvasOrigin.y); } bounds = object->gui->getPdBounds().expanded(Object::margin) + object->cnv->canvasOrigin; @@ -207,7 +207,7 @@ class AtomHelper { objectParams.addParam(param); } - void valueChanged(Value& v) + void valueChanged(Value const& v) { if (v.refersToSameSourceAs(labelPosition)) { setLabelPosition(getValue(labelPosition)); @@ -434,17 +434,15 @@ class AtomHelper { { if (auto atom = ptr.get()) { if (symbol.isEmpty() && *atom->a_symto->s_name) { - outlet_new(&atom->a_text, 0); + outlet_new(&atom->a_text, nullptr); cnv->performSynchronise(); - } - else if (!symbol.isEmpty() && !*atom->a_symto->s_name && atom->a_text.te_outlet) - { + } else if (!symbol.isEmpty() && !*atom->a_symto->s_name && atom->a_text.te_outlet) { canvas_deletelinesforio(atom->a_glist, &atom->a_text, - 0, atom->a_text.te_outlet); + nullptr, atom->a_text.te_outlet); outlet_free(atom->a_text.te_outlet); cnv->performSynchronise(); } - + atom->a_symto = pd->generateSymbol(symbol); atom->a_expanded_to = canvas_realizedollar(atom->a_glist, atom->a_symto); } @@ -454,17 +452,15 @@ class AtomHelper { { if (auto atom = ptr.get()) { if (symbol.isEmpty() && *atom->a_symfrom->s_name) { - inlet_new(&atom->a_text, &atom->a_text.te_pd, 0, 0); + inlet_new(&atom->a_text, &atom->a_text.te_pd, nullptr, nullptr); cnv->performSynchronise(); - } - else if (!symbol.isEmpty() && !*atom->a_symfrom->s_name && atom->a_text.te_inlet) - { + } else if (!symbol.isEmpty() && !*atom->a_symfrom->s_name && atom->a_text.te_inlet) { canvas_deletelinesforio(atom->a_glist, &atom->a_text, - atom->a_text.te_inlet, 0); + atom->a_text.te_inlet, nullptr); inlet_free(atom->a_text.te_inlet); cnv->performSynchronise(); } - + if (*atom->a_symfrom->s_name) pd_unbind(&atom->a_text.te_pd, canvas_realizedollar(atom->a_glist, atom->a_symfrom)); atom->a_symfrom = pd->generateSymbol(symbol); diff --git a/Source/Objects/BangObject.h b/Source/Objects/BangObject.h index 4d3e3eb611..cbe093ab6f 100644 --- a/Source/Objects/BangObject.h +++ b/Source/Objects/BangObject.h @@ -35,13 +35,18 @@ class BangObject final : public ObjectBase objectParameters.addParamInt("Min. flash time", cGeneral, &bangInterrupt, 50); objectParameters.addParamInt("Max. flash time", cGeneral, &bangHold, 250); - iemHelper.addIemParameters(objectParameters, true, true, 17, 7); + iemHelper.addIemParameters(objectParameters, true, true, true, 0, -10); } void onConstrainerCreate() override { constrainer->setFixedAspectRatio(1); } + + ResizeDirection getAllowedResizeDirections() const override + { + return DiagonalOnly; + } void update() override { @@ -54,12 +59,12 @@ class BangObject final : public ObjectBase iemHelper.update(); } - bool inletIsSymbol() override + bool hideInlet() override { return iemHelper.hasReceiveSymbol(); } - bool outletIsSymbol() override + bool hideOutlet() override { return iemHelper.hasSendSymbol(); } @@ -229,8 +234,7 @@ class BangObject final : public ObjectBase case hash("loadbang"): break; default: { - bool const wasIemMessage = iemHelper.receiveObjectMessage(symbol, atoms); - if (!wasIemMessage) { + if (bool const wasIemMessage = iemHelper.receiveObjectMessage(symbol, atoms); !wasIemMessage) { trigger(); } break; diff --git a/Source/Objects/BicoeffObject.h b/Source/Objects/BicoeffObject.h index 15ebe747ae..7615a40356 100644 --- a/Source/Objects/BicoeffObject.h +++ b/Source/Objects/BicoeffObject.h @@ -74,14 +74,13 @@ class BicoeffGraph final : public Component { return filterType == Highshelf || filterType == Lowshelf || filterType == EQ; } - + auto calcMagnitudePhase(float const f, float const a1, float const a2, float const b0, float const b1, float const b2) const { - struct MagnitudeAndPhase - { + struct MagnitudeAndPhase { float magnitude, phase; }; - + float const x1 = cos(-1.0 * f); float const x2 = cos(-2.0 * f); float const y1 = sin(-1.0 * f); @@ -115,30 +114,28 @@ class BicoeffGraph final : public Component phase = phase + MathConstants::pi * 2.0; } // scale phase values to pixels - float scaledPhase = halfFrameHeight * (-phase / MathConstants::pi) + halfFrameHeight; + float const scaledPhase = halfFrameHeight * (-phase / MathConstants::pi) + halfFrameHeight; - return MagnitudeAndPhase{ logMagnitude, scaledPhase }; + return MagnitudeAndPhase { logMagnitude, scaledPhase }; } auto calcCoefficients() const { - struct AlphaAndOmega - { + struct AlphaAndOmega { float alpha, omega; }; - + float const nn = filterCentre * 120.0f + 16.766f; float const nn2 = (filterWidth + filterCentre) * 120.0f + 16.766f; float const f = mtof(nn); float const bwf = mtof(nn2); float const bw = bwf / f - 1.0f; - float omega = MathConstants::pi * 2.0 * f / 44100.0f; - float alpha = std::sin(omega) * std::sinh(std::log(2.0) / 2.0 * bw * omega / std::sin(omega)); + float const omega = MathConstants::pi * 2.0 * f / 44100.0f; + float const alpha = std::sin(omega) * std::sinh(std::log(2.0) / 2.0 * bw * omega / std::sin(omega)); - return AlphaAndOmega{ alpha, omega }; + return AlphaAndOmega { alpha, omega }; } - void update() { @@ -188,17 +185,17 @@ class BicoeffGraph final : public Component for (int x = 0; x <= getWidth(); x++) { auto const nn = static_cast(x) / getWidth() * 120.0f + 16.766f; auto const freq = mtof(nn); - auto const result = calcMagnitudePhase(MathConstants::pi * 2.0f * freq / 44100.0f, a1, a2, b0, b1, b2); + auto const [magnitude, phase] = calcMagnitudePhase(MathConstants::pi * 2.0f * freq / 44100.0f, a1, a2, b0, b1, b2); - if (!std::isfinite(result.magnitude)) { + if (!std::isfinite(magnitude)) { continue; } if (x == 0) { - magnitudePath.startNewSubPath(x, result.magnitude); + magnitudePath.startNewSubPath(x, magnitude); } else { - magnitudePath.lineTo(x, result.magnitude); + magnitudePath.lineTo(x, magnitude); } } @@ -286,7 +283,6 @@ class BicoeffGraph final : public Component nvgStroke(nvg); } - void changeBandWidth(float const x, float const y, float const previousX, float const previousY) { float const xScale = x / getWidth(); diff --git a/Source/Objects/ButtonObject.h b/Source/Objects/ButtonObject.h index 5303940818..1546bd40f1 100644 --- a/Source/Objects/ButtonObject.h +++ b/Source/Objects/ButtonObject.h @@ -38,7 +38,6 @@ class ButtonObject final : public ObjectBase { void onConstrainerCreate() override { - constrainer->setFixedAspectRatio(1); constrainer->setMinimumHeight(9); constrainer->setMinimumWidth(9); } @@ -190,6 +189,9 @@ class ButtonObject final : public ObjectBase { void mouseUp(MouseEvent const& e) override { + if (!e.mods.isLeftButtonDown()) + return; + alreadyTriggered = false; if (mode == Latch) { state = false; @@ -239,9 +241,7 @@ class ButtonObject final : public ObjectBase { auto const& arr = *sizeProperty.getValue().getArray(); auto const width = std::max(static_cast(arr[0]), constrainer->getMinimumWidth()); auto height = std::max(static_cast(arr[1]), constrainer->getMinimumHeight()); - - constrainer->setFixedAspectRatio(static_cast(width) / height); - + setParameterExcludingListener(sizeProperty, VarArray(width, height)); if (auto button = ptr.get()) { button->x_w = width; diff --git a/Source/Objects/CanvasObject.h b/Source/Objects/CanvasObject.h index 11c8656e1d..1b243b52b1 100644 --- a/Source/Objects/CanvasObject.h +++ b/Source/Objects/CanvasObject.h @@ -33,7 +33,7 @@ class CanvasObject final : public ObjectBase { objectParameters.addParamSize(&sizeProperty); objectParameters.addParamInt("Active area size", ParameterCategory::cDimensions, &hitAreaSize, 15); objectParameters.addParamColour("Background", cGeneral, &iemHelper.secondaryColour, PlugDataColour::guiObjectInternalOutlineColour); - iemHelper.addIemParameters(objectParameters, false, true, 20, 12, 14); + iemHelper.addIemParameters(objectParameters, false, true, false, 20, 12, 14); setRepaintsOnMouseActivity(true); } @@ -46,12 +46,12 @@ class CanvasObject final : public ObjectBase { } } - bool inletIsSymbol() override + bool hideInlet() override { return iemHelper.hasReceiveSymbol(); } - bool outletIsSymbol() override + bool hideOutlet() override { return iemHelper.hasSendSymbol(); } @@ -149,7 +149,7 @@ class CanvasObject final : public ObjectBase { } } - static Rectangle getPDSize(t_my_canvas* cnvObj) + static Rectangle getPDSize(t_my_canvas const* cnvObj) { return Rectangle(0, 0, cnvObj->x_vis_w + 1, cnvObj->x_vis_h + 1); } diff --git a/Source/Objects/ColourPickerObject.h b/Source/Objects/ColourPickerObject.h index e4b1dead20..1cf3e72431 100644 --- a/Source/Objects/ColourPickerObject.h +++ b/Source/Objects/ColourPickerObject.h @@ -28,12 +28,12 @@ class ColourPickerObject final : public TextBase { #if ENABLE_TESTING return; #endif - + unsigned int red = 0, green = 0, blue = 0; if (auto colors = ptr.get()) { sscanf(colors->x_color, "#%02x%02x%02x", &red, &green, &blue); } - + ColourPicker::getInstance().show(findParentComponentOfClass(), getTopLevelComponent(), true, Colour(red, green, blue), Rectangle(1, 1).withPosition(Desktop::getInstance().getMousePosition()), [_this = SafePointer(this)](Colour const c) { if (!_this) return; @@ -48,11 +48,12 @@ class ColourPickerObject final : public TextBase { void receiveObjectMessage(hash32 const symbol, SmallArray const& atoms) override { switch (symbol) { - case hash("pick"): { showColourPicker(); break; } + default: + break; } } }; diff --git a/Source/Objects/CommentObject.h b/Source/Objects/CommentObject.h index 1ce5460a4a..d97ad3bfd6 100644 --- a/Source/Objects/CommentObject.h +++ b/Source/Objects/CommentObject.h @@ -23,7 +23,7 @@ class CommentObject final : public ObjectBase CommentObject(pd::WeakReference obj, Object* object) : ObjectBase(obj, object) { - objectParameters.addParamInt("Width (chars)", cDimensions, &sizeProperty, true, 0); + objectParameters.addParamInt("Width (chars)", cDimensions, &sizeProperty, true, false); locked = getValue(object->locked); updateTextLayout(); @@ -195,8 +195,13 @@ class CommentObject final : public ObjectBase { return TextObjectHelper::createConstrainer(object); } + + ResizeDirection getAllowedResizeDirections() const override + { + return ResizeDirection::HorizontalOnly; + } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { if (auto gobj = ptr.get()) { auto* patch = cnv->patch.getRawPointer(); diff --git a/Source/Objects/DropzoneObject.h b/Source/Objects/DropzoneObject.h index eae8058b36..fcdb6f1289 100644 --- a/Source/Objects/DropzoneObject.h +++ b/Source/Objects/DropzoneObject.h @@ -5,7 +5,9 @@ */ #pragma once -class DropzoneObject final : public ObjectBase, public FileDragAndDropTarget, public TextDragAndDropTarget { +class DropzoneObject final : public ObjectBase + , public FileDragAndDropTarget + , public TextDragAndDropTarget { bool isDraggingOver = false; Point lastPosition; Value sizeProperty = SynchronousValue(); @@ -22,12 +24,11 @@ class DropzoneObject final : public ObjectBase, public FileDragAndDropTarget, pu void render(NVGcontext* nvg) override { auto const b = getLocalBounds().toFloat(); - auto fillColour = Colours::transparentBlack; - auto outlineColour = cnv->editor->getLookAndFeel().findColour(object->isSelected() && !cnv->isGraph ? PlugDataColour::objectSelectedOutlineColourId : PlugDataColour::outlineColourId); + auto const fillColour = Colours::transparentBlack; + auto const outlineColour = cnv->editor->getLookAndFeel().findColour(object->isSelected() && !cnv->isGraph ? PlugDataColour::objectSelectedOutlineColourId : PlugDataColour::outlineColourId); nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), convertColour(fillColour), convertColour(outlineColour), Corners::objectCornerRadius); - - if(isDraggingOver) - { + + if (isDraggingOver) { auto const hoverBounds = getLocalBounds().reduced(1.5f).toFloat(); nvgBeginPath(nvg); nvgRoundedRect(nvg, hoverBounds.getX(), hoverBounds.getY(), hoverBounds.getWidth(), hoverBounds.getHeight(), Corners::objectCornerRadius); @@ -37,13 +38,13 @@ class DropzoneObject final : public ObjectBase, public FileDragAndDropTarget, pu } } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { if (auto gobj = ptr.get()) { auto* patch = cnv->patch.getRawPointer(); pd::Interface::moveObject(patch, gobj.get(), b.getX(), b.getY()); - + t_atom atoms[2]; SETFLOAT(atoms, b.getWidth() - 1); SETFLOAT(atoms + 1, b.getHeight() - 1); @@ -69,7 +70,7 @@ class DropzoneObject final : public ObjectBase, public FileDragAndDropTarget, pu { if (auto gobj = ptr.get()) { auto* patch = cnv->patch.getRawPointer(); - + int x = 0, y = 0, w = 0, h = 0; pd::Interface::getObjectBounds(patch, gobj.get(), &x, &y, &w, &h); sizeProperty = VarArray { var(w), var(h) }; @@ -82,7 +83,7 @@ class DropzoneObject final : public ObjectBase, public FileDragAndDropTarget, pu if (auto gobj = ptr.get()) { auto* patch = cnv->patch.getRawPointer(); - + int x = 0, y = 0, w = 0, h = 0; pd::Interface::getObjectBounds(patch, gobj.get(), &x, &y, &w, &h); sizeProperty = VarArray { var(w), var(h) }; @@ -98,11 +99,11 @@ class DropzoneObject final : public ObjectBase, public FileDragAndDropTarget, pu auto const height = std::max(static_cast(arr[1]), constrainer->getMinimumHeight()); setParameterExcludingListener(sizeProperty, VarArray { var(width), var(height) }); - + t_atom atoms[2]; SETFLOAT(atoms, width); SETFLOAT(atoms + 1, height); - + if (auto gobj = ptr.get()) { pd_typedmess(gobj.cast(), pd->generateSymbol("dim"), 2, atoms); } @@ -122,37 +123,41 @@ class DropzoneObject final : public ObjectBase, public FileDragAndDropTarget, pu return getValue(topLevel->locked) || getValue(topLevel->commandLocked) || topLevel->isGraph; } - - bool isInterestedInFileDrag(const StringArray& files) override { + + bool isInterestedInFileDrag(StringArray const& files) override + { return true; - }; + } - void fileDragEnter(const StringArray& files, int x, int y) override { + void fileDragEnter(StringArray const& files, int x, int y) override + { isDraggingOver = true; repaint(); - }; + } - void fileDragMove(const StringArray& files, int x, int y) override { - auto bounds = getPdBounds().toFloat(); + void fileDragMove(StringArray const& files, int const x, int const y) override + { + auto const bounds = getPdBounds().toFloat(); auto* patch = cnv->patch.getRawPointer(); char cnvName[32]; snprintf(cnvName, 32, ".x%lx", reinterpret_cast(glist_getcanvas(patch))); if (auto gobj = ptr.get()) { pd->sendMessage("__else_dnd_rcv", "_drag_over", { pd->generateSymbol(cnvName), x + bounds.getX(), y + bounds.getY() }); } - }; + } - void fileDragExit(const StringArray& files) override { + void fileDragExit(StringArray const& files) override + { if (auto gobj = ptr.get()) { pd->sendMessage("__else_dnd_rcv", "_drag_leave", {}); } isDraggingOver = false; repaint(); - }; + } - void filesDropped(const StringArray& files, int x, int y) override { - for(auto& file : files) - { + void filesDropped(StringArray const& files, int x, int y) override + { + for (auto& file : files) { auto* patch = cnv->patch.getRawPointer(); char cnvName[32]; snprintf(cnvName, 32, ".x%lx", reinterpret_cast(glist_getcanvas(patch))); @@ -162,19 +167,22 @@ class DropzoneObject final : public ObjectBase, public FileDragAndDropTarget, pu } isDraggingOver = false; repaint(); - }; - - bool isInterestedInTextDrag(const String& text) override { + } + + bool isInterestedInTextDrag(String const& text) override + { return true; } - - void textDragEnter(const String& text, int x, int y) override { + + void textDragEnter(String const& text, int x, int y) override + { isDraggingOver = true; repaint(); } - void textDragMove(const String& text, int x, int y) override { - auto bounds = getPdBounds().toFloat(); + void textDragMove(String const& text, int const x, int const y) override + { + auto const bounds = getPdBounds().toFloat(); auto* patch = cnv->patch.getRawPointer(); char cnvName[32]; snprintf(cnvName, 32, ".x%lx", reinterpret_cast(glist_getcanvas(patch))); @@ -183,7 +191,8 @@ class DropzoneObject final : public ObjectBase, public FileDragAndDropTarget, pu } } - void textDragExit(const String& text) override { + void textDragExit(String const& text) override + { if (auto gobj = ptr.get()) { pd->sendMessage("__else_dnd_rcv", "_drag_leave", {}); } @@ -191,7 +200,8 @@ class DropzoneObject final : public ObjectBase, public FileDragAndDropTarget, pu repaint(); } - void textDropped (const String& text, int x, int y) override { + void textDropped(String const& text, int x, int y) override + { auto* patch = cnv->patch.getRawPointer(); char cnvName[32]; snprintf(cnvName, 32, ".x%lx", reinterpret_cast(glist_getcanvas(patch))); diff --git a/Source/Objects/FloatAtomObject.h b/Source/Objects/FloatAtomObject.h index c89d907406..82e010eaae 100644 --- a/Source/Objects/FloatAtomObject.h +++ b/Source/Objects/FloatAtomObject.h @@ -139,12 +139,12 @@ class FloatAtomObject final : public ObjectBase { repaint(); } - bool inletIsSymbol() override + bool hideInlet() override { return atomHelper.hasReceiveSymbol(); } - bool outletIsSymbol() override + bool hideOutlet() override { return atomHelper.hasSendSymbol(); } @@ -224,7 +224,7 @@ class FloatAtomObject final : public ObjectBase { return atomHelper.getPdBounds(input.getFont().getStringWidth(input.formatNumber(input.getText().getDoubleValue()))); } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { atomHelper.setPdBounds(b); } diff --git a/Source/Objects/FunctionObject.h b/Source/Objects/FunctionObject.h index 0188d91f4c..5aad6ba3e7 100644 --- a/Source/Objects/FunctionObject.h +++ b/Source/Objects/FunctionObject.h @@ -55,7 +55,7 @@ class FunctionObject final : public ObjectBase { } } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { if (auto function = ptr.get()) { auto* patch = cnv->patch.getRawPointer(); @@ -431,13 +431,13 @@ class FunctionObject final : public ObjectBase { return { hex[0], hex[1], hex[2] }; } - bool inletIsSymbol() override + bool hideInlet() override { auto const rSymbol = receiveSymbol.toString(); return rSymbol.isNotEmpty() && rSymbol != "empty"; } - bool outletIsSymbol() override + bool hideOutlet() override { auto const sSymbol = sendSymbol.toString(); return sSymbol.isNotEmpty() && sSymbol != "empty"; diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 8c0c28349c..caab9bb69f 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -210,7 +210,7 @@ void GemCallOnMessageThread(std::function callback) &callback); } -UnorderedMap> gemJUCEWindow; +inline UnorderedMap> gemJUCEWindow; bool gemWinSetCurrent() { @@ -336,11 +336,11 @@ int topmostGemWindow(WindowInfo& info, int const state) return state; } -void titleGemWindow(WindowInfo& info, const char* title) +void titleGemWindow(WindowInfo& info, char const* title) { if (auto* window = info.getWindow()) { MessageManager::callAsync([window = Component::SafePointer(window), title = String::fromUTF8(title)] { - if(window) { + if (window) { if (auto* peer = window->getPeer()) { peer->setTitle(title); } diff --git a/Source/Objects/GraphOnParent.h b/Source/Objects/GraphOnParent.h index e16aec372c..b0953ee3b7 100644 --- a/Source/Objects/GraphOnParent.h +++ b/Source/Objects/GraphOnParent.h @@ -5,17 +5,15 @@ */ #pragma once - // Also used by garray -class GraphTicks -{ - int xTicksPerBig, yTicksPerBig; - float xTickPoint, yTickPoint; - float xTickInc, yTickInc; - float gl_x1, gl_x2, gl_y1, gl_y2; - +class GraphTicks { + int xTicksPerBig = 0, yTicksPerBig = 0; + float xTickPoint = 0, yTickPoint = 0; + float xTickInc = 0, yTickInc = 0; + float gl_x1 = 0.f, gl_x2 = 1.f, gl_y1 = 100.f, gl_y2 = 0.f; + public: - void update(t_glist* glist) + void update(t_glist const* glist) { xTicksPerBig = glist->gl_xtick.k_lperb; yTicksPerBig = glist->gl_ytick.k_lperb; @@ -28,12 +26,12 @@ class GraphTicks gl_x2 = glist->gl_x2; gl_y2 = glist->gl_y2; } - - void render(NVGcontext* nvg, Rectangle b) + + void render(NVGcontext* nvg, Rectangle const b) const { if (xTicksPerBig) { t_float const y1 = b.getY(), y2 = b.getBottom(), x1 = b.getX(), x2 = b.getRight(); - + t_float f = xTickPoint; for (int i = 0; f < 0.99f * gl_x2 + 0.01f * gl_x1; i++, f += xTickInc) { auto const xpos = jmap(f, gl_x2, gl_x1, x1, x2); @@ -101,7 +99,7 @@ class GraphTicks }; class GraphOnParent final : public ObjectBase { - + Value isGraphChild = SynchronousValue(var(false)); Value hideNameAndArgs = SynchronousValue(var(false)); Value xRange = SynchronousValue(); @@ -115,12 +113,12 @@ class GraphOnParent final : public ObjectBase { NVGImage openInGopBackground; std::unique_ptr editor; - - bool isLocked:1 = false; - bool isOpenedInSplitView:1 = false; - + + bool isLocked : 1 = false; + bool isOpenedInSplitView : 1 = false; + GraphTicks ticks; - + public: // Graph On Parent GraphOnParent(pd::WeakReference obj, Object* object) @@ -154,7 +152,6 @@ class GraphOnParent final : public ObjectBase { yRange = VarArray { var(glist->gl_y2), var(glist->gl_y1) }; sizeProperty = VarArray { var(glist->gl_pixwidth), var(glist->gl_pixheight) }; ticks.update(glist.get()); - } updateCanvas(); @@ -199,8 +196,11 @@ class GraphOnParent final : public ObjectBase { if (editor) { editor->setBounds(getLocalBounds().removeFromTop(18)); } - - textRenderer.prepareLayout(getText(), Fonts::getDefaultFont().withHeight(13), cnv->editor->getLookAndFeel().findColour(PlugDataColour::canvasTextColourId), getWidth(), getWidth(), false); + + auto text = getText(); + if(text != "graph" && !text.isNotEmpty()) { + textRenderer.prepareLayout(getText(), Fonts::getDefaultFont().withHeight(13), cnv->editor->getLookAndFeel().findColour(PlugDataColour::canvasTextColourId), getWidth(), getWidth(), false); + } updateCanvas(); updateDrawables(); @@ -210,7 +210,10 @@ class GraphOnParent final : public ObjectBase { void lookAndFeelChanged() override { - textRenderer.prepareLayout(getText(), Fonts::getDefaultFont().withHeight(13), cnv->editor->getLookAndFeel().findColour(PlugDataColour::canvasTextColourId), getWidth(), getWidth(), false); + auto text = getText(); + if(text != "graph" && !text.isNotEmpty()) { + textRenderer.prepareLayout(getText(), Fonts::getDefaultFont().withHeight(13), cnv->editor->getLookAndFeel().findColour(PlugDataColour::canvasTextColourId), getWidth(), getWidth(), false); + } } void showEditor() override @@ -292,7 +295,7 @@ class GraphOnParent final : public ObjectBase { return false; } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { if (auto glist = ptr.get<_glist>()) { auto* patch = cnv->patch.getRawPointer(); @@ -327,7 +330,7 @@ class GraphOnParent final : public ObjectBase { ~GraphOnParent() override { - if(getValue(isGraphChild)) { + if (getValue(isGraphChild)) { closeOpenedSubpatchers(); } } @@ -386,7 +389,7 @@ class GraphOnParent final : public ObjectBase { canvas->updateDrawables(); } - + void render(NVGcontext* nvg) override { // Strangly, the title goes below the graph content in pd @@ -404,11 +407,12 @@ class GraphOnParent final : public ObjectBase { auto const b = getLocalBounds().toFloat(); if (canvas) { auto invalidArea = cnv->currentRenderArea; - + invalidArea = invalidArea.getIntersection(cnv->getLocalArea(this, getLocalBounds())); - - if (invalidArea.isEmpty()) return; - + + if (invalidArea.isEmpty()) + return; + invalidArea = canvas->getLocalArea(cnv, invalidArea).expanded(1); NVGScopedState scopedState(nvg); @@ -464,6 +468,42 @@ class GraphOnParent final : public ObjectBase { nvgStrokeColor(nvg, cnv->guiObjectInternalOutlineCol); ticks.render(nvg, b); } + + std::unique_ptr createConstrainer() override + { + // Custom constrainer because a regular ComponentBoundsConstrainer will mess up the aspect ratio + class GraphBoundsConstrainer : public ComponentBoundsConstrainer { + + public: + explicit GraphBoundsConstrainer() + { + } + + void checkBounds(Rectangle& bounds, + Rectangle const& old, + Rectangle const& limits, + bool const isStretchingTop, + bool const isStretchingLeft, + bool const isStretchingBottom, + bool const isStretchingRight) override + { + bounds = old; // Don't allow resizing graph from the outside + } + }; + + return std::make_unique(); + } + + ResizeDirection getAllowedResizeDirections() const override + { + return ResizeDirection::None; + } + + + void onConstrainerCreate() override + { + constrainer->setFixedAspectRatio(1); + } pd::Patch::Ptr getPatch() override { @@ -472,7 +512,6 @@ class GraphOnParent final : public ObjectBase { void propertyChanged(Value& v) override { - if (v.refersToSameSourceAs(sizeProperty)) { auto const& arr = *sizeProperty.getValue().getArray(); auto const* constrainer = getConstrainer(); @@ -533,4 +572,9 @@ class GraphOnParent final : public ObjectBase { { menu.addItem("Open", [_this = SafePointer(this)] { if(_this) _this->openSubpatch(); }); } + + bool checkHvccCompatibility() override + { + return recurseHvccCompatibility(getText(), subpatch.get()); + } }; diff --git a/Source/Objects/IEMHelper.h b/Source/Objects/IEMHelper.h index d1d2b691d5..229fba8304 100644 --- a/Source/Objects/IEMHelper.h +++ b/Source/Objects/IEMHelper.h @@ -12,7 +12,7 @@ static int srl_is_valid(t_symbol const* s) } extern "C" { -char* pdgui_strnescape(char* dst, size_t dstlen, char const* src, size_t srclen); +EXTERN char* pdgui_strnescape(char* dst, size_t dstlen, char const* src, size_t srclen); } class IEMHelper { @@ -72,43 +72,24 @@ class IEMHelper { gui->repaint(); } - ObjectParameters makeIemParameters(bool const withAppearance = true, bool const withSymbols = true, int labelPosX = 0, int labelPosY = -8, int const labelHeightY = 10) + void addIemParameters(ObjectParameters& objectParams, bool const withAppearance = true, bool const withSymbols = true, bool withInit = true, int const labelPosX = 0, int const labelPosY = -8, int const labelHeightY = 10) { - ObjectParameters params; - if (withAppearance) { - params.addParamColourFG(&primaryColour); - params.addParamColourBG(&secondaryColour); + objectParams.addParamColourFG(&primaryColour); + objectParams.addParamColourBG(&secondaryColour); } if (withSymbols) { - params.addParamReceiveSymbol(&receiveSymbol); - params.addParamSendSymbol(&sendSymbol); + objectParams.addParamReceiveSymbol(&receiveSymbol); + objectParams.addParamSendSymbol(&sendSymbol); + } + if (withInit) { + objectParams.addParamBool("Initialise", cGeneral, &initialise, { "No", "Yes" }, 0); } - params.addParamString("Text", cLabel, &labelText, ""); - params.addParamColourLabel(&labelColour); - params.addParamRange("Position", cLabel, &labelPosition, { labelPosX, labelPosY }); - params.addParamInt("Height", cLabel, &labelHeight, labelHeightY, true, 4); - params.addParamBool("Initialise", cGeneral, &initialise, { "No", "Yes" }, 0); - - return params; - } - /** - * @brief Add IEM parameters to the object parameters - * @attention Allows customization for different default settings (PD's default positions are not consistent) - * - * @param objectParams the object parameter to add items to - * @param withAppearance customize the added IEM's to show appearance category - * @param withSymbols customize the added IEM's to show symbols category - * @param labelPosX customize the default labels x position - * @param labelPosY customize the default labels y position - * @param labelHeightY customize the default labels text height - */ - void addIemParameters(ObjectParameters& objectParams, bool const withAppearance = true, bool const withSymbols = true, int const labelPosX = 0, int const labelPosY = -8, int const labelHeightY = 10) - { - auto IemParams = makeIemParameters(withAppearance, withSymbols, labelPosX, labelPosY, labelHeightY); - for (auto const& param : IemParams.getParameters()) - objectParams.addParam(param); + objectParams.addParamString("Text", cLabel, &labelText, ""); + objectParams.addParamColourLabel(&labelColour); + objectParams.addParamRange("Position", cLabel, &labelPosition, { labelPosX, labelPosY }); + objectParams.addParamInt("Height", cLabel, &labelHeight, labelHeightY, true, 4); } bool receiveObjectMessage(hash32 const symbol, SmallArray const& atoms) @@ -204,7 +185,7 @@ class IEMHelper { return false; } - void valueChanged(Value& v) + void valueChanged(Value const& v) { if (v.refersToSameSourceAs(sendSymbol)) { setSendSymbol(sendSymbol.toString()); @@ -262,7 +243,7 @@ class IEMHelper { return false; } - Rectangle getPdBounds() + Rectangle getPdBounds() const { if (auto iemgui = ptr.get()) { return { iemgui->x_obj.te_xpix, iemgui->x_obj.te_ypix, iemgui->x_w + 1, iemgui->x_h + 1 }; @@ -281,7 +262,7 @@ class IEMHelper { } } - void updateLabel(OwnedArray& labels, Point offset = { 0, 0 }) + void updateLabel(OwnedArray& labels, Point const offset = { 0, 0 }) { String const text = labelText.toString(); @@ -491,7 +472,7 @@ class IEMHelper { } } - void setLabelPosition(Point position) + void setLabelPosition(Point const position) { if (auto iemgui = ptr.get()) { iemgui->x_ldx = position.x; @@ -519,7 +500,7 @@ class IEMHelper { Value secondaryColour = SynchronousValue(); Value labelColour = SynchronousValue(); - Value labelPosition; + Value labelPosition = SynchronousValue(); Value labelHeight = SynchronousValue(); Value labelText = SynchronousValue(); diff --git a/Source/Objects/ImplementationBase.cpp b/Source/Objects/ImplementationBase.cpp index 50e1504b6e..19e1ff6cdc 100644 --- a/Source/Objects/ImplementationBase.cpp +++ b/Source/Objects/ImplementationBase.cpp @@ -29,7 +29,7 @@ int clone_get_n(t_gobj*); #include "ObjectImplementations.h" #include "Gem.h" -ImplementationBase::ImplementationBase(t_gobj* obj, t_canvas* parent, PluginProcessor* processor) +ImplementationBase::ImplementationBase(t_gobj* obj, t_canvas const* parent, PluginProcessor* processor) : pd(processor) , ptr(obj, processor) , cnv(parent) @@ -38,7 +38,7 @@ ImplementationBase::ImplementationBase(t_gobj* obj, t_canvas* parent, PluginProc ImplementationBase::~ImplementationBase() = default; -Canvas* ImplementationBase::getMainCanvas(t_canvas* patchPtr, bool const alsoSearchRoot) const +Canvas* ImplementationBase::getMainCanvas(t_canvas const* patchPtr, bool const alsoSearchRoot) const { auto editors = pd->getEditors(); @@ -92,7 +92,7 @@ bool ImplementationBase::hasImplementation(char const* type) } } -ImplementationBase* ImplementationBase::createImplementation(String const& type, t_gobj* ptr, t_canvas* cnv, PluginProcessor* pd) +ImplementationBase* ImplementationBase::createImplementation(String const& type, t_gobj* ptr, t_canvas const* cnv, PluginProcessor* pd) { switch (hash(type)) { case hash("canvas.mouse"): @@ -127,9 +127,9 @@ ObjectImplementationManager::ObjectImplementationManager(pd::Instance* processor void ObjectImplementationManager::handleAsyncUpdate() { - SmallArray> allCanvases; - SmallArray> allImplementations; - UnorderedSet allObjects; + SmallArray> allCanvases; + SmallArray> allImplementations; + UnorderedSet allObjects; pd->setThis(); @@ -153,7 +153,7 @@ void ObjectImplementationManager::handleAsyncUpdate() for (auto it = objectImplementations.cbegin(); it != objectImplementations.cend();) { auto& [ptr, implementation] = *it; - if (allObjects.contains(ptr)) { + if (!allObjects.contains(ptr)) { it = objectImplementations.erase(it); // Erase and move iterator forward. } else { ++it; @@ -175,22 +175,22 @@ void ObjectImplementationManager::updateObjectImplementations() triggerAsyncUpdate(); } -void ObjectImplementationManager::getSubCanvases(t_canvas* top, t_canvas* canvas, SmallArray>& allCanvases) +void ObjectImplementationManager::getSubCanvases(t_canvas const* top, t_canvas const* canvas, SmallArray>& allCanvases) { for (t_gobj* y = canvas->gl_list; y; y = y->g_next) { if (pd_class(&y->g_pd) == canvas_class) { - allCanvases.add({ top, reinterpret_cast(y) }); + allCanvases.add({ top, reinterpret_cast(y) }); getSubCanvases(top, reinterpret_cast(y), allCanvases); } else if (pd_class(&y->g_pd) == clone_class) { for (int i = 0; i < clone_get_n(y); i++) { - allCanvases.add({ top, reinterpret_cast(y) }); + allCanvases.add({ top, reinterpret_cast(y) }); getSubCanvases(top, clone_get_instance(y, i), allCanvases); } } } } -void ObjectImplementationManager::clearObjectImplementationsForPatch(t_canvas* patch) +void ObjectImplementationManager::clearObjectImplementationsForPatch(t_canvas const* patch) { for (t_gobj* y = patch->gl_list; y; y = y->g_next) { if (pd_class(&y->g_pd) == canvas_class) { @@ -198,7 +198,7 @@ void ObjectImplementationManager::clearObjectImplementationsForPatch(t_canvas* p } if (pd_class(&y->g_pd) == clone_class) { for (int i = 0; i < clone_get_n(y); i++) { - auto* clone = clone_get_instance(y, i); + auto const* clone = clone_get_instance(y, i); clearObjectImplementationsForPatch(clone); } } diff --git a/Source/Objects/ImplementationBase.h b/Source/Objects/ImplementationBase.h index 790f083b93..3fc60244fc 100644 --- a/Source/Objects/ImplementationBase.h +++ b/Source/Objects/ImplementationBase.h @@ -16,20 +16,20 @@ class Canvas; class ImplementationBase { public: - ImplementationBase(t_gobj* obj, t_canvas* parent, PluginProcessor* pd); + ImplementationBase(t_gobj* obj, t_canvas const* parent, PluginProcessor* pd); virtual ~ImplementationBase(); - static ImplementationBase* createImplementation(String const& type, t_gobj* ptr, t_canvas* cnv, PluginProcessor* pd); + static ImplementationBase* createImplementation(String const& type, t_gobj* ptr, t_canvas const* cnv, PluginProcessor* pd); static bool hasImplementation(char const* type); virtual void update() { } - Canvas* getMainCanvas(t_canvas* patchPtr, bool alsoSearchRoot = false) const; + Canvas* getMainCanvas(t_canvas const* patchPtr, bool alsoSearchRoot = false) const; PluginProcessor* pd; pd::WeakReference ptr; - t_canvas* cnv; + t_canvas const* cnv; JUCE_DECLARE_WEAK_REFERENCEABLE(ImplementationBase) }; @@ -39,17 +39,17 @@ class ObjectImplementationManager final : public AsyncUpdater { explicit ObjectImplementationManager(pd::Instance* pd); void updateObjectImplementations(); - void clearObjectImplementationsForPatch(t_canvas* patch); + void clearObjectImplementationsForPatch(t_canvas const* patch); void handleAsyncUpdate() override; private: - static void getSubCanvases(t_canvas* top, t_canvas* patch, SmallArray>& allCanvases); + static void getSubCanvases(t_canvas const* top, t_canvas const* patch, SmallArray>& allCanvases); PluginProcessor* pd; - UnorderedSegmentedMap> objectImplementations; - SmallArray> targetImplementations; - UnorderedSegmentedSet targetObjects; + UnorderedSegmentedMap> objectImplementations; + SmallArray> targetImplementations; + UnorderedSegmentedSet targetObjects; CriticalSection objectImplementationLock; }; diff --git a/Source/Objects/KeyboardObject.h b/Source/Objects/KeyboardObject.h index 6309687cda..5372ab9279 100644 --- a/Source/Objects/KeyboardObject.h +++ b/Source/Objects/KeyboardObject.h @@ -61,6 +61,8 @@ class KeyboardObject final : public ObjectBase sendSymbol = sndSym != "empty" ? sndSym : ""; receiveSymbol = rcvSym != "empty" ? rcvSym : ""; + + updateMinimumSize(); MessageManager::callAsync([_this = SafePointer(this)] { if (_this) { @@ -183,7 +185,7 @@ class KeyboardObject final : public ObjectBase return {}; } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { if (auto gobj = ptr.get()) { auto* patch = cnv->patch.getRawPointer(); @@ -197,9 +199,7 @@ class KeyboardObject final : public ObjectBase { auto const numWhiteKeys = getNumWhiteKeys(); auto const newKeyWidth = static_cast(getWidth() / numWhiteKeys); - if (newKeyWidth > 7) { - keyWidth.setValue(newKeyWidth); object->setSize(static_cast(numWhiteKeys * getWhiteKeyWidth()) + Object::doubleMargin, object->getHeight()); } } @@ -361,67 +361,65 @@ class KeyboardObject final : public ObjectBase break; } } - + std::unique_ptr createConstrainer() override - { - // Custom constrainer because a regular ComponentBoundsConstrainer will mess up the aspect ratio - class KeyboardBoundsConstrainer: public ComponentBoundsConstrainer { - KeyboardObject* parent; - public: - KeyboardBoundsConstrainer(KeyboardObject * parent) : parent(parent) {}; - - void checkBounds(Rectangle& bounds, - Rectangle const& old, - Rectangle const& limits, - bool isStretchingTop, - bool isStretchingLeft, - bool isStretchingBottom, - bool isStretchingRight) override - { + { + // Custom constrainer because a regular ComponentBoundsConstrainer will mess up the aspect ratio + class KeyboardBoundsConstrainer : public ComponentBoundsConstrainer { + KeyboardObject* parent; + + public: + explicit KeyboardBoundsConstrainer(KeyboardObject* parent) + : parent(parent) + { + } + + void checkBounds(Rectangle& bounds, + Rectangle const& old, + Rectangle const& limits, + bool const isStretchingTop, + bool const isStretchingLeft, + bool const isStretchingBottom, + bool const isStretchingRight) override + { + if (isStretchingLeft) + bounds.setLeft(jlimit(old.getRight() - getMaximumWidth(), old.getRight() - getMinimumWidth(), bounds.getX())); + else + bounds.setWidth(jlimit(getMinimumWidth(), getMaximumWidth(), bounds.getWidth())); + + if (isStretchingTop) + bounds.setTop(jlimit(old.getBottom() - getMaximumHeight(), old.getBottom() - getMinimumHeight(), bounds.getY())); + else + bounds.setHeight(jlimit(getMinimumHeight(), getMaximumHeight(), bounds.getHeight())); + + if (bounds.isEmpty()) + return; + + auto const numWhiteKeys = parent->getNumWhiteKeys(); + auto const newKeyWidth = roundToInt(static_cast(bounds.getWidth() - Object::doubleMargin) / numWhiteKeys); + + if (newKeyWidth > 7.0f) { + parent->keyWidth.setValue(newKeyWidth); + bounds.setWidth(numWhiteKeys * newKeyWidth + Object::doubleMargin); + } + + if ((isStretchingTop || isStretchingBottom) && !(isStretchingLeft || isStretchingRight)) { + bounds.setX(old.getX() + (old.getWidth() - bounds.getWidth()) / 2); + } else if ((isStretchingLeft || isStretchingRight) && !(isStretchingTop || isStretchingBottom)) { + bounds.setY(old.getY() + (old.getHeight() - bounds.getHeight()) / 2); + } else { if (isStretchingLeft) - bounds.setLeft (jlimit (old.getRight() - getMaximumWidth(), old.getRight() - getMinimumWidth(), bounds.getX())); - else - bounds.setWidth (jlimit (getMinimumWidth(), getMaximumWidth(), bounds.getWidth())); - + bounds.setX(old.getRight() - bounds.getWidth()); + if (isStretchingTop) - bounds.setTop (jlimit (old.getBottom() - getMaximumHeight(), old.getBottom() - getMinimumHeight(), bounds.getY())); - else - bounds.setHeight (jlimit (getMinimumHeight(), getMaximumHeight(), bounds.getHeight())); - - if (bounds.isEmpty()) - return; - - auto const numWhiteKeys = parent->getNumWhiteKeys(); - auto const newKeyWidth = roundToInt(static_cast(bounds.getWidth() - Object::doubleMargin) / numWhiteKeys); - - if (newKeyWidth > 7.0f) { - parent->keyWidth.setValue(newKeyWidth); - bounds.setWidth((numWhiteKeys * newKeyWidth) + Object::doubleMargin); - } - - - if ((isStretchingTop || isStretchingBottom) && ! (isStretchingLeft || isStretchingRight)) - { - bounds.setX (old.getX() + (old.getWidth() - bounds.getWidth()) / 2); - } - else if ((isStretchingLeft || isStretchingRight) && ! (isStretchingTop || isStretchingBottom)) - { - bounds.setY (old.getY() + (old.getHeight() - bounds.getHeight()) / 2); - } - else - { - if (isStretchingLeft) - bounds.setX (old.getRight() - bounds.getWidth()); - - if (isStretchingTop) - bounds.setY (old.getBottom() - bounds.getHeight()); - } + bounds.setY(old.getBottom() - bounds.getHeight()); } - }; - - return std::make_unique(this); + } + }; + + return std::make_unique(this); } - + void updateMinimumSize() { if (auto* constrainer = getConstrainer()) { @@ -429,13 +427,13 @@ class KeyboardObject final : public ObjectBase } } - bool inletIsSymbol() override + bool hideInlet() override { auto const rSymbol = receiveSymbol.toString(); return rSymbol.isNotEmpty() && rSymbol != "empty"; } - bool outletIsSymbol() override + bool hideOutlet() override { auto const sSymbol = sendSymbol.toString(); return sSymbol.isNotEmpty() && sSymbol != "empty"; @@ -483,7 +481,7 @@ class KeyboardObject final : public ObjectBase return { start, start + width }; } - std::pair positionToNoteAndVelocity(Point pos) const + std::pair positionToNoteAndVelocity(Point const pos) const { auto constexpr rangeStart = 0; auto const rangeEnd = getValue(octaves) * 12; @@ -521,6 +519,9 @@ class KeyboardObject final : public ObjectBase void mouseDown(MouseEvent const& e) override { + if (!e.mods.isLeftButtonDown()) + return; + auto [midiNoteNumber, midiNoteVelocity] = positionToNoteAndVelocity(e.position); midiNoteNumber += getValue(lowC) * 12; @@ -564,6 +565,9 @@ class KeyboardObject final : public ObjectBase void mouseDrag(MouseEvent const& e) override { + if (!e.mods.isLeftButtonDown()) + return; + auto [midiNoteNumber, midiNoteVelocity] = positionToNoteAndVelocity(e.position); midiNoteNumber += getValue(lowC) * 12; @@ -593,6 +597,9 @@ class KeyboardObject final : public ObjectBase // So we completely replace mouseUpOnKey functionality here, mouseUp() will stop mouseUpOnKey() being called. void mouseUp(MouseEvent const& e) override { + if (!e.mods.isLeftButtonDown()) + return; + clickedKey = -1; if (!getValue(toggleMode) && !e.mods.isShiftDown()) { @@ -602,7 +609,7 @@ class KeyboardObject final : public ObjectBase repaint(); } - void sendNoteOn(int note, int const velocity) + void sendNoteOn(int note, int const velocity) const { note = std::clamp(note + 12, 0, 255); @@ -618,7 +625,7 @@ class KeyboardObject final : public ObjectBase } } - void sendNoteOff(int note) + void sendNoteOff(int note) const { note = std::clamp(note + 12, 0, 255); StackArray at; diff --git a/Source/Objects/KnobObject.h b/Source/Objects/KnobObject.h index 967e5056ec..d5b371ee64 100644 --- a/Source/Objects/KnobObject.h +++ b/Source/Objects/KnobObject.h @@ -6,6 +6,7 @@ #pragma once #include "TclColours.h" +#include "Components/PropertiesPanel.h" extern "C" { void knob_get_snd(void* x); @@ -33,7 +34,7 @@ class Knob final : public Component float maxValue = 1.0f; float mouseDragSensitivity = 200.f; float originalValue = 0.0f; - float arcBegin, arcEnd; + float arcBegin = 3.927, arcEnd = 8.639; float doubleClickValue = 0.0f; float interval = 0.0f; @@ -48,7 +49,7 @@ class Knob final : public Component ~Knob() override = default; - void drawTicks(NVGcontext* nvg, Rectangle knobBounds, float const startAngle, float const endAngle, float const tickWidth) + void drawTicks(NVGcontext* nvg, Rectangle const knobBounds, float const startAngle, float const endAngle, float const tickWidth) const { auto const centre = knobBounds.getCentre(); auto const radius = knobBounds.getWidth() * 0.5f * 1.05f; @@ -92,6 +93,7 @@ class Knob final : public Component } onDragStart(); + onValueChange(); // else/knob always outputs on mouseDown } void mouseDrag(MouseEvent const& e) override @@ -99,18 +101,19 @@ class Knob final : public Component if (!e.mods.isLeftButtonDown() || readOnly) return; - float delta = e.getDistanceFromDragStartY() - e.getDistanceFromDragStartX(); - bool jumpMouseDownEvent = jumpOnClick && !e.mouseWasDraggedSinceMouseDown(); + float const delta = e.getDistanceFromDragStartY() - e.getDistanceFromDragStartX(); + bool const jumpMouseDownEvent = jumpOnClick && !e.mouseWasDraggedSinceMouseDown(); + bool valueChanged = false; if (isCircular || jumpMouseDownEvent) { - float dx = e.position.x - getLocalBounds().getCentreX(); - float dy = e.position.y - getLocalBounds().getCentreY(); + float const dx = e.position.x - getLocalBounds().getCentreX(); + float const dy = e.position.y - getLocalBounds().getCentreY(); float angle = std::atan2(dx, -dy); while (angle < 0.0 || angle < arcBegin) angle += MathConstants::twoPi; if (isCircular) { - auto smallestAngleBetween = [](double a1, double a2) { + auto smallestAngleBetween = [](double const a1, double const a2) { return jmin(std::abs(a1 - a2), std::abs(a1 + MathConstants::twoPi - a2), std::abs(a2 + MathConstants::twoPi - a1)); @@ -125,20 +128,25 @@ class Knob final : public Component } } - float rangeSize = maxValue - minValue; - float normalizedAngle = (angle - arcBegin) / (arcEnd - arcBegin); + float const rangeSize = maxValue - minValue; + float const normalizedAngle = (angle - arcBegin) / (arcEnd - arcBegin); float newValue = minValue + normalizedAngle * rangeSize; - + newValue = std::ceil(newValue / interval) * interval; if (jumpMouseDownEvent) originalValue = newValue; + valueChanged = !approximatelyEqual(newValue, value); setValue(newValue); } else { - float newValue = originalValue - (delta / mouseDragSensitivity); + float newValue = originalValue - delta / mouseDragSensitivity; newValue = std::ceil(newValue / interval) * interval; - setValue(std::clamp(newValue, minValue, maxValue)); + newValue = std::clamp(newValue, minValue, maxValue); + valueChanged = !approximatelyEqual(newValue, value); + setValue(newValue); } - onValueChange(); + + if (valueChanged) + onValueChange(); } void mouseUp(MouseEvent const& e) override @@ -247,39 +255,39 @@ class Knob final : public Component float getValue() const { return value; } - void setValue(float newValue) + void setValue(float const newValue) { value = newValue; repaint(); } - void setRotaryParameters(float start, float end) + void setRotaryParameters(float const start, float const end) { arcBegin = start; arcEnd = end; } - void setJumpOnClick(bool snap) + void setJumpOnClick(bool const snap) { jumpOnClick = snap; } - void setDoubleClickValue(float newDoubleClickValue) + void setDoubleClickValue(float const newDoubleClickValue) { doubleClickValue = newDoubleClickValue; } - void setInterval(float newInterval) + void setInterval(float const newInterval) { interval = newInterval; } - void setCircular(bool newCircular) + void setCircular(bool const newCircular) { isCircular = newCircular; } - void setReadOnly(bool newReadOnly) + void setReadOnly(bool const newReadOnly) { readOnly = newReadOnly; } @@ -392,6 +400,11 @@ class KnobObject final : public ObjectBase { constrainer->setFixedAspectRatio(1.0f); constrainer->setMinimumSize(17, 17); } + + ResizeDirection getAllowedResizeDirections() const override + { + return DiagonalOnly; + } bool canReceiveMouseEvent(int const x, int const y) override { @@ -442,14 +455,14 @@ class KnobObject final : public ObjectBase { } if (key.getKeyCode() == KeyPress::returnKey) { if (auto obj = ptr.get()) { - auto value = typeBuffer.isEmpty() ? getValue() : typeBuffer.getFloatValue(); + auto const value = typeBuffer.isEmpty() ? getValue() : typeBuffer.getFloatValue(); pd->sendDirectMessage(obj.get(), value); typeBuffer = ""; } return true; } auto const chr = key.getTextCharacter(); - if (((chr >= '0' && chr <= '9') || chr == '+' || chr == '-' || chr == '.')) { + if ((chr >= '0' && chr <= '9') || chr == '+' || chr == '-' || chr == '.') { typeBuffer += chr; updateLabel(); return true; @@ -494,6 +507,8 @@ class KnobObject final : public ObjectBase { sizeProperty = knb->x_size; arcStart = knb->x_arcstart; numberSize = knb->n_size; + readOnly = knb->x_readonly; + jumpOnClick = knb->x_jump; showNumber = knb->x_number_mode + 1; numberPosition = VarArray(knb->x_xpos, knb->x_ypos); @@ -523,16 +538,18 @@ class KnobObject final : public ObjectBase { updateDoubleClickValue(); knob.setCircular(::getValue(circular)); knob.showArc(::getValue(showArc)); + knob.setJumpOnClick(::getValue(jumpOnClick)); + knob.setReadOnly(::getValue(readOnly)); updateColours(); } - bool inletIsSymbol() override + bool hideInlet() override { return hasReceiveSymbol(); } - bool outletIsSymbol() override + bool hideOutlet() override { return hasSendSymbol(); } @@ -699,7 +716,7 @@ class KnobObject final : public ObjectBase { } case hash("numberpos"): { if (atoms.size() > 1 && atoms[0].isFloat() && atoms[1].isFloat()) { - setParameterExcludingListener(numberPosition, VarArray{atoms[0].getFloat(), atoms[1].getFloat()}); + setParameterExcludingListener(numberPosition, VarArray { atoms[0].getFloat(), atoms[1].getFloat() }); updateLabel(); } break; @@ -798,10 +815,10 @@ class KnobObject final : public ObjectBase { circleBounds = circleBounds.reduced(lineThickness - 0.5f); NVGScopedState state(nvg); - float scaleFactor = 1.3f; - auto originalCentre = circleBounds.getCentre(); - float scaleOffsetX = originalCentre.x * (1.0f - scaleFactor); - float scaleOffsetY = originalCentre.y * (1.0f - scaleFactor); + float constexpr scaleFactor = 1.3f; + auto const originalCentre = circleBounds.getCentre(); + float const scaleOffsetX = originalCentre.x * (1.0f - scaleFactor); + float const scaleOffsetY = originalCentre.y * (1.0f - scaleFactor); nvgTranslate(nvg, scaleOffsetX, scaleOffsetY); nvgScale(nvg, scaleFactor, scaleFactor); @@ -844,7 +861,7 @@ class KnobObject final : public ObjectBase { auto sym = String::fromUTF8(knb->x_snd_raw->s_name); if (sym != "empty") { - return sym; + return sym.replace("\\ ", " "); } } @@ -861,7 +878,7 @@ class KnobObject final : public ObjectBase { auto sym = String::fromUTF8(knb->x_rcv_raw->s_name); if (sym != "empty") { - return sym; + return sym.replace("\\ ", " "); } } @@ -894,11 +911,11 @@ class KnobObject final : public ObjectBase { if (label) { auto const& arr = *numberPosition.getValue().getArray(); - auto height = ::getValue(numberSize); - auto font = Font(height); - auto labelText = String(getScaledValue(), 2); - auto width = font.getStringWidth(labelText); - auto bounds = Rectangle(object->getX() + 5 + static_cast(arr[0]), object->getY() + 3 + static_cast(arr[1]), width, height); + auto const height = ::getValue(numberSize); + auto const font = Font(height); + auto const labelText = String(getScaledValue(), 2); + auto const width = font.getStringWidth(labelText); + auto const bounds = Rectangle(object->getX() + 5 + static_cast(arr[0]), object->getY() + 3 + static_cast(arr[1]), width, height); label->setFont(font); label->setBounds(bounds); label->setText(typeBuffer.isEmpty() ? labelText : typeBuffer, dontSendNotification); @@ -920,7 +937,7 @@ class KnobObject final : public ObjectBase { { if (e.mods.isCommandDown()) { if (auto knob = ptr.get()) { - auto message = e.mods.isShiftDown() ? SmallString("forget") : SmallString("learn"); + auto const message = e.mods.isShiftDown() ? SmallString("forget") : SmallString("learn"); pd->sendDirectMessage(knob.cast(), message, {}); } } @@ -1024,16 +1041,16 @@ class KnobObject final : public ObjectBase { knob.setNumberOfTicks(numTicks); knob.repaint(); } - + void updateColours() { bgCol = convertColour(Colour::fromString(secondaryColour.toString())); repaint(); } - float clipArcStart(float newArcStart, float min, float max) + float clipArcStart(float const newArcStart, float const min, float const max) { - auto clampedValue = min >= max ? min : std::clamp(newArcStart, min, max); + auto const clampedValue = min >= max ? min : std::clamp(newArcStart, min, max); setParameterExcludingListener(arcStart, clampedValue); return clampedValue; } @@ -1043,32 +1060,30 @@ class KnobObject final : public ObjectBase { auto const* constrainer = getConstrainer(); auto const size = std::max(::getValue(sizeProperty), constrainer->getMinimumWidth()); setParameterExcludingListener(sizeProperty, size); - if (auto knob = ptr.get()) { + if (auto knob = ptr.get()) knob->x_size = size; - } object->updateBounds(); knob.setValue(getValue()); updateLabel(); } else if (value.refersToSameSourceAs(min)) { // set new min value and update knob - if (auto knb = ptr.get()) { - pd->sendDirectMessage(knb.get(), "range", {::getValue(min), knb->x_max}); - } + if (auto knb = ptr.get()) + pd->sendDirectMessage(knb.get(), "range", { ::getValue(min), static_cast(knb->x_max) }); + knob.setValue(getValue()); updateRange(); updateDoubleClickValue(); updateLabel(); } else if (value.refersToSameSourceAs(max)) { // set new min value and update knob - if (auto knb = ptr.get()) { - pd->sendDirectMessage(knb.get(), "range", {knb->x_min, ::getValue(max)}); - } + if (auto knb = ptr.get()) + pd->sendDirectMessage(knb.get(), "range", { static_cast(knb->x_min), ::getValue(max) }); + knob.setValue(getValue()); updateRange(); updateDoubleClickValue(); updateLabel(); - } else if (value.refersToSameSourceAs(initialValue)) { updateDoubleClickValue(); if (auto knb = ptr.get()) @@ -1079,26 +1094,22 @@ class KnobObject final : public ObjectBase { if (auto knb = ptr.get()) knb->x_circular = mode; } else if (value.refersToSameSourceAs(showTicks)) { - if (auto knb = ptr.get()) { + if (auto knb = ptr.get()) knb->x_ticks = ::getValue(showTicks); - } updateRotaryParameters(); } else if (value.refersToSameSourceAs(steps)) { steps = jmax(::getValue(steps), 0); - if (auto knb = ptr.get()) { + if (auto knb = ptr.get()) knb->x_steps = ::getValue(steps); - } updateRotaryParameters(); updateRange(); } else if (value.refersToSameSourceAs(angularRange)) { - if (auto knb = ptr.get()) { + if (auto knb = ptr.get()) pd->sendDirectMessage(knb.get(), "angle", { pd::Atom(::getValue(angularRange)) }); - } updateRotaryParameters(); } else if (value.refersToSameSourceAs(angularOffset)) { - if (auto knb = ptr.get()) { + if (auto knb = ptr.get()) pd->sendDirectMessage(knb.get(), "offset", { pd::Atom(::getValue(angularOffset)) }); - } updateRotaryParameters(); } else if (value.refersToSameSourceAs(showArc)) { bool const arc = ::getValue(showArc); @@ -1110,17 +1121,23 @@ class KnobObject final : public ObjectBase { knb->x_discrete = ::getValue(discrete); updateRange(); } else if (value.refersToSameSourceAs(square)) { - if (auto knb = ptr.get()) { + if (auto knb = ptr.get()) knb->x_square = ::getValue(square); - } repaint(); } else if (value.refersToSameSourceAs(exponential)) { - if (auto knb = ptr.get()) - knb->x_exp = ::getValue(exponential); + if (auto knb = ptr.get()) { + if (knb->x_expmode == 2) + knb->x_exp = ::getValue(exponential); + } } else if (value.refersToSameSourceAs(logMode)) { if (auto knb = ptr.get()) { knb->x_expmode = ::getValue(logMode) - 1; knb->x_log = knb->x_expmode == 1; + if (knb->x_expmode <= 1) { + knb->x_exp = 0; + } else { + knb->x_exp = ::getValue(exponential); + } } } else if (value.refersToSameSourceAs(sendSymbol)) { setSendSymbol(sendSymbol.toString()); @@ -1152,8 +1169,12 @@ class KnobObject final : public ObjectBase { knob.setArcColour(Colour::fromString(arcColour.toString())); repaint(); } else if (value.refersToSameSourceAs(readOnly)) { + if (auto knb = ptr.get()) + knb->x_readonly = ::getValue(readOnly); knob.setReadOnly(::getValue(readOnly)); } else if (value.refersToSameSourceAs(jumpOnClick)) { + if (auto knb = ptr.get()) + knb->x_jump = ::getValue(jumpOnClick); knob.setJumpOnClick(::getValue(jumpOnClick)); } else if (value.refersToSameSourceAs(parameterName)) { if (auto knb = ptr.get()) @@ -1206,7 +1227,7 @@ class KnobObject final : public ObjectBase { repaint(); } - void setValue(float pos, bool const sendNotification) + void setValue(float const pos, bool const sendNotification) { float fval = 0.0f; if (auto knb = ptr.get()) { diff --git a/Source/Objects/ListObject.h b/Source/Objects/ListObject.h index 4607f02b26..5a5e209974 100644 --- a/Source/Objects/ListObject.h +++ b/Source/Objects/ListObject.h @@ -77,6 +77,10 @@ class ListObject final : public ObjectBase { min = atomHelper.getMinimum(); max = atomHelper.getMaximum(); + + listLabel.setMinimum(getValue(min)); + listLabel.setMaximum(getValue(max)); + updateValue(); atomHelper.update(); @@ -131,7 +135,7 @@ class ListObject final : public ObjectBase { return atomHelper.getPdBounds(listLabel.getFont().getStringWidth(listLabel.getText())); } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { atomHelper.setPdBounds(b); } @@ -146,12 +150,12 @@ class ListObject final : public ObjectBase { atomHelper.updateLabel(labels); } - bool inletIsSymbol() override + bool hideInlet() override { return atomHelper.hasReceiveSymbol(); } - bool outletIsSymbol() override + bool hideOutlet() override { return atomHelper.hasSendSymbol(); } diff --git a/Source/Objects/LuaObject.h b/Source/Objects/LuaObject.h index 6888884b50..69228b35c8 100644 --- a/Source/Objects/LuaObject.h +++ b/Source/Objects/LuaObject.h @@ -68,7 +68,7 @@ class LuaObject final : public ObjectBase UnorderedSegmentedMap> guiCommandBuffer; UnorderedSegmentedMap> guiMessageQueue; - static inline UnorderedMap> allDrawTargets = UnorderedMap>(); + static inline auto allDrawTargets = UnorderedMap>(); public: LuaObject(pd::WeakReference obj, Object* parent) @@ -77,7 +77,7 @@ class LuaObject final : public ObjectBase if (auto pdlua = ptr.get()) { pdlua->gfx.plugdata_draw_callback = &drawCallback; allDrawTargets[pdlua.get()].add(this); - + libpd_set_instance(&pd_maininstance); pdluaxSymbol = gensym("pdluax"); pd->setThis(); @@ -107,11 +107,10 @@ class LuaObject final : public ObjectBase while (auto const* nextCanvas = topLevelCanvas->findParentComponentOfClass()) { topLevelCanvas = nextCanvas; } - if (topLevelCanvas) { - zoomScale.referTo(topLevelCanvas->zoomScale); - zoomScale.addListener(this); - sendRepaintMessage(); - } + + zoomScale.referTo(topLevelCanvas->zoomScale); + zoomScale.addListener(this); + sendRepaintMessage(); } Rectangle getPdBounds() override @@ -128,7 +127,7 @@ class LuaObject final : public ObjectBase return {}; } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { if (auto gobj = ptr.get()) { auto* patch = object->cnv->patch.getRawPointer(); @@ -157,13 +156,13 @@ class LuaObject final : public ObjectBase menu.addItem("Reload lua object", [_this = SafePointer(this)] { if (!_this) return; - + // prevent potential crash if this was selected _this->cnv->editor->sidebar->hideParameters(); - + if (auto pdlua = _this->ptr.get()) { // Reload the lua script - pd_typedmess(_this->pdluaxSymbol->s_thing, gensym("reload"), 0, NULL); + pd_typedmess(_this->pdluaxSymbol->s_thing, gensym("reload"), 0, nullptr); // Recreate this object if (auto patch = _this->cnv->patch.getPointer()) { @@ -236,8 +235,14 @@ class LuaObject final : public ObjectBase void render(NVGcontext* nvg) override { + NVGScopedState scopedState(nvg); + + auto scale = nvgCurrentPixelScale(nvg) * getValue(zoomScale); + nvgScale(nvg, 1.0f / scale, 1.0f / scale); + nvgTransformQuantize(nvg); + for (auto& [layer, fb] : framebuffers) { - fb.render(nvg, Rectangle(getWidth() + 1, getHeight())); + fb.render(nvg, Rectangle(std::ceil(getWidth() * scale), std::ceil(getHeight() * scale))); } } @@ -254,8 +259,8 @@ class LuaObject final : public ObjectBase case hash("lua_start_paint"): { if (getLocalBounds().isEmpty()) break; - - auto const pixelScale = 2.0f; + + auto const pixelScale = nvgCurrentPixelScale(nvg); auto const zoom = getValue(zoomScale); auto const imageScale = zoom * pixelScale; int const imageWidth = std::ceil(getWidth() * imageScale); @@ -297,6 +302,8 @@ class LuaObject final : public ObjectBase } return; } + default: + break; } switch (hashsym) { @@ -306,8 +313,7 @@ class LuaObject final : public ObjectBase currentColour = StackArray { cnv->findColour(PlugDataColour::guiObjectBackgroundColourId), cnv->findColour(PlugDataColour::canvasTextColourId), - cnv->findColour(PlugDataColour::guiObjectInternalOutlineColour) - }[colourID]; + cnv->findColour(PlugDataColour::guiObjectInternalOutlineColour) }[colourID]; nvgFillColor(nvg, convertColour(currentColour)); nvgStrokeColor(nvg, convertColour(currentColour)); } @@ -580,6 +586,11 @@ class LuaObject final : public ObjectBase void openTextEditor(File fileToOpen) { + if(fileToOpen.getFileName() == "pd.lua") + { + return; + } + if (textEditor) { textEditor->toFront(true); return; @@ -605,7 +616,7 @@ class LuaObject final : public ObjectBase if (result == 2) { fileToOpen.replaceWithText(newText); if (auto pdlua = _this->ptr.get()) { - pd_typedmess(_this->pdluaxSymbol->s_thing, gensym("reload"), 0, NULL); + pd_typedmess(_this->pdluaxSymbol->s_thing, gensym("reload"), 0, nullptr); // Recreate this object if (auto patch = _this->cnv->patch.getPointer()) { pd::Interface::recreateTextObject(patch.get(), pdlua.cast()); @@ -629,12 +640,12 @@ class LuaObject final : public ObjectBase fileToOpen.replaceWithText(newText); if (auto pdlua = ptr.get()) { - pd_typedmess(pdluaxSymbol->s_thing, gensym("reload"), 0, NULL); + pd_typedmess(pdluaxSymbol->s_thing, gensym("reload"), 0, nullptr); } sendRepaintMessage(); }; - const auto scaleFactor = getApproximateScaleFactorForComponent(cnv->editor); + auto const scaleFactor = getApproximateScaleFactorForComponent(cnv->editor); textEditor.reset(Dialogs::showTextEditorDialog(fileToOpen.loadFileAsString(), "lua: " + getText(), onClose, onSave, scaleFactor, true)); if (textEditor) @@ -648,7 +659,7 @@ class LuaTextObject final : public TextBase { std::unique_ptr textEditor; std::unique_ptr saveDialog; t_symbol* pdluaxSymbol; - + LuaTextObject(pd::WeakReference ptr, Object* object) : TextBase(ptr, object) { @@ -663,8 +674,11 @@ class LuaTextObject final : public TextBase { return; if (getValue(object->locked)) { - if (auto obj = ptr.get()) { - pd->sendDirectMessage(obj.get(), "menu-open", {}); + auto objectText = getText(); + if(objectText != "pdlua" && objectText != "pdluax") { + if (auto obj = ptr.get()) { + pd->sendDirectMessage(obj.get(), "menu-open", {}); + } } } } @@ -678,31 +692,38 @@ class LuaTextObject final : public TextBase { void getMenuOptions(PopupMenu& menu) override { - menu.addItem("Open lua editor", [_this = SafePointer(this)] { - if (!_this) - return; - if (auto obj = _this->ptr.get()) { - _this->pd->sendDirectMessage(obj.get(), "menu-open", {}); - } - }); - - menu.addItem("Reload lua object", [_this = SafePointer(this)] { - if (!_this) - return; - if (auto pdlua = _this->ptr.get()) { - // Reload the lua script - pd_typedmess(_this->pdluaxSymbol->s_thing, gensym("reload"), 0, NULL); - - // Recreate this object - if (auto patch = _this->cnv->patch.getPointer()) { - pd::Interface::recreateTextObject(patch.get(), pdlua.cast()); + auto objectText = getText(); + if(objectText != "pdlua" && objectText != "pdluax") { + menu.addItem("Open lua editor", [_this = SafePointer(this)] { + if (!_this) + return; + if (auto obj = _this->ptr.get()) { + _this->pd->sendDirectMessage(obj.get(), "menu-open", {}); } - } - }); + }); + + menu.addItem("Reload lua object", [_this = SafePointer(this)] { + if (!_this) + return; + if (auto pdlua = _this->ptr.get()) { + // Reload the lua script + pd_typedmess(_this->pdluaxSymbol->s_thing, gensym("reload"), 0, nullptr); + + // Recreate this object + if (auto patch = _this->cnv->patch.getPointer()) { + pd::Interface::recreateTextObject(patch.get(), pdlua.cast()); + } + } + }); + } } void openTextEditor(File fileToOpen) { + if(fileToOpen == ProjectInfo::appDataDir.getChildFile("Extra").getChildFile("pdlua").getChildFile("pd.lua")) { + return; + } + if (textEditor) { textEditor->toFront(true); return; @@ -725,7 +746,7 @@ class LuaTextObject final : public TextBase { if (result == 2) { fileToOpen.replaceWithText(newText); if (auto pdlua = ptr.get()) { - pd_typedmess(pdluaxSymbol->s_thing, gensym("reload"), 0, NULL); + pd_typedmess(pdluaxSymbol->s_thing, gensym("reload"), 0, nullptr); // Recreate this object if (auto patch = cnv->patch.getPointer()) { pd::Interface::recreateTextObject(patch.get(), pdlua.cast()); @@ -748,11 +769,13 @@ class LuaTextObject final : public TextBase { return; fileToOpen.replaceWithText(newText); if (auto pdlua = ptr.get()) { - pd_typedmess(pdluaxSymbol->s_thing, gensym("reload"), 0, NULL); + if(pdluaxSymbol->s_thing) { + pd_typedmess(pdluaxSymbol->s_thing, gensym("reload"), 0, nullptr); + } } }; - const auto scaleFactor = getApproximateScaleFactorForComponent(cnv->editor); + auto const scaleFactor = getApproximateScaleFactorForComponent(cnv->editor); textEditor.reset(Dialogs::showTextEditorDialog(fileToOpen.loadFileAsString(), "lua: " + getText(), onClose, onSave, scaleFactor, true)); if (textEditor) diff --git a/Source/Objects/MessageObject.h b/Source/Objects/MessageObject.h index 8b4991f0aa..dfc35c5508 100644 --- a/Source/Objects/MessageObject.h +++ b/Source/Objects/MessageObject.h @@ -109,7 +109,7 @@ class MessageObject final : public ObjectBase } } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { if (auto gobj = ptr.get()) { auto* patch = cnv->patch.getRawPointer(); @@ -171,7 +171,7 @@ class MessageObject final : public ObjectBase } } - void receiveObjectMessage(hash32 symbol, SmallArray const& atoms) override + void receiveObjectMessage(hash32 const symbol, SmallArray const& atoms) override { if (symbol == hash("float")) return; @@ -378,4 +378,9 @@ class MessageObject final : public ObjectBase { return TextObjectHelper::createConstrainer(object); } + + ResizeDirection getAllowedResizeDirections() const override + { + return ResizeDirection::HorizontalOnly; + } }; diff --git a/Source/Objects/MessboxObject.h b/Source/Objects/MessboxObject.h index 56f9d796a2..c60e33e3f2 100644 --- a/Source/Objects/MessboxObject.h +++ b/Source/Objects/MessboxObject.h @@ -29,7 +29,7 @@ class MessboxObject final : public ObjectBase editor.getProperties().set("NoBackground", true); editor.getProperties().set("NoOutline", true); editor.setColour(ScrollBar::thumbColourId, cnv->editor->getLookAndFeel().findColour(PlugDataColour::scrollbarThumbColourId)); - editor.onFocusLost = [this](){ + editor.onFocusLost = [this] { needsRepaint = true; repaint(); }; @@ -69,10 +69,12 @@ class MessboxObject final : public ObjectBase primaryColour = Colour(messbox->x_fg[0], messbox->x_fg[1], messbox->x_fg[2]).toString(); secondaryColour = Colour(messbox->x_bg[0], messbox->x_bg[1], messbox->x_bg[2]).toString(); sizeProperty = VarArray { var(messbox->x_width), var(messbox->x_height) }; + bold = pd->generateSymbol("bold") == messbox->x_font_weight; } - + + auto font = getValue(bold) ? Fonts::getBoldFont() : Fonts::getDefaultFont(); editor.applyColourToAllText(Colour::fromString(primaryColour.toString())); - editor.applyFontToAllText(editor.getFont().withHeight(getValue(fontSize))); + editor.applyFontToAllText(font.withHeight(getValue(fontSize))); repaint(); } @@ -90,7 +92,7 @@ class MessboxObject final : public ObjectBase return {}; } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { if (auto messbox = ptr.get()) { auto* patch = object->cnv->patch.getRawPointer(); @@ -131,9 +133,9 @@ class MessboxObject final : public ObjectBase } else { NVGScopedState state(nvg); nvgScale(nvg, 1.0f / scale, 1.0f / scale); - auto w = roundToInt (scale * (float) editor.getWidth()); - auto h = roundToInt (scale * (float) editor.getHeight()); - imageRenderer.render(nvg, {0, 0, w, h}, true); + auto w = roundToInt(scale * static_cast(editor.getWidth())); + auto h = roundToInt(scale * static_cast(editor.getHeight())); + imageRenderer.render(nvg, { 0, 0, w, h }, true); } } @@ -175,8 +177,10 @@ class MessboxObject final : public ObjectBase void hideEditor() override { - cnv->grabKeyboardFocus(); - repaint(); + if(cnv->isShowing()) { + cnv->grabKeyboardFocus(); + repaint(); + } } bool isEditorShown() override @@ -206,7 +210,7 @@ class MessboxObject final : public ObjectBase { SmallArray atoms; if (auto messObj = ptr.get()) { - auto* av = binbuf_getvec(messObj->x_state); + auto const* av = binbuf_getvec(messObj->x_state); auto const ac = binbuf_getnatom(messObj->x_state); atoms = pd::Atom::fromAtoms(ac, av); } @@ -320,12 +324,12 @@ class MessboxObject final : public ObjectBase auto const boldFont = Fonts::getBoldFont(); editor.applyFontToAllText(boldFont.withHeight(size)); if (auto messbox = ptr.get()) - messbox->x_font_weight = pd->generateSymbol("normal"); + messbox->x_font_weight = pd->generateSymbol("bold"); } else { auto const defaultFont = Fonts::getCurrentFont(); editor.applyFontToAllText(defaultFont.withHeight(size)); if (auto messbox = ptr.get()) - messbox->x_font_weight = pd->generateSymbol("bold"); + messbox->x_font_weight = pd->generateSymbol("normal"); } } } diff --git a/Source/Objects/MidiObjects.h b/Source/Objects/MidiObjects.h index 2e72d6ba98..fa0c37c026 100644 --- a/Source/Objects/MidiObjects.h +++ b/Source/Objects/MidiObjects.h @@ -48,7 +48,9 @@ class MidiObject final : public TextBase { auto const currentCC = text.size() > 2 ? text[2].getIntValue() : 0; if (midiInput) { - popupMenu.addItem(1, "All input devices", true, currentPort == 0); + popupMenu.addItem("All input devices", true, currentPort == 0, [this](){ + setChannel(0); + }); } auto& midiDeviceManager = pd->getMidiDeviceManager(); diff --git a/Source/Objects/MousePadObject.h b/Source/Objects/MousePadObject.h index e4a0fe5330..cc6a2254b3 100644 --- a/Source/Objects/MousePadObject.h +++ b/Source/Objects/MousePadObject.h @@ -12,7 +12,7 @@ class MousePadObject final : public ObjectBase { Point lastPosition; Value sizeProperty = SynchronousValue(); NVGcolor fillColour; - + public: MousePadObject(pd::WeakReference ptr, Object* object) : ObjectBase(ptr, object) @@ -92,7 +92,7 @@ class MousePadObject final : public ObjectBase { auto const* topLevel = cnv; while (auto const* nextCanvas = topLevel->findParentComponentOfClass()) { topLevel = nextCanvas; - if(auto* graph = dynamic_cast(topLevel->getParentComponent())) { + if (auto* graph = dynamic_cast(topLevel->getParentComponent())) { auto const pos = e.getEventRelativeTo(graph).getPosition(); if (!graph->getLocalBounds().contains(pos)) { return false; @@ -106,12 +106,12 @@ class MousePadObject final : public ObjectBase { void render(NVGcontext* nvg) override { auto const b = getLocalBounds().toFloat(); - auto outlineColour = object->isSelected() && !cnv->isGraph ? cnv->selectedOutlineCol : cnv->objectOutlineCol; + auto const outlineColour = object->isSelected() && !cnv->isGraph ? cnv->selectedOutlineCol : cnv->objectOutlineCol; nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), fillColour, outlineColour, Corners::objectCornerRadius); } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { if (auto pad = ptr.get()) { auto* patch = cnv->patch.getRawPointer(); diff --git a/Source/Objects/NoteObject.h b/Source/Objects/NoteObject.h index 50d0d269d5..256fece51e 100644 --- a/Source/Objects/NoteObject.h +++ b/Source/Objects/NoteObject.h @@ -6,11 +6,12 @@ #pragma once #include "Utility/Fonts.h" -class NoteObject final : public ObjectBase { +class NoteObject final : public ObjectBase, public AsyncUpdater { Colour textColour; BorderSize border { 1, 7, 1, 2 }; + String currentNoteText; TextEditor noteEditor; Value primaryColour = SynchronousValue(); @@ -53,7 +54,6 @@ class NoteObject final : public ObjectBase { noteEditor.setMultiLine(true); noteEditor.setReturnKeyStartsNewLine(true); noteEditor.setScrollbarsShown(false); - noteEditor.setIndents(0, 2); noteEditor.setScrollToShowCursor(true); noteEditor.setBorder(border); @@ -107,7 +107,7 @@ class NoteObject final : public ObjectBase { return true; } - bool inletIsSymbol() override + bool hideInlet() override { // we want to hide the note inlet regardless if it's symbol or not in locked mode auto const receiveSym = receiveSymbol.toString(); @@ -136,9 +136,9 @@ class NoteObject final : public ObjectBase { } else { NVGScopedState state(nvg); nvgScale(nvg, 1.0f / scale, 1.0f / scale); - auto w = roundToInt (scale * (float) noteEditor.getWidth()); - auto h = roundToInt (scale * (float) noteEditor.getHeight()); - imageRenderer.render(nvg, {0, 0, w, h}, true); + auto w = roundToInt(scale * static_cast(noteEditor.getWidth())); + auto h = roundToInt(scale * static_cast(noteEditor.getHeight())); + imageRenderer.render(nvg, { 0, 0, w, h }, true); } } @@ -146,13 +146,10 @@ class NoteObject final : public ObjectBase { void update() override { - auto const oldFont = getFont(); - - String newText; if (auto note = ptr.get()) { textColour = Colour(note->x_red, note->x_green, note->x_blue); - newText = getNote(); + currentNoteText = getNote(); primaryColour = Colour(note->x_red, note->x_green, note->x_blue).toString(); secondaryColour = Colour(note->x_bg[0], note->x_bg[1], note->x_bg[2]).toString(); fontSize = note->x_fontsize; @@ -175,10 +172,21 @@ class NoteObject final : public ObjectBase { receiveSymbol = receiveSym == "empty" ? "" : note->x_rcv_raw->s_name; } - noteEditor.setText(newText); - + updateFont(); + + triggerAsyncUpdate(); + } + + void handleAsyncUpdate() override + { auto const newFont = getFont(); - + + noteEditor.setIndents(0, 2); + noteEditor.setFont(newFont); + noteEditor.setText(currentNoteText); + noteEditor.applyColourToAllText(Colour::fromString(primaryColour.toString())); + noteEditor.repaint(); + auto const justificationType = getValue(justification); if (justificationType == 1) { noteEditor.setJustification(Justification::topLeft); @@ -187,15 +195,10 @@ class NoteObject final : public ObjectBase { } else if (justificationType == 3) { noteEditor.setJustification(Justification::topRight); } - - noteEditor.setColour(TextEditor::textColourId, Colour::fromString(primaryColour.toString())); - - if (oldFont != newFont) { - updateFont(); - } - + getLookAndFeel().setColour(Label::textWhenEditingColourId, cnv->editor->getLookAndFeel().findColour(Label::textWhenEditingColourId)); getLookAndFeel().setColour(Label::textColourId, cnv->editor->getLookAndFeel().findColour(Label::textColourId)); + needsRepaint = true; } void updateSizeProperty() override @@ -318,7 +321,7 @@ class NoteObject final : public ObjectBase { return std::make_unique(object, this); } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { if (auto note = ptr.get()) { auto* patch = cnv->patch.getRawPointer(); @@ -408,13 +411,13 @@ class NoteObject final : public ObjectBase { } else if (justificationType == 3) { noteEditor.setJustification(Justification::topRight); } + updateFont(); } else if (v.refersToSameSourceAs(outline)) { if (auto note = ptr.get()) { note->x_outline = getValue(outline); note->x_fontface = note->x_bold + 2 * note->x_italic + 4 * note->x_outline; } - needsRepaint = true; - repaint(); + updateFont(); } else if (v.refersToSameSourceAs(font)) { auto const fontName = font.toString(); if (auto note = ptr.get()) @@ -430,18 +433,17 @@ class NoteObject final : public ObjectBase { auto const isUnderlined = getValue(underline); auto const fontHeight = getValue(fontSize); - auto style = isBold * Font::bold | isItalic * Font::italic | isUnderlined * Font::underlined; - auto typefaceName = font.toString(); + auto const style = isBold * Font::bold | isItalic * Font::italic | isUnderlined * Font::underlined; + auto const typefaceName = font.toString(); if (typefaceName.isEmpty() || typefaceName == "Inter") { return Fonts::getVariableFont().withStyle(style).withHeight(fontHeight); } - + // Check if a system typeface exists, before we start searching for a font file // We do this because it's the most common case, and finding font files is slow auto typeface = Font(typefaceName, static_cast(fontHeight), style); - if(typeface.getTypefacePtr() != nullptr) - { + if (typeface.getTypefacePtr() != nullptr) { return typeface; } @@ -451,7 +453,7 @@ class NoteObject final : public ObjectBase { if (auto const patchFont = Fonts::findFont(currentFile, typefaceName); patchFont.has_value()) return patchFont->withStyle(style).withHeight(fontHeight); } - + return typeface; } @@ -536,6 +538,7 @@ class NoteObject final : public ObjectBase { if (auto note = ptr.get()) { outline = note->x_outline; } + repaint(); } case hash("receive"): { if (atoms.size() >= 1) diff --git a/Source/Objects/NumberObject.h b/Source/Objects/NumberObject.h index 15b96441d0..ac057d0ddd 100644 --- a/Source/Objects/NumberObject.h +++ b/Source/Objects/NumberObject.h @@ -108,37 +108,35 @@ class NumberObject final : public ObjectBase { void update() override { - if (input.isShowing()) - return; - - value = getValue(); - input.setValue(value, dontSendNotification); - - min = getMinimum(); - max = getMaximum(); - - input.setMinimum(::getValue(min)); - input.setMaximum(::getValue(max)); - if (auto nbx = ptr.get()) { widthProperty = var(nbx->x_numwidth); heightProperty = var(nbx->x_gui.x_h); logMode = nbx->x_lin0_log1; logHeight = nbx->x_log_height; + min = nbx->x_min; + max = nbx->x_max; + value = nbx->x_val; } + + input.setValue(value, dontSendNotification, false); + input.setMinimum(getValue(min)); + input.setMaximum(getValue(max)); + input.setLogarithmicHeight(getValue(logHeight)); + input.setDragMode(getValue(logMode) ? DraggableNumber::Logarithmic : DraggableNumber::Regular); iemHelper.update(); - - auto const fontHeight = ::getValue(iemHelper.labelHeight) + 3.0f; - input.setFont(Fonts::getTabularNumbersFont().withHeight(fontHeight)); + + auto const fontHeight = getValue(iemHelper.labelHeight); + input.setFont(Fonts::getTabularNumbersFont().withHeight(fontHeight + 3.0f)); + updateLabel(); } - bool inletIsSymbol() override + bool hideInlet() override { return iemHelper.hasReceiveSymbol(); } - bool outletIsSymbol() override + bool hideOutlet() override { return iemHelper.hasSendSymbol(); } @@ -170,7 +168,7 @@ class NumberObject final : public ObjectBase { return 10; } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { if (auto nbx = ptr.get()) { auto* patchPtr = cnv->patch.getRawPointer(); @@ -232,7 +230,7 @@ class NumberObject final : public ObjectBase { void lock(bool const isLocked) override { - input.setResetEnabled(::getValue(cnv->locked)); + input.setResetEnabled(getValue(cnv->locked)); setInterceptsMouseClicks(isLocked, isLocked); repaint(); } @@ -250,9 +248,11 @@ class NumberObject final : public ObjectBase { break; } case hash("range"): { - if (atoms.size() >= 2 && atoms[0].isFloat() && atoms[1].isFloat()) { - min = getMinimum(); - max = getMaximum(); + if (atoms.size() >= 2) { + if (auto nbx = ptr.get()) { + min = nbx->x_min; + max = nbx->x_max; + } } break; } @@ -311,10 +311,8 @@ class NumberObject final : public ObjectBase { void propertyChanged(Value& value) override { - if (value.refersToSameSourceAs(widthProperty)) { - auto const numWidth = std::max(::getValue(widthProperty), 1); - + auto const numWidth = std::max(getValue(widthProperty), 1); auto const width = calcFontWidth(numWidth) + 1; setParameterExcludingListener(widthProperty, var(numWidth)); @@ -326,35 +324,41 @@ class NumberObject final : public ObjectBase { object->updateBounds(); } else if (value.refersToSameSourceAs(heightProperty)) { - auto const height = std::max(::getValue(heightProperty), constrainer->getMinimumHeight()); + auto const height = std::max(getValue(heightProperty), constrainer->getMinimumHeight()); setParameterExcludingListener(heightProperty, var(height)); if (auto nbx = ptr.get()) { nbx->x_gui.x_h = height; } object->updateBounds(); } else if (value.refersToSameSourceAs(min)) { - setMinimum(::getValue(min)); + input.setMinimum(getValue(min)); + if (auto numbox = ptr.get()) { + numbox->x_min = getValue(min); + } } else if (value.refersToSameSourceAs(max)) { - setMaximum(::getValue(max)); + input.setMaximum(getValue(max)); + if (auto numbox = ptr.get()) { + numbox->x_max = getValue(max); + } } else if (value.refersToSameSourceAs(logHeight)) { - auto const height = ::getValue(logHeight); + auto const height = getValue(logHeight); if (auto nbx = ptr.get()) { nbx->x_log_height = height; } input.setLogarithmicHeight(height); } else if (value.refersToSameSourceAs(logMode)) { - auto const logarithmicDrag = ::getValue(logMode); + auto const logarithmicDrag = getValue(logMode); if (auto nbx = ptr.get()) { nbx->x_lin0_log1 = logarithmicDrag; } input.setDragMode(logarithmicDrag ? DraggableNumber::Logarithmic : DraggableNumber::Regular); } else if (value.refersToSameSourceAs(iemHelper.labelHeight)) { - iemHelper.setFontHeight(::getValue(iemHelper.labelHeight)); + auto const fontHeight = getValue(iemHelper.labelHeight); + iemHelper.setFontHeight(fontHeight); updateLabel(); - auto const fontHeight = ::getValue(iemHelper.labelHeight) + 3.0f; - input.setFont(Fonts::getTabularNumbersFont().withHeight(fontHeight)); + input.setFont(Fonts::getTabularNumbersFont().withHeight(fontHeight + 3.0f)); object->updateBounds(); } else { iemHelper.valueChanged(value); @@ -380,7 +384,7 @@ class NumberObject final : public ObjectBase { nvgLineTo(nvg, leftX, centreY - 5.0f); nvgClosePath(nvg); - bool const highlighted = hasKeyboardFocus(true) && ::getValue(object->locked); + bool const highlighted = hasKeyboardFocus(true) && getValue(object->locked); auto const flagCol = highlighted ? cnv->selectedOutlineCol : cnv->guiObjectInternalOutlineCol; nvgFillColor(nvg, flagCol); @@ -389,46 +393,6 @@ class NumberObject final : public ObjectBase { input.render(nvg); } - float getValue() const - { - if (auto numbox = ptr.get()) { - return numbox->x_val; - } - return 0.0f; - } - - float getMinimum() const - { - if (auto numbox = ptr.get()) { - return numbox->x_min; - } - return -std::numeric_limits::infinity(); - } - - float getMaximum() const - { - if (auto numbox = ptr.get()) { - return numbox->x_max; - } - return std::numeric_limits::infinity(); - } - - void setMinimum(float const value) - { - input.setMinimum(value); - if (auto numbox = ptr.get()) { - numbox->x_min = value; - } - } - - void setMaximum(float const value) - { - input.setMaximum(value); - if (auto numbox = ptr.get()) { - numbox->x_max = value; - } - } - std::unique_ptr createConstrainer() override { class NumboxBoundsConstrainer : public ComponentBoundsConstrainer { diff --git a/Source/Objects/NumboxTildeObject.h b/Source/Objects/NumboxTildeObject.h index d97d8d04b5..c172de3361 100644 --- a/Source/Objects/NumboxTildeObject.h +++ b/Source/Objects/NumboxTildeObject.h @@ -44,6 +44,7 @@ class NumboxTildeObject final : public ObjectBase pd_bang(obj.get()); } }; + input.setPrecision(5); startTimer(nextInterval); repaint(); @@ -67,6 +68,9 @@ class NumboxTildeObject final : public ObjectBase min = getMinimum(); max = getMaximum(); + + input.setMinimum(::getValue(min)); + input.setMaximum(::getValue(max)); if (auto object = ptr.get()) { interval = object->x_rate; @@ -156,7 +160,7 @@ class NumboxTildeObject final : public ObjectBase return constrainer; } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { if (auto nbx = ptr.get()) { auto* patch = cnv->patch.getRawPointer(); @@ -172,7 +176,7 @@ class NumboxTildeObject final : public ObjectBase void resized() override { - input.setBounds(getLocalBounds().withTrimmedLeft(getHeight() - 4)); + input.setBounds(getLocalBounds()); input.setFont(input.getFont().withHeight(getHeight() - 6)); } @@ -254,14 +258,6 @@ class NumboxTildeObject final : public ObjectBase nvgTranslate(nvg, input.getX(), input.getY()); input.render(nvg); } - - auto const icon = mode ? Icons::ThinDown : Icons::Sine; - auto const iconBounds = Rectangle(7, 3, getHeight(), getHeight()); - nvgFontFace(nvg, "icon_font-Regular"); - nvgFontSize(nvg, 12.0f); - nvgFillColor(nvg, convertColour(cnv->editor->getLookAndFeel().findColour(PlugDataColour::dataColourId))); - nvgTextAlign(nvg, NVG_ALIGN_TOP | NVG_ALIGN_LEFT); - nvgText(nvg, iconBounds.getX(), iconBounds.getY(), icon.toRawUTF8(), nullptr); } void timerCallback() override diff --git a/Source/Objects/ObjectBase.cpp b/Source/Objects/ObjectBase.cpp index daf28b153d..b0ab94cff1 100644 --- a/Source/Objects/ObjectBase.cpp +++ b/Source/Objects/ObjectBase.cpp @@ -189,6 +189,7 @@ ObjectBase::ObjectBase(pd::WeakReference obj, Object* parent) if (auto obj = _this->ptr.get()) { auto* canvas = _this->cnv->patch.getRawPointer(); pd::Interface::undoApply(canvas, obj.get()); + canvas_dirty(canvas, 1); } }; } @@ -205,13 +206,14 @@ ObjectBase::~ObjectBase() void ObjectBase::initialise() { - update(); + updateProperties(); + constrainer = createConstrainer(); onConstrainerCreate(); pd->registerMessageListener(ptr.getRawUnchecked(), this); - for (auto& param : objectParameters.getParameters()) { + for (auto const& param : objectParameters.getParameters()) { if (param.valuePtr) { param.valuePtr->addListener(&propertyListener); } @@ -256,23 +258,20 @@ String ObjectBase::getText() bool ObjectBase::checkHvccCompatibility() { auto type = getType(); - - if(type == "msg") // Prevent mixing up pd message and else/message + + if (type == "msg") // Prevent mixing up pd message and else/message { if (auto* objectPtr = ptr.getRaw()) { auto const origin = pd::Library::getObjectOrigin(objectPtr); - if(origin == "ELSE") { + if (origin == "ELSE") { pd->logWarning(String("Warning: object message is not supported in Compiled Mode").toRawUTF8()); return false; } } return true; - } - else if(HeavyCompatibleObjects::isCompatible(type)) - { + } else if (HeavyCompatibleObjects::isCompatible(type)) { return true; - } - else { + } else { pd->logWarning(String("Warning: object \"" + getType() + "\" is not supported in Compiled Mode").toRawUTF8()); return false; } @@ -351,6 +350,11 @@ void ObjectBase::setType() return "vsl"; else return "hsl"; + case hash("hradio"): + if (obj.cast()->x_orientation) + return "vradio"; + else + return "hradio"; default: break; } @@ -363,12 +367,6 @@ void ObjectBase::setType() type = getObjectType(); } -// Make sure the object can't be triggered if that palette is in drag mode -bool ObjectBase::hitTest(int const x, int const y) -{ - return Component::hitTest(x, y); -} - // Gets position from pd and applies it to Object Rectangle ObjectBase::getSelectableBounds() { @@ -391,7 +389,7 @@ void ObjectBase::closeOpenedSubpatchers() } } -bool ObjectBase::click(Point position, bool const shift, bool const alt) +bool ObjectBase::click(Point const position, bool const shift, bool const alt) { if (auto obj = ptr.get()) { @@ -506,7 +504,7 @@ float ObjectBase::getImageScale() } if (topLevel->editor->pluginMode) { auto const scale = std::sqrt(std::abs(topLevel->getTransform().getDeterminant())); - return object->editor->getRenderScale() * std::max(1.0f, scale); + return object->editor->getRenderScale() * scale; } return object->editor->getRenderScale() * getValue(topLevel->zoomScale); @@ -555,6 +553,7 @@ void ObjectBase::sendFloatValue(float const newValue) } } + ObjectBase* ObjectBase::createGui(pd::WeakReference ptr, Object* parent) { parent->cnv->pd->setThis(); @@ -696,8 +695,8 @@ ObjectBase* ObjectBase::createGui(pd::WeakReference ptr, Object* parent) return new KnobObject(ptr, parent); case hash("popmenu"): return new PopMenu(ptr, parent); - // case hash("dropzone"): - // return new DropzoneObject(ptr, parent); + // case hash("dropzone"): + // return new DropzoneObject(ptr, parent); case hash("openfile"): { if (auto checked = ptr.get()) { char* text; @@ -737,6 +736,13 @@ ObjectBase* ObjectBase::createGui(pd::WeakReference ptr, Object* parent) return new TextObject(ptr, parent); } +void ObjectBase::updateProperties() +{ + propertyListener.setNoCallback(true); + update(); + propertyListener.setNoCallback(false); +} + void ObjectBase::getMenuOptions(PopupMenu& menu) { if (auto obj = ptr.get()) { @@ -857,6 +863,11 @@ ComponentBoundsConstrainer* ObjectBase::getConstrainer() const return constrainer.get(); } +ObjectBase::ResizeDirection ObjectBase::getAllowedResizeDirections() const +{ + return Any; +} + std::unique_ptr ObjectBase::createConstrainer() { class ObjectBoundsConstrainer : public ComponentBoundsConstrainer { @@ -908,3 +919,42 @@ std::unique_ptr ObjectBase::createConstrainer() return std::make_unique(); } + +bool ObjectBase::recurseHvccCompatibility(String const& objectText, pd::Patch::Ptr patch, String const& prefix) +{ + auto const instance = patch->instance; + + if (objectText.startsWith("pd @hv_obj") || HeavyCompatibleObjects::isCompatible(objectText)) { + return true; + } + + bool compatible = true; + + for (auto object : patch->getObjects()) { + if (auto ptr = object.get()) { + String const type = pd::Interface::getObjectClassName(ptr.get()); + + if (type == "canvas" || type == "graph") { + pd::Patch::Ptr const subpatch = new pd::Patch(object, instance, false); + + if (subpatch->isSubpatch()) { + char* text = nullptr; + int size = 0; + pd::Interface::getObjectText(&ptr.cast()->gl_obj, &text, &size); + auto objName = String::fromUTF8(text, size); + + compatible = recurseHvccCompatibility(objName, subpatch, prefix + objName + " -> ") && compatible; + freebytes(text, static_cast(size) * sizeof(char)); + } else if (!HeavyCompatibleObjects::isCompatible(type)) { + compatible = false; + instance->logWarning(String("Warning: object \"" + prefix + type + "\" is not supported in Compiled Mode")); + } + } else if (!HeavyCompatibleObjects::isCompatible(type)) { + compatible = false; + instance->logWarning(String("Warning: object \"" + prefix + type + "\" is not supported in Compiled Mode")); + } + } + } + + return compatible; +} diff --git a/Source/Objects/ObjectBase.h b/Source/Objects/ObjectBase.h index 1b38a3a260..1a5da01208 100644 --- a/Source/Objects/ObjectBase.h +++ b/Source/Objects/ObjectBase.h @@ -48,9 +48,9 @@ class ObjectLabel : public Label virtual void renderLabel(NVGcontext* nvg, float const scale) { - auto w = roundToInt (scale * (float) getWidth()); - auto h = roundToInt (scale * (float) getHeight()); - + auto const w = roundToInt(scale * static_cast(getWidth())); + auto const h = roundToInt(scale * static_cast(getHeight())); + auto const textHash = hash(getText()); if (image.needsUpdate(w, h) || updateColour || lastTextHash != textHash || lastScale != scale) { updateImage(nvg, scale); @@ -91,7 +91,7 @@ class ObjectBase : public Component struct ObjectSizeListener final : public juce::ComponentListener , public Value::Listener { - ObjectSizeListener(Object* obj); + explicit ObjectSizeListener(Object* obj); void componentMovedOrResized(Component& component, bool moved, bool resized) override; @@ -102,7 +102,7 @@ class ObjectBase : public Component }; struct PropertyListener final : public Value::Listener { - PropertyListener(ObjectBase* parent); + explicit PropertyListener(ObjectBase* parent); void setNoCallback(bool skipCallback); @@ -131,12 +131,10 @@ class ObjectBase : public Component virtual void hideEditor() { } virtual bool isTransparent() { return false; } - - bool hitTest(int x, int y) override; - + // Some objects need to show/hide iolets when send/receive symbols are set - virtual bool inletIsSymbol() { return false; } - virtual bool outletIsSymbol() { return false; } + virtual bool hideInlet() { return false; } + virtual bool hideOutlet() { return false; } // Gets position from pd and applies it to Object virtual Rectangle getPdBounds() = 0; @@ -150,9 +148,6 @@ class ObjectBase : public Component // Called whenever a drawable changes virtual void updateDrawables() { } - // Called after creation, to initialise parameter listeners - virtual void update() { } - virtual void tabChanged() { } void render(NVGcontext* nvg) override; @@ -197,6 +192,8 @@ class ObjectBase : public Component void receiveMessage(t_symbol* symbol, SmallArray const& atoms) override; static ObjectBase* createGui(pd::WeakReference ptr, Object* parent); + + void updateProperties(); // Override this to return parameters that will be shown in the inspector virtual ObjectParameters getParameters(); @@ -206,7 +203,7 @@ class ObjectBase : public Component virtual void updateSizeProperty() { } virtual void updateLabel() { } - + // Implement this if you want to allow toggling an object by dragging over it in run mode virtual void toggleObject(Point position) { } virtual void untoggleObject() { } @@ -214,7 +211,7 @@ class ObjectBase : public Component virtual ObjectLabel* getLabel(int idx = 0); virtual String getText(); - + virtual bool checkHvccCompatibility(); virtual bool canEdgeOverrideAspectRatio() { return false; } @@ -224,10 +221,19 @@ class ObjectBase : public Component // Gets the scale factor we need to use of we want to draw images inside the component float getImageScale(); - + ComponentBoundsConstrainer* getConstrainer() const; - - ObjectParameters objectParameters; + + enum ResizeDirection + { + None, + HorizontalOnly, + VerticalOnly, + DiagonalOnly, + Any + }; + + virtual ResizeDirection getAllowedResizeDirections() const; protected: // Set parameter without triggering valueChanged @@ -243,9 +249,14 @@ class ObjectBase : public Component String getBinbufSymbol(int argIndex) const; virtual void propertyChanged(Value& v) { } + + virtual void update() { } // Send a float value to Pd void sendFloatValue(float value); + + static bool recurseHvccCompatibility(String const& objectText, pd::Patch::Ptr patch, String const& prefix = ""); + // Used by various ELSE objects, though sometimes with char*, sometimes with unsigned char* template @@ -263,7 +274,8 @@ class ObjectBase : public Component PluginProcessor* pd; OwnedArray labels; - + ObjectParameters objectParameters; + protected: String type; PropertyListener propertyListener; diff --git a/Source/Objects/ObjectImplementations.h b/Source/Objects/ObjectImplementations.h index 947ea3f92c..eeb41ea0d6 100644 --- a/Source/Objects/ObjectImplementations.h +++ b/Source/Objects/ObjectImplementations.h @@ -30,7 +30,7 @@ class KeyObject final : public ImplementationBase KeyObjectType type; Component::SafePointer attachedEditor = nullptr; - KeyObject(t_gobj* ptr, t_canvas* parent, PluginProcessor* pd, KeyObjectType const keyObjectType) + KeyObject(t_gobj* ptr, t_canvas const* parent, PluginProcessor* pd, KeyObjectType const keyObjectType) : ImplementationBase(ptr, parent, pd) , type(keyObjectType) { @@ -292,7 +292,7 @@ class CanvasMouseObject final : public ImplementationBase Component::SafePointer parentCanvas; public: - CanvasMouseObject(t_gobj* ptr, t_canvas* parent, PluginProcessor* pd) + CanvasMouseObject(t_gobj* ptr, t_canvas const* parent, PluginProcessor* pd) : ImplementationBase(ptr, parent, pd) { pd->registerMessageListener(this->ptr.getRawUnchecked(), this); @@ -558,7 +558,7 @@ class MouseObject final : public ImplementationBase , public Timer { public: - MouseObject(t_gobj* ptr, t_canvas* parent, PluginProcessor* pd) + MouseObject(t_gobj* ptr, t_canvas const* parent, PluginProcessor* pd) : ImplementationBase(ptr, parent, pd) , mouseSource(Desktop::getInstance().getMainMouseSource()) { @@ -627,17 +627,17 @@ class MouseStateObject final : public ImplementationBase GlobalMouseListener mouseListener; public: - MouseStateObject(t_gobj* object, t_canvas* parent, PluginProcessor* pd) + MouseStateObject(t_gobj* object, t_canvas const* parent, PluginProcessor* pd) : ImplementationBase(object, parent, pd) { pd->registerMessageListener(ptr.getRawUnchecked(), this); - mouseListener.globalMouseDown = [this](MouseEvent const& e) { + mouseListener.globalMouseDown = [this](MouseEvent const&) { if (auto obj = this->ptr.get()) { outlet_float(obj->ob_outlet, 1.0f); } }; - mouseListener.globalMouseUp = [this](MouseEvent const& e) { + mouseListener.globalMouseUp = [this](MouseEvent const&) { if (auto obj = this->ptr.get()) { outlet_float(obj->ob_outlet, 0.0f); } @@ -676,7 +676,7 @@ class KeycodeObject final : public ImplementationBase std::unique_ptr keyboard; Component::SafePointer attachedEditor = nullptr; - KeycodeObject(t_gobj* ptr, t_canvas* parent, PluginProcessor* pd) + KeycodeObject(t_gobj* ptr, t_canvas const* parent, PluginProcessor* pd) : ImplementationBase(ptr, parent, pd) { } @@ -730,7 +730,7 @@ class MouseFilterObject final : public ImplementationBase { } - MouseFilterProxy(pd::Instance* instance) + explicit MouseFilterProxy(pd::Instance* instance) : pd(instance) { } @@ -754,17 +754,17 @@ class MouseFilterObject final : public ImplementationBase static inline UnorderedMap proxy; public: - MouseFilterObject(t_gobj* object, t_canvas* parent, PluginProcessor* pd) + MouseFilterObject(t_gobj* object, t_canvas const* parent, PluginProcessor* pd) : ImplementationBase(object, parent, pd) { if (!proxy.count(pd)) { proxy[pd] = MouseFilterProxy(pd); - globalMouseDown = [pd](MouseEvent const& e) { + globalMouseDown = [pd](MouseEvent const&) { proxy[pd].setState(true); }; - globalMouseUp = [pd](MouseEvent const& e) { + globalMouseUp = [pd](MouseEvent const&) { proxy[pd].setState(false); }; } diff --git a/Source/Objects/ObjectParameters.h b/Source/Objects/ObjectParameters.h index 155d04fd74..5dce199061 100644 --- a/Source/Objects/ObjectParameters.h +++ b/Source/Objects/ObjectParameters.h @@ -39,7 +39,7 @@ struct ObjectParameter { bool clip; double min, max; - ObjectParameter(String const& name, ParameterType const type, ParameterCategory const category, Value* valuePtr, StringArray const& options, var defaultValue, CustomPanelCreateFn createFn = nullptr, InteractionFn interactionFn = nullptr, bool clip = false, double min = 0.0, double max = 0.0) + ObjectParameter(String const& name, ParameterType const type, ParameterCategory const category, Value* valuePtr, StringArray const& options, var defaultValue, CustomPanelCreateFn createFn = nullptr, InteractionFn interactionFn = nullptr, bool const clip = false, double const min = 0.0, double const max = 0.0) : name(name) , type(type) , category(category) @@ -87,12 +87,12 @@ class ObjectParameters { // ========= overloads for making different types of parameters ========= - void addParamFloat(String const& pString, ParameterCategory const pCat, Value* pVal, var const& pDefault = var(), bool clip = false, double min = 0.0, double max = 0.0) + void addParamFloat(String const& pString, ParameterCategory const pCat, Value* pVal, var const& pDefault = var(), bool const clip = false, double const min = 0.0, double const max = 1 << 30) { objectParameters.add(ObjectParameter(pString, tFloat, pCat, pVal, StringArray(), pDefault, nullptr, nullptr, clip, min, max)); } - void addParamInt(String const& pString, ParameterCategory const pCat, Value* pVal, var const& pDefault = var(), bool clip = false, int min = 0, int max = 1<<30, InteractionFn onInteractionFn = nullptr) + void addParamInt(String const& pString, ParameterCategory const pCat, Value* pVal, var const& pDefault = var(), bool const clip = false, int const min = 0, int const max = 1 << 30, InteractionFn const onInteractionFn = nullptr) { objectParameters.add(ObjectParameter(pString, tInt, pCat, pVal, StringArray(), pDefault, nullptr, onInteractionFn, clip, min, max)); } diff --git a/Source/Objects/OpenFileObject.h b/Source/Objects/OpenFileObject.h index e177b4d957..595a6362ec 100644 --- a/Source/Objects/OpenFileObject.h +++ b/Source/Objects/OpenFileObject.h @@ -19,6 +19,11 @@ class OpenFileObject final : public TextBase { : TextBase(ptr, object) { } + + bool hideInlet() override + { + return true; + } void showEditor() override { @@ -30,6 +35,8 @@ class OpenFileObject final : public TextBase { editor->setLookAndFeel(&object->getLookAndFeel()); editor->setBorder(border); editor->setBounds(getLocalBounds().withWidth(textWidth)); + editor->getProperties().set("NoBackground", true); + editor->getProperties().set("NoOutline", true); object->setSize(textWidth + Object::doubleMargin, getHeight() + Object::doubleMargin); setSize(textWidth, getHeight()); @@ -89,35 +96,41 @@ class OpenFileObject final : public TextBase { { auto const objText = getLinkText(); auto const mouseIsOver = isMouseOver(); - - int const textWidth = getTextObjectWidth() - 14; // Reserve a bit of extra space for the text margin - auto const currentLayoutHash = hash(objText); - auto const colour = cnv->editor->getLookAndFeel().findColour(PlugDataColour::canvasTextColourId); - - if (layoutTextHash != currentLayoutHash || colour.getARGB() != lastColourARGB || textWidth != lastTextWidth || mouseIsOver != mouseWasOver) { - bool const locked = getValue(object->locked) || getValue(object->commandLocked); - auto const colour = cnv->editor->getLookAndFeel().findColour(locked && mouseIsOver ? PlugDataColour::objectSelectedOutlineColourId : PlugDataColour::canvasTextColourId); - - auto attributedText = AttributedString(objText); - attributedText.setColour(colour); - attributedText.setJustification(Justification::centredLeft); - attributedText.setFont(Font(15)); - attributedText.setColour(colour); - - textLayout = TextLayout(); - textLayout.createLayout(attributedText, textWidth); - layoutTextHash = currentLayoutHash; - lastColourARGB = colour.getARGB(); - lastTextWidth = textWidth; + bool const locked = getValue(object->locked) || getValue(object->commandLocked); + auto colour = cnv->editor->getLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId); + if(locked && mouseIsOver) + colour = colour.withRotatedHue(0.5f); + + int const textWidth = getTextSize().getWidth() - 11; + if (cachedTextRender.prepareLayout(objText, Fonts::getCurrentFont().withHeight(15), colour, textWidth, getValue(sizeProperty), static_cast(cnv->getLookAndFeel()).getUseSyntaxHighlighting() && isValid)) { + repaint(); } } String getLinkText() const { auto tokens = StringArray::fromTokens(editor ? editor->getText() : objectText, true); - tokens.removeRange(0, tokens.indexOf("-h") + 2); + tokens.removeRange(0, tokens.indexOf("-h") + 1); + if(tokens.size() > 1) + tokens.removeRange(0, 1); + return tokens.joinIntoString(" "); } + + void render(NVGcontext* nvg) override + { + updateTextLayout(); + + auto const b = getLocalBounds(); + + nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), convertColour(backgroundColour), nvgRGBA(0, 0, 0, 0), Corners::objectCornerRadius); + + if (editor && editor->isVisible()) { + imageRenderer.renderJUCEComponent(nvg, *editor, getImageScale()); + } else { + cachedTextRender.renderText(nvg, border.subtractedFrom(b).toFloat(), getImageScale()); + } + } Rectangle getPdBounds() override { @@ -165,29 +178,6 @@ class OpenFileObject final : public TextBase { return std::make_unique(object); } - void paint(Graphics& g) override - { - updateTextLayout(); - - auto const backgroundColour = cnv->editor->getLookAndFeel().findColour(PlugDataColour::textObjectBackgroundColourId); - - g.setColour(backgroundColour); - g.fillRoundedRectangle(getLocalBounds().toFloat().reduced(0.5f), Corners::objectCornerRadius); - - auto const ioletAreaColour = cnv->editor->getLookAndFeel().findColour(PlugDataColour::ioletAreaColourId); - - if (ioletAreaColour != backgroundColour) { - g.setColour(ioletAreaColour); - g.fillRect(getLocalBounds().removeFromTop(3)); - g.fillRect(getLocalBounds().removeFromBottom(3)); - } - - if (!editor) { - auto const textArea = border.subtractedFrom(getLocalBounds()); - textLayout.draw(g, textArea.toFloat()); - } - } - void mouseEnter(MouseEvent const& e) override { updateTextLayout(); diff --git a/Source/Objects/PdTildeObject.h b/Source/Objects/PdTildeObject.h index 4a9c365017..2088e9ad93 100644 --- a/Source/Objects/PdTildeObject.h +++ b/Source/Objects/PdTildeObject.h @@ -7,7 +7,7 @@ class PdTildeObject final : public TextBase { public: - static inline File pdLocation = File(); + static inline auto pdLocation = File(); PdTildeObject(pd::WeakReference ptr, Object* object) : TextBase(ptr, object) diff --git a/Source/Objects/PictureObject.h b/Source/Objects/PictureObject.h index b15071db85..2f64c5842a 100644 --- a/Source/Objects/PictureObject.h +++ b/Source/Objects/PictureObject.h @@ -5,7 +5,7 @@ */ #pragma once -class PictureObject final : public ObjectBase { +class PictureObject final : public ObjectBase, public AsyncUpdater { Value filename = SynchronousValue(); Value latch = SynchronousValue(); @@ -25,13 +25,6 @@ class PictureObject final : public ObjectBase { PictureObject(pd::WeakReference ptr, Object* object) : ObjectBase(ptr, object) { - if (auto pic = this->ptr.get()) { - if (pic->x_filename) { - auto const filePath = String::fromUTF8(pic->x_filename->s_name); - openFile(filePath); - } - } - objectParameters.addParamSize(&sizeProperty); objectParameters.addParamString("File", cGeneral, &filename, ""); objectParameters.addParamBool("Latch", cGeneral, &latch, { "No", "Yes" }, 0); @@ -45,6 +38,11 @@ class PictureObject final : public ObjectBase { { } + void handleAsyncUpdate() override + { + openFile(filename.toString()); + } + bool isTransparent() override { return true; @@ -87,6 +85,7 @@ class PictureObject final : public ObjectBase { if (pic->x_filename) { filename = String::fromUTF8(pic->x_filename->s_name); + triggerAsyncUpdate(); } latch = pic->x_latch; @@ -98,7 +97,7 @@ class PictureObject final : public ObjectBase { sendSymbol = sndSym != "empty" ? sndSym : ""; receiveSymbol = rcvSym != "empty" ? rcvSym : ""; - + offsetX = pic->x_offset_x; offsetY = pic->x_offset_y; @@ -106,6 +105,7 @@ class PictureObject final : public ObjectBase { } repaint(); + object->updateIolets(); } void receiveObjectMessage(hash32 const symbol, SmallArray const& atoms) override @@ -131,22 +131,36 @@ class PictureObject final : public ObjectBase { } case hash("open"): { if (atoms.size() >= 1) - openFile(atoms[0].toString()); + filename = atoms[0].toString(); break; } + default: + break; } } + + bool hideInlet() override + { + auto const rSymbol = receiveSymbol.toString(); + return rSymbol.isNotEmpty() && rSymbol != "empty"; + } + + bool hideOutlet() override + { + auto const sSymbol = sendSymbol.toString(); + return sSymbol.isNotEmpty() && sSymbol != "empty"; + } void updateImage(NVGcontext* nvg) { if (!img.isValid() && File(imageFile).existsAsFile()) { img = ImageFileFormat::loadFrom(imageFile).convertedToFormat(Image::ARGB); } - + if (img.isValid()) { imageBuffer = NVGImage(nvg, img.getWidth(), img.getHeight(), [this](Graphics& g) { g.drawImageAt(img, 0, 0); - }); + }, NVGImage::MipMap); } img = Image(); // Clear image from CPU memory after upload @@ -156,6 +170,8 @@ class PictureObject final : public ObjectBase { void render(NVGcontext* nvg) override { + handleUpdateNowIfNeeded(); + if (imageNeedsReload || !imageBuffer.isValid()) updateImage(nvg); @@ -201,7 +217,7 @@ class PictureObject final : public ObjectBase { object->updateBounds(); } else if (value.refersToSameSourceAs(filename)) { - openFile(filename.toString()); + triggerAsyncUpdate(); } else if (value.refersToSameSourceAs(latch)) { if (auto pic = ptr.get()) pic->x_latch = getValue(latch); @@ -212,17 +228,21 @@ class PictureObject final : public ObjectBase { if (auto pic = ptr.get()) pic->x_size = getValue(reportSize); } else if (value.refersToSameSourceAs(sendSymbol)) { - auto const symbol = sendSymbol.toString(); + auto symbol = sendSymbol.toString(); + if(symbol.isEmpty()) symbol = "empty"; if (auto pic = ptr.get()) pd->sendDirectMessage(pic.get(), "send", { pd->generateSymbol(symbol) }); + object->updateIolets(); } else if (value.refersToSameSourceAs(receiveSymbol)) { - auto const symbol = receiveSymbol.toString(); + auto symbol = receiveSymbol.toString(); + if(symbol.isEmpty()) symbol = "empty"; if (auto pic = ptr.get()) pd->sendDirectMessage(pic.get(), "receive", { pd->generateSymbol(symbol) }); + object->updateIolets(); } } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { if (auto pic = ptr.get()) { auto* patch = cnv->patch.getRawPointer(); diff --git a/Source/Objects/PopMenu.h b/Source/Objects/PopMenu.h index 49accb6d34..092f952fb3 100644 --- a/Source/Objects/PopMenu.h +++ b/Source/Objects/PopMenu.h @@ -42,7 +42,7 @@ class PopMenu final : public ObjectBase { objectParameters.addParamString("Parameter", cGeneral, ¶meterName); objectParameters.addParamString("Variable", cGeneral, &variableName); objectParameters.addParamString("No selection label", cGeneral, &labelNoSelection); - objectParameters.addParamInt("Font size", cGeneral, &fontSize, 13, true, 0 ); + objectParameters.addParamInt("Font size", cGeneral, &fontSize, 13, true, 0); objectParameters.addParamBool("Save state", cGeneral, &savestate, { "No", "Yes" }); objectParameters.addParamBool("Loadbang", cGeneral, &loadbang, { "No", "Yes" }); @@ -67,7 +67,7 @@ class PopMenu final : public ObjectBase { void showMenu() { #if ENABLE_TESTING - return; + return; #endif auto menu = PopupMenu(); @@ -79,7 +79,7 @@ class PopMenu final : public ObjectBase { } menu.setLookAndFeel(&object->getLookAndFeel()); - menu.showMenuAsync(PopupMenu::Options().withTargetComponent(this), [_this = SafePointer(this)](int item) { + menu.showMenuAsync(PopupMenu::Options().withTargetComponent(this), [_this = SafePointer(this)](int const item) { if (item && _this) { _this->currentItem = item - 1; _this->currentText = _this->items[item - 1]; @@ -152,11 +152,11 @@ class PopMenu final : public ObjectBase { loadbang = menu->x_lb; fontSize = menu->x_fontsize; currentItem = menu->x_idx; - if(menu->x_idx >= 0 && menu->x_idx < items.size()) { + if (menu->x_idx >= 0 && menu->x_idx < items.size()) { currentText = items[currentItem]; } labelNoSelection = (menu->x_label == gensym("empty") || !menu->x_label) ? String("") : String::fromUTF8(menu->x_label->s_name); - + sendSymbol = getSendSymbol(); receiveSymbol = getReceiveSymbol(); @@ -227,13 +227,13 @@ class PopMenu final : public ObjectBase { auto b = getLocalBounds().toFloat(); nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), bgCol, object->isSelected() ? cnv->selectedOutlineCol : cnv->objectOutlineCol, Corners::objectCornerRadius); - + auto textBounds = getLocalBounds().reduced(2).translated(2, 0); - if(!textBounds.isEmpty()) { + if (!textBounds.isEmpty()) { textRenderer.renderText(nvg, textBounds.toFloat(), getImageScale()); } - - auto triangleBounds = b.removeFromRight(20).withSizeKeepingCentre(20, std::min(getHeight(), 12)); + + auto const triangleBounds = b.removeFromRight(20).withSizeKeepingCentre(20, std::min(getHeight(), 12)); nvgStrokeColor(nvg, fgCol); nvgBeginPath(nvg); @@ -309,8 +309,7 @@ class PopMenu final : public ObjectBase { menu->x_label = pd->generateSymbol(getValue(labelNoSelection)); updateTextLayout(); repaint(); - } - else if (value.refersToSameSourceAs(fontSize)) { + } else if (value.refersToSameSourceAs(fontSize)) { if (auto menu = ptr.get()) menu->x_fontsize = getValue(fontSize); updateTextLayout(); @@ -324,15 +323,14 @@ class PopMenu final : public ObjectBase { case hash("set"): { if (atoms.size() >= 1 && atoms[0].isFloat()) { currentItem = std::clamp(static_cast(atoms[0].getFloat()), -1, items.size() - 1); - if(currentItem >= 0) { + if (currentItem >= 0) { currentText = items[currentItem]; } updateTextLayout(); } break; } - case hash("clear"): - { + case hash("clear"): { items.clear(); currentText = ""; updateTextLayout(); @@ -343,8 +341,8 @@ class PopMenu final : public ObjectBase { if (auto menu = ptr.get()) { for (int i = 0; i < menu->x_n_items; i++) // Loop for menu items items.add(String::fromUTF8(menu->x_items[i]->s_name)); - - if(menu->x_idx >= 0 && menu->x_idx < items.size()) { + + if (menu->x_idx >= 0 && menu->x_idx < items.size()) { currentText = items[currentItem]; } } @@ -393,13 +391,13 @@ class PopMenu final : public ObjectBase { } } - bool inletIsSymbol() override + bool hideInlet() override { auto const rSymbol = receiveSymbol.toString(); return rSymbol.isNotEmpty() && rSymbol != "empty"; } - bool outletIsSymbol() override + bool hideOutlet() override { auto const sSymbol = sendSymbol.toString(); return sSymbol.isNotEmpty() && sSymbol != "empty"; diff --git a/Source/Objects/RadioObject.h b/Source/Objects/RadioObject.h index a0ec7a01ad..9ea12d3b16 100644 --- a/Source/Objects/RadioObject.h +++ b/Source/Objects/RadioObject.h @@ -7,13 +7,13 @@ class RadioObject final : public ObjectBase { int numItems = 0; - int selected; + int selected = 0; int hoverIdx = -1; - - bool mouseHover:1 = false; - bool alreadyToggled:1 = false; - bool isVertical:1; - + + bool mouseHover : 1 = false; + bool alreadyToggled : 1 = false; + bool isVertical : 1; + IEMHelper iemHelper; Value max = SynchronousValue(0.0f); @@ -54,12 +54,12 @@ class RadioObject final : public ObjectBase { updateAspectRatio(); } - bool inletIsSymbol() override + bool hideInlet() override { return iemHelper.hasReceiveSymbol(); } - bool outletIsSymbol() override + bool hideOutlet() override { return iemHelper.hasSendSymbol(); } @@ -94,7 +94,7 @@ class RadioObject final : public ObjectBase { return {}; } - void toggleObject(Point position) override + void toggleObject(Point const position) override { if (alreadyToggled) { alreadyToggled = false; @@ -243,19 +243,15 @@ class RadioObject final : public ObjectBase { void updateAspectRatio() { - auto const b = getPdBounds(); auto const minLongSide = object->minimumSize * numItems; constexpr auto minShortSide = Object::minimumSize; if (isVertical) { - float const verticalLength = b.getWidth() * numItems + Object::doubleMargin; - object->setSize(b.getWidth() + Object::doubleMargin, verticalLength); constrainer->setMinimumSize(minShortSide, minLongSide); } else { - float const horizontalLength = b.getHeight() * numItems + Object::doubleMargin; - object->setSize(horizontalLength, b.getHeight() + Object::doubleMargin); constrainer->setMinimumSize(minLongSide, minShortSide); } constrainer->setFixedAspectRatio(isVertical ? 1.0f / numItems : static_cast(numItems) / 1.0f); + object->updateBounds(); } void propertyChanged(Value& value) override @@ -321,90 +317,82 @@ class RadioObject final : public ObjectBase { setParameterExcludingListener(sizeProperty, isVertical ? var(radio->x_gui.x_w) : var(radio->x_gui.x_h)); } } - + std::unique_ptr createConstrainer() override { // Custom constrainer because a regular ComponentBoundsConstrainer will mess up the aspect ratio class RadioObjectBoundsConstrainer : public ComponentBoundsConstrainer { public: RadioObjectBoundsConstrainer() = default; - + void checkBounds(Rectangle& bounds, - Rectangle const& old, - Rectangle const& limits, - bool isStretchingTop, - bool isStretchingLeft, - bool isStretchingBottom, - bool isStretchingRight) override + Rectangle const& old, + Rectangle const& limits, + bool const isStretchingTop, + bool const isStretchingLeft, + bool const isStretchingBottom, + bool const isStretchingRight) override { if (isStretchingLeft) - bounds.setLeft (jlimit (old.getRight() - getMaximumWidth(), old.getRight() - getMinimumWidth(), bounds.getX())); + bounds.setLeft(jlimit(old.getRight() - getMaximumWidth(), old.getRight() - getMinimumWidth(), bounds.getX())); else - bounds.setWidth (jlimit (getMinimumWidth(), getMaximumWidth(), bounds.getWidth())); + bounds.setWidth(jlimit(getMinimumWidth(), getMaximumWidth(), bounds.getWidth())); if (isStretchingTop) - bounds.setTop (jlimit (old.getBottom() - getMaximumHeight(), old.getBottom() - getMinimumHeight(), bounds.getY())); + bounds.setTop(jlimit(old.getBottom() - getMaximumHeight(), old.getBottom() - getMinimumHeight(), bounds.getY())); else - bounds.setHeight (jlimit (getMinimumHeight(), getMaximumHeight(), bounds.getHeight())); + bounds.setHeight(jlimit(getMinimumHeight(), getMaximumHeight(), bounds.getHeight())); if (bounds.isEmpty()) return; - - const float aspect = getFixedAspectRatio(); - const int margin = Object::margin; + + float const aspect = getFixedAspectRatio(); + constexpr int margin = Object::margin; auto content = bounds.toFloat().reduced(margin + 0.5f); bool adjustWidth; - if ((isStretchingTop || isStretchingBottom) && ! (isStretchingLeft || isStretchingRight)) - { + if ((isStretchingTop || isStretchingBottom) && !(isStretchingLeft || isStretchingRight)) { adjustWidth = true; - } - else if ((isStretchingLeft || isStretchingRight) && ! (isStretchingTop || isStretchingBottom)) - { + } else if ((isStretchingLeft || isStretchingRight) && !(isStretchingTop || isStretchingBottom)) { adjustWidth = false; - } - else - { - const double oldRatio = (old.getHeight() > 0) ? std::abs (old.getWidth() / (double) old.getHeight()) : 0.0; - const double newRatio = std::abs (bounds.getWidth() / (double) bounds.getHeight()); + } else { + double const oldRatio = (old.getHeight() > 0) ? std::abs(old.getWidth() / static_cast(old.getHeight())) : 0.0; + double const newRatio = std::abs(bounds.getWidth() / static_cast(bounds.getHeight())); - adjustWidth = (oldRatio > newRatio); + adjustWidth = oldRatio > newRatio; } - if (adjustWidth) - { - content.setWidth (roundToInt (content.getHeight() * aspect)); - content.setHeight (roundToInt (content.getWidth() / aspect)); - } - else - { - content.setHeight (roundToInt (content.getWidth() / aspect)); - content.setWidth (roundToInt (content.getHeight() * aspect)); + if (adjustWidth) { + content.setWidth(roundToInt(content.getHeight() * aspect)); + content.setHeight(roundToInt(content.getWidth() / aspect)); + } else { + content.setHeight(roundToInt(content.getWidth() / aspect)); + content.setWidth(roundToInt(content.getHeight() * aspect)); } bounds = content.expanded(margin + 0.5f).toNearestInt(); - - if ((isStretchingTop || isStretchingBottom) && ! (isStretchingLeft || isStretchingRight)) - { - bounds.setX (old.getX() + (old.getWidth() - bounds.getWidth()) / 2); - } - else if ((isStretchingLeft || isStretchingRight) && ! (isStretchingTop || isStretchingBottom)) - { - bounds.setY (old.getY() + (old.getHeight() - bounds.getHeight()) / 2); - } - else - { + + if ((isStretchingTop || isStretchingBottom) && !(isStretchingLeft || isStretchingRight)) { + bounds.setX(old.getX() + (old.getWidth() - bounds.getWidth()) / 2); + } else if ((isStretchingLeft || isStretchingRight) && !(isStretchingTop || isStretchingBottom)) { + bounds.setY(old.getY() + (old.getHeight() - bounds.getHeight()) / 2); + } else { if (isStretchingLeft) - bounds.setX (old.getRight() - bounds.getWidth()); + bounds.setX(old.getRight() - bounds.getWidth()); if (isStretchingTop) - bounds.setY (old.getBottom() - bounds.getHeight()); + bounds.setY(old.getBottom() - bounds.getHeight()); } } }; return std::make_unique(); } + + ResizeDirection getAllowedResizeDirections() const override + { + return DiagonalOnly; + } }; diff --git a/Source/Objects/ScalarObject.h b/Source/Objects/ScalarObject.h index 2d91915195..e62827145f 100644 --- a/Source/Objects/ScalarObject.h +++ b/Source/Objects/ScalarObject.h @@ -95,16 +95,15 @@ class DrawableTemplate : public pd::MessageListener /* getting and setting values via fielddescs -- note confusing names; the above are setting up the fielddesc itself. */ - static t_float fielddesc_getfloat(t_fake_fielddesc* f, t_template* templ, t_word* wp, int const loud) + static t_float fielddesc_getfloat(t_fake_fielddesc const* f, t_template* templ, t_word* wp, int const loud) { if (f->fd_type == A_FLOAT) { if (f->fd_var) return template_getfloat(templ, f->fd_un.fd_varsym, wp, loud); - else - return f->fd_un.fd_float; - } else { - return 0; + + return f->fd_un.fd_float; } + return 0; } static int rangecolor(int const n) /* 0 to 9 in 5 steps */ @@ -235,8 +234,7 @@ class DrawableCurve final : public DrawableTemplate void update() override { - if(auto s = scalar.get()) - { + if (auto s = scalar.get()) { if (!s->sc_template) return; @@ -262,7 +260,7 @@ class DrawableCurve final : public DrawableTemplate n = 100; t_float width = fielddesc_getfloat(&object->x_width, templ, data, 1); - + for (int i = 0; i < n; i++) { auto* f = object->x_vec + i * 2; @@ -277,19 +275,19 @@ class DrawableCurve final : public DrawableTemplate width = 1; if (glist->gl_isgraph) width *= glist_getzoom(glist); - + auto const strokeColour = numberToColour(fielddesc_getfloat(&object->x_outlinecolor, templ, data, 1)); - + setStrokeFill(strokeColour); setStrokeThickness(width); - + if (closed) { auto const fillColour = numberToColour(fielddesc_getfloat(&object->x_fillcolor, templ, data, 1)); setFill(fillColour); } else { setFill(Colours::transparentBlack); } - + Path toDraw; if (flags & BEZ) { for (int i = 0; i < n; i++) { @@ -364,7 +362,7 @@ class DrawableSymbol final : public DrawableTemplate void render(NVGcontext* nvg) override { - + auto const scale = canvas->isZooming ? canvas->editor->getRenderScale() * 2.0f : canvas->editor->getRenderScale() * std::max(1.0f, getValue(canvas->zoomScale)); auto const bounds = getBoundingBox().getBoundingBox().toNearestInt(); NVGScopedState scopedState(nvg); @@ -411,40 +409,40 @@ class DrawableSymbol final : public DrawableTemplate void update() override { - if(auto const s = scalar.getRaw()) { + if (auto const s = scalar.getRaw()) { if (!s->sc_template) return; - + if (!fielddesc_getfloat(&object->x_vis, templ, data, 0)) { setText(""); return; } - + int xloc = 0, yloc = 0; if (auto glist = canvas->patch.getPointer()) { xloc = xToPixels(baseX + fielddesc_getcoord(reinterpret_cast(&object->x_xloc), templ, data, 0)) + canvas->canvasOrigin.x; yloc = yToPixels(baseY + fielddesc_getcoord(reinterpret_cast(&object->x_yloc), templ, data, 0)) + canvas->canvasOrigin.y; } - + char buf[DRAWNUMBER_BUFSIZE]; int type, onset; t_symbol* arraytype; - + if (!template_find_field(templ, object->x_fieldname, &onset, &type, &arraytype) || type == DT_ARRAY) { type = -1; } - + if (type < 0) buf[0] = 0; else { - strncpy(buf, object->x_label->s_name, DRAWNUMBER_BUFSIZE-1); + strncpy(buf, object->x_label->s_name, DRAWNUMBER_BUFSIZE - 1); buf[DRAWNUMBER_BUFSIZE - 1] = 0; int const nchars = static_cast(strlen(buf)); if (type == DT_TEXT) { char* buf2; int size2; binbuf_gettext(((t_word*)((char*)data + onset))->w_binbuf, - &buf2, &size2); + &buf2, &size2); int const ncopy = size2 > DRAWNUMBER_BUFSIZE - 1 - nchars ? DRAWNUMBER_BUFSIZE - 1 - nchars : size2; memcpy(buf + nchars, buf2, ncopy); buf[nchars + ncopy] = 0; @@ -460,12 +458,12 @@ class DrawableSymbol final : public DrawableTemplate atom_string(&at, buf + nchars, DRAWNUMBER_BUFSIZE - nchars); } } - + auto const symbolColour = numberToColour(fielddesc_getfloat(&object->x_color, templ, data, 1)); setColour(symbolColour); auto const text = String::fromUTF8(buf); auto const font = getFont(); - + setBoundingBox(Parallelogram(Rectangle(xloc, yloc, font.getStringWidthFloat(text) + 4.0f, font.getHeight() + 4.0f))); if (auto glist = canvas->patch.getPointer()) { setFontHeight(sys_hostfontsize(glist_getfont(glist.get()), glist_getzoom(glist.get()))); @@ -614,30 +612,31 @@ class DrawablePlot final : public DrawableTemplate void update() override { Path toDraw; - if(auto const s = scalar.getRaw()) { - - if (!s->sc_template) return; - + if (auto const s = scalar.getRaw()) { + + if (!s->sc_template) + return; + auto* glist = canvas->patch.getPointer().get(); - + auto* x = reinterpret_cast(object); int elemsize, yonset, wonset, xonset, i; t_canvas* elemtemplatecanvas; t_template* elemtemplate; t_symbol* elemtemplatesym; t_float linewidth, xloc, xinc, yloc, style, - vis, scalarvis, edit; + vis, scalarvis, edit; double xsum; t_array* array; int nelem; char* elem; t_fake_fielddesc *xfielddesc, *yfielddesc, *wfielddesc; - + if (!fielddesc_getfloat(&x->x_vis, templ, data, 0)) { setPath(Path()); return; } - + /* even if the array is "invisible", if its visibility is set by an instance variable you have to explicitly erase it, because the flag could earlier have been on when we were getting @@ -646,35 +645,35 @@ class DrawablePlot final : public DrawableTemplate cause no action because the tag matches nobody. LATER we might want to optimize this somehow. Ditto the "vis()" routines for other drawing instructions. */ - + if (readOwnerTemplate(x, data, templ, - &elemtemplatesym, &array, &linewidth, &xloc, &xinc, &yloc, &style, - &vis, &scalarvis, &edit, &xfielddesc, &yfielddesc, &wfielddesc) + &elemtemplatesym, &array, &linewidth, &xloc, &xinc, &yloc, &style, + &vis, &scalarvis, &edit, &xfielddesc, &yfielddesc, &wfielddesc) || array_getfields(elemtemplatesym, &elemtemplatecanvas, - &elemtemplate, &elemsize, reinterpret_cast(xfielddesc), reinterpret_cast(yfielddesc), reinterpret_cast(wfielddesc), - &xonset, &yonset, &wonset)) + &elemtemplate, &elemsize, reinterpret_cast(xfielddesc), reinterpret_cast(yfielddesc), reinterpret_cast(wfielddesc), + &xonset, &yonset, &wonset)) return; - + nelem = array->a_n; elem = array->a_vec; - + if (glist->gl_isgraph) linewidth *= glist_getzoom(glist); - + setStrokeThickness(linewidth); - + if (static_cast(style) == PLOTSTYLE_POINTS) { t_float minyval = 1e20, maxyval = -1e20; int ndrawn = 0; Colour colour = numberToColour(fielddesc_getfloat(&x->x_outlinecolor, templ, data, 1)); - + setStrokeFill(Colours::transparentBlack); setFill(colour); - + for (xsum = baseX + xloc, i = 0; i < nelem; i++) { t_float yval, usexloc; int ixpix, inextx; - + if (xonset >= 0) { usexloc = baseX + xloc + *(t_float*)(elem + elemsize * i + xonset); ixpix = xToPixels(fielddesc_cvttocoord(reinterpret_cast(xfielddesc), usexloc)); @@ -685,7 +684,7 @@ class DrawablePlot final : public DrawableTemplate ixpix = xToPixels(fielddesc_cvttocoord(reinterpret_cast(xfielddesc), usexloc)); inextx = xToPixels(fielddesc_cvttocoord(reinterpret_cast(xfielddesc), xsum)); } - + if (yonset >= 0) yval = yloc + *(t_float*)(elem + elemsize * i + yonset); else @@ -696,9 +695,9 @@ class DrawablePlot final : public DrawableTemplate if (yval > maxyval) maxyval = yval; if (i == nelem - 1 || inextx != ixpix) { - + toDraw.addRectangle(ixpix, yToPixels(baseY + fielddesc_cvttocoord(reinterpret_cast(yfielddesc), minyval)), inextx, yToPixels(baseY + fielddesc_cvttocoord(reinterpret_cast(yfielddesc), maxyval)) + linewidth); - + ndrawn++; minyval = 1e20; maxyval = -1e20; @@ -709,20 +708,20 @@ class DrawablePlot final : public DrawableTemplate } else { t_float coordinates[1024 * 2]; Colour outline = numberToColour( - fielddesc_getfloat(&x->x_outlinecolor, templ, data, 1)); - + fielddesc_getfloat(&x->x_outlinecolor, templ, data, 1)); + setStrokeFill(outline); setFill(Colours::transparentBlack); - + int lastpixel = -1, ndrawn = 0; t_float yval = 0, wval = 0, xpix; int ixpix = 0; // draw the trace - + if (wonset >= 0) { // found "w" field which controls linewidth. The trace is // a filled polygon with 2n points. - + setFill(outline); for (i = 0, xsum = xloc; i < nelem; i++) { t_float usexloc; @@ -773,30 +772,30 @@ class DrawablePlot final : public DrawableTemplate if (ndrawn * 2 >= std::size(coordinates)) goto ouch; } - + // TK will complain if there aren't at least 3 points. // There should be at least two already. if (ndrawn < 4) { coordinates[ndrawn * 2 + 0] = ixpix + 10; coordinates[ndrawn * 2 + 1] = yToPixels(baseY + yloc + fielddesc_cvttocoord(reinterpret_cast(yfielddesc), yval) - fielddesc_cvttocoord(reinterpret_cast(wfielddesc), wval)); ndrawn++; - + coordinates[ndrawn * 2 + 0] = ixpix + 10; coordinates[ndrawn * 2 + 1] = yToPixels(baseY + yloc + fielddesc_cvttocoord(reinterpret_cast(yfielddesc), yval) + fielddesc_cvttocoord(reinterpret_cast(wfielddesc), wval)); ndrawn++; } - ouch: - + ouch: + if (style == PLOTSTYLE_BEZ) { float startX = coordinates[0] + canvas->canvasOrigin.x; float startY = coordinates[1] + canvas->canvasOrigin.y; - + toDraw.startNewSubPath(startX, startY); - + for (int i = 0; i < ndrawn; i++) { float x0 = coordinates[2 * i] + canvas->canvasOrigin.x; float y0 = coordinates[2 * i + 1] + canvas->canvasOrigin.y; - + float x1, y1; if (i == ndrawn - 1) { x1 = startX; @@ -805,14 +804,14 @@ class DrawablePlot final : public DrawableTemplate x1 = coordinates[2 * (i + 1)] + canvas->canvasOrigin.x; y1 = coordinates[2 * (i + 1) + 1] + canvas->canvasOrigin.y; } - + toDraw.quadraticTo(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2); - + if (i == ndrawn - 1) { toDraw.quadraticTo((x0 + x1) / 2, (y0 + y1) / 2, x1, y1); } } - + toDraw.closeSubPath(); toDraw = toDraw.createPathWithRoundedCorners(6.0f); } else { @@ -837,7 +836,7 @@ class DrawablePlot final : public DrawableTemplate else yval = 0; yval = std::clamp(yval, -1e20, 1e20); - + xpix = xToPixels(baseX + fielddesc_cvttocoord(reinterpret_cast(xfielddesc), usexloc)); ixpix = xpix + 0.5; if (xonset >= 0 || ixpix != lastpixel) { @@ -849,7 +848,7 @@ class DrawablePlot final : public DrawableTemplate if (ndrawn * 2 >= std::size(coordinates)) break; } - + // TK will complain if there aren't at least 2 points... // Don't know about JUCE though... if (ndrawn == 1) { @@ -857,7 +856,7 @@ class DrawablePlot final : public DrawableTemplate coordinates[3] = yToPixels(baseY + yloc + fielddesc_cvttocoord(reinterpret_cast(yfielddesc), yval)); ndrawn = 2; } - + if (ndrawn) { /* toDraw.startNewSubPath(coordinates[0] + canvas->canvasOrigin.x, coordinates[1] + canvas->canvasOrigin.y); @@ -867,13 +866,13 @@ class DrawablePlot final : public DrawableTemplate if (style == PLOTSTYLE_BEZ) { float startX = coordinates[0] + canvas->canvasOrigin.x; float startY = coordinates[1] + canvas->canvasOrigin.y; - + toDraw.startNewSubPath(startX, startY); - + for (int i = 0; i < ndrawn; i++) { float x0 = coordinates[2 * i] + canvas->canvasOrigin.x; float y0 = coordinates[2 * i + 1] + canvas->canvasOrigin.y; - + float x1, y1; if (i == ndrawn - 1) { x1 = x0; @@ -882,14 +881,14 @@ class DrawablePlot final : public DrawableTemplate x1 = coordinates[2 * (i + 1)] + canvas->canvasOrigin.x; y1 = coordinates[2 * (i + 1) + 1] + canvas->canvasOrigin.y; } - + toDraw.quadraticTo(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2); - + if (i == ndrawn - 1) { toDraw.quadraticTo((x0 + x1) / 2, (y0 + y1) / 2, x1, y1); } } - + toDraw = toDraw.createPathWithRoundedCorners(6.0f); } else { toDraw.startNewSubPath(coordinates[0] + canvas->canvasOrigin.x, coordinates[1] + canvas->canvasOrigin.y); @@ -910,7 +909,7 @@ class DrawablePlot final : public DrawableTemplate { auto* s = scalar.getRaw(); - if(!s || !s->sc_template) + if (!s || !s->sc_template) return; auto* x = reinterpret_cast(object); @@ -1032,7 +1031,7 @@ struct ScalarObject final : public ObjectBase { } else if (name == "plot") { auto* plot = new DrawablePlot(scalar.get(), y, data, templ, cnv, static_cast(baseX), static_cast(baseY)); cnv->addAndMakeVisible(templates.add(plot)); - cnv->drawables.add(dynamic_cast(plot)); + cnv->drawables.add(plot); } } @@ -1086,7 +1085,7 @@ struct ScalarObject final : public ObjectBase { updateDrawables(); } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { if (auto scalar = ptr.get()) { auto* patch = cnv->patch.getRawPointer(); diff --git a/Source/Objects/ScopeObject.h b/Source/Objects/ScopeObject.h index 1dc562c949..2d109212fb 100644 --- a/Source/Objects/ScopeObject.h +++ b/Source/Objects/ScopeObject.h @@ -74,6 +74,8 @@ class ScopeObject final : public ObjectBase VarArray const arr = { scope->x_min, scope->x_max }; signalRange = var(arr); } + + object->updateIolets(); } static Colour colourFromHexArray(unsigned char* hex) @@ -96,8 +98,14 @@ class ScopeObject final : public ObjectBase return {}; } + + bool hideInlet() override + { + auto const rSymbol = receiveSymbol.toString(); + return rSymbol.isNotEmpty() && rSymbol != "empty"; + } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { if (auto scope = ptr.get()) { auto* patch = cnv->patch.getRawPointer(); @@ -297,15 +305,10 @@ class ScopeObject final : public ObjectBase auto const symbol = receiveSymbol.toString(); if (auto scope = ptr.get()) pd->sendDirectMessage(scope.get(), "receive", { pd->generateSymbol(symbol) }); + object->updateIolets(); } } - bool inletIsSymbol() override - { - auto const rSymbol = receiveSymbol.toString(); - return rSymbol.isNotEmpty() && rSymbol != "empty"; - } - void receiveObjectMessage(hash32 const symbol, SmallArray const& atoms) override { switch (symbol) { diff --git a/Source/Objects/SliderObject.h b/Source/Objects/SliderObject.h index e5da1b1fbb..d0062c7916 100644 --- a/Source/Objects/SliderObject.h +++ b/Source/Objects/SliderObject.h @@ -22,7 +22,6 @@ class ReversibleSlider final : public Slider setColour(Slider::textBoxOutlineColourId, Colours::transparentBlack); setTextBoxStyle(Slider::NoTextBox, false, 0, 0); setScrollWheelEnabled(false); - getProperties().set("Style", "SliderObject"); setVelocityModeParameters(1.0f, 1, 0.0f, false); setRepaintsOnMouseActivity(false); } @@ -110,6 +109,9 @@ class ReversibleSlider final : public Slider void mouseDrag(MouseEvent const& e) override { + if (!e.mods.isLeftButtonDown()) + return; + auto const snaps = getSliderSnapsToMousePosition(); if (snaps && shiftIsDown) setSliderSnapsToMousePosition(false); // We disable this temporarily, otherwise it breaks high accuracy mode @@ -120,6 +122,9 @@ class ReversibleSlider final : public Slider void mouseUp(MouseEvent const& e) override { + if (!e.mods.isLeftButtonDown()) + return; + setMouseDragSensitivity(std::max(1, isVertical ? getHeight() : getWidth())); Slider::mouseUp(e); shiftIsDown = false; @@ -162,6 +167,8 @@ class ReversibleSlider final : public Slider nvgFillColor(nvg, convertColour(getLookAndFeel().findColour(Slider::trackColourId))); nvgFillRoundedRect(nvg, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), cornerSize); } + + std::unique_ptr createAccessibilityHandler() override { return nullptr; }; }; class SliderObject final : public ObjectBase { @@ -252,12 +259,12 @@ class SliderObject final : public ObjectBase { getLookAndFeel().setColour(Slider::trackColourId, Colour::fromString(iemHelper.primaryColour.toString())); } - bool inletIsSymbol() override + bool hideInlet() override { return iemHelper.hasReceiveSymbol(); } - bool outletIsSymbol() override + bool hideOutlet() override { return iemHelper.hasSendSymbol(); } @@ -275,7 +282,7 @@ class SliderObject final : public ObjectBase { return iemHelper.getPdBounds().expanded(2, 0).withTrimmedLeft(-1); } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { // Hsl/vsl lies to us in slider_getrect: the x/y coordintates it returns are 2 or 3 px offset from what text_xpix/text_ypix reports if (isVertical) { diff --git a/Source/Objects/SubpatchObject.h b/Source/Objects/SubpatchObject.h index 463a916d86..a2d1843e0e 100644 --- a/Source/Objects/SubpatchObject.h +++ b/Source/Objects/SubpatchObject.h @@ -5,8 +5,7 @@ */ #pragma once -class SubpatchObject final : public TextBase -{ +class SubpatchObject final : public TextBase { pd::Patch::Ptr subpatch; Value isGraphChild = SynchronousValue(var(false)); @@ -32,7 +31,7 @@ class SubpatchObject final : public TextBase ~SubpatchObject() override { - if(!getValue(isGraphChild)) { + if (!getValue(isGraphChild)) { closeOpenedSubpatchers(); } } @@ -47,8 +46,6 @@ class SubpatchObject final : public TextBase // Change from subpatch to graph if (auto canvas = ptr.get()) { isGraphChild = static_cast(canvas->gl_isgraph); - } else { - return; } } @@ -120,49 +117,9 @@ class SubpatchObject final : public TextBase { return cnv->isGraph; } - + bool checkHvccCompatibility() override { return recurseHvccCompatibility(objectText, subpatch.get()); } - - static bool recurseHvccCompatibility(String const& objectText, pd::Patch::Ptr patch, String const& prefix = "") - { - auto instance = patch->instance; - - if (objectText.startsWith("pd @hv_obj") || HeavyCompatibleObjects::isCompatible(objectText)) { - return true; - } - - bool compatible = true; - - for (auto object : patch->getObjects()) { - if (auto ptr = object.get()) { - String const type = pd::Interface::getObjectClassName(ptr.get()); - - if (type == "canvas" || type == "graph") { - pd::Patch::Ptr const subpatch = new pd::Patch(object, instance, false); - - if(subpatch->isSubpatch()) { - char* text = nullptr; - int size = 0; - pd::Interface::getObjectText(&ptr.cast()->gl_obj, &text, &size); - auto objName = String::fromUTF8(text, size); - - compatible = recurseHvccCompatibility(objName, subpatch, prefix + objName + " -> ") && compatible; - freebytes(text, static_cast(size) * sizeof(char)); - } - else if(!HeavyCompatibleObjects::isCompatible(type)) { - compatible = false; - instance->logWarning(String("Warning: object \"" + prefix + type + "\" is not supported in Compiled Mode")); - } - } else if (!HeavyCompatibleObjects::isCompatible(type)) { - compatible = false; - instance->logWarning(String("Warning: object \"" + prefix + type + "\" is not supported in Compiled Mode")); - } - } - } - - return compatible; - } }; diff --git a/Source/Objects/SymbolAtomObject.h b/Source/Objects/SymbolAtomObject.h index 2cd6e14f40..030d816389 100644 --- a/Source/Objects/SymbolAtomObject.h +++ b/Source/Objects/SymbolAtomObject.h @@ -36,7 +36,9 @@ class SymbolAtomObject final : public ObjectBase input.onTextChange = [this] { startEdition(); - setSymbol(input.getText(true).toStdString()); + auto inputText = input.getText(true); + if (getText() != inputText) + setSymbol(inputText); stopEdition(); }; @@ -106,7 +108,7 @@ class SymbolAtomObject final : public ObjectBase return atomHelper.getPdBounds(input.getFont().getStringWidth(input.getText(true))); } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { atomHelper.setPdBounds(b); } @@ -180,12 +182,12 @@ class SymbolAtomObject final : public ObjectBase Corners::objectCornerRadius, ObjectFlagType::FlagTop, static_cast(cnv->getLookAndFeel()).getUseFlagOutline()); } - bool inletIsSymbol() override + bool hideInlet() override { return atomHelper.hasReceiveSymbol(); } - bool outletIsSymbol() override + bool hideOutlet() override { return atomHelper.hasSendSymbol(); } @@ -220,7 +222,7 @@ class SymbolAtomObject final : public ObjectBase } } } else if (key.getKeyCode() == KeyPress::returnKey) { - setSymbol(input.getText(true).toStdString()); + setSymbol(input.getText(true)); cnv->grabKeyboardFocus(); return true; } diff --git a/Source/Objects/TclColours.h b/Source/Objects/TclColours.h index f73bdd4496..bf825e0ed6 100644 --- a/Source/Objects/TclColours.h +++ b/Source/Objects/TclColours.h @@ -6,7 +6,7 @@ #pragma once -UnorderedMap tclColours { +inline UnorderedMap tclColours { { "alice blue", Colour(240, 248, 255) }, { "AliceBlue", Colour(240, 248, 255) }, { "antique white", Colour(250, 235, 215) }, diff --git a/Source/Objects/TextObject.h b/Source/Objects/TextObject.h index f2e3fa0bba..01f0cc5980 100644 --- a/Source/Objects/TextObject.h +++ b/Source/Objects/TextObject.h @@ -55,7 +55,7 @@ struct TextObjectHelper { auto const minimumWidth = std::max(1, maxIolets * 18 / fontWidth); TextObjectHelper::setWidthInChars(object->getPointer(), std::max(minimumWidth, newBounds.getWidth() / fontWidth)); } - + bounds = object->gui->getPdBounds().expanded(Object::margin) + object->cnv->canvasOrigin; // If we're resizing the left edge, move the object left @@ -96,7 +96,7 @@ struct TextObjectHelper { return text; } - static TextEditor* createTextEditor(Object* object, int const fontHeight) + static TextEditor* createTextEditor(Object const* object, int const fontHeight) { auto* editor = new TextEditor; editor->applyFontToAllText(Font(fontHeight)); @@ -291,7 +291,7 @@ class TextBase : public ObjectBase } else { // If width was set manually, calculate what the width is // We want to adjust the width so ideal text with aligns with fontWidth int const offset = (idealWidth - 5) % fontWidth; - textWidth = (charWidth * fontWidth + offset) + 5; + textWidth = charWidth * fontWidth + offset + 5; } auto const maxIolets = std::max(object->numInputs, object->numOutputs); @@ -499,6 +499,11 @@ class TextBase : public ObjectBase { return TextObjectHelper::createConstrainer(object); } + + ResizeDirection getAllowedResizeDirections() const override + { + return HorizontalOnly; + } }; // Actual text object, marked final for optimisation diff --git a/Source/Objects/ToggleObject.h b/Source/Objects/ToggleObject.h index 4fb3a15bf2..8c6fd58ee6 100644 --- a/Source/Objects/ToggleObject.h +++ b/Source/Objects/ToggleObject.h @@ -23,20 +23,25 @@ class ToggleObject final : public ObjectBase { objectParameters.addParamFloat("Non-zero value", cGeneral, &nonZero, 1.0f); objectParameters.addParamSize(&sizeProperty, true); - iemHelper.addIemParameters(objectParameters, true, true, 17, 7); + iemHelper.addIemParameters(objectParameters, true, true, true, 0, -10); } void onConstrainerCreate() override { constrainer->setFixedAspectRatio(1); } + + ResizeDirection getAllowedResizeDirections() const override + { + return DiagonalOnly; + } - bool inletIsSymbol() override + bool hideInlet() override { return iemHelper.hasReceiveSymbol(); } - bool outletIsSymbol() override + bool hideOutlet() override { return iemHelper.hasSendSymbol(); } @@ -51,7 +56,7 @@ class ToggleObject final : public ObjectBase { return iemHelper.getPdBounds(); } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { iemHelper.setPdBounds(b); } @@ -114,7 +119,7 @@ class ToggleObject final : public ObjectBase { } } - void sendToggleValue(float const newValue) + void sendToggleValue(float const newValue) const { if (auto iem = ptr.get()) { t_atom atom; diff --git a/Source/Objects/VUMeterObject.h b/Source/Objects/VUMeterObject.h index be4262690a..54bcd10d84 100644 --- a/Source/Objects/VUMeterObject.h +++ b/Source/Objects/VUMeterObject.h @@ -29,7 +29,7 @@ class VUScale final : public ObjectLabel { repaint(); } - void updateScales(NVGcontext* nvg, float scale) + void updateScales(NVGcontext* nvg) { // We calculate the largest size the text will ever be (canvas zoom * UI scale * desktop scale) auto constexpr maxUIScale = 3 * 2 * 2; @@ -58,7 +58,7 @@ class VUScale final : public ObjectLabel { if (!isVisible()) return; - updateScales(nvg, scale); + updateScales(nvg); bool const decimScaleText = getHeight() < 90; @@ -88,12 +88,9 @@ class VUMeterObject final : public ObjectBase { objectParameters.addParamReceiveSymbol(&iemHelper.receiveSymbol); objectParameters.addParamBool("Show scale", ParameterCategory::cAppearance, &showScale, { "No", "Yes" }, 1); objectParameters.addParamColour("Background", ParameterCategory::cAppearance, &iemHelper.secondaryColour); - iemHelper.addIemParameters(objectParameters, false, false, -1); + iemHelper.addIemParameters(objectParameters, false, false, false, -1); updateLabel(); - if (auto vu = ptr.get()) - showScale = vu->x_scale; - propertyChanged(showScale); iemHelper.iemColourChangedCallback = [this] { bgCol = convertColour(Colour::fromString(iemHelper.secondaryColour.toString())); @@ -116,12 +113,12 @@ class VUMeterObject final : public ObjectBase { } } - bool inletIsSymbol() override + bool hideInlet() override { return iemHelper.hasReceiveSymbol(); } - bool outletIsSymbol() override + bool hideOutlet() override { return iemHelper.hasSendSymbol(); } @@ -191,8 +188,10 @@ class VUMeterObject final : public ObjectBase { { if (auto vu = ptr.get()) { sizeProperty = VarArray { var(vu->x_gui.x_w), var(vu->x_gui.x_h) }; + showScale = vu->x_scale; } + updateLabel(); iemHelper.update(); } @@ -201,7 +200,7 @@ class VUMeterObject final : public ObjectBase { return iemHelper.getPdBounds(); } - void setPdBounds(Rectangle b) override + void setPdBounds(Rectangle const b) override { iemHelper.setPdBounds(b); } diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index 89ca5487ce..8176b792f8 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -46,7 +46,7 @@ class ConsoleMessageHandler final : public Timer { { if (consoleMessages.size()) { auto& [lastObject, lastMessage, lastType, lastLength, numMessages] = consoleMessages.back(); - if (object == lastObject && message == lastMessage && type == lastType) { + if (object == lastObject && message == lastMessage && static_cast(type) == lastType) { numMessages++; } else { consoleMessages.emplace_back(object, message, type, CachedStringWidth<14>::calculateStringWidth(message) + 40, 1); @@ -91,12 +91,11 @@ class ConsoleMessageHandler final : public Timer { } }; - static int length = 0; - printConcatBuffer[length] = '\0'; + printConcatBuffer[messageLength] = '\0'; int len = static_cast(strlen(message)); - while (length + len >= 2048) { - int const d = 2048 - 1 - length; + while (messageLength + len >= 2048) { + int const d = 2048 - 1 - messageLength; strncat(printConcatBuffer.data(), message, d); // Send concatenated line to plugdata! @@ -104,26 +103,26 @@ class ConsoleMessageHandler final : public Timer { message += d; len -= d; - length = 0; + messageLength = 0; printConcatBuffer[0] = '\0'; } strncat(printConcatBuffer.data(), message, len); - length += len; + messageLength += len; - if (length > 0 && printConcatBuffer[length - 1] == '\n') { - printConcatBuffer[length - 1] = '\0'; + if (messageLength > 0 && printConcatBuffer[messageLength - 1] == '\n') { + printConcatBuffer[messageLength - 1] = '\0'; // Send concatenated line to plugdata! forwardMessage(SmallString(printConcatBuffer.data())); - length = 0; + messageLength = 0; } } - + std::deque> consoleMessages; std::deque> consoleHistory; - + private: void timerCallback() override { @@ -144,10 +143,11 @@ class ConsoleMessageHandler final : public Timer { instance->updateConsole(numReceived, newWarning); } } - - StackArray printConcatBuffer; + + StackArray printConcatBuffer = {}; moodycamel::ConcurrentQueue> pendingMessages = moodycamel::ConcurrentQueue>(512); + int messageLength = 0; }; struct Instance::dmessage { @@ -156,7 +156,7 @@ struct Instance::dmessage { : object(ref, instance) , destination(dest) , selector(sel) - , list(std::move(atoms)) + , list(atoms) { } @@ -183,7 +183,7 @@ struct Instance::internal { ptr->enqueueGuiMessage({ SmallString("symbol"), SmallString(recv), SmallArray(1, ptr->generateSymbol(sym)) }); } - static void instance_multi_list(pd::Instance* ptr, char const* recv, int const argc, t_atom* argv) + static void instance_multi_list(pd::Instance* ptr, char const* recv, int const argc, t_atom const* argv) { Message mess { SmallString("list"), SmallString(recv), SmallArray(argc) }; for (int i = 0; i < argc; ++i) { @@ -196,7 +196,7 @@ struct Instance::internal { ptr->enqueueGuiMessage(mess); } - static void instance_multi_message(pd::Instance* ptr, char const* recv, char const* msg, int const argc, t_atom* argv) + static void instance_multi_message(pd::Instance* ptr, char const* recv, char const* msg, int const argc, t_atom const* argv) { Message mess { msg, String::fromUTF8(recv), SmallArray(argc) }; for (int i = 0; i < argc; ++i) { @@ -243,7 +243,7 @@ struct Instance::internal { ptr->receiveMidiByte(port + 1, byte); } - static void instance_multi_print(pd::Instance* ptr, void* object, char const* s) + static void instance_multi_print(pd::Instance const* ptr, void* object, char const* s) { ptr->consoleMessageHandler->processPrint(object, s); } @@ -263,11 +263,11 @@ Instance::~Instance() // (inside a scope so that "item" also gets fully deleted before we delete this class) { std::function item; - while (functionQueue.try_dequeue(item)) {} + while (functionQueue.try_dequeue(item)) { } } - + objectImplementations.reset(nullptr); // Make sure it gets deallocated before pd instance gets deleted - + libpd_set_instance(static_cast(instance)); pd_free(static_cast(messageReceiver)); pd_free(static_cast(midiReceiver)); @@ -338,55 +338,50 @@ void Instance::initialisePd(String& pdlua_version) if (inst->initialiseIntoPluginmode) return; - auto* pd = static_cast(inst); t_canvas* glist = reinterpret_cast(argv->a_w.w_gpointer); - if (auto const vis = atom_getfloat(argv + 1)) { - + if (atom_getfloat(argv + 1)) { File patchFile; if (canvas_isabstraction(glist)) { patchFile = File(String::fromUTF8(canvas_getdir(glist)->s_name)).getChildFile(String::fromUTF8(glist->gl_name->s_name)).withFileExtension("pd"); } - - MessageManager::callAsync([pd, inst = juce::WeakReference(inst), patchToOpen = pd::WeakReference(glist, pd), patchFile] { - if(!inst) return; - PluginEditor* activeEditor = nullptr; - for (auto* editor : pd->getEditors()) { - if (editor->isActiveWindow()) - { - activeEditor = editor; - break; + + MessageManager::callAsync([inst = juce::WeakReference(inst), patchToOpen = pd::WeakReference(glist, inst), patchFile] { + if (auto* pd = static_cast(inst.get())) { + PluginEditor* activeEditor = nullptr; + for (auto* editor : pd->getEditors()) { + if (editor->isActiveWindow()) { + activeEditor = editor; + break; + } } - } - if (!activeEditor || !patchToOpen.isValid()) - return; - - for(auto& patch : pd->patches) - { - if (patch->getRawPointer() == patchToOpen.getRaw()) - { - activeEditor->getTabComponent().openPatch(patch); + if (!activeEditor || !patchToOpen.isValid()) return; + + for (auto const& patch : pd->patches) { + if (patch->getRawPointer() == patchToOpen.getRaw()) { + activeEditor->getTabComponent().openPatch(patch); + return; + } } - } - - pd::Patch::Ptr subpatch = new pd::Patch(patchToOpen, pd, false); - if(patchFile.exists()) - { - subpatch->setCurrentFile(URL(patchFile)); - } - activeEditor->getTabComponent().openPatch(subpatch); + pd::Patch::Ptr const subpatch = new pd::Patch(patchToOpen, pd, false); + if (patchFile.exists()) { + subpatch->setCurrentFile(URL(patchFile)); + } + activeEditor->getTabComponent().openPatch(subpatch); + } }); } else { - MessageManager::callAsync([pd, inst = juce::WeakReference(inst), glist] { - if(!inst) return; - for (auto* editor : pd->getEditors()) { - for (auto* canvas : editor->getCanvases()) { - auto canvasPtr = canvas->patch.getPointer(); - if (canvasPtr && canvasPtr.get() == glist) { - canvas->editor->getTabComponent().closeTab(canvas); - break; + MessageManager::callAsync([inst = juce::WeakReference(inst), glist] { + if (auto const* pd = static_cast(inst.get())) { + for (auto* editor : pd->getEditors()) { + for (auto* canvas : editor->getCanvases()) { + auto canvasPtr = canvas->patch.getPointer(); + if (canvasPtr && canvasPtr.get() == glist) { + canvas->editor->getTabComponent().closeTab(canvas); + break; + } } } } @@ -396,31 +391,37 @@ void Instance::initialisePd(String& pdlua_version) } case hash("canvas_undo_redo"): { auto* inst = static_cast(instance); - auto* pd = static_cast(inst); - auto* glist = reinterpret_cast(argv->a_w.w_gpointer); - auto* undoName = atom_getsymbol(argv + 1); - auto* redoName = atom_getsymbol(argv + 2); - MessageManager::callAsync([pd, glist, undoName, redoName] { - for (auto const& patch : pd->patches) { - if (patch->ptr.getRaw() == glist) { - patch->updateUndoRedoState(SmallString(undoName->s_name), SmallString(redoName->s_name)); + auto const* glist = reinterpret_cast(argv->a_w.w_gpointer); + auto const* undoName = atom_getsymbol(argv + 1); + auto const* redoName = atom_getsymbol(argv + 2); + MessageManager::callAsync([instance = juce::WeakReference(inst), glist, undoName, redoName] { + if (auto* pd = static_cast(instance.get())) { + for (auto const& patch : pd->patches) { + if (patch->ptr.getRaw() == glist) { + patch->updateUndoRedoState(SmallString(undoName->s_name), SmallString(redoName->s_name)); + } } + for (auto* editor : pd->getEditors()) + editor->triggerAsyncUpdate(); } }); break; } case hash("canvas_title"): { auto* inst = static_cast(instance); - auto* pd = static_cast(inst); - auto* glist = reinterpret_cast(argv->a_w.w_gpointer); - auto* title = atom_getsymbol(argv + 1); + auto const* glist = reinterpret_cast(argv->a_w.w_gpointer); + auto const* title = atom_getsymbol(argv + 1); int isDirty = atom_getfloat(argv + 2); - MessageManager::callAsync([pd, glist, title, isDirty] { - for (auto const& patch : pd->patches) { - if (patch->ptr.getRaw() == glist) { - patch->updateTitle(SmallString(title->s_name), isDirty); + MessageManager::callAsync([instance = juce::WeakReference(inst), glist, title, isDirty] { + if (auto* pd = static_cast(instance.get())) { + for (auto const& patch : pd->patches) { + if (patch->ptr.getRaw() == glist) { + patch->updateTitle(SmallString(title->s_name), isDirty); + } } + for (auto* editor : pd->getEditors()) + editor->triggerAsyncUpdate(); } }); break; @@ -464,17 +465,16 @@ void Instance::initialisePd(String& pdlua_version) } case hash("cyclone_editor"): { auto const ptr = reinterpret_cast(argv->a_w.w_gpointer); - auto* inst = static_cast(instance); SmallString title; if (argc > 5) { - SmallString owner = SmallString(atom_getsymbol(argv + 3)->s_name); + auto owner = SmallString(atom_getsymbol(argv + 3)->s_name); title = SmallString(atom_getsymbol(argv + 4)->s_name); } else { title = SmallString(atom_getsymbol(argv + 3)->s_name); } - - auto save = [title, inst](String text, uint64_t const ptr) { + + auto save = [title, inst = static_cast(instance)](String text, uint64_t const ptr) { inst->lockAudioThread(); pd_typedmess(reinterpret_cast(ptr), gensym("clear"), 0, nullptr); @@ -527,8 +527,8 @@ void Instance::initialisePd(String& pdlua_version) pd_typedmess(reinterpret_cast(ptr), inst->generateSymbol("end"), 0, nullptr); inst->unlockAudioThread(); }; - - static_cast(instance)->showTextEditorDialog(ptr, title, save, [](uint64_t){}); + + static_cast(instance)->showTextEditorDialog(ptr, title, save, [](uint64_t) { }); break; } case hash("cyclone_editor_append"): { @@ -558,8 +558,8 @@ void Instance::initialisePd(String& pdlua_version) case hash("pdtk_textwindow_open"): { auto const ptr = reinterpret_cast(argv->a_w.w_gpointer); auto* inst = static_cast(instance); - auto* title = static_cast(atom_getsymbol(argv + 1)); - + auto const* title = atom_getsymbol(argv + 1); + auto save = [inst](String text, uint64_t const ptr) { inst->lockAudioThread(); pd_typedmess(reinterpret_cast(ptr), gensym("clear"), 0, nullptr); @@ -613,14 +613,13 @@ void Instance::initialisePd(String& pdlua_version) pd_typedmess(reinterpret_cast(ptr), inst->generateSymbol("notify"), 0, nullptr); inst->unlockAudioThread(); }; - - auto close = [inst](uint64_t const ptr) - { + + auto close = [inst](uint64_t const ptr) { inst->lockAudioThread(); pd_typedmess(reinterpret_cast(ptr), inst->generateSymbol("close"), 0, nullptr); inst->unlockAudioThread(); }; - + static_cast(instance)->showTextEditorDialog(ptr, String::fromUTF8(title->s_name), save, close); break; } @@ -639,11 +638,11 @@ void Instance::initialisePd(String& pdlua_version) auto const argv_start = argv + 1; // Create a binbuf to store the atoms - t_binbuf *b = binbuf_new(); - binbuf_add(b, argc - 1, argv_start); // Add atoms to binbuf + t_binbuf* b = binbuf_new(); + binbuf_add(b, argc - 1, argv_start); // Add atoms to binbuf // Convert binbuf to a string - char *text = nullptr; + char* text = nullptr; int length = 0; binbuf_gettext(b, &text, &length); if (text) { @@ -652,7 +651,7 @@ void Instance::initialisePd(String& pdlua_version) editorText = editorText.replace("\n\n", "\n"); static_cast(instance)->addTextToTextEditor(ptr, editorText); } - + freebytes(text, length); binbuf_free(b); break; @@ -667,6 +666,8 @@ void Instance::initialisePd(String& pdlua_version) static_cast(instance)->hideTextEditorDialog(ptr); break; } + default: + break; } }; @@ -720,7 +721,7 @@ int Instance::getBlockSize() return libpd_blocksize(); } -void Instance::prepareDSP(int const nins, int const nouts, double const samplerate, int const blockSize) +void Instance::prepareDSP(int const nins, int const nouts, double const samplerate) { libpd_set_instance(static_cast(instance)); libpd_init_audio(nins, nouts, static_cast(samplerate)); @@ -1073,12 +1074,12 @@ void Instance::logWarning(String const& warning) consoleMessageHandler->logWarning(nullptr, warning); } -std::deque>& Instance::getConsoleMessages() +std::deque>& Instance::getConsoleMessages() const { return consoleMessageHandler->consoleMessages; } -std::deque>& Instance::getConsoleHistory() +std::deque>& Instance::getConsoleHistory() const { return consoleMessageHandler->consoleHistory; } @@ -1100,7 +1101,7 @@ void Instance::createPanel(int const type, char const* snd, char const* location if (type) { MessageManager::callAsync( - [this, obj, defaultFile, openMode, callback = SmallString(callbackName)]() mutable { + [this, obj = generateSymbol(snd)->s_thing, defaultFile, openMode, callback = SmallString(callbackName)]() mutable { FileBrowserComponent::FileChooserFlags folderChooserFlags; if (openMode <= 0) { @@ -1113,12 +1114,21 @@ void Instance::createPanel(int const type, char const* snd, char const* location static std::unique_ptr openChooser; openChooser = std::make_unique("Open...", defaultFile, "", SettingsFile::getInstance()->wantsNativeDialog()); + openChooser->launchAsync(folderChooserFlags, [this, obj, callback](FileChooser const& fileChooser) { auto const files = fileChooser.getResults(); if (files.isEmpty()) return; - auto parentDirectory = files.getFirst().getParentDirectory(); +#if JUCE_IOS + // Create input streams to access the security scoped resource + SmallArray> scopedAccessStreams; + for(auto& url : fileChooser.getURLResults()) + { + scopedAccessStreams.emplace_back(url.createInputStream(URL::InputStreamOptions(URL::ParameterHandling::inAddress))); + } +#endif + auto const parentDirectory = files.getFirst().getParentDirectory(); SettingsFile::getInstance()->setLastBrowserPathForId("openpanel", parentDirectory); lockAudioThread(); @@ -1179,12 +1189,13 @@ bool Instance::loadLibrary(String const& libraryToLoad) void Instance::lockAudioThread() { - audioLock.enter(); + setThis(); + sys_lock(); } void Instance::unlockAudioThread() { - audioLock.exit(); + sys_unlock(); } void Instance::updateObjectImplementations() @@ -1192,7 +1203,7 @@ void Instance::updateObjectImplementations() objectImplementations->updateObjectImplementations(); } -void Instance::clearObjectImplementationsForPatch(pd::Patch* p) +void Instance::clearObjectImplementationsForPatch(pd::Patch const* p) { if (auto patch = p->getPointer()) { objectImplementations->clearObjectImplementationsForPatch(patch.get()); diff --git a/Source/Pd/Instance.h b/Source/Pd/Instance.h index 161a90c736..ddd8c2997d 100644 --- a/Source/Pd/Instance.h +++ b/Source/Pd/Instance.h @@ -33,7 +33,7 @@ class Atom { { auto* binbuf = binbuf_new(); binbuf_text(binbuf, str.toRawUTF8(), str.getNumBytesAsUTF8()); - auto* argv = binbuf_getvec(binbuf); + auto const* argv = binbuf_getvec(binbuf); auto const argc = binbuf_getnatom(binbuf); auto atoms = fromAtoms(argc, argv); @@ -42,7 +42,7 @@ class Atom { return atoms; } - static SmallArray fromAtoms(int const ac, t_atom* av) + static SmallArray fromAtoms(int const ac, t_atom const* av) { auto array = SmallArray(); array.reserve(ac); @@ -73,7 +73,7 @@ class Atom { { } - Atom(t_atom* atom) + Atom(t_atom const* atom) { if (atom->a_type == A_FLOAT) { type = FLOAT; @@ -167,7 +167,7 @@ class Instance : public AsyncUpdater { ~Instance() override; void initialisePd(String& pdlua_version); - void prepareDSP(int nins, int nouts, double samplerate, int blockSize); + void prepareDSP(int nins, int nouts, double samplerate); void startDSP(); void releaseDSP(); void performDSP(float const* inputs, float* outputs); @@ -206,7 +206,7 @@ class Instance : public AsyncUpdater { virtual void hideTextEditorDialog(uint64_t ptr) = 0; virtual void raiseTextEditorDialog(uint64_t ptr) = 0; virtual void showTextEditorDialog(uint64_t ptr, SmallString const& title, std::function save, std::function close) = 0; - virtual void clearTextEditor(uint64_t const ptr) = 0; + virtual void clearTextEditor(uint64_t ptr) = 0; virtual bool isTextEditorDialogShown(uint64_t ptr) = 0; virtual void receiveSysMessage(SmallString const& selector, SmallArray const& list) = 0; @@ -219,7 +219,7 @@ class Instance : public AsyncUpdater { void clearWeakReferences(void* ptr); static void registerLuaClass(char const* object); - bool isLuaClass(hash32 objectNameHash); + static bool isLuaClass(hash32 objectNameHash); virtual void updateConsole(int numMessages, bool newWarning) = 0; @@ -247,11 +247,11 @@ class Instance : public AsyncUpdater { void sendDirectMessage(void* object, float msg); void updateObjectImplementations(); - void clearObjectImplementationsForPatch(pd::Patch* p); + void clearObjectImplementationsForPatch(pd::Patch const* p); virtual void handleParameterMessage(SmallArray const& atoms) = 0; virtual void performLatencyCompensationChange(float value) = 0; - + virtual void fillDataBuffer(SmallArray const& list) = 0; virtual void parseDataBuffer(XmlElement const& xml) = 0; @@ -259,8 +259,8 @@ class Instance : public AsyncUpdater { void logError(String const& message); void logWarning(String const& message); - std::deque>& getConsoleMessages(); - std::deque>& getConsoleHistory(); + std::deque>& getConsoleMessages() const; + std::deque>& getConsoleHistory() const; void sendMessagesFromQueue(); void processSend(dmessage const& mess); @@ -277,7 +277,7 @@ class Instance : public AsyncUpdater { void lockAudioThread(); void unlockAudioThread(); - bool loadLibrary(String const& library); + static bool loadLibrary(String const& library); void* instance = nullptr; void* messageReceiver = nullptr; @@ -287,7 +287,7 @@ class Instance : public AsyncUpdater { void* printReceiver = nullptr; void* dataBufferReceiver = nullptr; - inline static String const defaultPatch = "#N canvas 827 239 734 565 12;"; + static inline String const defaultPatch = "#N canvas 827 239 734 565 12;"; bool initialiseIntoPluginmode = false; bool isPerformingGlobalSync = false; @@ -305,7 +305,7 @@ class Instance : public AsyncUpdater { moodycamel::ConcurrentQueue guiMessageQueue = moodycamel::ConcurrentQueue(64); std::unique_ptr openChooser; - static inline UnorderedSet luaClasses = UnorderedSet(); // Keep track of class names that correspond to pdlua objects + static inline auto luaClasses = UnorderedSet(); // Keep track of class names that correspond to pdlua objects protected: struct internal; diff --git a/Source/Pd/Interface.h b/Source/Pd/Interface.h index 287189eae9..07ebfb8b71 100644 --- a/Source/Pd/Interface.h +++ b/Source/Pd/Interface.h @@ -39,7 +39,7 @@ struct Interface { return cnv; } - static char const* getObjectClassName(t_pd* ptr) + static char const* getObjectClassName(t_pd const* ptr) { return class_getname(pd_class(ptr)); } @@ -71,7 +71,7 @@ struct Interface { return reinterpret_cast<_instanceeditor*>(libpd_this_instance()->pd_gui->i_editor); } - static void getObjectText(t_object* ptr, char** text, int* size) + static void getObjectText(t_object const* ptr, char** text, int* size) { *text = nullptr; *size = 0; @@ -91,7 +91,7 @@ struct Interface { *h -= *y; } - static int isTextObject(t_gobj* obj) + static int isTextObject(t_gobj const* obj) { return obj->g_pd->c_wb == &text_widgetbehavior; } @@ -153,7 +153,7 @@ struct Interface { instanceEditor->canvas_undo_already_set_move = 0; } - static t_gobj* getNewest(t_canvas* cnv) + static t_gobj* getNewest(t_canvas const* cnv) { // Regular pd_newest won't work because it doesn't get assigned for some gui components t_gobj* y; @@ -264,7 +264,7 @@ struct Interface { return gensym(arraybuf); } - static void selectConnection(t_canvas* cnv, t_outconnect* connection) + static void selectConnection(t_canvas* cnv, t_outconnect const* connection) { auto* ed = cnv->gl_editor; t_linetraverser t; @@ -284,7 +284,7 @@ struct Interface { ed->e_selectedline = 0; } - static void connectSelection(t_canvas* cnv, SmallArray const& objects, t_outconnect* connection) + static void connectSelection(t_canvas* cnv, SmallArray const& objects, t_outconnect const* connection) { glist_noselect(cnv); @@ -316,10 +316,10 @@ struct Interface { glist_noselect(cnv); } - static void swapConnections(t_canvas* cnv, t_outconnect* clicked, t_outconnect* selected) + static void swapConnections(t_canvas* cnv, t_outconnect const* clicked, t_outconnect const* selected) { - int in1 = -1, in1_idx, in2 = -1, in2_idx; - int out1 = -1, out1_idx, out2 = -1, out2_idx; + int in1 = -1, in1_idx = 0, in2 = -1, in2_idx = 0; + int out1 = -1, out1_idx = 0, out2 = -1, out2_idx = 0; t_linetraverser t; linetraverser_start(&t, cnv); @@ -365,7 +365,7 @@ struct Interface { glist_noselect(cnv); } - static t_gobj* triggerize(t_canvas* cnv, SmallArray const& objects, t_outconnect* connection) + static t_gobj* triggerize(t_canvas* cnv, SmallArray const& objects, t_outconnect const* connection) { glist_noselect(cnv); @@ -437,7 +437,7 @@ struct Interface { arrangeObject(cnv, obj, 0); } - static void duplicateSelection(t_canvas* cnv, SmallArray const& objects, t_outconnect* connection) + static void duplicateSelection(t_canvas* cnv, SmallArray const& objects, t_outconnect const* connection) { glist_noselect(cnv); @@ -452,7 +452,7 @@ struct Interface { canvas_unsetcurrent(cnv); } - static void shiftAutopatch(t_canvas* cnv, t_gobj* inObj, int const inletIndex, t_gobj* outObj, int const outletIndex, SmallArray selectedObjects, t_outconnect* connection) + static void shiftAutopatch(t_canvas* cnv, t_gobj* inObj, int const inletIndex, t_gobj* outObj, int const outletIndex, SmallArray selectedObjects, t_outconnect const* connection) { auto getRawObjectBounds = [](t_canvas* cnv, t_gobj* obj) -> Rectangle { int x1, y1, x2, y2; @@ -539,7 +539,7 @@ struct Interface { // This is needed since object creation happens in 2 undo steps in pd-vanilla, but is only 1 undo step in plugdata int const pos = glist_getindex(cnv, new_object); canvas_undo_add(glist_getcanvas(cnv), UNDO_RECREATE, "recreate", - (void*)canvas_undo_set_recreate(cnv, + canvas_undo_set_recreate(cnv, new_object, pos)); } @@ -556,7 +556,7 @@ struct Interface { // Can recreate any object of type t_text static void recreateTextObject(t_canvas* cnv, t_gobj* obj) { - if (auto* textObject = checkObject(obj)) { + if (auto const* textObject = checkObject(obj)) { char* text = nullptr; int len = 0; @@ -580,7 +580,7 @@ struct Interface { t_text* x_text; t_glist* x_glist; char x_tag[50]; - struct _rtext* x_next; + _rtext* x_next; }; auto const wasEditMode = cnv->gl_edit; diff --git a/Source/Pd/Library.cpp b/Source/Pd/Library.cpp index 6db154a1c0..69eeb4212d 100644 --- a/Source/Pd/Library.cpp +++ b/Source/Pd/Library.cpp @@ -85,7 +85,7 @@ void Library::updateLibrary() continue; auto newName = String::fromUTF8(m->me_name->s_name); - if (!(newName.startsWith("else/") || newName.startsWith("cyclone/") || newName.endsWith("_aliased"))) { + if (!(newName.startsWith("else/") || newName.startsWith("cyclone/") || newName.endsWith("_aliased") || newName.endsWith(":gfx"))) { allObjects.add(newName); } } @@ -99,7 +99,7 @@ void Library::updateLibrary() continue; for (auto const& file : OSUtils::iterateDirectory(file, false, true)) { - if (file.hasFileExtension("pd")) { + if (file.hasFileExtension("pd") || file.hasFileExtension("pd_lua")) { auto filename = file.getFileNameWithoutExtension(); if (!filename.startsWith("help-") && !filename.endsWith("-help")) { allObjects.add(filename); @@ -124,9 +124,9 @@ void Library::run() { HeapArray decodedDocs; decodedDocs.reserve(2 * 1024 * 1024); - - Decompress::extractXz((uint8_t const*)BinaryData::Documentation_bin, BinaryData::Documentation_binSize, decodedDocs); - + + Decompress::extractXz(reinterpret_cast(BinaryData::Documentation_bin), BinaryData::Documentation_binSize, decodedDocs); + MemoryInputStream instream(decodedDocs.data(), decodedDocs.size(), false); ValueTree documentationTree = ValueTree::readFromStream(instream); @@ -218,7 +218,7 @@ StringArray Library::autocomplete(String const& query, File const& patchDirector if (patchDirectory.isDirectory()) { for (auto const& file : OSUtils::iterateDirectory(patchDirectory, false, true, 20)) { auto filename = file.getFileNameWithoutExtension(); - if (file.hasFileExtension("pd") && filename.startsWith(query) && !filename.startsWith("help-") && !filename.endsWith("-help")) { + if ((file.hasFileExtension("pd") || file.hasFileExtension("pd_lua")) && filename.startsWith(query) && !filename.startsWith("help-") && !filename.endsWith("-help")) { result.add(filename); } } @@ -389,8 +389,8 @@ String Library::getObjectOrigin(t_gobj* obj) File Library::findHelpfile(String const& helpName) { - String firstName = helpName + "-help.pd"; - String secondName = "help-" + helpName + ".pd"; + String const firstName = helpName + "-help.pd"; + String const secondName = "help-" + helpName + ".pd"; for (auto& path : helpPaths) { if (!path.exists()) @@ -408,7 +408,7 @@ File Library::findHelpfile(String const& helpName) } } } - + return {}; } @@ -477,7 +477,6 @@ File Library::findHelpfile(t_gobj* obj, File const& parentPatchFile) }; for (auto& path : patchHelpPaths) { - if (!path.exists()) continue; diff --git a/Source/Pd/MessageListener.h b/Source/Pd/MessageListener.h index 19af8b4bd6..a387307815 100644 --- a/Source/Pd/MessageListener.h +++ b/Source/Pd/MessageListener.h @@ -12,6 +12,7 @@ namespace pd { class MessageListener { public: + virtual ~MessageListener() = default; virtual void receiveMessage(t_symbol* symbol, SmallArray const& atoms) = 0; void* object; @@ -20,7 +21,6 @@ class MessageListener { template class MessageVector { -private: static constexpr size_t Capacity = 1 << 20; static constexpr size_t OverflowBlockSize = 1024; AtomicValue size = 0; @@ -51,20 +51,20 @@ class MessageVector { void append(T const&& header, T const* values, int numValues) { - if (EXPECT_LIKELY((size + numValues + 1) < Capacity)) { + if (EXPECT_LIKELY(size + numValues + 1 < Capacity)) { std::copy(values, values + numValues, buffer + size); buffer[size + numValues] = header; size += (numValues + 1); } else { - auto spaceLeft = Capacity > size ? std::min(Capacity - size, numValues) : 0; - int numOverflow = numValues - spaceLeft; + auto const spaceLeft = Capacity > size ? std::min(Capacity - size, numValues) : 0; + int const numOverflow = numValues - spaceLeft; for (int i = 0; i < spaceLeft; i++) { buffer[size + i] = values[i]; } size += spaceLeft; for (int i = 0; i < numOverflow; i++) { addOverflow(values[spaceLeft + i]); - size++; + ++size; } if (size < Capacity) { @@ -72,7 +72,7 @@ class MessageVector { } else { addOverflow(header); } - size++; + ++size; } } @@ -94,7 +94,7 @@ class MessageVector { --size; } - void pop(int amount) + void pop(int const amount) { size -= amount; } @@ -109,7 +109,7 @@ class MessageVector { // MessageDispatcher handles the organising of messages from Pd to the plugdata GUI // It provides an optimised way to listen to messages within pd from the message thread, // it's guaranteed to be lock-free and wait-free, until memory allocation needs to happen -class MessageDispatcher : public AsyncUpdater { +class MessageDispatcher final : public AsyncUpdater { // Represents a single Pd message. // Holds target object, and symbol+size compressed into a single value @@ -121,7 +121,7 @@ class MessageDispatcher : public AsyncUpdater { }; struct Message { - Message() { }; + Message() { } // Both are 16 bytes, so we can squish them together into a single queue union { @@ -257,7 +257,7 @@ class MessageDispatcher : public AsyncUpdater { currentBuffer.store((currentBuffer.load() + 1) % 3); } - void handleAsyncUpdate() + void handleAsyncUpdate() override { dequeueMessages(); } diff --git a/Source/Pd/Patch.cpp b/Source/Pd/Patch.cpp index ea78ccc13e..e9b1658fec 100644 --- a/Source/Pd/Patch.cpp +++ b/Source/Pd/Patch.cpp @@ -93,7 +93,7 @@ void Patch::savePatch(URL const& locationURL) { auto location = locationURL.getLocalFile(); String const fullPathname = location.getParentDirectory().getFullPathName(); - String const filename = location.hasFileExtension("pd") ? location.getFileName() : location.getFileName() + ".pd"; + String const filename = location.withFileExtension("pd").getFileName(); auto* dir = instance->generateSymbol(fullPathname.replace("\\", "/")); auto* file = instance->generateSymbol(filename); @@ -340,28 +340,28 @@ void Patch::copy(SmallArray const& objects) } } -String Patch::translatePatchAsString(String const& patchAsString, Point position) +String Patch::translatePatchAsString(String const& patchAsString, Point const position) { int minX = std::numeric_limits::max(); int minY = std::numeric_limits::max(); int canvasDepth = 0; - auto isObject = [](StringArray& tokens) { + auto isObject = [](StringArray const& tokens) { return tokens[0] == "#X" && tokens[1] != "connect" && tokens[1] != "f" && tokens[2].containsOnly("-0123456789") && tokens[3].containsOnly("-0123456789"); }; // blank message objects have a comma after their position: "#X msg 0 0, f 9;" // this normally isn't an issue, but because they are blank, the comma is next to the number token and doesn't get parsed correctly - auto isMsg = [](StringArray& tokens) { + auto isMsg = [](StringArray const& tokens) { return tokens[0] == "#X" && tokens[1] == "msg"; }; - auto isStartingCanvas = [](StringArray& tokens) { + auto isStartingCanvas = [](StringArray const& tokens) { return tokens[0] == "#N" && tokens[1] == "canvas" && tokens[2].containsOnly("-0123456789") && tokens[3].containsOnly("-0123456789") && tokens[4].containsOnly("-0123456789") && tokens[5].containsOnly("-0123456789"); }; - auto isEndingCanvas = [](StringArray& tokens) { + auto isEndingCanvas = [](StringArray const& tokens) { return tokens[0] == "#X" && tokens[1] == "restore" && tokens[2].containsOnly("-0123456789") && tokens[3].containsOnly("-0123456789"); }; @@ -432,7 +432,7 @@ String Patch::translatePatchAsString(String const& patchAsString, Point pos return toPaste.joinIntoString("\n"); } -void Patch::paste(Point position) +void Patch::paste(Point const position) { auto const text = SystemClipboard::getTextFromClipboard(); @@ -443,7 +443,7 @@ void Patch::paste(Point position) } } -void Patch::duplicate(SmallArray const& objects, t_outconnect* connection) +void Patch::duplicate(SmallArray const& objects, t_outconnect const* connection) { if (auto patch = ptr.get()) { setCurrent(); @@ -642,8 +642,9 @@ void Patch::setTitle(String const& newTitle) pd_typedmess(patch.cast(), instance->generateSymbol("rename"), 2, args.data()); } - MessageManager::callAsync([instance = this->instance] { - instance->titleChanged(); + MessageManager::callAsync([instance = juce::WeakReference(this->instance)] { + if (instance) + instance->titleChanged(); }); } @@ -660,6 +661,11 @@ void Patch::setUntitled() setTitle("Untitled-" + String(lowestNumber)); } +URL Patch::getCurrentURL() const +{ + return currentURL; +} + File Patch::getCurrentFile() const { return currentFile; diff --git a/Source/Pd/Patch.h b/Source/Pd/Patch.h index a3e617b3e0..d8a56a38f4 100644 --- a/Source/Pd/Patch.h +++ b/Source/Pd/Patch.h @@ -58,7 +58,7 @@ class Patch final : public ReferenceCountedObject { void copy(SmallArray const& objects); void paste(Point position); - void duplicate(SmallArray const& objects, t_outconnect* connection); + void duplicate(SmallArray const& objects, t_outconnect const* connection); void startUndoSequence(String const& name); void endUndoSequence(String const& name); @@ -80,6 +80,7 @@ class Patch final : public ReferenceCountedObject { void savePatch(URL const& location); void savePatch(); + URL getCurrentURL() const; File getCurrentFile() const; File getPatchFile() const; diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index a48c8f84d7..fa8bd5e7a8 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -1774,8 +1774,8 @@ void Setup::initialiseELSE() pm6_tilde_setup(); velvet_tilde_setup(); popmenu_setup(); - //dropzone_setup(); - + // dropzone_setup(); + delace_setup(); delace_tilde_setup(); lace_setup(); diff --git a/Source/Pd/Setup.h b/Source/Pd/Setup.h index 969d8b4c54..baae3b472a 100644 --- a/Source/Pd/Setup.h +++ b/Source/Pd/Setup.h @@ -27,7 +27,6 @@ typedef void (*t_plugdata_printhook)(void* ptr, void* obj, char const* recv); namespace pd { - struct Setup { static int initialisePd(); diff --git a/Source/Pd/WeakReference.h b/Source/Pd/WeakReference.h index c6a65b6e1c..3b2ab62e83 100644 --- a/Source/Pd/WeakReference.h +++ b/Source/Pd/WeakReference.h @@ -54,7 +54,7 @@ struct WeakReference { sys_unlock(); } - operator bool() const + explicit operator bool() const { return weakRef && ptr != nullptr; } @@ -85,26 +85,26 @@ struct WeakReference { Ptr get() const { setThis(); - return Ptr(reinterpret_cast(ptr), weakRef); + return Ptr(static_cast(ptr), weakRef); } template T* getRaw() const { - return weakRef ? reinterpret_cast(ptr) : nullptr; + return weakRef ? static_cast(ptr) : nullptr; } template T* getRawUnchecked() const { - return reinterpret_cast(ptr); + return static_cast(ptr); } bool isValid() const noexcept { return weakRef && ptr != nullptr; } - + bool isDeleted() const noexcept { return !weakRef; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index b144c51591..50fe5382fe 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -62,7 +62,7 @@ class CalloutArea final : public Component void timerCallback() override { #if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD - setBounds(target->getScreenBounds() / getDesktopScaleFactor()); + setBounds(target->getScreenBounds() / getApproximateScaleFactorForComponent(target)); #else setBounds(target->getScreenBounds()); #endif @@ -78,7 +78,7 @@ class CalloutArea final : public Component #if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD float getDesktopScaleFactor() const override { - return getApproximateScaleFactorForComponent(target); + return getApproximateScaleFactorForComponent(target) * Desktop::getInstance().getGlobalScaleFactor(); }; #endif @@ -95,14 +95,12 @@ PluginEditor::PluginEditor(PluginProcessor& p) , nvgSurface(this) , openedDialog(nullptr) , pluginConstrainer(*getConstrainer()) - , tooltipWindow(nullptr, [this](){ return std::sqrt(std::abs(getTransform().getDeterminant())); }, - [](Component* c) { + , tooltipWindow([this] { return std::sqrt(std::abs(getTransform().getDeterminant())) * SettingsFile::getInstance()->getProperty("global_scale"); }, [](Component const* c) { if (auto const* cnv = c->findParentComponentOfClass()) { return !getValue(cnv->locked); } - return true; - }) + return true; }) , tabComponent(this) , pluginMode(nullptr) , touchSelectionHelper(std::make_unique(this)) @@ -139,7 +137,7 @@ PluginEditor::PluginEditor(PluginProcessor& p) addKeyListener(commandManager.getKeyMappings()); welcomePanel = std::make_unique(this); - addAndMakeVisible(*welcomePanel); + addChildComponent(*welcomePanel); welcomePanel->setAlwaysOnTop(true); welcomePanelSearchButton.setClickingTogglesState(true); @@ -309,11 +307,7 @@ PluginEditor::PluginEditor(PluginProcessor& p) sidebar->setSize(250, pd->lastUIHeight - statusbar->getHeight()); - if (ProjectInfo::isStandalone) { - setSize(pd->lastUIWidth, pd->lastUIHeight); - } else { - setSize(850, 650); - } + setSize(pd->lastUIWidth, pd->lastUIHeight); sidebar->toFront(false); @@ -323,10 +317,6 @@ PluginEditor::PluginEditor(PluginProcessor& p) addModifierKeyListener(statusbar.get()); - addAndMakeVisible(&callOutSafeArea); - callOutSafeArea.setAlwaysOnTop(true); - callOutSafeArea.setInterceptsMouseClicks(false, true); - addModifierKeyListener(this); connectionMessageDisplay = std::make_unique(this); @@ -396,11 +386,11 @@ PluginEditor::PluginEditor(PluginProcessor& p) settingsFile->resetSettingsState(); } }); - + #if JUCE_IOS pd->lnf->setMainComponent(this); #endif - + startTimerHz(90); } @@ -475,6 +465,9 @@ void PluginEditor::paint(Graphics& g) g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); g.fillRect(workArea.withTrimmedTop(5)); } + + // Update dialog background visibility, synced with repaint for smoothness + nvgSurface.updateWindowContextVisibility(); } // Paint file drop outline @@ -510,12 +503,12 @@ void PluginEditor::paintOverChildren(Graphics& g) } } -void PluginEditor::renderArea(NVGcontext* nvg, Rectangle area) +void PluginEditor::renderArea(NVGcontext* nvg, Rectangle const area) { if (isInPluginMode()) { nvgFillColor(nvg, NVGComponent::convertColour(findColour(PlugDataColour::canvasBackgroundColourId))); nvgFillRect(nvg, 0, 0, getWidth(), getHeight()); - + pluginMode->render(nvg, area); } else { if (welcomePanel->isVisible()) { @@ -533,7 +526,7 @@ void PluginEditor::renderArea(NVGcontext* nvg, Rectangle area) } } -CallOutBox& PluginEditor::showCalloutBox(std::unique_ptr content, Rectangle screenBounds) +CallOutBox& PluginEditor::showCalloutBox(std::unique_ptr content, Rectangle const screenBounds) { class CalloutDeletionListener : public ComponentListener { PluginEditor* editor; @@ -575,10 +568,6 @@ void PluginEditor::showWelcomePanel(bool const shouldShow) sidebar->setVisible(!shouldShow); statusbar->setWelcomePanelShown(shouldShow); - if(!shouldShow) { - welcomePanelSearchButton.setToggleState(false, sendNotification); - welcomePanel->setSearchQuery(""); - } welcomePanelSearchButton.setVisible(shouldShow); recentlyOpenedPanelSelector.setVisible(shouldShow); libraryPanelSelector.setVisible(shouldShow); @@ -588,10 +577,12 @@ void PluginEditor::showWelcomePanel(bool const shouldShow) sidebar->showSidebar(true); } else { welcomePanel->hide(); + welcomePanelSearchButton.setToggleState(false, sendNotification); + welcomePanel->setSearchQuery(""); } } -DragAndDropTarget* PluginEditor::findNextDragAndDropTarget(Point screenPos) +DragAndDropTarget* PluginEditor::findNextDragAndDropTarget(Point const screenPos) { return tabComponent.getScreenBounds().contains(screenPos) ? &tabComponent : nullptr; } @@ -601,9 +592,9 @@ void PluginEditor::resized() #if JUCE_IOS static bool alreadyResized = false; if (auto* window = dynamic_cast(getTopLevelComponent())) { - if(!alreadyResized) { + if (!alreadyResized) { ScopedValueSetter recursionBlock(alreadyResized, true); - + auto totalArea = Desktop::getInstance().getDisplays().getPrimaryDisplay()->totalArea; totalArea = OSUtils::getSafeAreaInsets().subtractedFrom(totalArea); setBounds(totalArea); @@ -614,7 +605,10 @@ void PluginEditor::resized() OSUtils::ScrollTracker::create(peer); } #endif - + + pd->lastUIWidth = getWidth(); + pd->lastUIHeight = getHeight(); + if (isInPluginMode()) { nvgSurface.updateBounds(getLocalBounds().withTrimmedTop(pluginMode->isWindowFullscreen() ? 0 : 40)); return; @@ -624,8 +618,6 @@ void PluginEditor::resized() if (!palettes->isVisible()) paletteWidth = 0; - callOutSafeArea.setBounds(0, toolbarHeight, getWidth(), getHeight() - toolbarHeight - 30); - statusbar->setBounds(0, getHeight() - Statusbar::statusbarHeight, getWidth(), Statusbar::statusbarHeight); auto const workAreaHeight = getHeight() - toolbarHeight - Statusbar::statusbarHeight; @@ -701,9 +693,6 @@ void PluginEditor::resized() welcomePanelSearchInput.setBounds(libraryPanelSelector.getRight() + 10, 4, welcomePanelSearchButton.getX() - libraryPanelSelector.getRight() - 20, toolbarHeight - 4); - pd->lastUIWidth = getWidth(); - pd->lastUIHeight = getHeight(); - repaint(); // Some outlines are dependent on whether or not the sidebars are expanded, or whether or not a patch is opened } @@ -750,7 +739,7 @@ void PluginEditor::parentSizeChanged() resized(); } -void PluginEditor::updateIoletGeometryForAllObjects(PluginProcessor* pd) +void PluginEditor::updateIoletGeometryForAllObjects(PluginProcessor const* pd) { // update all object's iolet position for (auto const& editor : pd->getEditors()) { @@ -788,7 +777,6 @@ void PluginEditor::mouseDown(MouseEvent const& e) } isMaximised = !isMaximised; - return; #else findParentComponentOfClass()->maximiseButtonPressed(); @@ -813,14 +801,14 @@ void PluginEditor::mouseDrag(MouseEvent const& e) if (!isMaximised) { if (auto* window = findParentComponentOfClass()) { if (!window->useNativeTitlebar()) - windowDragger.dragWindow(window, e.getEventRelativeTo(window), nullptr); + windowDragger.dragWindow(window, e.getEventRelativeTo(window)); } } #endif } bool PluginEditor::isInterestedInFileDrag(StringArray const& files) -{ +{ if (openedDialog) return false; @@ -861,37 +849,58 @@ void PluginEditor::fileDragMove(StringArray const& files, int const x, int const repaint(); } - void PluginEditor::installPackage(File const& file) { - auto zip = ZipFile(file); - auto patchesDir = ProjectInfo::appDataDir.getChildFile("Patches"); - auto const result = zip.uncompressTo(patchesDir, false); - if (result.wasOk()) { - auto const macOSTrash = ProjectInfo::appDataDir.getChildFile("Patches").getChildFile("__MACOSX"); - if (macOSTrash.isDirectory()) { - macOSTrash.deleteRecursively(); - } - - auto extractedLocation = patchesDir.getChildFile(zip.getEntry(0)->filename); - auto const metaFile = extractedLocation.getChildFile("meta.json"); - if (!metaFile.existsAsFile()) { - PatchInfo info; - info.title = file.getFileNameWithoutExtension(); - info.setInstallTime(Time::currentTimeMillis()); - auto json = info.json; - metaFile.replaceWithText(info.json); + auto install = [this, file]{ + auto zip = ZipFile(file); + auto patchesDir = ProjectInfo::appDataDir.getChildFile("Patches"); + auto extractedDir = File::createTempFile(""); + auto const result = zip.uncompressTo(extractedDir, false); + if (result.wasOk()) { + auto const macOSTrash = ProjectInfo::appDataDir.getChildFile("Patches").getChildFile("__MACOSX"); + if (macOSTrash.isDirectory()) { + macOSTrash.deleteRecursively(); + } + + for(auto extractedLocation : OSUtils::iterateDirectory(extractedDir, false, false)) + { + if(!extractedLocation.isDirectory() || extractedLocation.getFileName() == "__MACOSX") continue; + + auto const metaFile = extractedLocation.getChildFile("meta.json"); + if (!metaFile.existsAsFile()) { + PatchInfo info; + info.title = file.getFileNameWithoutExtension(); + auto json = info.json; + metaFile.replaceWithText(info.json); + } else { + auto info = PatchInfo(JSON::fromString(metaFile.loadFileAsString())); + auto json = info.json; + metaFile.replaceWithText(info.json); + extractedLocation.moveFileTo(patchesDir.getChildFile(info.getNameInPatchFolder())); + } + } + + MessageManager::callAsync([this, file]{ + Dialogs::showMultiChoiceDialog(&openedDialog, this, "Successfully installed " + file.getFileNameWithoutExtension(), [](int) { }, { "Dismiss" }, Icons::Checkmark); + }); + } else { - auto info = PatchInfo(JSON::fromString(metaFile.loadFileAsString())); - info.setInstallTime(Time::currentTimeMillis()); - auto json = info.json; - metaFile.replaceWithText(info.json); + MessageManager::callAsync([this, file]{ + Dialogs::showMultiChoiceDialog(&openedDialog, this, "Failed to install " + file.getFileNameWithoutExtension(), [](int) { }, { "Dismiss" }); + }); } - - Dialogs::showMultiChoiceDialog(&openedDialog, this, "Successfully installed " + file.getFileNameWithoutExtension(), [](int) { }, { "Dismiss" }, Icons::Checkmark); + }; + + if(OSUtils::isFileQuarantined(file)) + { + Dialogs::showMultiChoiceDialog(&openedDialog, this, "This patch was downloaded from the internet. Installing patches from untrusted sources may pose security risks. Do you want to proceed?" , [install, file](int const choice) { + if (choice == 0) { + OSUtils::removeFromQuarantine(file); + install(); + } }, { "Trust and Install", "Cancel" }, Icons::Warning); } else { - Dialogs::showMultiChoiceDialog(&openedDialog, this, "Failed to install " + file.getFileNameWithoutExtension(), [](int) { }, { "Dismiss" }); + install(); } } @@ -903,14 +912,7 @@ void PluginEditor::filesDropped(StringArray const& files, int const x, int const auto file = File(path); if (file.exists() && file.hasFileExtension("pd")) { openedPdFiles = true; - pd->autosave->checkForMoreRecentAutosave(URL(file), this, [this](URL const& patchFile, URL const& patchPath) { - auto* cnv = tabComponent.openPatch(patchFile); - if(cnv) - { - cnv->patch.setCurrentFile(patchPath); - } - SettingsFile::getInstance()->addToRecentlyOpened(patchPath.getLocalFile()); - }); + tabComponent.openPatch(URL(file)); } if (file.exists() && file.hasFileExtension("plugdata")) { installPackage(file); @@ -1065,24 +1067,22 @@ void PluginEditor::updateSelection(Canvas* cnv) } } -void PluginEditor::showCalloutArea(bool shouldBeVisible) +void PluginEditor::showCalloutArea(bool const shouldBeVisible) { - if(shouldBeVisible) - { + if (shouldBeVisible) { calloutArea->addToDesktop(ComponentPeer::windowIsTemporary, OSUtils::getDesktopParentPeer(this)); - - } - else { + + } else { calloutArea->removeFromDesktop(); } } Component* PluginEditor::getCalloutAreaComponent() { - return static_cast(calloutArea.get()); + return calloutArea.get(); } -void PluginEditor::setCommandButtonObject(Object* obj) +void PluginEditor::setCommandButtonObject(Object const* obj) { auto name = String("empty"); if (obj->cnv) { @@ -1495,9 +1495,8 @@ void PluginEditor::getCommandInfo(CommandID const commandID, ApplicationCommandI result.addDefaultKeypress(key, mods); } } - - if(pluginMode) - { + + if (pluginMode) { result.setActive(false); // Disable all shortcuts in pluginmode } } @@ -1550,23 +1549,20 @@ bool PluginEditor::perform(InvocationInfo const& info) return true; } case CommandIDs::ShowSettings: { - if(openedDialog) - { + if (openedDialog) { openedDialog.reset(nullptr); - } - else { + } else { Dialogs::showSettingsDialog(this); } return true; } case CommandIDs::CloseTab: { - if(openedDialog) - { + if (openedDialog) { openedDialog.reset(nullptr); return true; } - + if (auto* cnv = getCurrentCanvas()) { MessageManager::callAsync([this, cnv = SafePointer(cnv)]() mutable { if (cnv && cnv->patch.isDirty()) { @@ -1587,9 +1583,11 @@ bool PluginEditor::perform(InvocationInfo const& info) }); return true; } - + return false; } + default: + break; } auto* cnv = getCurrentCanvas(); @@ -1860,7 +1858,7 @@ bool PluginEditor::wantsRoundedCorners() const // Since this is called in a paint routine, use reinterpret_cast instead of dynamic_cast for efficiency // For the standalone, the top-level component should always be DocumentWindow derived! - if (auto* window = reinterpret_cast(getTopLevelComponent())) { + if (auto const* window = reinterpret_cast(getTopLevelComponent())) { return !window->useNativeTitlebar() && !window->isMaximised() && ProjectInfo::canUseSemiTransparentWindows(); } return true; diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index e83b49ebed..61b2c89ddd 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -24,8 +24,6 @@ #include "Utility/ObjectThemeManager.h" #include "NVGSurface.h" - - class ConnectionMessageDisplay; class Sidebar; class Statusbar; @@ -47,8 +45,7 @@ class PluginEditor final : public AudioProcessorEditor , public ModifierKeyListener , public ZoomableDragAndDropContainer , public AsyncUpdater - , public Timer -{ + , public Timer { public: explicit PluginEditor(PluginProcessor&); @@ -65,7 +62,7 @@ class PluginEditor final : public AudioProcessorEditor void parentSizeChanged() override; void parentHierarchyChanged() override; void broughtToFront() override; - + void timerCallback() override; void lookAndFeelChanged() override; @@ -80,7 +77,7 @@ class PluginEditor final : public AudioProcessorEditor SmallArray getCanvases(); Canvas* getCurrentCanvas(); - + float getRenderScale() const; void modifierKeysChanged(ModifierKeys const& modifiers) override; @@ -91,8 +88,8 @@ class PluginEditor final : public AudioProcessorEditor void handleAsyncUpdate() override; void updateSelection(Canvas* cnv); - void setCommandButtonObject(Object* obj); - + void setCommandButtonObject(Object const* obj); + void installPackage(File const& file); bool isInterestedInFileDrag(StringArray const& files) override; @@ -116,11 +113,11 @@ class PluginEditor final : public AudioProcessorEditor CallOutBox& showCalloutBox(std::unique_ptr content, Rectangle screenBounds); - static void updateIoletGeometryForAllObjects(PluginProcessor* pd); + static void updateIoletGeometryForAllObjects(PluginProcessor const* pd); void commandKeyChanged(bool isHeld) override; void setUseBorderResizer(bool shouldUse); - + void showCalloutArea(bool shouldBeVisible); Component* getCalloutAreaComponent(); @@ -141,11 +138,8 @@ class PluginEditor final : public AudioProcessorEditor std::unique_ptr palettes; NVGSurface nvgSurface; - - std::unique_ptr openedDialog; - // used to display callOutBoxes only in a safe area between top & bottom toolbars - Component callOutSafeArea; + std::unique_ptr openedDialog; ComponentBoundsConstrainer constrainer; ComponentBoundsConstrainer& pluginConstrainer; @@ -195,7 +189,7 @@ class PluginEditor final : public AudioProcessorEditor Rectangle workArea; std::unique_ptr calloutArea; - + // Used in plugin std::unique_ptr> cornerResizer; diff --git a/Source/PluginMode.h b/Source/PluginMode.h index ef444dc7a2..3601607f5f 100644 --- a/Source/PluginMode.h +++ b/Source/PluginMode.h @@ -21,17 +21,19 @@ class PluginMode final : public Component , desktopWindow(editor->getPeer()) , windowBounds(editor->getBounds().withPosition(editor->getTopLevelComponent()->getPosition())) { + setAccessible(false); // Having accessibility enabled seems to cause crashes in Ableton + editor->pd->initialiseIntoPluginmode = false; #if !JUCE_IOS if (ProjectInfo::isStandalone) { // If the window is already maximised, unmaximise it to prevent problems -#if JUCE_LINUX || JUCE_BSD +# if JUCE_LINUX || JUCE_BSD OSUtils::maximiseX11Window(desktopWindow->getNativeHandle(), false); -#else +# else if (desktopWindow->isFullScreen()) { desktopWindow->setFullScreen(false); } -#endif +# endif } if (ProjectInfo::isStandalone) { auto const frameSize = desktopWindow->getFrameSizeIfPresent(); @@ -78,7 +80,7 @@ class PluginMode final : public Component for (auto const scale : pluginScales) { itemList.add(String(scale.intScale) + "%"); } - + scaleComboBox.addItemList(itemList, 1); if (ProjectInfo::isStandalone) { scaleComboBox.addSeparator(); @@ -89,19 +91,50 @@ class PluginMode final : public Component scaleComboBox.setBounds(8, 8, 70, titlebarHeight - 16); scaleComboBox.setColour(ComboBox::outlineColourId, Colours::transparentBlack); scaleComboBox.setColour(ComboBox::backgroundColourId, findColour(PlugDataColour::toolbarHoverColourId).withAlpha(0.8f)); - scaleComboBox.onChange = [this] { + + auto metaFile = patchPtr.get()->getPatchFile().getSiblingFile("meta.json"); + scaleComboBox.onChange = [this, metaFile] { auto const itemId = scaleComboBox.getSelectedId(); - if (itemId == 0) return; + if (itemId == 0) + return; if (itemId == 8) { setKioskMode(true); return; } - if (selectedItemId != itemId) { - selectedItemId = itemId; + if (selectedZoom != itemId) { + selectedZoom = itemId; setWidthAndHeight(pluginScales[itemId - 1].floatScale); patchPtr->pluginModeScale = pluginScales[itemId - 1].intScale; + + // If the patch has a meta.json file, remember the zoom amount + if (metaFile.existsAsFile()) { + auto json = JSON::parse(metaFile.loadFileAsString()); + if (json.isObject()) { + json.getDynamicObject()->setProperty("Zoom", selectedZoom); + metaFile.replaceWithText(JSON::toString(json)); + } + } } }; + + if (metaFile.existsAsFile()) { + auto json = JSON::parse(metaFile.loadFileAsString()); + if (json.isObject()) { + auto jsonObject = json.getDynamicObject(); + if (jsonObject != nullptr) { + if(jsonObject->hasProperty("Scale")) { + scaleDPIMult = jsonObject->getProperty("Scale"); + } + if(jsonObject->hasProperty("Zoom")) { + selectedZoom = static_cast(jsonObject->getProperty("Zoom")); + scaleComboBox.setSelectedId(selectedZoom, dontSendNotification); + setWidthAndHeight(pluginScales[selectedZoom - 1].floatScale); + patchPtr->pluginModeScale = pluginScales[selectedZoom - 1].intScale; + zoomLoadedFromJson = true; + } + } + } + } titleBar.addAndMakeVisible(scaleComboBox); #if JUCE_IOS @@ -135,17 +168,20 @@ class PluginMode final : public Component setWidthAndHeight(previousScale * 0.01f); } - void setWidthAndHeight(float const scale) + void setWidthAndHeight(float scale) { + scale *= scaleDPIMult; + cnv->zoomScale = scale; + auto newWidth = static_cast(width * scale); auto newHeight = static_cast(height * scale) + titlebarHeight + nativeTitleBarHeight; #if JUCE_LINUX || JUCE_BSD - // We need to add the window margin for the shadow on Linux, or else X11 will try to make the window smaller than it should be when the window moves - newHeight += 36; - newWidth += 36; + // We need to add the window margin for the shadow on Linux, or else X11 will try to make the window smaller than it should be when the window moves + newHeight += 36; + newWidth += 36; #endif - + if (auto* mainWindow = dynamic_cast(editor->getTopLevelComponent())) { #if JUCE_IOS editor->constrainer.setSizeLimits(1, 1, 99000, 99000); @@ -173,23 +209,23 @@ class PluginMode final : public Component editor->setSize(newWidth - 1, newHeight - 1); editor->setSize(newWidth, newHeight); } - Timer::callAfterDelay(100, [_editor = SafePointer(editor)](){ - if(_editor) { + Timer::callAfterDelay(100, [_editor = SafePointer(editor)] { + if (_editor) { _editor->nvgSurface.invalidateAll(); } }); } - - void render(NVGcontext* nvg, Rectangle area) + + void render(NVGcontext* nvg, Rectangle const area) { NVGScopedState scopedState(nvg); auto const scale = pluginModeScale; #if !JUCE_IOS - if(isWindowFullscreen()) + if (isWindowFullscreen()) #endif - nvgScissor(nvg, (getWidth() - (width * scale)) / 2, (getHeight() - (height * scale)) / 2, width * scale, height * scale); - - nvgTranslate(nvg, 0, (isWindowFullscreen() ? 0 : -titlebarHeight)); + nvgScissor(nvg, (getWidth() - (width * scale)) / 2, (getHeight() - height * scale) / 2, width * scale, height * scale); + + nvgTranslate(nvg, 0, isWindowFullscreen() ? 0 : -titlebarHeight); nvgScale(nvg, scale, scale); nvgTranslate(nvg, cnv->getX(), cnv->getY()); @@ -261,22 +297,24 @@ class PluginMode final : public Component } void resized() override - { + { // Detect if the user exited fullscreen with the macOS's fullscreen button #if JUCE_MAC if (ProjectInfo::isStandalone && isWindowFullscreen() && !desktopWindow->isFullScreen()) { setKioskMode(false); } #endif - + // On iOS, we always scale pluginmode patches to full size, and we also always show the patch title bar #if JUCE_IOS // Calculate the scale factor required to fit the editor in the screen float const scaleX = static_cast(getWidth()) / width; float const scaleY = static_cast(getHeight()) / height; float scale = jmin(scaleX, scaleY); - + + cnv->zoomScale = scale; pluginModeScale = scale; + scaleComboBox.setVisible(false); editorButton->setVisible(true); @@ -285,7 +323,7 @@ class PluginMode final : public Component int const scaledHeight = static_cast(height * scale); int const x = (getWidth() - scaledWidth) / 2; int const y = (getHeight() - scaledHeight) / 2; - + titleBar.setBounds(0, 0, getWidth(), titlebarHeight); scaleComboBox.setBounds(8, 8, 74, titlebarHeight - 16); editorButton->setBounds(getWidth() - titlebarHeight, 0, titlebarHeight, titlebarHeight); @@ -310,6 +348,7 @@ class PluginMode final : public Component int const y = (getHeight() - scaledHeight) / 2; pluginModeScale = scale; + cnv->zoomScale = scale; // Hide titlebar titleBar.setBounds(0, 0, 0, 0); @@ -324,6 +363,7 @@ class PluginMode final : public Component } else { float scale = getWidth() / width; pluginModeScale = scale; + scaleComboBox.setVisible(true); editorButton->setVisible(true); @@ -379,7 +419,7 @@ class PluginMode final : public Component return; #if !JUCE_MAC && !JUCE_IOS if (auto* mainWindow = dynamic_cast(editor->getTopLevelComponent())) { - windowDragger.dragWindow(mainWindow, e.getEventRelativeTo(mainWindow), nullptr); + windowDragger.dragWindow(mainWindow, e.getEventRelativeTo(mainWindow)); } #endif } @@ -410,7 +450,7 @@ class PluginMode final : public Component #if JUCE_IOS return; #endif - + auto* window = dynamic_cast(getTopLevelComponent()); if (!window) @@ -426,7 +466,7 @@ class PluginMode final : public Component parentSizeChanged(); } else { setFullScreen(window, false); - selectedItemId = 3; + selectedZoom = 3; scaleComboBox.setText("100%"); setWidthAndHeight(1.0f); desktopWindow = window->getPeer(); @@ -461,19 +501,21 @@ class PluginMode final : public Component ComboBox scaleComboBox; std::unique_ptr editorButton; - int selectedItemId = 3; // default is 100% for now + int selectedZoom = 3; // default is 100% for now WindowDragger windowDragger; - bool isDraggingWindow:1 = false; - bool isFullScreenKioskMode:1 = false; + bool isDraggingWindow : 1 = false; + bool isFullScreenKioskMode : 1 = false; + bool zoomLoadedFromJson : 1 = false; Rectangle originalPluginWindowBounds; Rectangle windowBounds; float const width = static_cast(cnv->patchWidth.getValue()) + 1.0f; float const height = static_cast(cnv->patchHeight.getValue()) + 1.0f; - + float pluginModeScale = 1.0f; + float scaleDPIMult = 1.0f; String lastTheme; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 23640ef95e..4e04b8de8c 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -64,8 +64,8 @@ AudioProcessor::BusesProperties PluginProcessor::buildBusesProperties() AudioProcessor::BusesProperties busesProperties; if (ProjectInfo::isStandalone) { - busesProperties.addBus(true, "Main Input", AudioChannelSet::canonicalChannelSet(16), true); - busesProperties.addBus(false, "Main Output", AudioChannelSet::canonicalChannelSet(16), true); + busesProperties.addBus(true, "Main Input", AudioChannelSet::stereo(), true); + busesProperties.addBus(false, "Main Output", AudioChannelSet::stereo(), true); } else { busesProperties.addBus(true, "Main Input", AudioChannelSet::stereo(), true); @@ -294,13 +294,16 @@ void PluginProcessor::syncDirectoryFiles(File const& sourceDir, File const& targ if (hasValidLastInitTime) { const auto parentPath = targetFile.getParentDirectory().getFullPathName(); const auto dirModTime = directoryModTimes[parentPath]; - - if (dirModTime > lastInitTime) { + const auto fileModTime = targetFile.getLastModificationTime(); + + // Don't delete if either the directory OR the file was modified after last init + // This handles both renamed files (dir mod time) and copied files (file mod time) + if (dirModTime > lastInitTime || fileModTime > lastInitTime) { shouldDelete = false; - } else { } } + if (shouldDelete) { const auto parentDir = targetFile.getParentDirectory(); const auto parentPath = parentDir.getFullPathName(); @@ -352,9 +355,11 @@ bool PluginProcessor::initialiseFilesystem() { auto const& homeDir = ProjectInfo::appDataDir; auto const& versionDataDir = ProjectInfo::versionDataDir; - auto dekenDir = homeDir.getChildFile("Externals"); - auto patchesDir = homeDir.getChildFile("Patches"); + auto const dekenDir = homeDir.getChildFile("Externals"); + auto const patchesDir = homeDir.getChildFile("Patches"); + FileSystemWatcher::addGlobalIgnorePath(homeDir.getChildFile("Toolchain")); + #if JUCE_IOS // TODO: remove this later. This is for iOS version transition auto oldDir = File::getSpecialLocation(File::SpecialLocationType::userDocumentsDirectory).getChildFile("plugdata"); @@ -400,7 +405,7 @@ bool PluginProcessor::initialiseFilesystem() } versionDataDir.getParentDirectory().createDirectory(); - int maxRetries = 3; + int constexpr maxRetries = 3; int retryCount = 0; while(!extractionCompleted && retryCount < maxRetries) { @@ -428,6 +433,11 @@ bool PluginProcessor::initialiseFilesystem() if (!dekenDir.exists()) { dekenDir.createDirectory(); } +#if !JUCE_IOS + if (!patchesDir.exists()) { + patchesDir.createDirectory(); + } +#endif auto const testTonePatch = homeDir.getChildFile("testtone.pd"); auto const cpuTestPatch = homeDir.getChildFile("load-meter.pd"); @@ -440,7 +450,7 @@ bool PluginProcessor::initialiseFilesystem() File(versionDataDir.getChildFile("./Documentation/7.stuff/tools/testtone.pd")).copyFileTo(testTonePatch); File(versionDataDir.getChildFile("./Documentation/7.stuff/tools/load-meter.pd")).copyFileTo(cpuTestPatch); - auto createLinkWithRetry = [&extractionCompleted](const File& linkPath, const File& targetPath, int maxRetries = 3) { + auto createLinkWithRetry = [&extractionCompleted](const File& linkPath, const File& targetPath, int const maxRetries = 3) { for (int retry = 0; retry < maxRetries; retry++) { #if JUCE_WINDOWS // Clean up existing link/directory @@ -689,6 +699,11 @@ void PluginProcessor::setEnableLimiter(bool const enabled) enableLimiter = enabled; } +bool PluginProcessor::getEnableLimiter() +{ + return enableLimiter; +} + void PluginProcessor::numChannelsChanged() { auto const blockSize = AudioProcessor::getBlockSize(); @@ -707,7 +722,7 @@ void PluginProcessor::prepareToPlay(double const sampleRate, int const samplesPe float const oversampleFactor = 1 << oversampling; auto maxChannels = std::max(getTotalNumInputChannels(), getTotalNumOutputChannels()); - prepareDSP(getTotalNumInputChannels(), getTotalNumOutputChannels(), sampleRate * oversampleFactor, samplesPerBlock * oversampleFactor); + prepareDSP(getTotalNumInputChannels(), getTotalNumOutputChannels(), sampleRate * oversampleFactor); oversampler = std::make_unique>(std::max(1, maxChannels), oversampling, dsp::Oversampling::filterHalfBandPolyphaseIIR, false); @@ -715,7 +730,7 @@ void PluginProcessor::prepareToPlay(double const sampleRate, int const samplesPe auto const internalSynthPort = midiDeviceManager.getInternalSynthPort(); if (internalSynthPort >= 0 && ProjectInfo::isStandalone) { - internalSynth->prepare(sampleRate, samplesPerBlock, maxChannels); + internalSynth->prepare(sampleRate, samplesPerBlock); } audioAdvancement = 0; @@ -756,7 +771,7 @@ void PluginProcessor::prepareToPlay(double const sampleRate, int const samplesPe smoothedGain.reset(AudioProcessor::getSampleRate(), 0.02); if(!ProjectInfo::isStandalone) { - backupRunLoopInterval = static_cast((samplesPerBlock / sampleRate) * 2000.0); + backupRunLoopInterval = static_cast(samplesPerBlock / sampleRate * 2000.0); backupRunLoopInterval = jmax(24, backupRunLoopInterval); backupRunLoop.startTimer(backupRunLoopInterval * 32); } @@ -906,12 +921,12 @@ void PluginProcessor::processBlock(AudioBuffer& buffer, MidiBuffer& midiB } else if (internalSynthPort < 0 && internalSynth->isReady()) { internalSynth->unprepare(); } else if (internalSynthPort >= 0 && !internalSynth->isReady()) { - internalSynth->prepare(getSampleRate(), AudioProcessor::getBlockSize(), std::max(totalNumInputChannels, totalNumOutputChannels)); + internalSynth->prepare(getSampleRate(), AudioProcessor::getBlockSize()); } midiBufferInternalSynth.clear(); midiInputHistory.addEvents(midiDeviceManager.getInputHistory(), 0, buffer.getNumSamples(), 0); - statusbarSource->process(midiInputHistory, midiDeviceManager.getOutputHistory(), totalNumOutputChannels); + statusbarSource->process(midiInputHistory, midiDeviceManager.getOutputHistory()); midiDeviceManager.clearMidiOutputBuffers(blockOut.getNumSamples()); statusbarSource->setCPUUsage(cpuLoadMeasurer.getLoadAsPercentage()); @@ -956,7 +971,7 @@ void PluginProcessor::processConstant(dsp::AudioBlock buffer) midiByteBuffer[2] = 0; } - midiDeviceManager.dequeueMidiInput(pdBlockSize, [this](int const port, int const blockSize, MidiBuffer& buffer) { + midiDeviceManager.dequeueMidiInput(pdBlockSize, [this](int const port, MidiBuffer const& buffer) { sendMidiBuffer(port, buffer); }); @@ -1013,10 +1028,10 @@ void PluginProcessor::processVariable(dsp::AudioBlock buffer, MidiBuffer& inputFifo->readAudioAndMidi(audioBufferIn, blockMidiBuffer); if (!ProjectInfo::isStandalone) { - sendMidiBuffer(1, blockMidiBuffer); + sendMidiBuffer(0, blockMidiBuffer); } - midiDeviceManager.dequeueMidiInput(pdBlockSize, [this](int const port, int const blockSize, MidiBuffer& buffer) { + midiDeviceManager.dequeueMidiInput(pdBlockSize, [this](int const port, MidiBuffer const& buffer) { sendMidiBuffer(port, buffer); }); @@ -1067,13 +1082,13 @@ void PluginProcessor::sendPlayhead() lockAudioThread(); setThis(); if (infos.hasValue()) { - atoms_playhead[0] = static_cast(infos->getIsPlaying()); + atoms_playhead[0] = infos->getIsPlaying(); sendMessage("__playhead", "playing", atoms_playhead); - atoms_playhead[0] = static_cast(infos->getIsRecording()); + atoms_playhead[0] = infos->getIsRecording(); sendMessage("__playhead", "recording", atoms_playhead); - atoms_playhead[0] = static_cast(infos->getIsLooping()); + atoms_playhead[0] = infos->getIsLooping(); auto loopPoints = infos->getLoopPoints(); if (loopPoints.hasValue()) { @@ -1167,7 +1182,7 @@ MidiDeviceManager& PluginProcessor::getMidiDeviceManager() return midiDeviceManager; } -void PluginProcessor::sendMidiBuffer(int const device, MidiBuffer& buffer) +void PluginProcessor::sendMidiBuffer(int const device, MidiBuffer const& buffer) { if (acceptsMidi()) { for (auto event : buffer) { @@ -1190,16 +1205,16 @@ void PluginProcessor::sendMidiBuffer(int const device, MidiBuffer& buffer) sendProgramChange(channel, message.getProgramChangeNumber()); } else if (message.isSysEx()) { for (int i = 0; i < message.getSysExDataSize(); ++i) { - sendSysEx(device, static_cast(message.getSysExData()[i])); + sendSysEx(device, message.getSysExData()[i]); } } else if (message.isMidiClock() || message.isMidiStart() || message.isMidiStop() || message.isMidiContinue() || message.isActiveSense() || (message.getRawDataSize() == 1 && message.getRawData()[0] == 0xff)) { for (int i = 0; i < message.getRawDataSize(); ++i) { - sendSysRealTime(device, static_cast(message.getRawData()[i])); + sendSysRealTime(device, message.getRawData()[i]); } } for (int i = 0; i < message.getRawDataSize(); i++) { - sendMidiByte(device, static_cast(message.getRawData()[i])); + sendMidiByte(device, message.getRawData()[i]); } } } @@ -1234,7 +1249,7 @@ void PluginProcessor::getStateInformation(MemoryBlock& destData) ostream.writeInt(patches.size()); - auto patchesDir = ProjectInfo::appDataDir.getChildFile("Patches"); + auto const patchesDir = ProjectInfo::appDataDir.getChildFile("Patches"); auto const patchesTree = new XmlElement("Patches"); @@ -1313,6 +1328,48 @@ void PluginProcessor::getStateInformation(MemoryBlock& destData) } } +String PluginProcessor::findLostPatch(String const& patchPath) const +{ + auto patchesDir = ProjectInfo::appDataDir.getChildFile("Patches"); + + auto trashLocation = File(patchPath.replace("${PATCHES_DIR}", patchesDir.getChildFile(".trash").getFullPathName())); + if(trashLocation.existsAsFile()) + { + return trashLocation.getFullPathName(); + } + + SmallArray> libraryMetaFiles; + for(auto dir : OSUtils::iterateDirectory(patchesDir, false, false)) + { + auto meta = dir.getChildFile("meta.json"); + if(meta.existsAsFile()) + libraryMetaFiles.add({dir, JSON::fromString(meta.loadFileAsString())}); + } + + auto path = File(patchPath); + auto dirName = path.getParentDirectory().getFileName(); + auto hashedDirName = dirName.toLowerCase().replace(" ", "-").upToLastOccurrenceOf("-", false, false); + auto patchName = path.getFileName(); + + for(auto [dir, meta] : libraryMetaFiles) + { + if(meta["Title"].toString().toLowerCase().replace(" ", "-") == hashedDirName) + return dir.getChildFile(meta["Patch"].toString()).getFullPathName(); + + if(meta["Title"].toString() == dirName) + return dir.getChildFile(meta["Patch"].toString()).getFullPathName(); + } + + // Last resort, find a patch with a matching name + for(auto [dir, meta] : libraryMetaFiles) + { + if(meta["Patch"].toString() == patchName) + return dir.getChildFile(meta["Patch"].toString()).getFullPathName(); + } + + return patchPath.replace("${PATCHES_DIR}", patchesDir.getFullPathName()); +} + void PluginProcessor::setStateInformation(void const* data, int const sizeInBytes) { if (sizeInBytes == 0) @@ -1320,7 +1377,7 @@ void PluginProcessor::setStateInformation(void const* data, int const sizeInByte MemoryInputStream istream(data, sizeInBytes, false); - lockAudioThread(); + audioLock.enter(); // Enter audio lock without global readlock setThis(); @@ -1350,6 +1407,11 @@ void PluginProcessor::setStateInformation(void const* data, int const sizeInByte auto patchesDir = ProjectInfo::appDataDir.getChildFile("Patches"); path = path.replace("${PATCHES_DIR}", patchesDir.getFullPathName()); + + // In case we try to load a DAW preset saved from Windows on any other OS +#if !JUCE_WINDOWS + path = path.replaceCharacter('\\', '/'); +#endif newPatches.emplace_back(state, File(path)); } @@ -1366,7 +1428,7 @@ void PluginProcessor::setStateInformation(void const* data, int const sizeInByte jassert(xmlState); - auto openPatch = [this](String const& content, File const& location, bool const pluginMode = false, int pluginModeScale = 100, int const splitIndex = 0) { + auto openPatch = [this](String const& content, File const& location, bool const pluginMode = false, int const pluginModeScale = 100, int const splitIndex = 0) { // CHANGED IN v0.9.0: // We now prefer loading the patch content over the patch file, if possible if (content.isNotEmpty()) { @@ -1415,8 +1477,20 @@ void PluginProcessor::setStateInformation(void const* data, int const sizeInByte location = location.replace("${PRESET_DIR}", presetDir.getFullPathName()); auto patchesDir = ProjectInfo::appDataDir.getChildFile("Patches"); - location = location.replace("${PATCHES_DIR}", patchesDir.getFullPathName()); + if(location.contains("${PATCHES_DIR}")) { + auto newLocation = location.replace("${PATCHES_DIR}", patchesDir.getFullPathName()); + if(File(newLocation).existsAsFile()) + { + location = newLocation; + } + else { + location = findLostPatch(location); + } + } +#if !JUCE_WINDOWS + location = location.replaceCharacter('\\', '/'); +#endif openPatch(content, location, pluginMode, pluginModeScale, splitIndex); } @@ -1430,8 +1504,6 @@ void PluginProcessor::setStateInformation(void const* data, int const sizeInByte updateEnabledParameters(); - auto versionString = String("0.6.1"); // latest version that didn't have version inside the daw state - if (!xmlState->hasAttribute("Legacy") || xmlState->getBoolAttribute("Legacy")) { setLatencySamples(legacyLatency + Instance::getBlockSize()); setOversampling(legacyOversampling); @@ -1442,18 +1514,14 @@ void PluginProcessor::setStateInformation(void const* data, int const sizeInByte tailLength = xmlState->getDoubleAttribute("TailLength"); } - if (xmlState->hasAttribute("Version")) { - versionString = xmlState->getStringAttribute("Version"); - } - if (xmlState->hasAttribute("Height") && xmlState->hasAttribute("Width")) { int windowWidth = xmlState->getIntAttribute("Width", 1000); int windowHeight = xmlState->getIntAttribute("Height", 650); lastUIWidth = windowWidth; lastUIHeight = windowHeight; - if (auto* editor = getActiveEditor()) { + if (auto* editor = dynamic_cast(getActiveEditor())) { MessageManager::callAsync([editor = Component::SafePointer(editor), windowWidth, windowHeight] { - if (!editor) + if (!editor || editor->pluginMode) return; #if !JUCE_IOS editor->setSize(windowWidth, windowHeight); @@ -1466,7 +1534,7 @@ void PluginProcessor::setStateInformation(void const* data, int const sizeInByte parseDataBuffer(*xmlState); } - unlockAudioThread(); + audioLock.exit(); delete[] xmlData; @@ -1482,40 +1550,18 @@ void PluginProcessor::setStateInformation(void const* data, int const sizeInByte pd::Patch::Ptr PluginProcessor::loadPatch(URL const& patchURL) { auto patchFile = patchURL.getLocalFile(); - - lockAudioThread(); - + #if JUCE_IOS - auto tempFile = File::createTempFile(".pd"); - auto patchContent = patchFile.loadFileAsString(); - + // Create input stream to allow scoped file access auto inputStream = patchURL.createInputStream(URL::InputStreamOptions(URL::ParameterHandling::inAddress)); - tempFile.appendText(inputStream->readEntireStreamAsString()); - - auto dirname = patchFile.getParentDirectory().getFullPathName().replace("\\", "/"); - auto filename = patchFile.getFileName(); - - if (!glob_hasforcedfilename()) { - glob_forcefilename(generateSymbol(filename), generateSymbol(dirname)); - } - auto newPatch = openPatch(tempFile); - if (newPatch) { - if (auto patch = newPatch->getPointer()) { - newPatch->setTitle(filename); - newPatch->setCurrentFile(patchURL); - } - } -#else - auto newPatch = openPatch(patchFile); #endif + auto newPatch = openPatch(patchFile); if (initialiseIntoPluginmode) { newPatch->openInPluginMode = true; initialiseIntoPluginmode = false; } - unlockAudioThread(); - if (!newPatch->getPointer()) { logError("Couldn't open patch"); return nullptr; @@ -1584,7 +1630,7 @@ void PluginProcessor::runBackupLoop() // Only run backup timer if GUI is visible if(!getActiveEditor()) return; - int blocksToProcess = backupRunLoopInterval / (int)((DEFDACBLKSIZE / AudioProcessor::getSampleRate()) * 1000.0); + int blocksToProcess = backupRunLoopInterval / std::max(1, static_cast((DEFDACBLKSIZE / AudioProcessor::getSampleRate()) * 1000.0)); if(blocksToProcess < 1) { blocksToProcess = jmax(1, blocksToProcess); // At least 1 block @@ -1767,8 +1813,8 @@ void PluginProcessor::receiveSysMessage(SmallString const& selector, SmallArray< } case hash("limit"): { bool limit = list[0].getFloat(); + setEnableLimiter(limit); for (auto* editor : getEditors()) { - editor->pd->setEnableLimiter(limit); editor->statusbar->showLimiterState(limit); } break; @@ -1832,12 +1878,13 @@ void PluginProcessor::receiveSysMessage(SmallString const& selector, SmallArray< } break; } + default: break; } } void PluginProcessor::addTextToTextEditor(uint64_t const ptr, SmallString const& text) { - MessageManager::callAsync([this, ptr, editorText = text.toString()](){ + MessageManager::callAsync([this, ptr, editorText = text.toString()]{ if(textEditorDialogs.contains(ptr)) { Dialogs::appendTextToTextEditorDialog(textEditorDialogs[ptr].get(), editorText); } @@ -1846,7 +1893,7 @@ void PluginProcessor::addTextToTextEditor(uint64_t const ptr, SmallString const& void PluginProcessor::clearTextEditor(uint64_t const ptr) { - MessageManager::callAsync([this, ptr](){ + MessageManager::callAsync([this, ptr]{ if(textEditorDialogs.contains(ptr)) { Dialogs::clearTextEditorDialog(textEditorDialogs[ptr].get()); } @@ -1865,7 +1912,7 @@ void PluginProcessor::hideTextEditorDialog(uint64_t ptr) }); } -void PluginProcessor::raiseTextEditorDialog(uint64_t ptr) +void PluginProcessor::raiseTextEditorDialog(uint64_t const ptr) { if(textEditorDialogs.contains(ptr)) { @@ -1873,7 +1920,7 @@ void PluginProcessor::raiseTextEditorDialog(uint64_t ptr) } } -void PluginProcessor::showTextEditorDialog(uint64_t ptr, SmallString const& title, std::function save, std::function close) +void PluginProcessor::showTextEditorDialog(uint64_t const ptr, SmallString const& title, std::function save, std::function close) { MessageManager::callAsync([this, ptr, weakRef = pd::WeakReference(reinterpret_cast(ptr), this), title, save, close]() { static std::unique_ptr saveDialog = nullptr; @@ -1952,8 +1999,8 @@ void PluginProcessor::handleParameterMessage(SmallArray const& atoms) }; if (atoms.size() >= 2) { - auto name = atoms[0].toSmallString(); - auto selector = hash(atoms[1].toSmallString()); + auto const name = atoms[0].toSmallString(); + auto const selector = hash(atoms[1].toSmallString()); switch(selector) { case hash("create"): { @@ -1962,7 +2009,16 @@ void PluginProcessor::handleParameterMessage(SmallArray const& atoms) if (atoms.size() >= 3 && atoms[2].isFloat()) { if(auto* param = getEnabledParameter(name)) { - param->setDefaultValue(atoms[2].getFloat()); + auto defaultValue = atoms[2].getFloat(); + if (atoms.size() >= 5 && atoms[3].isFloat() && atoms[4].isFloat()) { + param->setRange(atoms[3].getFloat(), atoms[4].getFloat()); + defaultValue = jmap(defaultValue, atoms[3].getFloat(), atoms[4].getFloat(), 0.0f, 1.0f); + } + else if(atoms.size() >= 4) { // Either not float, or not enough args + logWarning("[param]: incomplete args for create message (invalid range)"); + } + + param->setDefaultValue(defaultValue); if (ProjectInfo::isStandalone) { for (auto const* editor : getEditors()) { editor->sidebar->updateAutomationParameterValue(param); @@ -1997,7 +2053,7 @@ void PluginProcessor::handleParameterMessage(SmallArray const& atoms) if (atoms.size() > 3 && atoms[2].isFloat() && atoms[3].isFloat()) { if(auto* param = getEnabledParameter(name)) { - float min = atoms[2].getFloat(); + float const min = atoms[2].getFloat(); float max = atoms[3].getFloat(); max = std::max(max, min + 0.000001f); param->setRange(min, max); @@ -2017,15 +2073,15 @@ void PluginProcessor::handleParameterMessage(SmallArray const& atoms) } case hash("change"): { if (atoms.size() > 2 && atoms[2].isFloat()) { - if(auto* param = getEnabledParameter(name)) - { + if (auto* param = getEnabledParameter(name)) { int const state = atoms[2].getFloat() != 0; param->setGestureState(state); } } break; } - } + default: break; + } } } @@ -2069,7 +2125,7 @@ void PluginProcessor::disableAudioParameter(SmallString const& name) param->setRange(0.0f, 1.0f); param->setMode(PlugDataParameter::Float); param->clearLoadedFromDAWFlag(); - + param->setUnchanged(); param->notifyDAW(); break; } @@ -2102,7 +2158,7 @@ void PluginProcessor::fillDataBuffer(SmallArray const& vec) if (auto* list = extraData->getChildByName(childName)) extraData->removeChildElement(list, true); } - if (auto list = extraData->createNewChildElement(childName)) { + if (auto* list = extraData->createNewChildElement(childName)) { for (size_t i = 0; i < vec.size(); ++i) { if (vec[i].isFloat()) { list->setAttribute(String("float") + String(i + 1), vec[i].getFloat()); diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index df78e685e9..74990b98b0 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -49,9 +49,11 @@ class PluginProcessor final : public AudioProcessor void setOversampling(int amount); void setLimiterThreshold(int amount); void setEnableLimiter(bool enabled); + bool getEnableLimiter(); + void prepareToPlay(double sampleRate, int samplesPerBlock) override; void numChannelsChanged() override; - void releaseResources() override {}; + void releaseResources() override { } void updateAllEditorsLNF(); @@ -84,6 +86,8 @@ class PluginProcessor final : public AudioProcessor void getStateInformation(MemoryBlock& destData) override; void setStateInformation(void const* data, int sizeInBytes) override; + + String findLostPatch(String const& name) const; pd::Patch::Ptr findPatchInPluginMode(int editorIndex); @@ -131,7 +135,7 @@ class PluginProcessor final : public AudioProcessor #endif void updateSearchPaths(); - void sendMidiBuffer(int device, MidiBuffer& buffer); + void sendMidiBuffer(int device, MidiBuffer const& buffer); void sendPlayhead(); void sendParameters(); @@ -143,7 +147,7 @@ class PluginProcessor final : public AudioProcessor void enableAudioParameter(SmallString const& name); void disableAudioParameter(SmallString const& name); void handleParameterMessage(SmallArray const& atoms) override; - + void performLatencyCompensationChange(float value) override; void sendParameterInfoChangeMessage(); @@ -157,7 +161,7 @@ class PluginProcessor final : public AudioProcessor void titleChanged() override; void setTheme(String themeToUse, bool force = false); - + void runBackupLoop(); int lastUIWidth = 1000, lastUIHeight = 650; @@ -165,7 +169,7 @@ class PluginProcessor final : public AudioProcessor AtomicValue* volume; ValueTree pluginModeTheme; float pluginModeScale = 1.0f; - + String currentThemeName; SettingsFile* settingsFile; @@ -245,7 +249,7 @@ class PluginProcessor final : public AudioProcessor static inline String const else_version = "ELSE v1.0-rc13"; static inline String const cyclone_version = "cyclone v0.9-2"; - static inline String const heavylib_version = "heavylib v0.4"; + static inline String const heavylib_version = "heavylib v0.4.1"; static inline String const gem_version = "Gem v0.94"; // this gets updated with live version data later static String pdlua_version; @@ -279,9 +283,9 @@ class PluginProcessor final : public AudioProcessor }; HostInfoUpdater hostInfoUpdater; - + int backupRunLoopInterval; - TimedCallback backupRunLoop = TimedCallback([this](){ runBackupLoop(); }); + TimedCallback backupRunLoop = TimedCallback([this] { runBackupLoop(); }); CriticalSection backupLoopLock; std::atomic isProcessingAudio; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginProcessor) diff --git a/Source/Sidebar/AutomationPanel.h b/Source/Sidebar/AutomationPanel.h index 18a3bf46c1..184584cc75 100644 --- a/Source/Sidebar/AutomationPanel.h +++ b/Source/Sidebar/AutomationPanel.h @@ -155,9 +155,8 @@ class AutomationItem final : public ObjectDragAndDrop rangeProperty.setVisible(toggleState); modeProperty.setVisible(toggleState); - - if(auto* parent = getParentComponent()) - { + + if (auto* parent = getParentComponent()) { parent->resized(); } }; @@ -293,8 +292,7 @@ class AutomationItem final : public ObjectDragAndDrop if (String(canvas->gl_name->s_name) == "param.pd") { auto const binName = canvas->gl_obj.te_binbuf; t_atom* atoms; - int const argc = binbuf_getnatom(binName); - if (argc > 1) { + if (int const argc = binbuf_getnatom(binName); argc > 1) { atoms = binbuf_getvec(binName); if (atoms[1].a_type == A_SYMBOL) { if (String(atom_getsymbol(&atoms[1])->s_name) == lastName) { @@ -516,7 +514,7 @@ class AutomationItem final : public ObjectDragAndDrop } // TOOD: hides non-virtual function! - bool isEnabled() const + bool isParameterEnabled() const { return param->isEnabled(); } @@ -818,11 +816,11 @@ class AutomationComponent final : public Component { viewportPosY -= autoScrollOffset.getY(); int const idx = rows.indexOf(draggedItem); - if (idx > 0 && draggedItem->getBounds().getCentreY() < rows[idx - 1]->getBounds().getCentreY() && rows[idx - 1]->isEnabled()) { + if (idx > 0 && draggedItem->getBounds().getCentreY() < rows[idx - 1]->getBounds().getCentreY() && rows[idx - 1]->isParameterEnabled()) { rows.swap(idx, idx - 1); shouldAnimate = true; resized(); - } else if (idx < rows.size() - 1 && draggedItem->getBounds().getCentreY() > rows[idx + 1]->getBounds().getCentreY() && rows[idx + 1]->isEnabled()) { + } else if (idx < rows.size() - 1 && draggedItem->getBounds().getCentreY() > rows[idx + 1]->getBounds().getCentreY() && rows[idx + 1]->isParameterEnabled()) { rows.swap(idx, idx + 1); shouldAnimate = true; resized(); @@ -833,7 +831,7 @@ class AutomationComponent final : public Component { { StringArray takenNames; for (auto const* row : rows) { - if (row->isEnabled()) { + if (row->isParameterEnabled()) { takenNames.add(row->param->getTitle().toString()); } } @@ -879,7 +877,7 @@ class AutomationComponent final : public Component { slider->reorderButton.addMouseListener(this, false); - slider->onDelete = [this](AutomationItem* toDelete) { + slider->onDelete = [this](AutomationItem const* toDelete) { StringArray paramNames; for (auto const* param : getParameters()) { @@ -1017,7 +1015,7 @@ class AutomationPanel final : public Component sliders.setSize(getWidth(), std::max(sliders.getTotalHeight(), viewport.getMaximumVisibleHeight())); } - void updateParameterValue(PlugDataParameter* changedParameter) + void updateParameterValue(PlugDataParameter const* changedParameter) { for (int p = 0; p < sliders.rows.size(); p++) { auto const* param = sliders.rows[p]->param; diff --git a/Source/Sidebar/CommandInput.h b/Source/Sidebar/CommandInput.h index 512e21e692..ec400bac16 100644 --- a/Source/Sidebar/CommandInput.h +++ b/Source/Sidebar/CommandInput.h @@ -22,6 +22,7 @@ extern "C" { class CommandProcessor { public: + virtual ~CommandProcessor() = default; virtual SmallArray> executeCommand(pd::Instance* pd, String message) = 0; }; @@ -60,7 +61,7 @@ class LuaExpressionParser { } } - void executeScript(juce::String const& filePath) + void executeScript(juce::String const& filePath) const { // Load the script without executing it if (luaL_loadfile(L, filePath.toRawUTF8()) == LUA_OK) { @@ -82,7 +83,7 @@ class LuaExpressionParser { } // Function to execute an expression and return result as LuaResult (either double or string) - LuaResult executeExpression(String const& expression, bool const hasReturnValue) + LuaResult executeExpression(String const& expression, bool const hasReturnValue) const { String luaCode = "local __eval = function()\n"; if (hasReturnValue) @@ -106,7 +107,7 @@ class LuaExpressionParser { return result; } if (lua_isboolean(L, -1)) { - bool result = lua_toboolean(L, -1); + bool const result = lua_toboolean(L, -1); lua_pop(L, 1); // Remove result from stack return static_cast(result); } @@ -426,7 +427,7 @@ class CommandInput final }; // Post error if argv has only one arg (argv can change during command execution) - auto isObjectNameProvided = [pd](StringArray& argv) -> bool { + auto isObjectNameProvided = [pd](StringArray const& argv) -> bool { if (argv.size() == 1) { pd->logError("No object query provided"); return false; @@ -601,6 +602,7 @@ class CommandInput final case hash("search"): pd->logMessage(argv[2] + ": Search object IDs on current canvas. Usage: " + argv[2] + " ."); break; + default: break; } } case hash("?"): @@ -832,11 +834,11 @@ class CommandInput final for (auto& command : commands) { commandHistory.push_back(command); } - }; + } private: PluginEditor* editor; - static inline UnorderedMap> luas = UnorderedMap>(); + static inline auto luas = UnorderedMap>(); LuaExpressionParser* lua; int consoleTargetLength = 10; diff --git a/Source/Sidebar/Console.h b/Source/Sidebar/Console.h index a3e40adc4d..9602da6359 100644 --- a/Source/Sidebar/Console.h +++ b/Source/Sidebar/Console.h @@ -480,7 +480,7 @@ class Console final : public Component return std::unique_ptr(settingsCalloutButton); } - static int calculateNumLines(String& message, int const length, int maxWidth) + static int calculateNumLines(String const& message, int const length, int maxWidth) { maxWidth -= 38.0f; if (message.containsAnyOf("\n\r") && message.containsNonWhitespaceChars()) { diff --git a/Source/Sidebar/DocumentationBrowser.h b/Source/Sidebar/DocumentationBrowser.h index a6cce6090d..44228d6eac 100644 --- a/Source/Sidebar/DocumentationBrowser.h +++ b/Source/Sidebar/DocumentationBrowser.h @@ -123,10 +123,10 @@ class DocumentationBrowserUpdateThread final : public Thread } private: - static inline Identifier const fileIdentifier = Identifier("File"); - static inline Identifier const nameIdentifier = Identifier("Name"); - static inline Identifier const pathIdentifier = Identifier("Path"); - static inline Identifier const iconIdentifier = Identifier("Icon"); + static inline auto const fileIdentifier = Identifier("File"); + static inline auto const nameIdentifier = Identifier("Name"); + static inline auto const pathIdentifier = Identifier("Path"); + static inline auto const iconIdentifier = Identifier("Icon"); ValueTree generateDirectoryValueTree(File const& directory) { @@ -265,12 +265,11 @@ class DocumentationBrowser final : public Component searchInput.setInterceptsMouseClicks(true, true); addAndMakeVisible(searchInput); - auto returnAndClickFn = [this](ValueTree& tree) { + auto returnAndClickFn = [this](ValueTree const& tree) { auto const file = File(tree.getProperty("Path").toString()); if (file.existsAsFile() && file.hasFileExtension("pd")) { auto* editor = findParentComponentOfClass(); editor->getTabComponent().openPatch(URL(file)); - SettingsFile::getInstance()->addToRecentlyOpened(file); } else if (file.isDirectory()) { file.revealToUser(); } else if (file.existsAsFile()) { @@ -285,7 +284,7 @@ class DocumentationBrowser final : public Component fileList.onReturn = returnAndClickFn; fileList.onClick = returnAndClickFn; - fileList.onDragStart = [this](ValueTree& tree) { + fileList.onDragStart = [this](ValueTree const& tree) { DragAndDropContainer::performExternalDragDropOfFiles({ tree.getProperty("Path") }, false, this, nullptr); }; diff --git a/Source/Sidebar/Inspector.h b/Source/Sidebar/Inspector.h index 5d820c6ddf..b85bd8ee80 100644 --- a/Source/Sidebar/Inspector.h +++ b/Source/Sidebar/Inspector.h @@ -10,7 +10,7 @@ class Inspector final : public Component { class PropertyRedirector final : public Value::Listener { public: - PropertyRedirector(Inspector* parent) + explicit PropertyRedirector(Inspector* parent) : inspector(parent) { } @@ -69,7 +69,7 @@ class Inspector final : public Component { value->setValue(v.getValue()); } - if (isInsideUndoSequence) { + if (isInsideUndoSequence && currentPatch) { currentPatch->endUndoSequence("properties"); } break; @@ -117,26 +117,16 @@ class Inspector final : public Component { panel.setContentWidth(getWidth() - 16); } - PropertiesPanelProperty* createPanel(int const type, String const& name, Value* value, StringArray& options, bool clip, double min, double max, std::function const& onInteractionFn = nullptr) + PropertiesPanelProperty* createPanel(int const type, String const& name, Value const* value, StringArray const& options, bool const clip, double const min, double const max, std::function const& onInteractionFn = nullptr) { switch (type) { case tString: return new PropertiesPanel::EditableComponent(name, *value); case tFloat: { - auto* c = new PropertiesPanel::EditableComponent(name, *value); - if(clip) { - c->setRangeMin(min); - c->setRangeMax(max); - } - return c; + return new PropertiesPanel::EditableComponent(name, *value, clip, min, max); } case tInt: { - auto* c = new PropertiesPanel::EditableComponent(name, *value); - if(clip) { - c->setRangeMin(min); - c->setRangeMax(max); - } - return c; + return new PropertiesPanel::EditableComponent(name, *value, clip, min, max, onInteractionFn); } case tColour: return new PropertiesPanel::InspectorColourComponent(name, *value); @@ -166,7 +156,7 @@ class Inspector final : public Component { loadParameters(properties); } - bool isEmpty() + bool isEmpty() const { return properties.empty(); } @@ -231,7 +221,7 @@ class Inspector final : public Component { newPanel->setPreferredHeight(30); panels.add(newPanel); } else { - auto* redirectedProperty = redirector.addProperty(value, otherValues); + auto const* redirectedProperty = redirector.addProperty(value, otherValues); auto newPanel = createPanel(type, name, redirectedProperty, options, clip, min, max); newPanel->setPreferredHeight(30); panels.add(newPanel); diff --git a/Source/Sidebar/PaletteItem.cpp b/Source/Sidebar/PaletteItem.cpp index d7d33336e3..a07b128477 100644 --- a/Source/Sidebar/PaletteItem.cpp +++ b/Source/Sidebar/PaletteItem.cpp @@ -50,9 +50,9 @@ PaletteItem::PaletteItem(PluginEditor* e, PaletteDraggableList* parent, ValueTre isSubpatch = isSubpatchOrAbstraction(palettePatch); if (isSubpatch) { - auto const iolets = countIolets(palettePatch); - inlets = iolets.first; - outlets = iolets.second; + auto const [fst, snd] = countIolets(palettePatch); + inlets = fst; + outlets = snd; } lookAndFeelChanged(); @@ -320,19 +320,19 @@ std::pair, SmallArray> PaletteItem::countIolets(String co auto& [inlets, outlets] = iolets.data_; int canvasDepth = patchAsString.startsWith("#N canvas") ? -1 : 0; - auto isObject = [](StringArray& tokens) { + auto isObject = [](StringArray const& tokens) { return tokens[0] == "#X" && tokens[1] != "connect" && tokens[2].containsOnly("-0123456789") && tokens[3].containsOnly("-0123456789"); }; - auto isStartingCanvas = [](StringArray& tokens) { + auto isStartingCanvas = [](StringArray const& tokens) { return tokens[0] == "#N" && tokens[1] == "canvas" && tokens[2].containsOnly("-0123456789") && tokens[3].containsOnly("-0123456789") && tokens[4].containsOnly("-0123456789") && tokens[5].containsOnly("-0123456789"); }; - auto isEndingCanvas = [](StringArray& tokens) { + auto isEndingCanvas = [](StringArray const& tokens) { return tokens[0] == "#X" && tokens[1] == "restore" && tokens[2].containsOnly("-0123456789") && tokens[3].containsOnly("-0123456789"); }; - auto countIolet = [&inlets = iolets[0], &outlets = iolets[1]](StringArray& tokens) { + auto countIolet = [&inlets = iolets[0], &outlets = iolets[1]](StringArray const& tokens) { auto position = Point(tokens[2].getIntValue(), tokens[3].getIntValue()); auto const name = tokens[4]; if (name == "inlet") diff --git a/Source/Sidebar/Palettes.h b/Source/Sidebar/Palettes.h index c367f5f128..e95d8c9ce8 100644 --- a/Source/Sidebar/Palettes.h +++ b/Source/Sidebar/Palettes.h @@ -89,7 +89,7 @@ class PaletteDraggableList final : public Component pasteButton.onClick = [this] { auto const clipboardText = SystemClipboard::getTextFromClipboard(); if (!OfflineObjectRenderer::checkIfPatchIsValid(clipboardText)) { - Dialogs::showMultiChoiceDialog(&editor->openedDialog, editor, "Clipboard contents not valid PD patch", [](int){}, {"Dismiss"}); + Dialogs::showMultiChoiceDialog(&editor->openedDialog, editor, "Clipboard contents not valid PD patch", [](int) { }, { "Dismiss" }); return; } ValueTree itemTree("Item"); @@ -489,7 +489,7 @@ class Palettes final : public Component if (existingTree.isValid()) { showPalette(existingTree); } else { - ValueTree categoryTree = ValueTree("Category"); + auto categoryTree = ValueTree("Category"); categoryTree.setProperty("Name", name, nullptr); for (auto& [paletteName, patch] : palette) { @@ -578,11 +578,11 @@ class Palettes final : public Component for (auto const& [name, palette] : defaultPalettes) { - ValueTree categoryTree = ValueTree("Category"); + auto categoryTree = ValueTree("Category"); categoryTree.setProperty("Name", name, nullptr); for (auto& [paletteName, patch] : palette) { - ValueTree paletteTree("Item"); + auto paletteTree = ValueTree("Item"); paletteTree.setProperty("Name", paletteName, nullptr); paletteTree.setProperty("Patch", patch, nullptr); categoryTree.appendChild(paletteTree, nullptr); @@ -659,7 +659,7 @@ class Palettes final : public Component for (auto* button : paletteSelectors) { String buttonText = button->getButtonText(); int const height = button->getHeight(); - + if (button != draggedTab) { auto bounds = Rectangle(offset, totalHeight, 30, height); if (shouldAnimate) { @@ -967,8 +967,9 @@ class Palettes final : public Component void mouseDrag(MouseEvent const& e) override { - if(rateReducer.tooFast()) return; - + if (rateReducer.tooFast()) + return; + int newWidth = dragStartWidth + e.getDistanceFromDragStartX(); newWidth = std::clamp(newWidth, 100, std::max(target->getParentWidth() / 2, 150)); diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index ce4cc6ea9e..2221fdc01c 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -52,10 +52,10 @@ class OpenInspector final : public Component { class SearchPanelSettings final : public Component { public: - struct SearchPanelSettingsButton final : public TextButton { + class SearchPanelSettingsButton final : public TextButton { String const icon; String const description; - + public: SearchPanelSettingsButton(String iconString, String descriptionString, String const& settingsProperty) : icon(std::move(iconString)) , description(std::move(descriptionString)) @@ -128,13 +128,12 @@ class SearchPanel final : public Component input.onTextChange = [this] { patchTree.setFilterString(input.getText()); }; - input.addKeyListener(this); patchTree.addKeyListener(this); // onReturn makes the current node active and gains focus for keyboard traversal - patchTree.onReturn = [this](ValueTree& tree) { + patchTree.onReturn = [this](ValueTree const& tree) { auto* ptr = reinterpret_cast(static_cast(tree.getProperty("Object"))); if (auto obj = editor->highlightSearchTarget(ptr, true)) { auto launchInspector = [this, obj, ptr] { @@ -148,7 +147,7 @@ class SearchPanel final : public Component } }; - patchTree.onClick = [this](ValueTree& tree) { + patchTree.onClick = [this](ValueTree const& tree) { auto* ptr = reinterpret_cast(static_cast(tree.getProperty("Object"))); if (auto obj = editor->highlightSearchTarget(ptr, true)) { auto launchInspector = [this, obj] { @@ -161,7 +160,7 @@ class SearchPanel final : public Component } }; - patchTree.onSelect = [this](ValueTree& tree) { + patchTree.onSelect = [this](ValueTree const& tree) { auto* ptr = reinterpret_cast(static_cast(tree.getProperty("TopLevel"))); if (auto obj = editor->highlightSearchTarget(ptr, false)) { auto launchInspector = [this, obj] { @@ -175,7 +174,7 @@ class SearchPanel final : public Component } }; - patchTree.onRightClick = [this](ValueTree& tree) { + patchTree.onRightClick = [this](ValueTree const& tree) { auto* ptr = reinterpret_cast(static_cast(tree.getProperty("Object"))); auto const pos = Desktop::getInstance().getMousePosition(); @@ -274,7 +273,7 @@ class SearchPanel final : public Component Fonts::drawIcon(g, Icons::Search, 2, 1, 32, colour, 12); } - std::unique_ptr getExtraSettingsComponent() + std::unique_ptr getExtraSettingsComponent() const { auto* settingsCalloutButton = new SmallIconButton(Icons::More); auto* pluginEditor = findParentComponentOfClass(); @@ -656,7 +655,7 @@ class SearchPanel final : public Component return patchTree; } - static void updateIconsForChildTrees(ValueTree& tree) + static void updateIconsForChildTrees(ValueTree const& tree) { for (auto child : tree) { // Check if this child has its own children @@ -685,6 +684,7 @@ class SearchPanel final : public Component PluginEditor* editor; ValueTreeViewerComponent patchTree = ValueTreeViewerComponent("(Subpatch)"); SearchEditor input; + private: static inline SafePointer currentCalloutBox = nullptr; }; diff --git a/Source/Sidebar/Sidebar.cpp b/Source/Sidebar/Sidebar.cpp index 1e9f681546..46f387c3d4 100644 --- a/Source/Sidebar/Sidebar.cpp +++ b/Source/Sidebar/Sidebar.cpp @@ -161,8 +161,7 @@ void Sidebar::resized() if (SettingsFile::getInstance()->getProperty("centre_sidepanel_buttons")) { buttonBarBounds = buttonBarBounds.withSizeKeepingCentre(30, 144 + 30 + 8 + 30); - } - else { + } else { buttonBarBounds = buttonBarBounds.withTrimmedTop(34); } @@ -240,8 +239,9 @@ void Sidebar::mouseDown(MouseEvent const& e) void Sidebar::mouseDrag(MouseEvent const& e) { if (draggingSidebar) { - if(rateReducer.tooFast()) return; - + if (rateReducer.tooFast()) + return; + int newWidth = dragStartWidth - e.getDistanceFromDragStartX(); newWidth = std::clamp(newWidth, 230, std::max(getParentWidth() / 2, 150)); @@ -299,7 +299,7 @@ void Sidebar::showPanel(SidePanel const panelToShow) showSidebar(true); // Set one of the panels to active, and the rest to inactive - auto setPanelVis = [this](Component* panel, SidePanel const panelEnum) { + auto setPanelVis = [this](Component const* panel, SidePanel const panelEnum) { for (auto pb : panelAndButton) { inspectorButton.showIndicator(!inspectorButton.isInspectorActive()); if (pb.panel == panel) { @@ -337,7 +337,7 @@ void Sidebar::showPanel(SidePanel const panelToShow) break; case SidePanel::InspectorPan: if (!sidebarHidden) { - auto const isVisible = (inspectorButton.isInspectorPinned() || (inspectorButton.isInspectorAuto() && !inspector->isEmpty())); + auto const isVisible = inspectorButton.isInspectorPinned() || (inspectorButton.isInspectorAuto() && !inspector->isEmpty()); if (!areParamObjectsAllValid()) { clearInspector(); } @@ -385,7 +385,7 @@ bool Sidebar::isShowingSearch() const return searchPanel->isVisible(); } -void Sidebar::updateAutomationParameterValue(PlugDataParameter* param) +void Sidebar::updateAutomationParameterValue(PlugDataParameter const* param) { if (ProjectInfo::isStandalone && automationPanel) { automationPanel->updateParameterValue(param); @@ -488,7 +488,7 @@ void Sidebar::updateSearch(bool const resetInspector) } } -void Sidebar::setActiveSearchItem(void* objPtr) +void Sidebar::setActiveSearchItem(void const* objPtr) { searchPanel->patchTree.makeNodeActive(objPtr); } diff --git a/Source/Sidebar/Sidebar.h b/Source/Sidebar/Sidebar.h index f9e1fd5505..843dcdf71a 100644 --- a/Source/Sidebar/Sidebar.h +++ b/Source/Sidebar/Sidebar.h @@ -263,7 +263,7 @@ class Sidebar final : public Component void showParameters(SmallArray& objects, SmallArray& params, bool showOnSelect = false); void hideParameters(); - void setActiveSearchItem(void* objPtr); + void setActiveSearchItem(void const* objPtr); void clearInspector(); @@ -291,7 +291,7 @@ class Sidebar final : public Component void clearSearchOutliner(); - void updateAutomationParameterValue(PlugDataParameter* param); + void updateAutomationParameterValue(PlugDataParameter const* param); void updateAutomationParameters(); static constexpr int dragbarWidth = 6; @@ -335,7 +335,7 @@ class Sidebar final : public Component }; SmallArray panelAndButton; - + RateReducer rateReducer = RateReducer(45); int dragStartWidth = 0; diff --git a/Source/Standalone/InternalSynth.cpp b/Source/Standalone/InternalSynth.cpp index 32baca8d49..3bf0daa00a 100644 --- a/Source/Standalone/InternalSynth.cpp +++ b/Source/Standalone/InternalSynth.cpp @@ -45,19 +45,19 @@ void InternalSynth::run() unprepareLock.lock(); // Fluidlite does not like setups with <2 channels - internalBuffer.setSize(std::max(2, lastNumChannels.load()), lastBlockSize); + internalBuffer.setSize(2, lastBlockSize); internalBuffer.clear(); // Check if soundfont exists to prevent crashing if (soundFont.existsAsFile()) { - auto pathName = soundFont.getFullPathName(); + auto const pathName = soundFont.getFullPathName(); // Initialise fluidsynth settings = new_fluid_settings(); fluid_settings_setint(settings, "synth.ladspa.active", 0); fluid_settings_setint(settings, "synth.midi-channels", 16); fluid_settings_setnum(settings, "synth.gain", 0.9f); - fluid_settings_setnum(settings, "synth.audio-channels", lastNumChannels); + fluid_settings_setnum(settings, "synth.audio-channels", 2); fluid_settings_setnum(settings, "synth.sample-rate", lastSampleRate); synth = new_fluid_synth(settings); // Create fluidsynth instance: @@ -90,7 +90,6 @@ void InternalSynth::unprepare() lastSampleRate = 0; lastBlockSize = 0; - lastNumChannels = 0; ready = false; @@ -103,28 +102,30 @@ void InternalSynth::unprepare() #endif } -void InternalSynth::prepare(int sampleRate, int blockSize, int numChannels) +void InternalSynth::handleAsyncUpdate() { -#ifdef PLUGDATA_STANDALONE + waitForThreadToExit(-1); + startThread(); +} - if (ready && !isThreadRunning() && sampleRate == lastSampleRate && blockSize == lastBlockSize && numChannels == lastNumChannels) { +void InternalSynth::prepare(int const sampleRate, int const blockSize) +{ +#ifdef PLUGDATA_STANDALONE + if (sampleRate == lastSampleRate && blockSize == lastBlockSize) { return; } else { lastSampleRate = sampleRate; lastBlockSize = blockSize; - lastNumChannels = numChannels; - - startThread(); + triggerAsyncUpdate(); } - #endif } -void InternalSynth::process(AudioBuffer& buffer, MidiBuffer& midiMessages) +void InternalSynth::process(AudioBuffer& buffer, MidiBuffer const& midiMessages) { #ifdef PLUGDATA_STANDALONE - if (buffer.getNumChannels() != lastNumChannels || buffer.getNumSamples() > lastBlockSize) { + if (buffer.getNumSamples() > lastBlockSize) { unprepare(); return; } @@ -135,7 +136,7 @@ void InternalSynth::process(AudioBuffer& buffer, MidiBuffer& midiMessages for (auto const& event : midiMessages) { auto const message = event.getMessage(); - auto channel = message.getChannel() - 1; + auto const channel = message.getChannel() - 1; if (message.isNoteOn()) { fluid_synth_noteon(synth, channel, message.getNoteNumber(), message.getVelocity()); @@ -166,9 +167,10 @@ void InternalSynth::process(AudioBuffer& buffer, MidiBuffer& midiMessages internalBuffer.clear(); // Run audio through fluidsynth - fluid_synth_process(synth, buffer.getNumSamples(), std::max(2, buffer.getNumChannels()), const_cast(internalBuffer.getArrayOfReadPointers()), std::max(2, buffer.getNumChannels()), const_cast(internalBuffer.getArrayOfWritePointers())); + fluid_synth_process(synth, internalBuffer.getNumSamples(), internalBuffer.getNumChannels(), const_cast(internalBuffer.getArrayOfReadPointers()), internalBuffer.getNumChannels(), const_cast(internalBuffer.getArrayOfWritePointers())); - for (int ch = 0; ch < buffer.getNumChannels(); ch++) { + int const numChannelsToProcess = std::min(buffer.getNumChannels(), 2); + for (int ch = 0; ch < numChannelsToProcess; ch++) { buffer.addFrom(ch, 0, internalBuffer, ch, 0, buffer.getNumSamples()); } diff --git a/Source/Standalone/InternalSynth.h b/Source/Standalone/InternalSynth.h index e83ddccec4..ee29f1b515 100644 --- a/Source/Standalone/InternalSynth.h +++ b/Source/Standalone/InternalSynth.h @@ -15,7 +15,7 @@ typedef struct _fluid_synth_t FluidSynth; typedef struct _fluid_hashtable_t FluidSettings; -class InternalSynth final : public Thread { +class InternalSynth final : public Thread, public AsyncUpdater { public: InternalSynth(); @@ -27,11 +27,13 @@ class InternalSynth final : public Thread { void unprepare(); - void prepare(int sampleRate, int blockSize, int numChannels); + void prepare(int sampleRate, int blockSize); - void process(AudioBuffer& buffer, MidiBuffer& midiMessages); + void process(AudioBuffer& buffer, MidiBuffer const& midiMessages); bool isReady(); + + void handleAsyncUpdate() override; private: File soundFont = ProjectInfo::versionDataDir.getChildFile("Extra").getChildFile("else").getChildFile("sf").getChildFile("GeneralUser_GS.sf3"); diff --git a/Source/Standalone/PlugDataApp.cpp b/Source/Standalone/PlugDataApp.h similarity index 95% rename from Source/Standalone/PlugDataApp.cpp rename to Source/Standalone/PlugDataApp.h index e87d64f3bd..321809b48c 100644 --- a/Source/Standalone/PlugDataApp.cpp +++ b/Source/Standalone/PlugDataApp.h @@ -96,7 +96,6 @@ class PlugDataApp final : public JUCEApplication { if (pd && editor && file.existsAsFile()) { auto* editor = dynamic_cast(mainWindow->mainComponent->getEditor()); editor->getTabComponent().openPatch(URL(file)); - SettingsFile::getInstance()->addToRecentlyOpened(file); } } else if (file.hasFileExtension("plugdata")) { auto* editor = dynamic_cast(mainWindow->mainComponent->getEditor()); @@ -183,7 +182,6 @@ class PlugDataApp final : public JUCEApplication { auto* editor = dynamic_cast(mainWindow->mainComponent->getEditor()); editor->getTabComponent().openPatch(URL(toOpen)); - SettingsFile::getInstance()->addToRecentlyOpened(toOpen); openedPatches.add(toOpen.getFullPathName()); } } @@ -232,7 +230,7 @@ class PlugDataApp final : public JUCEApplication { protected: ApplicationProperties appProperties; - PlugDataWindow* mainWindow; + PlugDataWindow* mainWindow = nullptr; }; void PlugDataWindow::closeAllPatches() @@ -293,5 +291,3 @@ void StandalonePluginHolder::shutDownAudioDevices() deviceManager.removeAudioCallback(this); #endif } - -START_JUCE_APPLICATION(PlugDataApp) diff --git a/Source/Standalone/PlugDataMain.cpp b/Source/Standalone/PlugDataMain.cpp new file mode 100644 index 0000000000..d5b8b44854 --- /dev/null +++ b/Source/Standalone/PlugDataMain.cpp @@ -0,0 +1,3 @@ +#include "PlugDataApp.h" + +START_JUCE_APPLICATION(PlugDataApp) diff --git a/Source/Standalone/PlugDataWindow.h b/Source/Standalone/PlugDataWindow.h index 49c19bbf11..215fab020a 100644 --- a/Source/Standalone/PlugDataWindow.h +++ b/Source/Standalone/PlugDataWindow.h @@ -110,7 +110,7 @@ class StandalonePluginHolder final : private AudioIODeviceCallback shutDownAudioDevices(); } - virtual void createPlugin() + void createPlugin() { processor = createPluginFilterOfType(AudioProcessor::wrapperType_Standalone); processor->disableNonMainBuses(); @@ -200,20 +200,20 @@ class StandalonePluginHolder final : private AudioIODeviceCallback bool isInterAppAudioConnected() { - #if JUCE_IOS - if (auto device = dynamic_cast (deviceManager.getCurrentAudioDevice())) +#if JUCE_IOS + if (auto device = dynamic_cast(deviceManager.getCurrentAudioDevice())) return device->isInterAppAudioConnected(); - #endif +#endif return false; } - Image getIAAHostIcon ([[maybe_unused]] int size) + Image getIAAHostIcon([[maybe_unused]] int size) { - #if JUCE_IOS - if (auto device = dynamic_cast (deviceManager.getCurrentAudioDevice())) - return device->getIcon (size); - #endif +#if JUCE_IOS + if (auto device = dynamic_cast(deviceManager.getCurrentAudioDevice())) + return device->getIcon(size); +#endif return {}; } @@ -376,7 +376,7 @@ class PlugDataWindow final : public DocumentWindow store its settings (it can also be null). If takeOwnershipOfSettings is true, then the settings object will be owned and deleted by this object. */ - PlugDataWindow(AudioProcessorEditor* pluginEditor) + explicit PlugDataWindow(AudioProcessorEditor* pluginEditor) : DocumentWindow("plugdata", LookAndFeel::getDefaultLookAndFeel().findColour(ResizableWindow::backgroundColourId), DocumentWindow::minimiseButton | DocumentWindow::maximiseButton | DocumentWindow::closeButton) , editor(pluginEditor) { @@ -536,7 +536,7 @@ class PlugDataWindow final : public DocumentWindow #endif } - bool useNativeTitlebar() + static bool useNativeTitlebar() { return SettingsFile::getInstance()->getProperty("native_window"); } @@ -566,22 +566,30 @@ class PlugDataWindow final : public DocumentWindow #if JUCE_LINUX || JUCE_BSD void paint(Graphics& g) override { - if (drawWindowShadow && !useNativeTitlebar() && !isFullScreen()) { + if (drawWindowShadow && !useNativeTitlebar() && !isMaximised()) { auto b = getLocalBounds(); Path localPath; - localPath.addRoundedRectangle(b.toFloat().reduced(22.0f), Corners::windowCornerRadius); + localPath.addRoundedRectangle(b.toFloat().reduced(18.0f), Corners::windowCornerRadius); - int radius = isActiveWindow() ? 22 : 17; - StackShadow::renderDropShadow(hash("plugdata_window"), g, localPath, Colour(0, 0, 0).withAlpha(0.6f), radius, { 0, 2 }); + float opacity = isActiveWindow() ? 0.42f : 0.20f; + StackShadow::renderDropShadow(hash("plugdata_window"), g, localPath, Colour(0, 0, 0).withAlpha(opacity), 17.0f, { 0, 0 }); } } -#elif JUCE_WINDOWS +#endif + +#if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD void paintOverChildren(Graphics& g) override { +#if JUCE_WINDOWS if (SystemStats::getOperatingSystemType() != SystemStats::Windows11) { g.setColour(findColour(PlugDataColour::outlineColourId)); g.drawRect(0, 0, getWidth(), getHeight()); } +#else + if(drawWindowShadow && !useNativeTitlebar() && !isMaximised()) { g.setColour(findColour(PlugDataColour::outlineColourId).withAlpha(isActiveWindow() ? 1.0f : 0.5f)); + g.drawRoundedRectangle(18, 18, getWidth() - 36, getHeight() - 36, Corners::windowCornerRadius, 1.0f); + } +#endif } #endif @@ -731,7 +739,7 @@ class PlugDataWindow final : public DocumentWindow #if JUCE_IOS // This is safe on iOS because we can't have multiple plugdata windows // We also hit an assertion on shutdown without this line - editor->processor.editorBeingDeleted (editor.getComponent()); + editor->processor.editorBeingDeleted(editor.getComponent()); #endif } } @@ -739,10 +747,9 @@ class PlugDataWindow final : public DocumentWindow #if JUCE_IOS void paint(Graphics& g) override { - if(editor) { + if (editor) { g.fillAll(editor->getLookAndFeel().findColour(PlugDataColour::toolbarBackgroundColourId)); - } - else { + } else { g.fillAll(findColour(PlugDataColour::toolbarBackgroundColourId)); } } diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index b9664e0e71..6755249c4a 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -266,19 +266,13 @@ class StatusbarTextButton final : public TextButton { }; class LatencyDisplayButton final : public Component - , public MultiTimer , public SettableTooltipClient { Label latencyValue; Label icon; - bool isHover = false; + bool isHovered = false; Colour bgColour; int currentLatencyValue = 0; - enum TimerRoutine { Timeout, - Animate }; - float alpha = 1.0f; - bool fading = false; - public: std::function onClick = [] { }; LatencyDisplayButton() @@ -310,35 +304,6 @@ class LatencyDisplayButton final : public Component buttonStateChanged(); } - void timerCallback(int const ID) override - { - switch (ID) { - case Timeout: - startTimer(Animate, 1000 / 30.0f); - break; - case Animate: - alpha = pow(alpha, 1.0f / 2.2f); - alpha -= 0.02f; - alpha = pow(alpha, 2.2f); - alpha = std::clamp(alpha, 0.0f, 1.0f); - alpha = std::isfinite(alpha) ? alpha : 0.0f; - fading = true; - if (alpha <= 0.01f) { - alpha = 0.0f; - stopTimer(Animate); - setVisible(false); - if(auto* parent = getParentComponent()) - { - parent->resized(); - } - } - buttonStateChanged(); - break; - default: - break; - } - } - void paint(Graphics& g) override { auto const b = getLocalBounds().reduced(1, 6).toFloat(); @@ -349,27 +314,11 @@ class LatencyDisplayButton final : public Component void setLatencyValue(int const value) { currentLatencyValue = value; - updateValue(); - if (value == 0) { - startTimer(Timeout, 1000 / 3.0f); - } else { - stopTimer(Timeout); - stopTimer(Animate); - fading = false; - setVisible(true); - alpha = 1.0f; - buttonStateChanged(); - } - } + setVisible(value != 0); + buttonStateChanged(); - void updateValue() - { - if (isHover && !fading) { - latencyValue.setJustificationType(Justification::centredLeft); - latencyValue.setText("Reset", dontSendNotification); - } else { - latencyValue.setJustificationType(Justification::centredRight); - latencyValue.setText(String(currentLatencyValue) + " smpl", dontSendNotification); + if (auto* parent = getParentComponent()) { + parent->resized(); } } @@ -382,25 +331,29 @@ class LatencyDisplayButton final : public Component void buttonStateChanged() { - bgColour = getLookAndFeel().findColour(isHover ? PlugDataColour::toolbarHoverColourId : PlugDataColour::toolbarActiveColourId).withAlpha(alpha); - auto const textColour = bgColour.contrasting().withAlpha(alpha); + bgColour = getLookAndFeel().findColour(isHovered ? PlugDataColour::toolbarHoverColourId : PlugDataColour::toolbarActiveColourId); + auto const textColour = bgColour.contrasting(); icon.setColour(Label::textColourId, textColour); latencyValue.setColour(Label::textColourId, textColour); - updateValue(); - - repaint(); + if (isHovered) { + latencyValue.setJustificationType(Justification::centredLeft); + latencyValue.setText("Reset", dontSendNotification); + } else { + latencyValue.setJustificationType(Justification::centredRight); + latencyValue.setText(String(currentLatencyValue) + " smpl", dontSendNotification); + } } void mouseEnter(MouseEvent const& e) override { - isHover = true; + isHovered = true; buttonStateChanged(); } void mouseExit(MouseEvent const& e) override { - isHover = false; + isHovered = false; buttonStateChanged(); } @@ -470,7 +423,7 @@ class VolumeSlider final : public Slider updatePopup(getMouseXYRelative()); } - void updatePopup(Point mousePosition) + void updatePopup(Point const mousePosition) { auto const value = getValue(); auto const thumbSize = getHeight() * 0.7f; @@ -898,6 +851,9 @@ class MIDIBlinker final : public Component void mouseUp(MouseEvent const& e) override { + if (!e.mods.isLeftButtonDown()) + return; + if (!isCallOutBoxActive) { auto midiLogger = std::make_unique(messages); auto* editor = findParentComponentOfClass(); @@ -1144,10 +1100,12 @@ class CPUMeter final : public Component void timerCallback() override { auto const lastCpuUsage = cpuUsage.last(); + auto const oldCpuUsage = cpuUsageToDraw; cpuUsageToDraw = round(lastCpuUsage); cpuUsageLongHistory.push(lastCpuUsage); updateCPUGraphLong(); - repaint(); + if (oldCpuUsage != cpuUsageToDraw) + repaint(); } void mouseDown(MouseEvent const& e) override @@ -1162,14 +1120,17 @@ class CPUMeter final : public Component void mouseUp(MouseEvent const& e) override { + if (!e.mods.isLeftButtonDown()) + return; + if (!isCallOutBoxActive) { auto cpuHistory = std::make_unique(cpuUsage, cpuUsageLongHistory); updateCPUGraph = cpuHistory->getUpdateFunc(); updateCPUGraphLong = cpuHistory->getUpdateFuncLongHistory(); cpuHistory->onClose = [this] { - updateCPUGraph = [] { return; }; - updateCPUGraphLong = [] { return; }; + updateCPUGraph = [] { }; + updateCPUGraphLong = [] { }; repaint(); }; @@ -1187,8 +1148,8 @@ class CPUMeter final : public Component updateCPUGraph(); } - std::function updateCPUGraph = [] { return; }; - std::function updateCPUGraphLong = [] { return; }; + std::function updateCPUGraph = [] { }; + std::function updateCPUGraphLong = [] { }; static inline SafePointer currentCalloutBox = nullptr; bool isCallOutBoxActive = false; @@ -1211,8 +1172,7 @@ class ZoomLabel final : public Component { private: void paint(Graphics& g) override { - // We can use a tabular numbers font here, but I'm not sure it really looks better that way - // g.setFont(Fonts::getTabularNumbersFont().withHeight(14)); + g.setFont(Fonts::getTabularNumbersFont().withHeight(14)); if (isEnabled()) { g.setColour(findColour(PlugDataColour::toolbarTextColourId).contrasting(isMouseOver() ? 0.35f : 0.0f)); } else { @@ -1401,7 +1361,7 @@ Statusbar::Statusbar(PluginProcessor* processor, PluginEditor* e) limiterButton = std::make_unique(); limiterButton->setButtonText("Limit"); - limiterButton->setToggleState(SettingsFile::getInstance()->getProperty("protected"), dontSendNotification); + limiterButton->setToggleState(pd->getEnableLimiter(), dontSendNotification); limiterButton->setClickingTogglesState(true); limiterButton->onStateChange = [this] { @@ -1590,10 +1550,10 @@ void Statusbar::resized() midiBlinker->setBounds(position(33, true) + 10, 0, 33, getHeight()); cpuMeter->setBounds(position(40, true), 0, 50, getHeight()); - if(latencyDisplayButton->isVisible()) { + if (latencyDisplayButton->isVisible()) { latencyDisplayButton->setBounds(position(104, true), 0, 100, getHeight()); } - + commandInputButton->setTopRightPosition(position(10, true), getHeight() * 0.5f - commandInputButton->getHeight() * 0.5f); } @@ -1619,8 +1579,7 @@ void Statusbar::setHasActiveCanvas(bool const hasActiveCanvas) centreButton.setEnabled(hasActiveCanvas); zoomComboButton.setEnabled(hasActiveCanvas); zoomLabel->setEnabled(hasActiveCanvas); - if(!hasActiveCanvas) - { + if (!hasActiveCanvas) { commandInputButton->setCommandButtonText(); } } @@ -1666,7 +1625,7 @@ void StatusbarSource::setBufferSize(int const bufferSize) this->bufferSize = bufferSize; } -void StatusbarSource::process(MidiBuffer const& midiInput, MidiBuffer const& midiOutput, int channels) +void StatusbarSource::process(MidiBuffer const& midiInput, MidiBuffer const& midiOutput) { for (auto event : midiOutput) lastMidiSent.enqueue(event.getMessage()); diff --git a/Source/Statusbar.h b/Source/Statusbar.h index 872c072426..715c4d868f 100644 --- a/Source/Statusbar.h +++ b/Source/Statusbar.h @@ -27,6 +27,7 @@ class StatusbarSource final : public Timer { public: struct Listener { + virtual ~Listener() = default; virtual void midiReceivedChanged(bool midiReceived) { ignoreUnused(midiReceived); } virtual void midiSentChanged(bool midiSent) { ignoreUnused(midiSent); } virtual void midiMessageReceived(MidiMessage const& message) { ignoreUnused(message); } @@ -39,7 +40,7 @@ class StatusbarSource final : public Timer { StatusbarSource(); - void process(MidiBuffer const& midiInput, MidiBuffer const& midiOutput, int outChannels); + void process(MidiBuffer const& midiInput, MidiBuffer const& midiOutput); void setSampleRate(double sampleRate); @@ -71,7 +72,7 @@ class StatusbarSource final : public Timer { lastPeak.resize(numChannels, 0); } - void write(AudioBuffer& samples) + void write(AudioBuffer const& samples) { for (int ch = 0; ch < std::min(sampleQueue.size(), samples.getNumChannels()); ch++) { int index = 0; @@ -120,7 +121,7 @@ class StatusbarSource final : public Timer { HeapArray>> sampleQueue; int maxBuffersPerFrame; }; - + AudioPeakMeter peakBuffer; private: diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index b304ac23ca..f1ea3de601 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -13,7 +13,8 @@ class TabComponent::TabBarButtonComponent final : public Component { - struct TabDragConstrainer final : public ComponentBoundsConstrainer { + class TabDragConstrainer final : public ComponentBoundsConstrainer { + public: explicit TabDragConstrainer(TabComponent* parent) : parent(parent) { @@ -22,7 +23,7 @@ class TabComponent::TabBarButtonComponent final : public Component { { bounds = bounds.withPosition(std::clamp(bounds.getX(), 30, parent->getWidth() - bounds.getWidth()), 0); } - + private: TabComponent* parent; }; @@ -201,7 +202,7 @@ class TabComponent::TabBarButtonComponent final : public Component { }); // Show the popup menu at the mouse position - auto position = e.getScreenPosition(); + auto const position = e.getScreenPosition(); tabMenu.showMenuAsync(PopupMenu::Options().withMinimumWidth(150).withMaximumNumColumns(1).withTargetComponent(this).withTargetScreenArea(Rectangle(position, position.translated(1, 1)))); } else if (cnv && e.originalComponent == this) { toFront(false); @@ -239,11 +240,11 @@ class TabComponent::TabBarButtonComponent final : public Component { SafePointer cnv; TabComponent* parent; ScaledImage tabImage; - bool isDragging = false; ComponentDragger dragger; TabDragConstrainer tabDragConstrainer; CloseTabButton closeButton = CloseTabButton(Icons::Clear); + bool isDragging : 1 = false; }; TabComponent::TabComponent(PluginEditor* editor) @@ -283,33 +284,68 @@ Canvas* TabComponent::newPatch() return openPatch(pd::Instance::defaultPatch); } -Canvas* TabComponent::openPatch(const URL& path) +void TabComponent::openHelpPatch(const URL& path) { - auto const patchFile = path.getLocalFile(); - - for (auto* editor : pd->getEditors()) { - for (auto* cnv : editor->getCanvases()) { - if (cnv->patch.getCurrentFile() == patchFile) { - pd->logError("Patch is already open"); - editor->getTopLevelComponent()->toFront(true); - editor->getTabComponent().showTab(cnv, cnv->patch.splitViewIndex); - editor->getTabComponent().setActiveSplit(cnv); - return cnv; - } - } - } - auto const patch = pd->loadPatch(path); - // If we're opening a temp file, assume it's dirty upon opening - // This is so that you can recover an autosave without directly overewriting it, but still be prompted to save if you close the autosaved patch - if(path.getLocalFile().getParentDirectory() == File::getSpecialLocation(File::tempDirectory)) - { - if(auto p = patch->getPointer()) { - canvas_dirty(p.get(), 1.0f); - } + if (auto p = patch->getPointer()) { + p->gl_edit = 0; } - return openPatch(patch, true); + + openPatch(patch, true); +} + +void TabComponent::openPatch(const URL& path) +{ + editor->pd->autosave->checkForMoreRecentAutosave(path, editor, [this](URL const& file, URL const& patchPath) { + auto checkQuarantine = [this](File const& f, std::function callback) { + if(OSUtils::isFileQuarantined(f)) + { + Dialogs::showMultiChoiceDialog(&editor->openedDialog, editor, "This patch was downloaded from the internet. Opening patches from untrusted sources may pose security risks. Do you want to proceed?" , [callback, f](int const choice) { + if (choice == 0) { + OSUtils::removeFromQuarantine(f); + callback(); + } }, { "Trust and Open", "Cancel" }, Icons::Warning); + } + else { + callback(); + } + }; + + auto const patchFile = file.getLocalFile(); + + for (auto* editor : pd->getEditors()) { + for (auto* cnv : editor->getCanvases()) { + if (cnv->patch.getCurrentFile() == patchFile) { + pd->logError("Patch is already open"); + editor->getTopLevelComponent()->toFront(true); + editor->getTabComponent().showTab(cnv, cnv->patch.splitViewIndex); + editor->getTabComponent().setActiveSplit(cnv); + } + } + } + + checkQuarantine(patchFile, [this, url = file, patchPath]() mutable { +#if JUCE_IOS + url.setBookmarkData(patchPath.getBookmarkData()); +#endif + auto const patch = pd->loadPatch(url); + + // If we're opening a temp file, assume it's dirty upon opening + // This is so that you can recover an autosave without directly overewriting it, but still be prompted to save if you close the autosaved patch + if(url.getLocalFile().getParentDirectory() == File::getSpecialLocation(File::tempDirectory)) + { + if(auto p = patch->getPointer()) { + canvas_dirty(p.get(), 1.0f); + } + } + + if(auto* cnv = openPatch(patch, true)) { + cnv->patch.setCurrentFile(patchPath); + } + SettingsFile::getInstance()->addToRecentlyOpened(patchPath); + }); + }); } Canvas* TabComponent::openPatch(String const& patchContent) @@ -350,7 +386,7 @@ Canvas* TabComponent::openPatch(pd::Patch::Ptr existingPatch, bool const warnIfA } auto* cnv = canvases.add(new Canvas(editor, existingPatch)); - + auto const patchTitle = existingPatch->getTitle(); // Open help files and references in Locked Mode if (patchTitle.contains("-help") || patchTitle.equalsIgnoreCase("reference")) @@ -377,20 +413,49 @@ void TabComponent::openPatch() Dialogs::showOpenDialog([this](URL resultURL) { auto result = resultURL.getLocalFile(); if (result.exists() && result.getFileExtension().equalsIgnoreCase(".pd")) { - editor->pd->autosave->checkForMoreRecentAutosave(resultURL, editor, [this](URL const& patchFile, URL const& patchPath) { - auto* cnv = openPatch(patchFile); - if(cnv) - { - cnv->patch.setCurrentFile(patchPath); - } - SettingsFile::getInstance()->addToRecentlyOpened(patchPath.getLocalFile()); - }); + openPatch(resultURL); } }, true, false, "*.pd", "Patch", this); } -void TabComponent::moveToLeftSplit(TabBarButtonComponent* tab) +#if JUCE_IOS +void TabComponent::openPatchFolder() +{ + Dialogs::showOpenDialog([this](URL resultURL) { + auto result = resultURL.getLocalFile(); + HeapArray pdFiles; + StringArray pdFileNames; + + for(auto file : OSUtils::iterateDirectory(result, false, false)) + { + if(file.hasFileExtension("pd")) + { + pdFiles.add(file); + pdFileNames.add(file.getFileName()); + } + } + + if(pdFiles.size() == 1) + { + auto patchURL = URL(pdFiles[0]); + patchURL.setBookmarkData(resultURL.getBookmarkData()); + openPatch(patchURL); + } + else if(pdFiles.size() != 0){ + OSUtils::showMobileChoiceMenu(editor->getPeer(), pdFileNames, [this, pdFiles, resultURL](int choice){ + if(choice < 0) return; + auto patchURL = URL(pdFiles[choice]); + patchURL.setBookmarkData(resultURL.getBookmarkData()); + openPatch(patchURL); + }); + } + }, + false, true, "", "PatchFolder", this); +} +#endif + +void TabComponent::moveToLeftSplit(TabBarButtonComponent const* tab) { if (tab->parent != this) // Move to another window { @@ -452,7 +517,7 @@ void TabComponent::moveToLeftSplit(TabBarButtonComponent* tab) } } -void TabComponent::moveToRightSplit(TabBarButtonComponent* tab) +void TabComponent::moveToRightSplit(TabBarButtonComponent const* tab) { if (tab->parent != this) // Move to another window { @@ -605,7 +670,7 @@ void TabComponent::handleAsyncUpdate() { if (canvases.isEmpty() && pd->getEditors().size() > 1) { bool editorHasPatches = false; - for (auto& patch : pd->patches) { + for (auto const& patch : pd->patches) { if (patch->windowIndex == editor->editorIndex) editorHasPatches = true; } @@ -636,7 +701,17 @@ void TabComponent::handleAsyncUpdate() for (auto* cnv : getCanvases()) { cnv->saveViewportState(); } - + + UnorderedMap> oldTabBounds; + for(auto& tabbar : tabbars) + { + for(auto* tab : tabbar) + { + oldTabBounds.insert({tab->cnv, tab->getBounds()}); + } + } + animateTabs = oldTabBounds.size() > 0; + tabbars[0].clear(); tabbars[1].clear(); @@ -646,6 +721,7 @@ void TabComponent::handleAsyncUpdate() if (patchInPluginMode->windowIndex == editorIndex) { // Initialise plugin mode clearCanvases(); + editor->showWelcomePanel(false); if (!editor->isInPluginMode() || editor->pluginMode->getPatch()->getPointer().get() != patchInPluginMode->getUncheckedPointer()) { editor->pluginMode = std::make_unique(editor, patchInPluginMode); } @@ -692,6 +768,13 @@ void TabComponent::handleAsyncUpdate() // Create tab buttons auto* newTabButton = new TabBarButtonComponent(cnv, this); + if(oldTabBounds.contains(cnv)) { + newTabButton->setBounds(oldTabBounds[cnv]); + } + else { + newTabButton->setBounds(getWidth(), 0, 0, 30); + } + tabbars[patch->splitViewIndex == 1].add(newTabButton); addAndMakeVisible(newTabButton); } @@ -701,7 +784,6 @@ void TabComponent::handleAsyncUpdate() // Show welcome panel if there are no tabs if (tabbars[0].size() == 0 && tabbars[1].size() == 0) { editor->showWelcomePanel(true); - editor->nvgSurface.renderAll(); editor->resized(); editor->parentSizeChanged(); } else { @@ -795,9 +877,9 @@ void TabComponent::showTab(Canvas* cnv, int const splitIndex) splits[splitIndex] = cnv; if (cnv) { + editor->showWelcomePanel(false); addAndMakeVisible(cnv->viewport.get()); cnv->setVisible(true); - cnv->grabKeyboardFocus(); cnv->patch.splitViewIndex = splitIndex; activeSplitIndex = splitIndex; } @@ -805,8 +887,6 @@ void TabComponent::showTab(Canvas* cnv, int const splitIndex) resized(); repaint(); - editor->nvgSurface.invalidateAll(); - tabVisibilityMessageUpdater.triggerAsyncUpdate(); editor->sidebar->hideParameters(); @@ -911,6 +991,9 @@ void TabComponent::resized() { auto const isSplit = splits[1] != nullptr; auto bounds = getLocalBounds(); + bool boundsChanged = lastBounds != bounds; + lastBounds = bounds; + auto tabbarBounds = bounds.removeFromTop(30); auto& animator = Desktop::getInstance().getAnimator(); @@ -931,7 +1014,6 @@ void TabComponent::resized() } bool wasOverflown = false; - for (auto* tabButton : tabButtons) { if (tabWidth > splitBounds.getWidth()) { wasOverflown = true; @@ -941,7 +1023,7 @@ void TabComponent::resized() continue; } tabButton->setVisible(true); - + auto targetBounds = splitBounds.removeFromLeft(tabWidth); if (tabButton->isDragging) { tabButton->setSize(tabWidth, 30); @@ -951,11 +1033,7 @@ void TabComponent::resized() continue; // We reserve space for it, but don't set the bounds to create a ghost tab } - if (draggingOverTabbar) { - animator.animateComponent(tabButton, targetBounds, 1.0f, 200, false, 3.0, 0.0); - } else { - tabButton->setBounds(targetBounds); - } + animator.animateComponent(tabButton, targetBounds, 1.0f, (boundsChanged || !animateTabs) ? 0 : 200, false, 4.0, 0.5); } tabOverflowButtons[i].setVisible(wasOverflown); @@ -1060,7 +1138,7 @@ void TabComponent::addLastShownTab(Canvas* tab, int const split) lastShownTabs[split].add(tab); } -Canvas* TabComponent::getLastShownTab(Canvas* current, int const split) +Canvas* TabComponent::getLastShownTab(Canvas const* current, int const split) { Canvas* lastShownTab = nullptr; for (auto it = lastShownTabs[split].rbegin(); it != lastShownTabs[split].rend(); ++it) { @@ -1077,7 +1155,7 @@ Canvas* TabComponent::getLastShownTab(Canvas* current, int const split) return lastShownTab; } -void TabComponent::sendTabUpdateToVisibleCanvases() +void TabComponent::sendTabUpdateToVisibleCanvases() const { for (auto* editorWindow : pd->getEditors()) { for (auto* cnv : editorWindow->getTabComponent().getVisibleCanvases()) { @@ -1147,7 +1225,7 @@ void TabComponent::setActiveSplit(Canvas* cnv) } } -Canvas* TabComponent::getCanvasAtScreenPosition(Point screenPosition) +Canvas* TabComponent::getCanvasAtScreenPosition(Point const screenPosition) { auto const localPoint = getLocalPoint(nullptr, screenPosition); if (!getLocalBounds().contains(localPoint)) @@ -1194,7 +1272,7 @@ void TabComponent::itemDropped(SourceDetails const& dragSourceDetails) return; } - if (auto* tab = dynamic_cast(dragSourceDetails.sourceComponent.get())) { + if (auto const* tab = dynamic_cast(dragSourceDetails.sourceComponent.get())) { if (getLocalBounds().removeFromRight(getWidth() - splitSize).contains(dragSourceDetails.localPosition) && (dragSourceDetails.localPosition.y > 30 || splits[1])) // Dragging to right split { moveToRightSplit(tab); diff --git a/Source/TabComponent.h b/Source/TabComponent.h index 15a6aaa0f0..07db794f30 100644 --- a/Source/TabComponent.h +++ b/Source/TabComponent.h @@ -17,10 +17,16 @@ class TabComponent final : public Component Canvas* newPatch(); - Canvas* openPatch(const URL& path); + void openHelpPatch(const URL& path); + void openPatch(const URL& path); + Canvas* openPatch(String const& patchContent); Canvas* openPatch(pd::Patch::Ptr existingPatch, bool warnIfAlreadyOpen = false); void openPatch(); + +#if JUCE_IOS + void openPatchFolder(); +#endif void openInPluginMode(pd::Patch::Ptr patch); @@ -53,12 +59,12 @@ class TabComponent final : public Component void clearCanvases(); void handleAsyncUpdate() override; - void sendTabUpdateToVisibleCanvases(); + void sendTabUpdateToVisibleCanvases() const; void resized() override; - void moveToLeftSplit(TabComponent::TabBarButtonComponent* tab); - void moveToRightSplit(TabComponent::TabBarButtonComponent* tab); + void moveToLeftSplit(TabComponent::TabBarButtonComponent const* tab); + void moveToRightSplit(TabComponent::TabBarButtonComponent const* tab); void saveTabPositions(); void closeEmptySplits(); @@ -75,7 +81,7 @@ class TabComponent final : public Component void mouseMove(MouseEvent const& e) override; void addLastShownTab(Canvas* tab, int split); - Canvas* getLastShownTab(Canvas* current, int split); + Canvas* getLastShownTab(Canvas const* current, int split); void showHiddenTabsMenu(int splitIndex); @@ -92,7 +98,9 @@ class TabComponent final : public Component bool draggingOverTabbar = false; bool draggingSplitResizer = false; + bool animateTabs = false; Rectangle splitDropBounds; + Rectangle lastBounds; float splitProportion = 2; int splitSize = 0; @@ -103,13 +111,15 @@ class TabComponent final : public Component PluginEditor* editor; PluginProcessor* pd; - struct TabVisibilityMessageUpdater final : public AsyncUpdater { + class TabVisibilityMessageUpdater final : public AsyncUpdater { + public: explicit TabVisibilityMessageUpdater(TabComponent* parent) : parent(parent) { } void handleAsyncUpdate() override; + private: TabComponent* parent; }; diff --git a/Source/Utility/Autosave.h b/Source/Utility/Autosave.h index 29b82282c7..35cb79023c 100644 --- a/Source/Utility/Autosave.h +++ b/Source/Utility/Autosave.h @@ -7,8 +7,8 @@ class Autosave final : public Timer , public AsyncUpdater , public Value::Listener { - static inline File const autoSaveFile = ProjectInfo::appDataDir.getChildFile(".autosave"); - static inline ValueTree autoSaveTree = ValueTree("Autosave"); + static inline auto const autoSaveFile = ProjectInfo::appDataDir.getChildFile(".autosave"); + static inline auto autoSaveTree = ValueTree("Autosave"); Value autosaveInterval; Value autosaveEnabled; @@ -53,16 +53,14 @@ class Autosave final : public Timer MemoryOutputStream ostream; Base64::convertFromBase64(ostream, lastAutoSavedPatch.getProperty("Patch").toString()); auto const autosavedPatch = String::fromUTF8(static_cast(ostream.getData()), ostream.getDataSize()); - + glob_forcefilename(editor->pd->generateSymbol(patchPath.getFileName().toRawUTF8()), editor->pd->generateSymbol(patchPath.getParentDirectory().getFullPathName().replaceCharacter('\\', '/').toRawUTF8())); - auto patchFile = File::createTempFile(".pd"); + auto const patchFile = File::createTempFile(".pd"); patchFile.replaceWithText(autosavedPatch); callback(URL(patchFile), patchUrl); - } - else { + } else { callback(URL(patchPath), patchUrl); } - }, { "Yes", "No" }); } else { @@ -90,16 +88,17 @@ class Autosave final : public Timer return; auto patches = pd->patches; + // Hold patches in lambda capture so refcount can't become 0 (?) pd->enqueueFunctionAsync([_this = WeakReference(this), patches] { if (_this) { _this->pd->lockAudioThread(); - _this->save(patches); + _this->save(); _this->pd->unlockAudioThread(); } }); } - void save(SmallArray const& patches) + void save() { for (auto const& patch : pd->patches) { auto const* patchPtr = patch->getPointer().get(); @@ -183,7 +182,8 @@ class Autosave final : public Timer }; class AutosaveHistoryComponent final : public Component { - struct AutoSaveHistory final : public Component { + class AutoSaveHistory final : public Component { + public: AutoSaveHistory(PluginEditor* editor, ValueTree autoSaveTree) { patchPath = autoSaveTree.getProperty("Path").toString(); @@ -245,7 +245,8 @@ class AutosaveHistoryComponent final : public Component { TextButton openPatch = TextButton("Open"); }; - struct ContentComponent final : public Component { + class ContentComponent final : public Component { + public: explicit ContentComponent(PluginEditor* editor) { for (auto const child : Autosave::autoSaveTree) { diff --git a/Source/Utility/CachedStringWidth.h b/Source/Utility/CachedStringWidth.h index d925be50b4..7d515d38e0 100644 --- a/Source/Utility/CachedStringWidth.h +++ b/Source/Utility/CachedStringWidth.h @@ -13,8 +13,7 @@ struct CachedStringWidth { { auto const stringHash = hash(singleLine); - auto const cacheHit = stringWidthCache.find(stringHash); - if (cacheHit != stringWidthCache.end()) + if (auto const cacheHit = stringWidthCache.find(stringHash); cacheHit != stringWidthCache.end()) return cacheHit->second; auto const stringWidth = Font(FontSize).getStringWidth(singleLine); @@ -38,7 +37,7 @@ struct CachedStringWidth { stringWidthCache.clear(); } - static inline UnorderedMap stringWidthCache = UnorderedMap(); + static inline auto stringWidthCache = UnorderedMap(); }; struct CachedFontStringWidth final : public DeletedAtShutdown { @@ -53,8 +52,7 @@ struct CachedFontStringWidth final : public DeletedAtShutdown { for (auto& [cachedFont, cache] : stringWidthCache) { if (cachedFont == font) { - auto const cacheHit = cache.find(stringHash); - if (cacheHit != cache.end()) + if (auto const cacheHit = cache.find(stringHash); cacheHit != cache.end()) return cacheHit->second; auto const stringWidth = font.getStringWidthFloat(singleLine); diff --git a/Source/Utility/CachedTextRender.h b/Source/Utility/CachedTextRender.h index b2788da710..6d61f5d19c 100644 --- a/Source/Utility/CachedTextRender.h +++ b/Source/Utility/CachedTextRender.h @@ -15,15 +15,15 @@ class CachedTextRender { } NVGScopedState scopedState(nvg); - + nvgScale(nvg, 1.0f / scale, 1.0f / scale); nvgTranslate(nvg, roundToInt(bounds.getX() * scale), roundToInt((bounds.getY() - 1) * scale)); - + // Since JUCE text was calculated on a pixel grid, we need to make sure that we also display the text on a whole pixel grid nvgTransformQuantize(nvg); - - int imageW = roundToInt(bounds.getWidth() * scale) + 1; - int imageH = roundToInt(bounds.getHeight() * scale) + 1; + + int const imageW = roundToInt(bounds.getWidth() * scale) + 1; + int const imageH = roundToInt(bounds.getHeight() * scale) + 1; nvgIntersectScissor(nvg, 0, 0, imageW, imageH); auto const imagePattern = isSyntaxHighlighted ? nvgImagePattern(nvg, 0, 0, imageW, imageH, 0, image.getImageId(), 1.0f) : nvgImageAlphaPattern(nvg, 0, 0, imageW, imageH, 0, image.getImageId(), NVGComponent::convertColour(lastColour)); @@ -43,7 +43,8 @@ class CachedTextRender { bool firstToken = true; bool hadFlag = false; bool mathExpression = false; - for(auto& line : lines) { + for (int i = 0; i < lines.size(); i++) { + auto& line = lines[i]; auto tokens = StringArray::fromTokens(line, true); for (int i = 0; i < tokens.size(); i++) { auto token = tokens[i]; @@ -66,7 +67,8 @@ class CachedTextRender { attributedText.append(token, font, colour); } } - attributedText.append("\n", font, colour); + if(i != lines.size() - 1) + attributedText.append("\n", font, colour); } return attributedText; @@ -119,17 +121,16 @@ class CachedTextRender { Point offset; { NVGScopedState scopedState(nvg); - + nvgScale(nvg, 1.0f / scale, 1.0f / scale); nvgTranslate(nvg, roundToInt(bounds.getX() * scale), roundToInt(bounds.getY() * scale)); nvgTransformGetSubpixelOffset(nvg, &offset.x, &offset.y); } - + image = NVGImage(nvg, width, height, [this, bounds, scale, offset](Graphics& g) { g.addTransform(AffineTransform::translation(offset.x, offset.y)); g.addTransform(AffineTransform::scale(scale, scale)); - layout.draw(g, bounds.withZeroOrigin()); - }, isSyntaxHighlighted ? 0 : NVGImage::AlphaImage); + layout.draw(g, bounds.withZeroOrigin()); }, isSyntaxHighlighted ? 0 : NVGImage::AlphaImage); } Rectangle getTextBounds() diff --git a/Source/Utility/Config.h b/Source/Utility/Config.h index 894d4acffa..81693fe09b 100644 --- a/Source/Utility/Config.h +++ b/Source/Utility/Config.h @@ -23,8 +23,8 @@ struct ProjectInfo { static bool isStandalone; static bool isFx; - static inline char const* companyName = "plugdata"; - static inline char const* versionString = PLUGDATA_VERSION; + static inline auto const* companyName = "plugdata"; + static inline auto const* versionString = PLUGDATA_VERSION; static AudioDeviceManager* getDeviceManager(); diff --git a/Source/Utility/Containers.h b/Source/Utility/Containers.h index e89005e8b1..c9fd0cfd41 100644 --- a/Source/Utility/Containers.h +++ b/Source/Utility/Containers.h @@ -37,6 +37,10 @@ # define GSL_OWNER #endif +#ifndef __has_builtin +# define __has_builtin(x) 0 +#endif + #if __has_builtin(__builtin_expect) || defined(__GNUC__) # define EXPECT_LIKELY(EXPR) __builtin_expect((bool)(EXPR), true) # define EXPECT_UNLIKELY(EXPR) __builtin_expect((bool)(EXPR), false) @@ -45,10 +49,10 @@ # define EXPECT_UNLIKELY(EXPR) (EXPR) #endif -#if __has_attribute(returns_nonnull) -# define ATTRIBUTE_RETURNS_NONNULL __attribute__((returns_nonnull)) -#elif defined(_MSC_VER) +#if defined(_MSC_VER) # define ATTRIBUTE_RETURNS_NONNULL _Ret_notnull_ +#elif __has_attribute(returns_nonnull) +# define ATTRIBUTE_RETURNS_NONNULL __attribute__((returns_nonnull)) #else # define LLVM_ATTRIBUTE_RETURNS_NONNULL #endif @@ -68,9 +72,9 @@ template class iterator_range; template -using EnableIfConvertibleToInputIterator = std::enable_if_t::iterator_category, - std::input_iterator_tag>::value>; + std::input_iterator_tag>>; /// This is all the stuff common to all SmallArrays. /// @@ -102,13 +106,13 @@ class SmallArrayBase { /// This is a helper for \a grow() that's out of line to reduce code /// duplication. This function will report a fatal error if it can't grow at /// least to \p MinSize. - void* mallocForGrow(void* FirstEl, size_t MinSize, size_t TSize, + void* mallocForGrow(void const* FirstEl, size_t MinSize, size_t TSize, size_t& NewCapacity); /// This is an implementation of the grow() method which only works /// on POD-like data types and is out of line to reduce code duplication. /// This function will report a fatal error if it cannot increase capacity. - void grow_pod(void* FirstEl, size_t MinSize, size_t TSize); + void grow_pod(void const* FirstEl, size_t MinSize, size_t TSize); public: size_t size() const { return Size; } @@ -172,14 +176,21 @@ class SmallArrayTemplateCommon } // Space after 'FirstEl' is clobbered, do not add any instance vars after it. - SmallArrayTemplateCommon(size_t Size) + explicit SmallArrayTemplateCommon(size_t Size) : Base(getFirstEl(), Size) { } void grow_pod(size_t MinSize, size_t TSize) { +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif Base::grow_pod(getFirstEl(), MinSize, TSize); +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif } /// Return true if this is a SmallArray which has not had dynamic @@ -197,7 +208,7 @@ class SmallArrayTemplateCommon static bool isReferenceToRange(void const* V, void const* First, void const* Last) { // Use std::less to avoid UB. - std::less<> LessThan; + std::less<> constexpr LessThan; return !LessThan(V, First) && LessThan(V, Last); } @@ -256,7 +267,7 @@ class SmallArrayTemplateCommon } template< class ItTy, - std::enable_if_t, T*>::value, + std::enable_if_t, T*>, bool> = false> static void assertSafeToReferenceAfterClear(ItTy, ItTy) { } @@ -271,7 +282,7 @@ class SmallArrayTemplateCommon } template< class ItTy, - std::enable_if_t, T*>::value, + std::enable_if_t, T*>, bool> = false> static void assertSafeToAddRange(ItTy, ItTy) { } @@ -384,7 +395,7 @@ class SmallArrayTemplateCommon /// copy these types with memcpy, there is no way for the type to observe this. /// This catches the important case of std::pair, which is not /// trivially assignable. -template::value && std::is_trivially_move_constructible::value && std::is_trivially_destructible::value> +template && std::is_trivially_move_constructible_v && std::is_trivially_destructible_v> class SmallArrayTemplateBase : public SmallArrayTemplateCommon { friend class SmallArrayTemplateCommon; @@ -392,7 +403,7 @@ class SmallArrayTemplateBase : public SmallArrayTemplateCommon { static constexpr bool TakesParamByValue = false; using ValueParamT = T const&; - SmallArrayTemplateBase(size_t Size) + explicit SmallArrayTemplateBase(size_t Size) : SmallArrayTemplateCommon(Size) { } @@ -560,7 +571,7 @@ class SmallArrayTemplateBase : public SmallArrayTemplateCommon { /// parameters by value. using ValueParamT = std::conditional_t; - SmallArrayTemplateBase(size_t Size) + explicit SmallArrayTemplateBase(size_t Size) : SmallArrayTemplateCommon(Size) { } @@ -591,7 +602,7 @@ class SmallArrayTemplateBase : public SmallArrayTemplateCommon { template static void uninitialized_copy( T1* I, T1* E, T2* Dest, - std::enable_if_t, T2>::value>* = nullptr) + std::enable_if_t, T2>>* = nullptr) { // Use memcpy for PODs iterated by pointers (which includes SmallArray // iterators): std::uninitialized_copy optimizes to memmove, but we can @@ -928,7 +939,7 @@ class SmallArrayImpl : public SmallArrayTemplateBase { return Result; } - void swap(SmallArrayImpl& RHS); + void swap(SmallArrayImpl& RHS) noexcept; /// Add the specified range to the end of the SmallArray. template> @@ -1030,8 +1041,8 @@ class SmallArrayImpl : public SmallArrayTemplateBase { { // Callers ensure that ArgType is derived from T. static_assert( - std::is_same>, - T>::value, + std::is_same_v>, + T>, "ArgType must be derived from T!"); if (I == this->end()) { // Important special case for empty vector. @@ -1053,7 +1064,7 @@ class SmallArrayImpl : public SmallArrayTemplateBase { // If we just moved the element we're inserting, be sure to update // the reference (never happens if TakesParamByValue). - static_assert(!TakesParamByValue || std::is_same::value, + static_assert(!TakesParamByValue || std::is_same_v, "ArgType must be 'T' when taking by value!"); if (!TakesParamByValue && this->isReferenceToRange(EltPtr, I, this->end())) ++EltPtr; @@ -1217,9 +1228,9 @@ class SmallArrayImpl : public SmallArrayTemplateBase { return this->back(); } - SmallArrayImpl& operator=(SmallArrayImpl const& RHS); + SmallArrayImpl& operator=(SmallArrayImpl const& RHS) noexcept; - SmallArrayImpl& operator=(SmallArrayImpl&& RHS); + SmallArrayImpl& operator=(SmallArrayImpl&& RHS) noexcept; bool operator==(SmallArrayImpl const& RHS) const { @@ -1243,7 +1254,7 @@ class SmallArrayImpl : public SmallArrayTemplateBase { }; template -void SmallArrayImpl::swap(SmallArrayImpl& RHS) +void SmallArrayImpl::swap(SmallArrayImpl& RHS) noexcept { if (this == &RHS) return; @@ -1283,7 +1294,7 @@ void SmallArrayImpl::swap(SmallArrayImpl& RHS) template SmallArrayImpl& SmallArrayImpl:: -operator=(SmallArrayImpl const& RHS) +operator=(SmallArrayImpl const& RHS) noexcept { // Avoid self-assignment. if (this == &RHS) @@ -1332,7 +1343,7 @@ operator=(SmallArrayImpl const& RHS) } template -SmallArrayImpl& SmallArrayImpl::operator=(SmallArrayImpl&& RHS) +SmallArrayImpl& SmallArrayImpl::operator=(SmallArrayImpl&& RHS) noexcept { // Avoid self-assignment. if (this == &RHS) @@ -1537,7 +1548,7 @@ class GSL_OWNER SmallArray : public SmallArrayImpl return *this; } - SmallArray(SmallArray&& RHS) + SmallArray(SmallArray&& RHS) noexcept : SmallArrayImpl(N) { if (!RHS.empty()) @@ -1551,7 +1562,7 @@ class GSL_OWNER SmallArray : public SmallArrayImpl SmallArrayImpl::operator=(::std::move(RHS)); } - SmallArray& operator=(SmallArray&& RHS) + SmallArray& operator=(SmallArray&& RHS) noexcept { if (N) { SmallArrayImpl::operator=(::std::move(RHS)); @@ -1584,7 +1595,7 @@ class GSL_OWNER SmallArray : public SmallArrayImpl }; template -inline size_t capacity_in_bytes(SmallArray const& X) +size_t capacity_in_bytes(SmallArray const& X) { return X.capacity_in_bytes(); } @@ -1629,16 +1640,16 @@ namespace std { /// Implement std::swap in terms of SmallArray swap. template -inline void -swap(SmallArrayImpl& LHS, SmallArrayImpl& RHS) +void +swap(SmallArrayImpl& LHS, SmallArrayImpl& RHS) noexcept { LHS.swap(RHS); } /// Implement std::swap in terms of SmallArray swap. template -inline void -swap(SmallArray& LHS, SmallArray& RHS) +void +swap(SmallArray& LHS, SmallArray& RHS) noexcept { LHS.swap(RHS); } @@ -1739,7 +1750,7 @@ static void* replaceAllocation(void* NewElts, size_t const TSize, size_t const N // Note: Moving this function into the header may cause performance regression. template -void* SmallArrayBase::mallocForGrow(void* FirstEl, size_t const MinSize, +void* SmallArrayBase::mallocForGrow(void const* FirstEl, size_t const MinSize, size_t const TSize, size_t& NewCapacity) { @@ -1754,7 +1765,7 @@ void* SmallArrayBase::mallocForGrow(void* FirstEl, size_t const MinSize, // Note: Moving this function into the header may cause performance regression. template -void SmallArrayBase::grow_pod(void* FirstEl, size_t const MinSize, +void SmallArrayBase::grow_pod(void const* FirstEl, size_t const MinSize, size_t const TSize) { size_t const NewCapacity = getNewCapacity(MinSize, TSize, this->capacity()); @@ -1805,7 +1816,7 @@ class HeapArray { { } - HeapArray(size_t size) + explicit HeapArray(size_t size) : data_(size) { } @@ -1961,12 +1972,7 @@ class HeapArray { std::sort(data_.begin(), data_.end(), sort_fn); } - void sort(int (*sort_fn)(T const, T const)) - { - std::sort(data_.begin(), data_.end(), sort_fn); - } - - void sort(std::function sort_fn) + void sort(int (*sort_fn)(T, T)) { std::sort(data_.begin(), data_.end(), sort_fn); } @@ -2034,7 +2040,7 @@ class StackArray { std::array data_; - size_t size() const { return N; } + static size_t size() { return N; } T& operator[](size_t index) { @@ -2152,7 +2158,7 @@ class StackArray { std::sort(data_.begin(), data_.end(), sort_fn); } - void sort(int (*sort_fn)(T const, T const)) + void sort(int (*sort_fn)(T, T)) { std::sort(data_.begin(), data_.end(), sort_fn); } @@ -2162,7 +2168,7 @@ class StackArray { std::sort(data_.begin(), data_.end(), sort_fn); } - void sort(std::function sort_fn) + void sort(std::function sort_fn) { std::sort(data_.begin(), data_.end(), sort_fn); } @@ -2294,7 +2300,7 @@ class PooledPtrArray { std::sort(data_.begin(), data_.end(), sort_fn); } - void sort(int (*sort_fn)(T const, T const)) + void sort(int (*sort_fn)(T, T)) { std::sort(data_.begin(), data_.end(), sort_fn); } @@ -2424,7 +2430,7 @@ class PooledPtrArray { // Only initialise stack buffer if template struct StorageSelector { - using type = typename std::aligned_storage::type; + using type = std::aligned_storage_t; }; template @@ -2494,16 +2500,16 @@ struct IsPointerLike { // Provide PointerLikeTypeTraits for non-cvr pointers. template struct PointerLikeTypeTraits { - static inline void* getAsVoidPointer(T* P) { return P; } - static inline T* getFromVoidPointer(void* P) { return static_cast(P); } + static void* getAsVoidPointer(T* P) { return P; } + static T* getFromVoidPointer(void* P) { return static_cast(P); } static constexpr int NumLowBitsAvailable = ConstantLog2::value; }; template<> struct PointerLikeTypeTraits { - static inline void* getAsVoidPointer(void* P) { return P; } - static inline void* getFromVoidPointer(void* P) { return P; } + static void* getAsVoidPointer(void* P) { return P; } + static void* getFromVoidPointer(void* P) { return P; } /// Note, we assume here that void* is related to raw malloc'ed memory and /// that malloc returns objects at least 4-byte aligned. However, this may be @@ -2520,11 +2526,11 @@ template struct PointerLikeTypeTraits { typedef PointerLikeTypeTraits NonConst; - static inline void const* getAsVoidPointer(T const P) + static void const* getAsVoidPointer(T const P) { return NonConst::getAsVoidPointer(P); } - static inline T getFromVoidPointer(void const* P) + static T getFromVoidPointer(void const* P) { return NonConst::getFromVoidPointer(const_cast(P)); } @@ -2536,11 +2542,11 @@ template struct PointerLikeTypeTraits { typedef PointerLikeTypeTraits NonConst; - static inline void const* getAsVoidPointer(T const* P) + static void const* getAsVoidPointer(T const* P) { return NonConst::getAsVoidPointer(const_cast(P)); } - static inline T const* getFromVoidPointer(void const* P) + static T const* getFromVoidPointer(void const* P) { return NonConst::getFromVoidPointer(const_cast(P)); } @@ -2550,11 +2556,11 @@ struct PointerLikeTypeTraits { // Provide PointerLikeTypeTraits for uintptr_t. template<> struct PointerLikeTypeTraits { - static inline void* getAsVoidPointer(uintptr_t const P) + static void* getAsVoidPointer(uintptr_t const P) { return reinterpret_cast(P); } - static inline uintptr_t getFromVoidPointer(void* P) + static uintptr_t getFromVoidPointer(void* P) { return reinterpret_cast(P); } @@ -2573,12 +2579,12 @@ struct PointerLikeTypeTraits { template struct FunctionPointerLikeTypeTraits { static constexpr int NumLowBitsAvailable = ConstantLog2::value; - static inline void* getAsVoidPointer(FunctionPointerT P) + static void* getAsVoidPointer(FunctionPointerT P) { assert((reinterpret_cast(P) & ~(static_cast(-1) << NumLowBitsAvailable)) == 0 && "Alignment not satisfied for an actual function pointer!"); return reinterpret_cast(P); } - static inline FunctionPointerT getFromVoidPointer(void* P) + static FunctionPointerT getFromVoidPointer(void* P) { return reinterpret_cast(P); } @@ -2601,9 +2607,9 @@ struct PunnedPointer { // Asserts that allow us to let the compiler implement the destructor and // copy/move constructors - static_assert(std::is_trivially_destructible::value, ""); - static_assert(std::is_trivially_copy_constructible::value, ""); - static_assert(std::is_trivially_move_constructible::value, ""); + static_assert(std::is_trivially_destructible_v, ""); + static_assert(std::is_trivially_copy_constructible_v, ""); + static_assert(std::is_trivially_move_constructible_v, ""); explicit constexpr PunnedPointer(intptr_t i = 0) { *this = i; } @@ -2791,7 +2797,7 @@ struct PointerIntPairInfo { static intptr_t updateInt(intptr_t OrigValue, intptr_t const Int) { - intptr_t IntWord = static_cast(Int); + intptr_t IntWord = Int; assert((IntWord & ~IntMask) == 0 && "Integer too large for field"); // Preserve all bits other than the ones we are updating. @@ -2803,19 +2809,19 @@ template struct PointerLikeTypeTraits< PointerIntPair> { - static inline void* + static void* getAsVoidPointer(PointerIntPair const& P) { return P.getOpaqueValue(); } - static inline PointerIntPair + static PointerIntPair getFromVoidPointer(void* P) { return PointerIntPair::getFromOpaqueValue(P); } - static inline PointerIntPair + static PointerIntPair getFromVoidPointer(void const* P) { return PointerIntPair::getFromOpaqueValue(P); @@ -3083,7 +3089,7 @@ class StackString { return data_; } - StackString operator+(StackString const& rhs) + StackString operator+(StackString const& rhs) { auto result = *this; // Copy current object result.getArray().insert(result.getArray().end(), rhs.data_.begin(), rhs.data_.end()); diff --git a/Source/Utility/Decompress.h b/Source/Utility/Decompress.h index abfe12eb3b..6860f94414 100644 --- a/Source/Utility/Decompress.h +++ b/Source/Utility/Decompress.h @@ -22,7 +22,7 @@ namespace fs = ghc::filesystem; struct Decompress { - static bool extractXz(uint8_t const* data, int dataSize, HeapArray& decompressedData) + static bool extractXz(uint8_t const* data, int const dataSize, HeapArray& decompressedData) { lzma_stream strm = LZMA_STREAM_INIT; if (lzma_stream_decoder(&strm, UINT64_MAX, 0) != LZMA_OK) { @@ -31,16 +31,15 @@ struct Decompress strm.next_in = data; strm.avail_in = dataSize; - + uint8_t buffer[8192]; lzma_ret ret; - do { strm.next_out = buffer; strm.avail_out = sizeof(buffer); ret = lzma_code(&strm, LZMA_FINISH); - size_t written = sizeof(buffer) - strm.avail_out; + size_t const written = sizeof(buffer) - strm.avail_out; decompressedData.insert(decompressedData.end(), buffer, buffer + written); if (ret != LZMA_OK && ret != LZMA_STREAM_END) { @@ -145,7 +144,7 @@ struct Decompress #if !JUCE_WINDOWS // Get file permissions - fs::perms permissions = fs::perms::none; + auto permissions = fs::perms::none; mode_t mode = static_cast( std::strtoul(reinterpret_cast(header + 100), nullptr, 8) ); @@ -270,7 +269,7 @@ struct Decompress } } #endif - } catch (const fs::filesystem_error& e) { + } catch (const fs::filesystem_error&) { // Handle filesystem errors return false; } @@ -284,7 +283,7 @@ struct Decompress return true; } - static bool extractTarXz(uint8_t const* data, int dataSize, const File& destRoot, int expectedSize = 0) + static bool extractTarXz(uint8_t const* data, int const dataSize, const File& destRoot, int const expectedSize = 0) { HeapArray decompressedData; if(expectedSize > 0) { diff --git a/Source/Utility/FileSystemWatcher.cxx b/Source/Utility/FileSystemWatcher.cpp similarity index 71% rename from Source/Utility/FileSystemWatcher.cxx rename to Source/Utility/FileSystemWatcher.cpp index 179df431b9..d8bc8ceea8 100644 --- a/Source/Utility/FileSystemWatcher.cxx +++ b/Source/Utility/FileSystemWatcher.cpp @@ -10,7 +10,6 @@ For more information visit www.rabiensoftware.com using namespace juce; #include "FileSystemWatcher.h" -#include "Containers.h" #ifdef _WIN32 #include @@ -32,77 +31,6 @@ using namespace juce; #include #endif -#if JUCE_MAC -class FileSystemWatcher::Impl -{ -public: - Impl (FileSystemWatcher& o, File f) : owner (o), folder (f) - { - NSString* newPath = [NSString stringWithUTF8String:folder.getFullPathName().toRawUTF8()]; - - paths = [[NSArray arrayWithObject:newPath] retain]; - context.version = 0L; - context.info = this; - context.retain = nil; - context.release = nil; - context.copyDescription = nil; - - stream = FSEventStreamCreate (kCFAllocatorDefault, callback, &context, (CFArrayRef)paths, kFSEventStreamEventIdSinceNow, 0.05, - kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents); - if (stream) - { - FSEventStreamScheduleWithRunLoop (stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); - FSEventStreamStart (stream); - } - - } - - ~Impl() - { - if (stream) - { - FSEventStreamStop (stream); - FSEventStreamUnscheduleFromRunLoop (stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); - FSEventStreamInvalidate (stream); - FSEventStreamRelease (stream); - } - } - - static void callback (ConstFSEventStreamRef streamRef, void* clientCallBackInfo, size_t numEvents, void* eventPaths, - const FSEventStreamEventFlags* eventFlags, const FSEventStreamEventId* eventIds) - { - ignoreUnused (streamRef, numEvents, eventIds, eventPaths, eventFlags); - - Impl* impl = (Impl*)clientCallBackInfo; - - char** files = (char**)eventPaths; - - for (int i = 0; i < int (numEvents); i++) - { - char* file = files[i]; - FSEventStreamEventFlags evt = eventFlags[i]; - - File path = String::fromUTF8 (file); - if (evt & kFSEventStreamEventFlagItemModified) - impl->owner.fileChanged (path, FileSystemEvent::fileUpdated); - else if (evt & kFSEventStreamEventFlagItemRemoved) - impl->owner.fileChanged (path, FileSystemEvent::fileDeleted); - else if (evt & kFSEventStreamEventFlagItemRenamed) - impl->owner.fileChanged (path, path.exists() ? FileSystemEvent::fileRenamedNewName : FileSystemEvent::fileRenamedOldName); - else if (evt & kFSEventStreamEventFlagItemCreated) - impl->owner.fileChanged (path, FileSystemEvent::fileCreated); - } - } - - FileSystemWatcher& owner; - const File folder; - - NSArray* paths; - FSEventStreamRef stream; - struct FSEventStreamContext context; -}; -#endif - #ifdef JUCE_LINUX #define BUF_LEN (10 * (sizeof(struct inotify_event) + NAME_MAX + 1)) @@ -153,23 +81,23 @@ class FileSystemWatcher::Impl : public Thread, char buf[BUF_LEN]; const struct inotify_event* iNotifyEvent; char* ptr; - + while (!shouldQuit) { struct pollfd pfd; pfd.fd = fd; pfd.events = POLLIN; - + // Poll with 100ms timeout int pollResult = poll(&pfd, 1, 100); - + if (threadShouldExit()) break; // Check exit condition regularly - + if (pollResult > 0 && (pfd.revents & POLLIN)) { int numRead = read(fd, buf, BUF_LEN); if (numRead <= 0 || threadShouldExit()) break; - + for (ptr = buf; ptr < buf + numRead; ptr += sizeof(struct inotify_event) + iNotifyEvent->len) { iNotifyEvent = (const struct inotify_event*)ptr; @@ -222,9 +150,8 @@ class FileSystemWatcher::Impl : public Thread, int fd; int wd; }; -#endif -#ifdef JUCE_WINDOWS +#elif JUCE_WINDOWS class FileSystemWatcher::Impl : private AsyncUpdater, private Thread { @@ -356,10 +283,9 @@ class FileSystemWatcher::Impl : private AsyncUpdater, HANDLE folderHandle; }; -#endif // Dummy implementation for OS where we don't support this yet -#if JUCE_BSD || JUCE_IOS +#elif JUCE_BSD class FileSystemWatcher::Impl { public: @@ -376,7 +302,7 @@ class FileSystemWatcher::Impl }; #endif -#if defined JUCE_MAC || defined JUCE_WINDOWS || defined JUCE_LINUX || defined JUCE_BSD || defined JUCE_IOS +#if defined JUCE_WINDOWS || defined JUCE_LINUX || defined JUCE_BSD FileSystemWatcher::FileSystemWatcher() { } @@ -411,23 +337,4 @@ void FileSystemWatcher::removeAllFolders() { watched.clear(); } - -void FileSystemWatcher::addListener (Listener* newListener) -{ - listeners.add (newListener); -} - -void FileSystemWatcher::removeListener (Listener* listener) -{ - listeners.remove (listener); -} - -void FileSystemWatcher::fileChanged (const File& file, FileSystemEvent fsEvent) -{ - if(file.getFileName().endsWith(".autosave")) return; - - listeners.call (&FileSystemWatcher::Listener::fileChanged, file, fsEvent); -} - - #endif diff --git a/Source/Utility/FileSystemWatcher.h b/Source/Utility/FileSystemWatcher.h index 32fdabd0a0..00a6a60100 100644 --- a/Source/Utility/FileSystemWatcher.h +++ b/Source/Utility/FileSystemWatcher.h @@ -6,6 +6,7 @@ For more information visit www.rabiensoftware.com ==============================================================================*/ #pragma once +#include "Containers.h" #if JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD || JUCE_IOS @@ -26,20 +27,10 @@ class FileSystemWatcher { FileSystemWatcher(); ~FileSystemWatcher(); - /** Adds a folder to be watched */ void addFolder(File const& folder); - - /** Removes a folder from being watched */ void removeFolder(File const& folder); - - /** Removes all folders from being watched */ void removeAllFolders(); - /** A set of events that can happen to a file. - When a file is renamed it will appear as the - original filename being deleted and the new - filename being created - */ enum FileSystemEvent { fileCreated, fileDeleted, @@ -63,35 +54,56 @@ class FileSystemWatcher { if you need to reload a file when it's contents change */ virtual void fileChanged(File const f, FileSystemEvent) { - // By default, don't respond to hidden files (which would be .settings and .autosave) - // If you want that to respond to hidden file changes, override this - if (f.isHidden() || f.getFileName().startsWith(".")) - return; - triggerAsyncUpdate(); } virtual void filesystemChanged() { } }; - - /** Registers a listener to be told when things happen to the text. - @see removeListener - */ - void addListener(Listener* newListener); - - /** Deregisters a listener. - @see addListener - */ - void removeListener(Listener* listener); - + + + void addListener (Listener* newListener) + { + listeners.add (newListener); + } + + void removeListener (Listener* listener) + { + listeners.remove (listener); + } + + static void addGlobalIgnorePath(File const& pathToIgnore) + { + pathsToIgnore.add_unique(pathToIgnore); + } + + static void removeGlobalIgnorePath(File const& pathToIgnore) + { + pathsToIgnore.remove_one(pathToIgnore); + } + private: class Impl; - void fileChanged(File const& file, FileSystemEvent fsEvent); + void fileChanged(File const& f, FileSystemEvent fsEvent) + { + // By default, don't respond to hidden files (which would be .settings and .autosave) + // If you want that to respond to hidden file changes, override this + if (f.isHidden() || f.getFileName().startsWith(".")) + return; + + for(auto const& pathToIgnore : pathsToIgnore) + { + if(f.isAChildOf(pathToIgnore) || f == pathToIgnore) { + return; + } + } + + listeners.call (&FileSystemWatcher::Listener::fileChanged, f, fsEvent); + } ListenerList listeners; - OwnedArray watched; + static inline SmallArray pathsToIgnore; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FileSystemWatcher) }; diff --git a/Source/Utility/FileSystemWatcher.mm b/Source/Utility/FileSystemWatcher.mm index 7e2350a97e..ac0311b154 100644 --- a/Source/Utility/FileSystemWatcher.mm +++ b/Source/Utility/FileSystemWatcher.mm @@ -1,2 +1,204 @@ #import -#include "FileSystemWatcher.cxx" + +#include +#include + +using namespace juce; +#include "FileSystemWatcher.h" + +#if JUCE_MAC +class FileSystemWatcher::Impl +{ +public: + Impl (FileSystemWatcher& o, File f) : owner (o), folder (f) + { + NSString* newPath = [NSString stringWithUTF8String:folder.getFullPathName().toRawUTF8()]; + + paths = [[NSArray arrayWithObject:newPath] retain]; + context.version = 0L; + context.info = this; + context.retain = nil; + context.release = nil; + context.copyDescription = nil; + + stream = FSEventStreamCreate (kCFAllocatorDefault, callback, &context, (CFArrayRef)paths, kFSEventStreamEventIdSinceNow, 0.05, + kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents); + if (stream) + { + FSEventStreamScheduleWithRunLoop (stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + FSEventStreamStart (stream); + } + + } + + ~Impl() + { + if (stream) + { + FSEventStreamStop (stream); + FSEventStreamUnscheduleFromRunLoop (stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + FSEventStreamInvalidate (stream); + FSEventStreamRelease (stream); + } + } + + static void callback (ConstFSEventStreamRef streamRef, void* clientCallBackInfo, size_t numEvents, void* eventPaths, + const FSEventStreamEventFlags* eventFlags, const FSEventStreamEventId* eventIds) + { + ignoreUnused (streamRef, numEvents, eventIds, eventPaths, eventFlags); + + Impl* impl = (Impl*)clientCallBackInfo; + + char** files = (char**)eventPaths; + + for (int i = 0; i < int (numEvents); i++) + { + char* file = files[i]; + FSEventStreamEventFlags evt = eventFlags[i]; + + File path = String::fromUTF8 (file); + if (evt & kFSEventStreamEventFlagItemModified) + impl->owner.fileChanged (path, FileSystemEvent::fileUpdated); + else if (evt & kFSEventStreamEventFlagItemRemoved) + impl->owner.fileChanged (path, FileSystemEvent::fileDeleted); + else if (evt & kFSEventStreamEventFlagItemRenamed) + impl->owner.fileChanged (path, path.exists() ? FileSystemEvent::fileRenamedNewName : FileSystemEvent::fileRenamedOldName); + else if (evt & kFSEventStreamEventFlagItemCreated) + impl->owner.fileChanged (path, FileSystemEvent::fileCreated); + } + } + + FileSystemWatcher& owner; + const File folder; + + NSArray* paths; + FSEventStreamRef stream; + struct FSEventStreamContext context; +}; + +#elif JUCE_IOS + +class FileSystemWatcher::Impl +{ +public: + Impl (FileSystemWatcher& o, File f) : owner (o), folder (f), fileDescriptor(-1), dispatchSource(nullptr) + { + NSString* path = [NSString stringWithUTF8String:folder.getFullPathName().toRawUTF8()]; + + // Open the directory for monitoring + fileDescriptor = open([path UTF8String], O_EVTONLY); + + if (fileDescriptor == -1) + { + DBG("Failed to open file descriptor for: " + folder.getFullPathName()); + return; + } + + // Create dispatch source for monitoring + dispatchSource = dispatch_source_create( + DISPATCH_SOURCE_TYPE_VNODE, + fileDescriptor, + DISPATCH_VNODE_WRITE | DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME | DISPATCH_VNODE_EXTEND, + dispatch_get_main_queue() + ); + + if (!dispatchSource) + { + close(fileDescriptor); + fileDescriptor = -1; + return; + } + + // Capture 'this' for the event handler + Impl* implPtr = this; + + dispatch_source_set_event_handler(dispatchSource, ^{ + unsigned long flags = dispatch_source_get_data(dispatchSource); + + if (flags & DISPATCH_VNODE_WRITE) + { + implPtr->owner.fileChanged(implPtr->folder, FileSystemEvent::fileUpdated); + } + if (flags & DISPATCH_VNODE_DELETE) + { + implPtr->owner.fileChanged(implPtr->folder, FileSystemEvent::fileDeleted); + } + if (flags & DISPATCH_VNODE_RENAME) + { + implPtr->owner.fileChanged(implPtr->folder, FileSystemEvent::fileRenamedNewName); + } + if (flags & DISPATCH_VNODE_EXTEND) + { + implPtr->owner.fileChanged(implPtr->folder, FileSystemEvent::fileCreated); + } + }); + + dispatch_source_set_cancel_handler(dispatchSource, ^{ + if (implPtr->fileDescriptor != -1) + { + close(implPtr->fileDescriptor); + implPtr->fileDescriptor = -1; + } + }); + + dispatch_resume(dispatchSource); + } + + ~Impl() + { + if (dispatchSource) + { + dispatch_source_cancel(dispatchSource); + dispatch_release(dispatchSource); + dispatchSource = nullptr; + } + + if (fileDescriptor != -1) + { + close(fileDescriptor); + fileDescriptor = -1; + } + } + + FileSystemWatcher& owner; + const File folder; + int fileDescriptor; + dispatch_source_t dispatchSource; +}; + +#endif + +FileSystemWatcher::FileSystemWatcher() +{ +} + +FileSystemWatcher::~FileSystemWatcher() +{ +} + +void FileSystemWatcher::addFolder (const File& folder) +{ + SmallArray allFolders; + for (auto w : watched) + allFolders.add (w->folder); + + if ( !allFolders.contains (folder)) + watched.add (new Impl (*this, folder)); +} + +void FileSystemWatcher::removeFolder (const File& folder) +{ + for (int i = watched.size(); --i >= 0;) + { + if (watched[i]->folder == folder) + { + watched.remove (i); + break; + } + } +} + +void FileSystemWatcher::removeAllFolders() +{ + watched.clear(); +} diff --git a/Source/Utility/Fonts.h b/Source/Utility/Fonts.h index 669fba96b4..b4a4afa083 100644 --- a/Source/Utility/Fonts.h +++ b/Source/Utility/Fonts.h @@ -20,9 +20,8 @@ struct Fonts { HeapArray interUnicodeZip; interUnicodeZip.reserve(7 * 1024 * 1024); int i = 0; - - while (true) - { + + while (true) { int size = 0; auto* resource = BinaryData::getNamedResource(("InterUnicode_" + String(i)).toRawUTF8(), size); if (!resource) @@ -42,40 +41,39 @@ struct Fonts { auto numEntries = zipFile.getNumEntries(); - if(numEntries != 0) { - auto fileEntry = zipFile.getEntry(numEntries - 1); // or use 0 if you want first entry - + if (numEntries != 0) { + auto const fileEntry = zipFile.getEntry(numEntries - 1); // or use 0 if you want first entry + // Create a InputStream for the file entry std::unique_ptr fileStream(zipFile.createStreamForEntry(*fileEntry)); - if(fileStream) { + if (fileStream) { // Read the decompressed font data into interUnicode array - const int bufferSize = 8192; + constexpr int bufferSize = 8192; char buffer[bufferSize]; - - while (!fileStream->isExhausted()) - { - auto bytesRead = fileStream->read(buffer, bufferSize); + + while (!fileStream->isExhausted()) { + auto const bytesRead = fileStream->read(buffer, bufferSize); if (bytesRead <= 0) break; - interUnicode.insert(interUnicode.end(), (const uint8_t*)buffer, (const uint8_t*)buffer + bytesRead); + interUnicode.insert(interUnicode.end(), reinterpret_cast(buffer), reinterpret_cast(buffer) + bytesRead); } } } // Initialise typefaces - if(interUnicode.size()) { + if (interUnicode.size()) { defaultTypeface = Typeface::createSystemTypefaceFor(interUnicode.data(), interUnicode.size()); - } - else { + } else { defaultTypeface = Typeface::createSystemTypefaceFor(BinaryData::InterRegular_ttf, BinaryData::InterRegular_ttfSize); } - + currentTypeface = defaultTypeface; boldTypeface = Typeface::createSystemTypefaceFor(BinaryData::InterBold_ttf, BinaryData::InterBold_ttfSize); semiBoldTypeface = Typeface::createSystemTypefaceFor(BinaryData::InterSemiBold_ttf, BinaryData::InterSemiBold_ttfSize); iconTypeface = Typeface::createSystemTypefaceFor(BinaryData::IconFont_ttf, BinaryData::IconFont_ttfSize); monoTypeface = Typeface::createSystemTypefaceFor(BinaryData::RobotoMono_Regular_ttf, BinaryData::RobotoMono_Regular_ttfSize); + monoBoldTypeface = Typeface::createSystemTypefaceFor(BinaryData::RobotoMono_Bold_ttf, BinaryData::RobotoMono_Bold_ttfSize); variableTypeface = Typeface::createSystemTypefaceFor(BinaryData::InterVariable_ttf, BinaryData::InterVariable_ttfSize); tabularTypeface = Typeface::createSystemTypefaceFor(BinaryData::InterTabular_ttf, BinaryData::InterTabular_ttfSize); @@ -88,6 +86,7 @@ struct Fonts { static Font getSemiBoldFont() { return Font(instance->semiBoldTypeface); } static Font getIconFont() { return Font(instance->iconTypeface); } static Font getMonospaceFont() { return Font(instance->monoTypeface); } + static Font getMonospaceBoldFont() { return Font(instance->monoBoldTypeface); } static Font getVariableFont() { return Font(instance->variableTypeface); } static Font getTabularNumbersFont() { return Font(instance->tabularTypeface); } @@ -125,7 +124,7 @@ struct Fonts { } // For drawing icons with icon font - static void drawIcon(Graphics& g, String const& icon, Rectangle bounds, Colour const colour, int fontHeight = -1, bool const centred = true) + static void drawIcon(Graphics& g, String const& icon, Rectangle const bounds, Colour const colour, int fontHeight = -1, bool const centred = true) { if (fontHeight < 0) fontHeight = bounds.getHeight() / 1.2f; @@ -176,14 +175,14 @@ struct Fonts { } // rectangle float version - static void drawStyledText(Graphics& g, String const& textToDraw, Rectangle bounds, Colour const colour, FontStyle const style, int const fontHeight = 15, Justification const justification = Justification::centredLeft) + static void drawStyledText(Graphics& g, String const& textToDraw, Rectangle const bounds, Colour const colour, FontStyle const style, int const fontHeight = 15, Justification const justification = Justification::centredLeft) { drawStyledTextSetup(g, colour, style, fontHeight); g.drawText(textToDraw, bounds, justification); } // rectangle int version - static void drawStyledText(Graphics& g, String const& textToDraw, Rectangle bounds, Colour const colour, FontStyle const style, int const fontHeight = 15, Justification const justification = Justification::centredLeft) + static void drawStyledText(Graphics& g, String const& textToDraw, Rectangle const bounds, Colour const colour, FontStyle const style, int const fontHeight = 15, Justification const justification = Justification::centredLeft) { drawStyledTextSetup(g, colour, style, fontHeight); g.drawText(textToDraw, bounds, justification); @@ -197,7 +196,7 @@ struct Fonts { } // For drawing regular text - static void drawText(Graphics& g, String const& textToDraw, Rectangle bounds, Colour const colour, int const fontHeight = 15, Justification const justification = Justification::centredLeft) + static void drawText(Graphics& g, String const& textToDraw, Rectangle const bounds, Colour const colour, int const fontHeight = 15, Justification const justification = Justification::centredLeft) { g.setFont(Fonts::getCurrentFont().withHeight(fontHeight)); g.setColour(colour); @@ -205,7 +204,7 @@ struct Fonts { } // For drawing regular text - static void drawText(Graphics& g, String const& textToDraw, Rectangle bounds, Colour const colour, int const fontHeight = 15, Justification const justification = Justification::centredLeft) + static void drawText(Graphics& g, String const& textToDraw, Rectangle const bounds, Colour const colour, int const fontHeight = 15, Justification const justification = Justification::centredLeft) { g.setFont(Fonts::getCurrentFont().withHeight(fontHeight)); g.setColour(colour); @@ -217,7 +216,7 @@ struct Fonts { drawText(g, textToDraw, Rectangle(x, y, w, h), colour, fontHeight, justification); } - static void drawFittedText(Graphics& g, String const& textToDraw, Rectangle bounds, Colour const colour, int const numLines = 1, float const minimumHoriontalScale = 1.0f, float const fontHeight = 15.0f, Justification const justification = Justification::centredLeft, FontStyle const style = FontStyle::Regular) + static void drawFittedText(Graphics& g, String const& textToDraw, Rectangle const bounds, Colour const colour, int const numLines = 1, float const minimumHoriontalScale = 1.0f, float const fontHeight = 15.0f, Justification const justification = Justification::centredLeft, FontStyle const style = FontStyle::Regular) { g.setFont(getFontFromStyle(style).withHeight(fontHeight)); g.setColour(colour); @@ -242,8 +241,9 @@ struct Fonts { Typeface::Ptr semiBoldTypeface; Typeface::Ptr iconTypeface; Typeface::Ptr monoTypeface; + Typeface::Ptr monoBoldTypeface; Typeface::Ptr variableTypeface; Typeface::Ptr tabularTypeface; - static inline UnorderedMap fontTable = UnorderedMap(); + static inline auto fontTable = UnorderedMap(); }; diff --git a/Source/Utility/MidiDeviceManager.h b/Source/Utility/MidiDeviceManager.h index 260e1bfa4a..6a22408711 100644 --- a/Source/Utility/MidiDeviceManager.h +++ b/Source/Utility/MidiDeviceManager.h @@ -81,25 +81,31 @@ class MidiDeviceManager final : public ChangeListener String getPortDescription(bool const isInput, int const port) { - if (isInput && inputPorts[port + 1].enabled) { - auto const numDevices = inputPorts[port + 1].devices.size(); + int portNum = port + 1; + bool isDawPort = !ProjectInfo::isStandalone && port == 0; + if (isInput && (inputPorts[portNum].enabled || isDawPort)) { + auto numDevices = inputPorts[portNum].devices.size() + isDawPort; + if(numDevices == 1 && isDawPort) + return "Port " + String(portNum) + " (" + "DAW input" + ")"; if (numDevices == 1) { - return "Port " + String(port + 1) + " (" + String(inputPorts[port + 1].devices.getFirst()->getName()) + ")"; + return "Port " + String(portNum) + " (" + String(inputPorts[port + 1].devices.getFirst()->getName()) + ")"; } if (numDevices > 0) { - return "Port " + String(port + 1) + " (" + String(numDevices) + " devices)"; + return "Port " + String(portNum) + " (" + String(numDevices) + " devices)"; } - } else if (!isInput && outputPorts[port + 1].enabled) { - auto const numDevices = outputPorts[port + 1].devices.size(); + } else if (!isInput && (outputPorts[portNum].enabled || isDawPort)) { + auto numDevices = outputPorts[portNum].devices.size() + isDawPort; + if(numDevices == 1 && isDawPort) + return "Port " + String(portNum) + " (" + "DAW output" + ")"; if (numDevices == 1) { - return "Port " + String(port + 1) + " (" + String(outputPorts[port + 1].devices.getFirst()->getName()) + ")"; + return "Port " + String(portNum) + " (" + String(outputPorts[port + 1].devices.getFirst()->getName()) + ")"; } if (numDevices > 0) { - return "Port " + String(port + 1) + " (" + String(numDevices) + " devices)"; + return "Port " + String(portNum) + " (" + String(numDevices) + " devices)"; } } - return "Port " + String(port + 1); + return "Port " + String(portNum); } int getMidiDevicePort(bool const isInput, MidiDeviceInfo& info) @@ -107,13 +113,13 @@ class MidiDeviceManager final : public ChangeListener int portIndex = -1; if (isInput) { for (auto& port : inputPorts) { - if (std::ranges::find_if(port.devices, [info](MidiInput* input) { return input && input->getIdentifier() == info.identifier; }) != port.devices.end()) + if (std::ranges::find_if(port.devices, [info](MidiInput const* input) { return input && input->getIdentifier() == info.identifier; }) != port.devices.end()) return portIndex; portIndex++; } } else { for (auto& port : outputPorts) { - if (std::ranges::find_if(port.devices, [info](MidiOutput* output) { return output && output->getIdentifier() == info.identifier; }) != port.devices.end()) + if (std::ranges::find_if(port.devices, [info](MidiOutput const* output) { return output && output->getIdentifier() == info.identifier; }) != port.devices.end()) return portIndex; portIndex++; } @@ -145,7 +151,7 @@ class MidiDeviceManager final : public ChangeListener return nullptr; } - void setMidiDevicePort(bool const isInput, String const& name, String const& identifier, int const port) + void setMidiDevicePort(bool const isInput, String const& identifier, int const port) { bool const shouldBeEnabled = port >= 0; if (isInput) { @@ -180,7 +186,7 @@ class MidiDeviceManager final : public ChangeListener } // Function to enqueue external MIDI (like the DAW's MIDI coming in with processBlock) - void enqueueMidiInput(int const port, MidiBuffer& buffer) + void enqueueMidiInput(int const port, MidiBuffer const& buffer) { auto& inputPort = inputPorts[port + 1]; if (inputPort.enabled) { @@ -193,7 +199,7 @@ class MidiDeviceManager final : public ChangeListener } // Handle midi input events in a callback - void dequeueMidiInput(int const numSamples, std::function inputCallback) + void dequeueMidiInput(int const numSamples, std::function inputCallback) { auto const timeNow = Time::getMillisecondCounterHiRes(); auto const msElapsed = timeNow - lastCallbackTime; @@ -227,7 +233,7 @@ class MidiDeviceManager final : public ChangeListener midiBufferIn.addEvent(midiMessage, pos); midiInputHistory.addEvent(midiMessage, pos); } - inputCallback(port, numSamples, midiBufferIn); + inputCallback(port, midiBufferIn); } else { startSample = numSamples - numSourceSamples; while (inputPort.queue.try_dequeue(message)) { @@ -236,7 +242,7 @@ class MidiDeviceManager final : public ChangeListener midiBufferIn.addEvent(midiMessage, pos); midiInputHistory.addEvent(midiMessage, pos); } - inputCallback(port, numSamples, midiBufferIn); + inputCallback(port, midiBufferIn); } port++; } @@ -287,7 +293,11 @@ class MidiDeviceManager final : public ChangeListener if (!outputPort.buffer.isEmpty()) { for (auto* device : outputPort.devices) { - device->sendBlockOfMessages(outputPort.buffer, Time::getMillisecondCounterHiRes(), currentSampleRate); + if (device->isBackgroundThreadRunning()) { + device->sendBlockOfMessages(outputPort.buffer, Time::getMillisecondCounter(), currentSampleRate); + } else { + device->sendBlockOfMessagesNow(outputPort.buffer); + } } } } @@ -334,7 +344,7 @@ class MidiDeviceManager final : public ChangeListener auto const port = midiPort.hasProperty("Port") ? static_cast(midiPort.getProperty("Port")) : 0; for (auto& output : availableMidiOutputs) { if (output.name == name) { - setMidiDevicePort(false, output.name, output.identifier, port); + setMidiDevicePort(false, output.identifier, port); break; } } @@ -347,7 +357,7 @@ class MidiDeviceManager final : public ChangeListener auto const port = midiPort.hasProperty("Port") ? static_cast(midiPort.getProperty("Port")) : 0; for (auto& input : availableMidiInputs) { if (input.name == name) { - setMidiDevicePort(true, input.name, input.identifier, port); + setMidiDevicePort(true, input.identifier, port); break; } } @@ -368,7 +378,8 @@ class MidiDeviceManager final : public ChangeListener if (!port.enabled) continue; for (auto const* device : port.devices) { - if(midiOutputsTree.getChildWithProperty("Name", device->getName()).isValid()) continue; + if (midiOutputsTree.getChildWithProperty("Name", device->getName()).isValid()) + continue; ValueTree midiOutputPort("MidiPort"); midiOutputPort.setProperty("Name", device->getName(), nullptr); midiOutputPort.setProperty("Port", outputPorts.index_of_address(port) - 1, nullptr); @@ -382,7 +393,8 @@ class MidiDeviceManager final : public ChangeListener if (!port.enabled) continue; for (auto const* device : port.devices) { - if(midiInputsTree.getChildWithProperty("Name", device->getName()).isValid()) continue; + if (midiInputsTree.getChildWithProperty("Name", device->getName()).isValid()) + continue; ValueTree midiInputPort("MidiPort"); midiInputPort.setProperty("Name", device->getName(), nullptr); midiInputPort.setProperty("Port", inputPorts.index_of_address(port) - 1, nullptr); @@ -391,15 +403,6 @@ class MidiDeviceManager final : public ChangeListener } } - void getLastMidiOutputEvents(MidiBuffer& buffer, int const numSamples) - { - for (auto& port : outputPorts) { - if (!port.enabled) - continue; - buffer.addEvents(port.buffer, 0, numSamples, 0); - } - } - private: void handleIncomingMidiMessage(MidiInput* input, MidiMessage const& message) override { diff --git a/Source/Utility/ModifierKeyListener.h b/Source/Utility/ModifierKeyListener.h index a6f2b504b2..25f0ef1e74 100644 --- a/Source/Utility/ModifierKeyListener.h +++ b/Source/Utility/ModifierKeyListener.h @@ -11,7 +11,10 @@ // A class that does more reliable modifier listening than JUCE has by default // Create one broadcaster class and attach listeners to that -struct ModifierKeyListener { +class ModifierKeyListener { +public: + virtual ~ModifierKeyListener() = default; + virtual void shiftKeyChanged(bool isHeld) { ignoreUnused(isHeld); } virtual void commandKeyChanged(bool isHeld) { ignoreUnused(isHeld); } virtual void altKeyChanged(bool isHeld) { ignoreUnused(isHeld); } @@ -29,6 +32,8 @@ class ModifierKeyBroadcaster { { } + virtual ~ModifierKeyBroadcaster() = default; + void addModifierKeyListener(ModifierKeyListener* listener) { listeners.add(listener); @@ -146,15 +151,15 @@ class ModifierKeyBroadcaster { bool ctrlWasDown = false; bool spaceWasDown = false; bool middleMouseWasDown = false; - - class ModifierKeyTimer : public Timer - { + + class ModifierKeyTimer final : public Timer { public: - ModifierKeyTimer(ModifierKeyBroadcaster& parent) : p(parent) + explicit ModifierKeyTimer(ModifierKeyBroadcaster& parent) + : p(parent) { startTimer(50); } - + void timerCallback() override { // If a window that's not coming from our app is top-level, ignore @@ -166,10 +171,10 @@ class ModifierKeyBroadcaster { auto const mods = ModifierKeys::getCurrentModifiersRealtime(); p.setModifierKeys(mods); } - + ModifierKeyBroadcaster& p; }; - + ModifierKeyTimer timer = ModifierKeyTimer(*this); HeapArray> listeners; diff --git a/Source/Utility/NVGGraphicsContext.cpp b/Source/Utility/NVGGraphicsContext.cpp index 4d3505bc1c..6fb8ecb3a5 100644 --- a/Source/Utility/NVGGraphicsContext.cpp +++ b/Source/Utility/NVGGraphicsContext.cpp @@ -40,7 +40,7 @@ NVGGraphicsContext::~NVGGraphicsContext() bool NVGGraphicsContext::isVectorDevice() const { return false; } -void NVGGraphicsContext::setOrigin(juce::Point origin) +void NVGGraphicsContext::setOrigin(juce::Point const origin) { nvgTranslate(nvg, origin.getX(), origin.getY()); } @@ -97,7 +97,7 @@ void NVGGraphicsContext::clipToImageAlpha(juce::Image const& sourceImage, juce:: // Create a new Nanovg image from the bitmap data int const width = singleChannelImage.getWidth(); int const height = singleChannelImage.getHeight(); - auto const image = nvgCreateImageRGBA(nvg, width, height, 0, pixelData); + auto const image = nvgCreateImageARGB_sRGB(nvg, width, height, 0, pixelData); auto const paint = nvgImagePattern(nvg, 0, 0, width, height, 0, image, 1); nvgSave(nvg); @@ -266,7 +266,7 @@ void NVGGraphicsContext::setPath(juce::Path const& path, juce::AffineTransform c nvgBeginPath(nvg); juce::Path::Iterator i(p); - + // Flag is used to flip winding when drawing shapes with holes. bool solid = true; nvgPathWinding(nvg, path.isUsingNonZeroWinding() ? NVG_NONZERO : NVG_SOLID); @@ -287,7 +287,7 @@ void NVGGraphicsContext::setPath(juce::Path const& path, juce::AffineTransform c break; case juce::Path::Iterator::closePath: nvgClosePath(nvg); - if(!path.isUsingNonZeroWinding()) { + if (!path.isUsingNonZeroWinding()) { nvgPathWinding(nvg, solid ? NVG_SOLID : NVG_HOLE); solid = !solid; } @@ -391,7 +391,7 @@ void NVGGraphicsContext::setFont(juce::Font const& f) juce::String str; for (juce::juce_wchar c = 32; c < 127; ++c) // Only map printable characters str += juce::String::charToString(c); - str += juce::String::charToString(static_cast(41952)); // for some reason we need this char? + str += juce::String::charToString(41952); // for some reason we need this char? return str; }(); @@ -566,24 +566,8 @@ int NVGGraphicsContext::getNvgImageId(juce::Image const& image) argbImage = argbImage.convertedToFormat(juce::Image::PixelFormat::ARGB); juce::Image::BitmapData const bitmap(argbImage, juce::Image::BitmapData::readOnly); - - for (int y = 0; y < argbImage.getHeight(); ++y) { - auto* scanLine = reinterpret_cast(bitmap.getLinePointer(y)); - - for (int x = 0; x < argbImage.getWidth(); ++x) { - juce::uint32 const argb = scanLine[x]; - - juce::uint8 const a = argb >> 24; - juce::uint8 const r = argb >> 16; - juce::uint8 const g = argb >> 8; - juce::uint8 const b = argb; - - // order bytes as abgr - scanLine[x] = a << 24 | b << 16 | g << 8 | r; - } - } - - id = nvgCreateImageRGBA(nvg, argbImage.getWidth(), argbImage.getHeight(), NVG_IMAGE_PREMULTIPLIED, bitmap.data); + + id = nvgCreateImageARGB(nvg, argbImage.getWidth(), argbImage.getHeight(), 0, bitmap.data); if (images.size() >= maxImageCacheSize) reduceImageCache(); diff --git a/Source/Utility/NVGGraphicsContext.h b/Source/Utility/NVGGraphicsContext.h index 9b191685f4..d1cbb22eff 100644 --- a/Source/Utility/NVGGraphicsContext.h +++ b/Source/Utility/NVGGraphicsContext.h @@ -17,7 +17,7 @@ using namespace juce::gl; class NVGGraphicsContext final : public juce::LowLevelGraphicsContext { public: - NVGGraphicsContext(NVGcontext* nativeHandle); + explicit NVGGraphicsContext(NVGcontext* nativeHandle); ~NVGGraphicsContext() override; bool isVectorDevice() const override; @@ -87,7 +87,7 @@ class NVGGraphicsContext final : public juce::LowLevelGraphicsContext { using GlyphToCharMap = UnorderedMap; // Mapping font names to glyph-to-character tables - static inline UnorderedMap loadedFonts = UnorderedMap(); + static inline auto loadedFonts = UnorderedMap(); GlyphToCharMap* currentGlyphToCharMap; // Tracking images mapped tomtextures. diff --git a/Source/Utility/NVGUtils.cpp b/Source/Utility/NVGUtils.cpp index a357e76c97..75327ad133 100644 --- a/Source/Utility/NVGUtils.cpp +++ b/Source/Utility/NVGUtils.cpp @@ -11,7 +11,7 @@ #include "NVGSurface.h" NVGComponent::NVGComponent(Component* comp) -: component(*comp) + : component(*comp) { } @@ -24,7 +24,7 @@ NVGcolor NVGComponent::convertColour(Colour const c) Colour NVGComponent::convertColour(NVGcolor const c) { - return Colour(c.r, c.b, c.g, c.a); + return Colour(c.r, c.g, c.b, c.a); } NVGcolor NVGComponent::findNVGColour(int const colourId) const @@ -35,28 +35,28 @@ NVGcolor NVGComponent::findNVGColour(int const colourId) const void NVGComponent::setJUCEPath(NVGcontext* nvg, Path const& p) { Path::Iterator i(p); - + nvgBeginPath(nvg); - + while (i.next()) { switch (i.elementType) { - case Path::Iterator::startNewSubPath: - nvgMoveTo(nvg, i.x1, i.y1); - break; - case Path::Iterator::lineTo: - nvgLineTo(nvg, i.x1, i.y1); - break; - case Path::Iterator::quadraticTo: - nvgQuadTo(nvg, i.x1, i.y1, i.x2, i.y2); - break; - case Path::Iterator::cubicTo: - nvgBezierTo(nvg, i.x1, i.y1, i.x2, i.y2, i.x3, i.y3); - break; - case Path::Iterator::closePath: - nvgClosePath(nvg); - break; - default: - break; + case Path::Iterator::startNewSubPath: + nvgMoveTo(nvg, i.x1, i.y1); + break; + case Path::Iterator::lineTo: + nvgLineTo(nvg, i.x1, i.y1); + break; + case Path::Iterator::quadraticTo: + nvgQuadTo(nvg, i.x1, i.y1, i.x2, i.y2); + break; + case Path::Iterator::cubicTo: + nvgBezierTo(nvg, i.x1, i.y1, i.x2, i.y2, i.x3, i.y3); + break; + case Path::Iterator::closePath: + nvgClosePath(nvg); + break; + default: + break; } } } @@ -69,17 +69,16 @@ void NVGComponent::render(NVGcontext*) { } - NVGImage::NVGImage(NVGcontext* nvg, int width, int height, std::function renderCall, int const imageFlags, Colour const clearColour) { bool const clearImage = !(imageFlags & NVGImageFlags::DontClear); bool const repeatImage = imageFlags & NVGImageFlags::RepeatImage; bool const withMipmaps = imageFlags & NVGImageFlags::MipMap; - + // When JUCE image format is SingleChannel the graphics context will render only the alpha component // into the image data, it is not a greyscale image of the graphics context. auto const imageFormat = imageFlags & NVGImageFlags::AlphaImage ? Image::SingleChannel : Image::ARGB; - + auto image = Image(imageFormat, width, height, false); if (clearImage) image.clear({ 0, 0, width, height }, clearColour); @@ -104,7 +103,7 @@ NVGImage::NVGImage(NVGImage& other) totalHeight = other.totalHeight; onImageInvalidate = other.onImageInvalidate; isDirty = false; - + other.subImages.clear(); allImages.insert(this); } @@ -123,18 +122,18 @@ NVGImage& NVGImage::operator=(NVGImage&& other) noexcept nvgDeleteImage(nvg, subImage.imageId); } } - + nvg = other.nvg; subImages = other.subImages; totalWidth = other.totalWidth; totalHeight = other.totalHeight; onImageInvalidate = other.onImageInvalidate; isDirty = false; - + other.subImages.clear(); // Important, makes sure the old buffer can't delete this buffer allImages.insert(this); } - + return *this; } @@ -144,7 +143,7 @@ NVGImage::~NVGImage() allImages.erase(this); } -void NVGImage::clearAll(NVGcontext* nvg) +void NVGImage::clearAll(NVGcontext const* nvg) { for (auto* image : allImages) { if (image->isValid() && image->nvg == nvg) { @@ -167,30 +166,29 @@ void NVGImage::renderJUCEComponent(NVGcontext* nvg, Component& component, float { nvgSave(nvg); nvgScale(nvg, 1.0f / scale, 1.0f / scale); - + Point offset; nvgTransformGetSubpixelOffset(nvg, &offset.x, &offset.y); - - auto w = roundToInt (scale * (float) component.getWidth()); - auto h = roundToInt (scale * (float) component.getHeight()); - - if(w > 0 && h > 0) { - Image componentImage (component.isOpaque() ? Image::RGB : Image::ARGB, w, h, true); + + auto w = roundToInt(scale * static_cast(component.getWidth())); + auto h = roundToInt(scale * static_cast(component.getHeight())); + + if (w > 0 && h > 0) { + Image componentImage(component.isOpaque() ? Image::RGB : Image::ARGB, w, h, true); { - Graphics g (componentImage); + Graphics g(componentImage); g.addTransform(AffineTransform::translation(offset.x, offset.y)); g.addTransform(AffineTransform::scale(scale, scale)); - component.paintEntireComponent (g, true); + component.paintEntireComponent(g, true); } - + loadJUCEImage(nvg, componentImage); - + render(nvg, { 0, 0, w, h }, true); } nvgRestore(nvg); } - void NVGImage::deleteImage() { if (subImages.size() && nvg) { @@ -198,19 +196,19 @@ void NVGImage::deleteImage() if (auto* surface = NVGSurface::getSurfaceForContext(nvg)) { surface->makeContextActive(); } - + nvgDeleteImage(nvg, subImage.imageId); } subImages.clear(); } } -void NVGImage::loadJUCEImage(NVGcontext* context, Image& image, int const repeatImage, int const withMipmaps) +void NVGImage::loadJUCEImage(NVGcontext* context, Image const& image, int const repeatImage, int const withMipmaps) { totalWidth = image.getWidth(); totalHeight = image.getHeight(); nvg = context; - + static int maximumTextureSize = 0; if (!maximumTextureSize) { if (auto* ctx = NVGSurface::getSurfaceForContext(nvg)) { @@ -219,34 +217,34 @@ void NVGImage::loadJUCEImage(NVGcontext* context, Image& image, int const repeat } } int const textureSizeLimit = maximumTextureSize == 0 ? 8192 : maximumTextureSize; - + // Most of the time, the image is small enough, so we optimise for that if (totalWidth <= textureSizeLimit && totalHeight <= textureSizeLimit) { Image::BitmapData const imageData(image, Image::BitmapData::readOnly); - + if (subImages.size() && subImages[0].bounds == image.getBounds() && nvg == context) { nvgUpdateImage(nvg, subImages[0].imageId, imageData.data); return; } - + SubImage subImage; auto flags = repeatImage ? NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY : 0; flags |= withMipmaps ? NVG_IMAGE_GENERATE_MIPMAPS : 0; - + if (image.isARGB()) - subImage.imageId = nvgCreateImageARGB(nvg, totalWidth, totalHeight, flags | NVG_IMAGE_PREMULTIPLIED, imageData.data); + subImage.imageId = nvgCreateImageARGB_sRGB(nvg, totalWidth, totalHeight, flags, imageData.data); else if (image.isSingleChannel()) subImage.imageId = nvgCreateImageAlpha(nvg, totalWidth, totalHeight, flags, imageData.data); - + deleteImage(); - + subImage.bounds = image.getBounds(); subImages.add(subImage); return; } - + deleteImage(); - + int x = 0; while (x < totalWidth) { int y = 0; @@ -255,21 +253,21 @@ void NVGImage::loadJUCEImage(NVGcontext* context, Image& image, int const repeat int const h = std::min(textureSizeLimit, totalHeight - y); auto bounds = Rectangle(x, y, w, h); auto clip = image.getClippedImage(bounds); - + // We need to create copies to make sure the pixels are lined up :( // At least we only take this hit for very large images clip.duplicateIfShared(); Image::BitmapData const imageData(clip, Image::BitmapData::readOnly); - + SubImage subImage; auto flags = repeatImage ? NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY : 0; flags |= withMipmaps ? NVG_IMAGE_GENERATE_MIPMAPS : 0; - + if (image.isARGB()) - subImage.imageId = nvgCreateImageARGB(nvg, w, h, flags | NVG_IMAGE_PREMULTIPLIED, imageData.data); + subImage.imageId = nvgCreateImageARGB_sRGB(nvg, w, h, flags, imageData.data); else if (image.isSingleChannel()) subImage.imageId = nvgCreateImageAlpha(nvg, w, h, flags, imageData.data); - + y += textureSizeLimit; subImage.bounds = bounds; subImages.add(subImage); @@ -279,37 +277,36 @@ void NVGImage::loadJUCEImage(NVGcontext* context, Image& image, int const repeat isDirty = false; } -void NVGImage::renderAlphaImage(NVGcontext* nvg, Rectangle b, NVGcolor const col) +void NVGImage::renderAlphaImage(NVGcontext* nvg, Rectangle const b, NVGcolor const col) { nvgSave(nvg); - + nvgScale(nvg, b.getWidth() / static_cast(totalWidth), b.getHeight() / static_cast(totalHeight)); for (auto const& subImage : subImages) { auto scaledBounds = subImage.bounds; nvgFillPaint(nvg, nvgImageAlphaPattern(nvg, b.getX() + scaledBounds.getX(), b.getY() + scaledBounds.getY(), scaledBounds.getWidth(), scaledBounds.getHeight(), 0, subImage.imageId, col)); - + nvgFillRect(nvg, b.getX() + scaledBounds.getX(), b.getY() + scaledBounds.getY(), scaledBounds.getWidth(), scaledBounds.getHeight()); } nvgRestore(nvg); } -void NVGImage::render(NVGcontext* nvg, Rectangle b, bool quantize) +void NVGImage::render(NVGcontext* nvg, Rectangle const b, bool const quantize) { nvgSave(nvg); - + float const scaleW = b.getWidth() / static_cast(totalWidth); float const scaleH = b.getHeight() / static_cast(totalHeight); nvgScale(nvg, scaleW, scaleH); - if(quantize) - { + if (quantize) { // Make sure image pixel grid aligns with physical pixels nvgTransformQuantize(nvg); } for (auto const& subImage : subImages) { auto scaledBounds = subImage.bounds; nvgFillPaint(nvg, nvgImagePattern(nvg, b.getX() + scaledBounds.getX(), b.getY() + scaledBounds.getY(), scaledBounds.getWidth(), scaledBounds.getHeight(), 0, subImage.imageId, 1.0f)); - - nvgFillRect(nvg, (b.getX() / scaleW) + scaledBounds.getX(), (b.getY() / scaleH) + scaledBounds.getY(), scaledBounds.getWidth(), scaledBounds.getHeight()); + + nvgFillRect(nvg, b.getX() / scaleW + scaledBounds.getX(), b.getY() / scaleH + scaledBounds.getY(), scaledBounds.getWidth(), scaledBounds.getHeight()); } nvgRestore(nvg); } @@ -343,14 +340,14 @@ NVGFramebuffer::~NVGFramebuffer() if (auto* surface = NVGSurface::getSurfaceForContext(nvg)) { surface->makeContextActive(); } - + nvgDeleteFramebuffer(fb); fb = nullptr; } allFramebuffers.erase(this); } -void NVGFramebuffer::clearAll(NVGcontext* nvg) +void NVGFramebuffer::clearAll(NVGcontext const* nvg) { for (auto* buffer : allFramebuffers) { if (buffer->nvg == nvg && buffer->fb) { @@ -381,11 +378,11 @@ void NVGFramebuffer::bind(NVGcontext* ctx, int const width, int const height) nvg = ctx; if (fb) nvgDeleteFramebuffer(fb); - fb = nvgCreateFramebuffer(nvg, width, height, NVG_IMAGE_PREMULTIPLIED); + fb = nvgCreateFramebuffer(nvg, width, height, 0); fbWidth = width; fbHeight = height; } - + nvgBindFramebuffer(fb); } @@ -402,7 +399,7 @@ void NVGFramebuffer::renderToFramebuffer(NVGcontext* nvg, int const width, int c fbDirty = false; } -void NVGFramebuffer::render(NVGcontext* nvg, Rectangle b) +void NVGFramebuffer::render(NVGcontext* nvg, Rectangle const b) { if (fb) { nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, b.getWidth(), b.getHeight(), 0, fb->image, 1)); @@ -414,7 +411,7 @@ int NVGFramebuffer::getImage() const { if (!fb) return -1; - + return fb->image; } @@ -432,7 +429,7 @@ NVGCachedPath::~NVGCachedPath() allCachedPaths.erase(this); } -void NVGCachedPath::clearAll(NVGcontext* nvg) +void NVGCachedPath::clearAll(NVGcontext const* nvg) { for (auto* buffer : allCachedPaths) { if (buffer->nvg == nvg) { @@ -485,7 +482,7 @@ bool NVGCachedPath::fill() } NVGScopedState::NVGScopedState(NVGcontext* nvg) -: nvg(nvg) + : nvg(nvg) { nvgSave(nvg); } diff --git a/Source/Utility/NVGUtils.h b/Source/Utility/NVGUtils.h index c00fbcacf5..7d464fdca0 100644 --- a/Source/Utility/NVGUtils.h +++ b/Source/Utility/NVGUtils.h @@ -16,13 +16,13 @@ using namespace juce::gl; class NVGComponent { public: - NVGComponent(Component* comp); + explicit NVGComponent(Component* comp); virtual ~NVGComponent(); - static NVGcolor convertColour(Colour const c); - static Colour convertColour(NVGcolor const c); + static NVGcolor convertColour(Colour c); + static Colour convertColour(NVGcolor c); - NVGcolor findNVGColour(int const colourId) const; + NVGcolor findNVGColour(int colourId) const; static void setJUCEPath(NVGcontext* nvg, Path const& p); @@ -45,27 +45,27 @@ class NVGImage { MipMap = 1 << 3 }; - NVGImage(NVGcontext* nvg, int width, int height, std::function renderCall, int const imageFlags = 0, Colour const clearColour = Colours::transparentBlack); + NVGImage(NVGcontext* nvg, int width, int height, std::function renderCall, int imageFlags = 0, Colour clearColour = Colours::transparentBlack); NVGImage(); NVGImage(NVGImage& other); NVGImage& operator=(NVGImage&& other) noexcept; ~NVGImage(); - static void clearAll(NVGcontext* nvg); + static void clearAll(NVGcontext const* nvg); bool isValid() const; - void renderJUCEComponent(NVGcontext* nvg, Component& component, float const scale); + void renderJUCEComponent(NVGcontext* nvg, Component& component, float scale); void deleteImage(); - void loadJUCEImage(NVGcontext* context, Image& image, int const repeatImage = false, int const withMipmaps = false); + void loadJUCEImage(NVGcontext* context, Image const& image, int repeatImage = false, int withMipmaps = false); - void renderAlphaImage(NVGcontext* nvg, Rectangle b, NVGcolor const col); + void renderAlphaImage(NVGcontext* nvg, Rectangle b, NVGcolor col); void render(NVGcontext* nvg, Rectangle b, bool quantize = false); - bool needsUpdate(int const width, int const height) const; + bool needsUpdate(int width, int height) const; int getImageId(); @@ -83,27 +83,27 @@ class NVGImage { std::function onImageInvalidate = nullptr; - static inline UnorderedSet allImages = UnorderedSet(); + static inline auto allImages = UnorderedSet(); }; class NVGFramebuffer { public: NVGFramebuffer(); ~NVGFramebuffer(); - - static void clearAll(NVGcontext* nvg); - - bool needsUpdate(int const width, int const height) const; + + static void clearAll(NVGcontext const* nvg); + + bool needsUpdate(int width, int height) const; bool isValid() const; void setDirty(); - - void bind(NVGcontext* ctx, int const width, int const height); + + void bind(NVGcontext* ctx, int width, int height); static void unbind(); - - void renderToFramebuffer(NVGcontext* nvg, int const width, int const height, std::function renderCallback); + + void renderToFramebuffer(NVGcontext* nvg, int width, int height, std::function renderCallback); void render(NVGcontext* nvg, Rectangle b); @@ -123,7 +123,7 @@ class NVGCachedPath { NVGCachedPath(); ~NVGCachedPath(); - static void clearAll(NVGcontext* nvg); + static void clearAll(NVGcontext const* nvg); static void resetAll(); void clear(); @@ -142,7 +142,7 @@ class NVGCachedPath { }; struct NVGScopedState { - NVGScopedState(NVGcontext* nvg); + explicit NVGScopedState(NVGcontext* nvg); ~NVGScopedState(); NVGcontext* nvg; diff --git a/Source/Utility/OSUtils.cpp b/Source/Utility/OSUtils.cpp index f42e12b7a2..54705e9358 100644 --- a/Source/Utility/OSUtils.cpp +++ b/Source/Utility/OSUtils.cpp @@ -13,6 +13,8 @@ #if !defined(__APPLE__) # undef JUCE_GUI_BASICS_INCLUDE_XHEADERS # include +#else +#include #endif #if (defined(__cpp_lib_filesystem) || __has_include()) && (!defined(__APPLE__) || __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101500) @@ -309,6 +311,7 @@ OSUtils::KeyboardLayout OSUtils::getKeyboardLayout() } #endif // Linux/BSD + bool OSUtils::isDirectoryFast(const juce::String& path) { std::error_code ec; @@ -320,7 +323,7 @@ bool OSUtils::isDirectoryFast(const juce::String& path) return result; } -bool OSUtils::isFileFast(const juce::String& path) +bool OSUtils::isFileFast(juce::String const& path) { std::error_code ec; #ifdef _WIN32 @@ -330,7 +333,7 @@ bool OSUtils::isFileFast(const juce::String& path) #endif } -hash32 OSUtils::getUniqueFileHash(const juce::String& path) +hash32 OSUtils::getUniqueFileHash(juce::String const& path) { std::error_code ec; #ifdef _WIN32 @@ -338,8 +341,7 @@ hash32 OSUtils::getUniqueFileHash(const juce::String& path) #else auto canonicalPath = fs::canonical(std::string(path.toRawUTF8()), ec); #endif - if (ec) - { + if (ec) { jassertfalse; std::cerr << "fs hash error: " + juce::String(ec.message()) << std::endl; return 0; @@ -348,7 +350,7 @@ hash32 OSUtils::getUniqueFileHash(const juce::String& path) return hash(canonicalPath.c_str()); } -inline fs::directory_iterator dirIterFromJuceString(const juce::File& file) +inline fs::directory_iterator dirIterFromJuceFile(juce::File const& file) { std::error_code ec; #ifdef _WIN32 @@ -356,15 +358,14 @@ inline fs::directory_iterator dirIterFromJuceString(const juce::File& file) #else fs::directory_iterator it(std::string(file.getFullPathName().toRawUTF8()), ec); #endif - if (ec) - { + if (ec) { jassertfalse; std::cerr << "fs iter error: " + juce::String(ec.message()) << std::endl; } return it; } -inline fs::recursive_directory_iterator recursiveDirIterFromJuceString(const juce::File& file) +inline fs::recursive_directory_iterator recursiveDirIterFromJuceFile(juce::File const& file) { std::error_code ec; #ifdef _WIN32 @@ -372,8 +373,7 @@ inline fs::recursive_directory_iterator recursiveDirIterFromJuceString(const juc #else fs::recursive_directory_iterator it(std::string(file.getFullPathName().toRawUTF8()), ec); #endif - if (ec) - { + if (ec) { jassertfalse; std::cerr << "fs recursive iter error: " + juce::String(ec.message()) << std::endl; } @@ -386,7 +386,7 @@ SmallArray iterateDirectoryPaths(juce::File const& directory, bool con if (recursive) { try { - for (auto const& dirEntry : recursiveDirIterFromJuceString(directory)) { + for (auto const& dirEntry : recursiveDirIterFromJuceFile(directory)) { auto const isDir = dirEntry.is_directory(); if ((isDir && !onlyFiles) || !isDir) { result.add(dirEntry.path().string()); @@ -400,7 +400,7 @@ SmallArray iterateDirectoryPaths(juce::File const& directory, bool con } } else { try { - for (auto const& dirEntry : dirIterFromJuceString(directory)) { + for (auto const& dirEntry : dirIterFromJuceFile(directory)) { auto const isDir = dirEntry.is_directory(); if ((isDir && !onlyFiles) || !isDir) { result.add(dirEntry.path()); @@ -490,12 +490,12 @@ void* OSUtils::getDesktopParentPeer(Component* component) { // On iOS AUv3 plugins, all dialogs need to have a parent window specified #if JUCE_IOS - if(!component || ProjectInfo::isStandalone) + if (!component || ProjectInfo::isStandalone) return nullptr; - - if(auto* peer = component->getPeer()) + + if (auto* peer = component->getPeer()) return peer->getNativeHandle(); - + return nullptr; #else return nullptr; @@ -540,3 +540,47 @@ bool OSUtils::is24HourTimeFormat() return std::strstr(formattedTime, "AM") == nullptr && std::strstr(formattedTime, "PM") == nullptr; #endif } + +bool OSUtils::isFileQuarantined(const juce::File& file) +{ +#if JUCE_MAC + const char* attrName = "com.apple.quarantine"; + const char* path = file.getFullPathName().toRawUTF8(); + ssize_t result = getxattr(path, attrName, nullptr, 0, 0, 0); + return result != -1; +#elif JUCE_WINDOWS + const auto adsPath = file.getFullPathName() + ":Zone.Identifier"; + HANDLE h = CreateFileW (adsPath.toWideCharPointer(), + GENERIC_READ, + FILE_SHARE_READ, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr); + + if (h == INVALID_HANDLE_VALUE) + return false; + + CloseHandle (h); + return true; +#else + // Linux / other platforms: no standard mechanism + juce::ignoreUnused (file); + return false; +#endif +} + + +void OSUtils::removeFromQuarantine(const juce::File& file) +{ +#if JUCE_MAC + const char* attrName = "com.apple.quarantine"; + const char* path = file.getFullPathName().toRawUTF8(); + removexattr(path, attrName, 0); +#elif JUCE_WINDOWS + const auto adsPath = file.getFullPathName() + ":Zone.Identifier"; + DeleteFileW(adsPath.toWideCharPointer()); +#else + juce::ignoreUnused(file); +#endif +} diff --git a/Source/Utility/OSUtils.h b/Source/Utility/OSUtils.h index bef185a59d..769493dd2d 100644 --- a/Source/Utility/OSUtils.h +++ b/Source/Utility/OSUtils.h @@ -47,6 +47,8 @@ struct OSUtils { static KeyboardLayout getKeyboardLayout(); static bool is24HourTimeFormat(); + static bool isFileQuarantined(const juce::File& file); + static void removeFromQuarantine(const juce::File& file); #if JUCE_MAC || JUCE_IOS static float MTLGetPixelScale(void* view); @@ -61,9 +63,9 @@ struct OSUtils { ~ScrollTracker(); - static ScrollTracker* create() + static std::unique_ptr create() { - return new ScrollTracker(); + return std::make_unique(); } static bool isScrolling() @@ -74,7 +76,7 @@ struct OSUtils { private: bool scrolling = false; void* observer; - static inline ScrollTracker* instance = create(); + static inline std::unique_ptr instance = create(); }; #elif JUCE_IOS class ScrollTracker { @@ -87,10 +89,10 @@ struct OSUtils { { if (instance) return instance; - - if(!peer->getComponent().isVisible()) + + if (!peer->getComponent().isVisible()) return nullptr; - + return instance = new ScrollTracker(peer); } @@ -98,9 +100,15 @@ struct OSUtils { { return instance->scrolling; } + + static void setAllowOneFingerScroll(bool shouldAllowOneFingerScroll) + { + instance->allowOneFingerScroll = shouldAllowOneFingerScroll; + } private: bool scrolling = false; + bool allowOneFingerScroll = false; void* observer; static inline ScrollTracker* instance = nullptr; }; @@ -108,6 +116,7 @@ struct OSUtils { static juce::BorderSize getSafeAreaInsets(); static bool isIPad(); static float getScreenCornerRadius(); + static void showMobileChoiceMenu(juce::ComponentPeer* peer, juce::StringArray options, std::function callback); static void showMobileMainMenu(juce::ComponentPeer* peer, std::function callback); static void showMobileCanvasMenu(juce::ComponentPeer* peer, std::function callback); static bool addOpenURLMethodToDelegate(); diff --git a/Source/Utility/OSUtils.mm b/Source/Utility/OSUtils.mm index e377a244f9..4852f886e8 100644 --- a/Source/Utility/OSUtils.mm +++ b/Source/Utility/OSUtils.mm @@ -9,6 +9,7 @@ #if JUCE_MAC #import #import +#import #include #import #include @@ -254,7 +255,11 @@ - (void)scrollEventOccurred:(NSEvent*)event { void OSUtils::MTLSetVisible(void* view, bool shouldBeVisible) { auto* viewToShow = reinterpret_cast(view); + + [CATransaction begin]; + [CATransaction setDisableActions:YES]; [viewToShow setHidden:!shouldBeVisible]; + [CATransaction commit]; } @@ -286,16 +291,30 @@ @implementation ScrollEventObserver { juce::Point lastPosition; double lastScale; bool* isScrolling; + bool* allowOneFingerScroll; + + // Inertia variables + juce::Point velocity; + juce::Point lastMousePosition; + int lastNumberOfTouches; + std::unique_ptr inertiaTimer; } -- (instancetype)initWithComponentPeer:(juce::ComponentPeer*)componentPeer scrollState:(bool*)scrollState { +- (instancetype)initWithComponentPeer:(juce::ComponentPeer*)componentPeer scrollState:(bool*)scrollState allowsOneFingerScroll:(bool*)allowsOneFingerScroll { self = [super init]; peer = componentPeer; view = (UIView*)peer->getNativeHandle(); isScrolling = scrollState; + allowOneFingerScroll = allowsOneFingerScroll; lastScale = 1.0; - + velocity = {0.0f, 0.0f}; + lastMousePosition = {0.0f, 0.0f}; + + inertiaTimer = std::make_unique([self]() { + [self updateInertia]; + }); + UIPinchGestureRecognizer* pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchEventOccurred:)]; [view addGestureRecognizer:pinchGesture]; @@ -304,9 +323,12 @@ - (instancetype)initWithComponentPeer:(juce::ComponentPeer*)componentPeer scroll UIPanGestureRecognizer* panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(scrollEventOccurred:)]; [panGesture setMaximumNumberOfTouches: 2]; - [panGesture setMinimumNumberOfTouches: 2]; + [panGesture setMinimumNumberOfTouches: 1]; [view addGestureRecognizer:panGesture]; + UITapGestureRecognizer* tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapEventOccurred:)]; + [tapGesture setCancelsTouchesInView: NO]; // Don't interfere with other gestures + [view addGestureRecognizer:tapGesture]; return self; } @@ -318,7 +340,7 @@ - (instancetype)initWithComponentPeer:(juce::ComponentPeer*)componentPeer scroll - (void)longPressEventOccurred:(UILongPressGestureRecognizer*)gesture { juce::ModifierKeys::currentModifiers = juce::ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (juce::ModifierKeys::rightButtonModifier); - + const auto eventPosition = [gesture locationOfTouch:0 inView:view]; auto pos = juce::Point(eventPosition.x, eventPosition.y); const auto time = (juce::Time::currentTimeMillis() - juce::Time::getMillisecondCounter()) + (juce::int64) ([[NSProcessInfo processInfo] systemUptime] * 1000.0); @@ -350,6 +372,13 @@ - (void)pinchEventOccurred:(UIPinchGestureRecognizer*)gesture { - (void)scrollEventOccurred:(UIPanGestureRecognizer*)gesture { + if(gesture.numberOfTouches == 1 && !*allowOneFingerScroll) + { + [gesture setCancelsTouchesInView: false]; + inertiaTimer->stopTimer(); + return; + } + const auto offset = [gesture translationInView: view]; const auto scale = 1.0f / 256.0f; @@ -357,17 +386,32 @@ - (void)scrollEventOccurred:(UIPanGestureRecognizer*)gesture { { *isScrolling = true; lastPosition = {0.0f, 0.0f}; + velocity = {0.0f, 0.0f}; + inertiaTimer->stopTimer(); [gesture setCancelsTouchesInView: true]; return; } + if(gesture.state == UIGestureRecognizerStateEnded) { *isScrolling = false; + + // Get velocity and start inertia + CGPoint gestureVelocity = [gesture velocityInView: view]; + velocity = {(float)gestureVelocity.x, (float)gestureVelocity.y}; + + const float minVelocity = 100.0f; + if (lastNumberOfTouches == 1 && *allowOneFingerScroll && (std::abs(velocity.x) > minVelocity || std::abs(velocity.y) > minVelocity)) + { + inertiaTimer->startTimerHz(60); // 60 FPS + } + + *allowOneFingerScroll = false; lastPosition = {0.0f, 0.0f}; [gesture setCancelsTouchesInView: false]; return; } - + juce::MouseWheelDetails details; details.deltaX = scale * (float) (offset.x - lastPosition.x); details.deltaY = scale * (float) (offset.y - lastPosition.y); @@ -376,21 +420,59 @@ - (void)scrollEventOccurred:(UIPanGestureRecognizer*)gesture { details.isInertial = false; lastPosition = juce::Point(offset.x, offset.y); - + lastNumberOfTouches = gesture.numberOfTouches; + const auto eventMousePosition = [gesture locationInView: view]; - const auto reconstructedMousePosition = juce::Point(eventMousePosition.x, eventMousePosition.y) - juce::Point(offset.x, offset.y); + const auto reconstructedMousePosition = juce::Point(eventMousePosition.x, eventMousePosition.y) - juce::Point(offset.x, offset.y); + lastMousePosition = reconstructedMousePosition; + const auto time = (juce::Time::currentTimeMillis() - juce::Time::getMillisecondCounter()) + (juce::int64) ([[NSProcessInfo processInfo] systemUptime] * 1000.0); - peer->handleMouseWheel (juce::MouseInputSource::InputSourceType::touch, reconstructedMousePosition, time, details); + peer->handleMouseWheel(juce::MouseInputSource::InputSourceType::touch, reconstructedMousePosition, time, details); +} + +- (void)tapEventOccurred:(UITapGestureRecognizer*)gesture { + // Cancel any ongoing inertia scrolling + inertiaTimer->stopTimer(); + velocity = {0.0f, 0.0f}; + *allowOneFingerScroll = false; } +- (void)updateInertia +{ + const float decelerationRate = 0.95f; + const float scale = 1.0f / 256.0f; + const float dt = 1.0f / 60.0f; // Frame time at 60 FPS + + velocity.x *= decelerationRate; + velocity.y *= decelerationRate; + + const float minVelocity = 10.0f; + if (std::abs(velocity.x) < minVelocity && std::abs(velocity.y) < minVelocity) + { + inertiaTimer->stopTimer(); + return; + } + + juce::MouseWheelDetails details; + details.deltaX = scale * velocity.x * dt; + details.deltaY = scale * velocity.y * dt; + details.isReversed = false; + details.isSmooth = true; + details.isInertial = true; + + const auto time = (juce::Time::currentTimeMillis() - juce::Time::getMillisecondCounter()) + + (juce::int64) ([[NSProcessInfo processInfo] systemUptime] * 1000.0); + + peer->handleMouseWheel(juce::MouseInputSource::InputSourceType::touch, lastMousePosition, time, details); +} @end OSUtils::ScrollTracker::ScrollTracker(juce::ComponentPeer* peer) { // Create the ScrollEventObserver instance - observer = [[ScrollEventObserver alloc] initWithComponentPeer:peer scrollState: &scrolling]; + observer = [[ScrollEventObserver alloc] initWithComponentPeer:peer scrollState: &scrolling allowsOneFingerScroll: &allowOneFingerScroll]; } OSUtils::ScrollTracker::~ScrollTracker() @@ -406,9 +488,9 @@ - (void)scrollEventOccurred:(UIPanGestureRecognizer*)gesture { UIWindow* window = [[UIApplication sharedApplication] keyWindow]; if (@available(iOS 11.0, *)) { UIEdgeInsets insets = window.safeAreaInsets; - return juce::BorderSize(insets.top + (isIPad() ? 26 : 0), insets.left, insets.bottom + (isIPad() ? -20 : 0), insets.right); + return juce::BorderSize(insets.top + (isIPad() ? 26 : 0), insets.left, insets.bottom - std::max(0, insets.bottom - (isIPad() ? 20 : 0)), insets.right); } - + // Fallback for older iOS versions or devices without safeAreaInsets return juce::BorderSize(); } @@ -418,6 +500,74 @@ - (void)scrollEventOccurred:(UIPanGestureRecognizer*)gesture { return [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad; } +void OSUtils::showMobileChoiceMenu(juce::ComponentPeer* peer, juce::StringArray options, std::function callback) +{ + auto* view = (UIView*) peer->getNativeHandle(); + if (view == nil) + return; + + // Find the parent view controller + UIViewController* viewController = nil; + UIResponder* responder = view; + while (responder != nil) + { + if ([responder isKindOfClass:[UIViewController class]]) + { + viewController = (UIViewController*) responder; + break; + } + responder = [responder nextResponder]; + } + + if (viewController == nil) + return; + + UIAlertController* alertController = + [UIAlertController alertControllerWithTitle:nil + message:nil + preferredStyle:UIAlertControllerStyleActionSheet]; + + // Add option actions + for (int i = 0; i < options.size(); ++i) + { + NSString* option = [[NSString alloc] initWithUTF8String:options[i].toRawUTF8()]; + UIAlertAction* action = [UIAlertAction actionWithTitle:option + style:UIAlertActionStyleDefault + handler:^(UIAlertAction*) + { + callback(i); + }]; + + [alertController addAction:action]; + } + + // Cancel button + UIAlertAction* cancel = [UIAlertAction actionWithTitle:@"Cancel" + style:UIAlertActionStyleCancel + handler:^(UIAlertAction*) + { + callback(-1); + }]; + + [alertController addAction:cancel]; + + if (isIPad()) + { + alertController.preferredContentSize = view.frame.size; + + if (auto* popoverController = alertController.popoverPresentationController) + { + popoverController.sourceView = view; + popoverController.sourceRect = CGRectMake (35.0f, 1.0f, 50.0f, 50.0f); + popoverController.canOverlapSourceViewRect = YES; + } + } + + + // Present the alert controller using the found view controller + [viewController presentViewController:alertController animated:YES completion:nil]; +} + void OSUtils::showMobileMainMenu(juce::ComponentPeer* peer, std::function callback) { auto* view = (UIView*)peer->getNativeHandle(); @@ -451,13 +601,13 @@ - (void)scrollEventOccurred:(UIPanGestureRecognizer*)gesture { UIAlertAction *subAction1 = [UIAlertAction actionWithTitle:@"First Theme (Light)" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - callback(7); + callback(8); }]; UIAlertAction *subAction2 = [UIAlertAction actionWithTitle:@"Second Theme (dark)" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - callback(8); + callback(9); }]; UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" @@ -500,26 +650,32 @@ - (void)scrollEventOccurred:(UIPanGestureRecognizer*)gesture { callback(2); }]; + UIAlertAction *openPatchFolderAction = [UIAlertAction actionWithTitle:@"Open Folder" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * _Nonnull action) { + callback(3); + }]; + UIAlertAction *savePatchAction = [UIAlertAction actionWithTitle:@"Save Patch" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - callback(3); + callback(4); }]; UIAlertAction *savePatchAsAction = [UIAlertAction actionWithTitle:@"Save Patch As" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - callback(4); + callback(5); }]; UIAlertAction *settingsAction = [UIAlertAction actionWithTitle:@"Settings" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - callback(5); + callback(6); }]; UIAlertAction *aboutAction = [UIAlertAction actionWithTitle:@"About" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - callback(6); + callback(7); }]; UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" @@ -530,6 +686,7 @@ - (void)scrollEventOccurred:(UIPanGestureRecognizer*)gesture { [alertController addAction:newPatchAction]; [alertController addAction:openPatchAction]; + [alertController addAction:openPatchFolderAction]; [alertController addAction:savePatchAction]; [alertController addAction:savePatchAsAction]; [alertController addAction:themeAction]; @@ -658,7 +815,7 @@ BOOL openURLImplementation(id self, SEL _cmd, UIApplication* app, NSURL* url, NS juce::String juceFilePath = juce::String::fromUTF8([filePath UTF8String]); [url startAccessingSecurityScopedResource]; - if (auto* juceApp = juce::JUCEApplicationBase::getInstance()) + if (juce::JUCEApplicationBase::getInstance()) { juce::MessageManager::callAsync([juceFilePath]() { @@ -727,7 +884,7 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { - (void)commonInit { self.metalLayer = (CAMetalLayer *)self.layer; - [self.metalLayer setPresentsWithTransaction:TRUE]; + [self.metalLayer setPresentsWithTransaction:FALSE]; [self.metalLayer setFramebufferOnly:FALSE]; } diff --git a/Source/Utility/ObjectThemeManager.h b/Source/Utility/ObjectThemeManager.h index 3f4238140a..4f9bad174d 100644 --- a/Source/Utility/ObjectThemeManager.h +++ b/Source/Utility/ObjectThemeManager.h @@ -34,7 +34,7 @@ class ObjectThemeManager { ln = lnf.findColour(PlugDataColour::guiObjectInternalOutlineColour); } - String getCompleteFormat(String& name) const + String getCompleteFormat(String const& name) const { StringArray token; token.add(name); @@ -58,7 +58,7 @@ class ObjectThemeManager { }; auto preset = guiDefaults.at(tokens[0]); - + preset = preset.replace("@bgColour_rgb", colourToRGB(bg)); preset = preset.replace("@fgColour_rgb", colourToRGB(fg)); preset = preset.replace("@lblColour_rgb", colourToRGB(lbl)); @@ -83,9 +83,9 @@ class ObjectThemeManager { // Taken from pd save files, this will make sure that it directly initialises objects with the right parameters static inline UnorderedMap const guiDefaults = { // UI OBJECTS: - { "bng", "25 250 50 0 empty empty empty 17 7 0 10 @bgColour @fgColour @lblColour" }, - { "tgl", "25 0 empty empty empty 17 7 0 10 @bgColour @fgColour @lblColour 0 1" }, - { "toggle", "25 0 empty empty empty 17 7 0 10 @bgColour @fgColour @lblColour 0 1" }, + { "bng", "25 250 50 0 empty empty empty 0 -10 0 10 @bgColour @fgColour @lblColour" }, + { "tgl", "25 0 empty empty empty 0 -10 0 10 @bgColour @fgColour @lblColour 0 1" }, + { "toggle", "25 0 empty empty empty 0 -10 0 10 @bgColour @fgColour @lblColour 0 1" }, { "button", "25 25 @bgColour_rgb @fgColour_rgb" }, { "knob", "50 0 127 0 0 empty empty @bgColour @lnColour @fgColour 1 0 0 0 1 270 0 0 0 empty empty 0 12 6 -15 0 1 0 0" }, { "vsl", "17 128 0 127 0 0 empty empty empty 0 -9 0 10 @bgColour @fgColour @lblColour 0 1" }, @@ -101,7 +101,7 @@ class ObjectThemeManager { { "keyboard", "16 80 4 2 0 0 empty empty" }, { "messbox", "180 60 @bgColour_rgb @lblColour_rgb 0 12" }, { "vu", "20 120 empty empty -1 -8 0 10 #404040 @lblColour 1 0" }, - { "popmenu", "128 26 12 @bgColour @fgColour \\ empty empty empty empty 1 0 -1 1 0 1 0 0 0 0 0"}, + { "popmenu", "128 26 12 @bgColour @fgColour \\ empty empty empty empty 1 0 -1 1 0 1 0 0 0 0 0" }, // ADDITIONAL UI OBJECTS: { "floatbox", "5 0 0 0 - - - 12" }, { "symbolbox", "5 0 0 0 - - - 12" }, diff --git a/Source/Utility/OfflineObjectRenderer.cpp b/Source/Utility/OfflineObjectRenderer.cpp index ad79d51d7c..fa9af2b144 100644 --- a/Source/Utility/OfflineObjectRenderer.cpp +++ b/Source/Utility/OfflineObjectRenderer.cpp @@ -77,29 +77,29 @@ void OfflineObjectRenderer::parsePatch(String const& patch, std::function= 4 && tokens[1] != "f" && tokens[2].containsOnly("-0123456789") && tokens[3].containsOnly("-0123456789"); }; - auto isMessage = [](StringArray& tokens) { + auto isMessage = [](StringArray const& tokens) { return tokens[0] == "#X" && tokens[1] == "msg" && tokens.size() >= 4 && tokens[1] != "f" && tokens[2].containsOnly("-0123456789") && tokens[3].containsOnly("-0123456789"); }; - auto isObject = [](StringArray& tokens) { + auto isObject = [](StringArray const& tokens) { return tokens[0] == "#X" && tokens[1] != "connect" && tokens.size() >= 4 && tokens[1] != "f" && tokens[2].containsOnly("-0123456789") && tokens[3].containsOnly("-0123456789"); }; - auto isConnection = [](StringArray& tokens) { + auto isConnection = [](StringArray const& tokens) { return tokens[0] == "#X" && tokens[1] == "connect" && tokens[2].containsOnly("0123456789") && tokens[3].containsOnly("0123456789") && tokens[4].containsOnly("0123456789") && tokens[5].containsOnly("0123456789"); }; - auto isStartingCanvas = [](StringArray& tokens) { + auto isStartingCanvas = [](StringArray const& tokens) { return tokens[0] == "#N" && tokens[1] == "canvas" && tokens.size() >= 6 && tokens[2].containsOnly("-0123456789") && tokens[3].containsOnly("-0123456789") && tokens[4].containsOnly("-0123456789") && tokens[5].containsOnly("-0123456789"); }; - auto isEndingCanvas = [](StringArray& tokens) { + auto isEndingCanvas = [](StringArray const& tokens) { return tokens[0] == "#X" && tokens[1] == "restore" && tokens.size() >= 4 && tokens[2].containsOnly("-0123456789") && tokens[3].containsOnly("-0123456789"); }; - auto isGraphCoords = [](StringArray& tokens) { + auto isGraphCoords = [](StringArray const& tokens) { return tokens[0] == "#X" && tokens[1] == "coords" && tokens.size() >= 7 && tokens[5].containsOnly("-0123456789") && tokens[6].containsOnly("-0123456789"); }; @@ -360,14 +360,13 @@ bool OfflineObjectRenderer::checkIfPatchIsValid(String const& patch) // Split the patch into lines auto lines = StringArray::fromLines(patch); - for (const auto& line : lines) - { + for (auto const& line : lines) { if (line.startsWith("#X") || line.startsWith("#N") || line.startsWith("#A") || !line.containsNonWhitespaceChars()) continue; - + return false; } - + return true; } diff --git a/Source/Utility/OfflineObjectRenderer.h b/Source/Utility/OfflineObjectRenderer.h index 048c0a137a..3257444a7d 100644 --- a/Source/Utility/OfflineObjectRenderer.h +++ b/Source/Utility/OfflineObjectRenderer.h @@ -12,7 +12,7 @@ class ImageWithOffset { public: - ImageWithOffset(Image const& withImage = Image(), Point withOffset = Point()) + explicit ImageWithOffset(Image const& withImage = Image(), Point const withOffset = Point()) : image(withImage) , offset(withOffset) { diff --git a/Source/Utility/PatchInfo.h b/Source/Utility/PatchInfo.h index 0eba4c3b67..0565629d48 100644 --- a/Source/Utility/PatchInfo.h +++ b/Source/Utility/PatchInfo.h @@ -18,11 +18,11 @@ class PatchInfo { String size; String json; String version; - int64 installTime; + String folderOverride; PatchInfo() = default; - PatchInfo(var const& jsonData) + explicit PatchInfo(var const& jsonData) { title = jsonData["Title"]; author = jsonData["Author"]; @@ -32,23 +32,13 @@ class PatchInfo { price = jsonData["Price"]; thumbnailUrl = jsonData["StoreThumb"]; version = jsonData["Version"]; - if (jsonData.hasProperty("InstallTime")) { - installTime = static_cast(jsonData["InstallTime"]); - } else { - installTime = 0; + if (jsonData.hasProperty("FolderName")) { + folderOverride = jsonData["FolderName"]; } + json = JSON::toString(jsonData, false); } - void setInstallTime(int64 millisSinceEpoch) - { - auto jsonData = JSON::fromString(json); - if (auto* obj = jsonData.getDynamicObject()) { - obj->setProperty("InstallTime", millisSinceEpoch); - json = JSON::toString(jsonData, false); - } - } - bool isPatchArchive() const { auto const fileName = URL(download).getFileName(); @@ -57,6 +47,11 @@ class PatchInfo { String getNameInPatchFolder() const { + if(folderOverride.isNotEmpty()) + { + return folderOverride; + } + return title.toLowerCase().replace(" ", "-") + "-" + String::toHexString(hash(author) + hash(version)); } @@ -85,7 +80,7 @@ class PatchInfo { if (file.getFileName() == patchFileName) { auto metaFile = file.getChildFile("meta.json"); - if (metaFile.existsAsFile()) { + if (metaFile.existsAsFile() && version.isNotEmpty()) { return JSON::parse(metaFile)["Version"].toString() != version; } } diff --git a/Source/Utility/PluginParameter.h b/Source/Utility/PluginParameter.h index e4f961d142..665cb1c366 100644 --- a/Source/Utility/PluginParameter.h +++ b/Source/Utility/PluginParameter.h @@ -28,7 +28,6 @@ class PlugDataParameter final : public RangedAudioParameter { , enabled(enabled) , rangeStart(minimum) , rangeEnd(maximum) - , rangeInterval(0) , mode(Float) { value = NormalisableRange(rangeStart, rangeEnd, rangeInterval, rangeSkew).convertFrom0to1(getDefaultValue()); @@ -41,11 +40,10 @@ class PlugDataParameter final : public RangedAudioParameter { int getNumSteps() const override { auto const range = getNormalisableRange(); - if(mode == Integer) - { - return (range.end - range.start) + 1; + if (mode == Integer) { + return range.end - range.start + 1; } - + return static_cast((range.end - range.start) / std::numeric_limits::epsilon()) + 1; } @@ -138,7 +136,7 @@ class PlugDataParameter final : public RangedAudioParameter { auto const oldValue = value.load(); value = std::clamp(newValue, range.start, range.end); sendValueChangedMessageToListeners(getValue()); - valueChanged = valueChanged || (oldValue != value); + valueChanged = valueChanged || oldValue != value; } float getValue() const override @@ -152,19 +150,19 @@ class PlugDataParameter final : public RangedAudioParameter { auto const range = getNormalisableRange(); auto const oldValue = value.load(); value = range.convertFrom0to1(newValue); - valueChanged = valueChanged || (oldValue != value); + valueChanged = valueChanged || oldValue != value; } - - void setDefaultValue(float newDefaultValue) + + void setDefaultValue(float const newDefaultValue) { defaultValue = newDefaultValue; - if(enabled && !loadedFromDAW) - { + if (enabled && !loadedFromDAW) { setValue(newDefaultValue); + sendValueChangedMessageToListeners(newDefaultValue); setChanged(); } } - + void clearLoadedFromDAWFlag() { loadedFromDAW = false; @@ -239,7 +237,7 @@ class PlugDataParameter final : public RangedAudioParameter { paramXml->setAttribute(String("max"), param->getNormalisableRange().end); paramXml->setAttribute(String("enabled"), param->enabled); - paramXml->setAttribute(String("value"), static_cast(param->getValue())); + paramXml->setAttribute(String("value"), param->getValue()); paramXml->setAttribute(String("index"), param->index); paramXml->setAttribute(String("mode"), param->mode); @@ -351,7 +349,7 @@ class PlugDataParameter final : public RangedAudioParameter { private: float defaultValue; bool loadedFromDAW = false; - + AtomicValue valueChanged; AtomicValue gestureState = 0.0f; AtomicValue index; diff --git a/Source/Utility/SeqLock.h b/Source/Utility/SeqLock.h index 44b57524bd..ec3f849cc3 100644 --- a/Source/Utility/SeqLock.h +++ b/Source/Utility/SeqLock.h @@ -89,12 +89,12 @@ class SeqLock { // bytes pointed to by dest. Each individual load operation from a source // byte is atomic with memory order order. These individual loads are // unsequenced with respect to each other. - inline void* atomic_load_per_byte_memcpy(void* dest, void const* source, size_t const count, std::memory_order order) const + static void* atomic_load_per_byte_memcpy(void* dest, void const* source, size_t const count, std::memory_order const order) { assert(order == std::memory_order_acquire || order == std::memory_order_relaxed); - char* dest_bytes = reinterpret_cast(dest); - char const* src_bytes = reinterpret_cast(source); + auto* dest_bytes = static_cast(dest); + auto const* src_bytes = static_cast(source); for (std::size_t i = 0; i < count; ++i) { @@ -122,14 +122,14 @@ class SeqLock { // bytes pointed to by dest. Each individual store operation to a // destination byte is atomic with memory order order. These individual // stores are unsequenced with respect to each other. - static inline void* atomic_store_per_byte_memcpy(void* dest, void const* source, size_t const count, std::memory_order order) + static void* atomic_store_per_byte_memcpy(void* dest, void const* source, size_t const count, std::memory_order order) { assert(order == std::memory_order_release || order == std::memory_order_relaxed); std::atomic_thread_fence(order); - char* dest_bytes = reinterpret_cast(dest); - char const* src_bytes = reinterpret_cast(source); + auto* dest_bytes = static_cast(dest); + auto const* src_bytes = static_cast(source); for (size_t i = 0; i < count; ++i) { #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) @@ -259,7 +259,7 @@ class AtomicValue { // Atomic increment (prefix ++) template - auto operator++() noexcept -> std::enable_if_t>> + auto operator++() noexcept -> std::enable_if_t>, U> { return storage_value.fetch_add(1, getWriteOrder()) + 1; } diff --git a/Source/Utility/SettingsFile.cpp b/Source/Utility/SettingsFile.cpp index 0cd6b0e6d8..5d9017732c 100644 --- a/Source/Utility/SettingsFile.cpp +++ b/Source/Utility/SettingsFile.cpp @@ -242,7 +242,7 @@ ValueTree SettingsFile::getTheme(String const& name) const return getColourThemesTree().getChildWithProperty("theme", name); } -void SettingsFile::setLastBrowserPathForId(String const& identifier, File& path) +void SettingsFile::setLastBrowserPathForId(String const& identifier, File const& path) { if (identifier.isEmpty() || !path.exists() || path.isRoot()) return; @@ -343,27 +343,41 @@ void SettingsFile::initialiseCommandHistory() CommandInput::setCommandHistory(commands); } -void SettingsFile::addToRecentlyOpened(File const& path) +void SettingsFile::addToRecentlyOpened(URL const& url) { auto recentlyOpened = settingsTree.getChildWithName("RecentlyOpened"); - + auto path = url.getLocalFile().getFullPathName(); + if (!recentlyOpened.isValid()) { recentlyOpened = ValueTree("RecentlyOpened"); SettingsFile::getInstance()->getValueTree().appendChild(recentlyOpened, nullptr); } - if (recentlyOpened.getChildWithProperty("Path", path.getFullPathName()).isValid()) { - - recentlyOpened.getChildWithProperty("Path", path.getFullPathName()).setProperty("Time", Time::getCurrentTime().toMilliseconds(), nullptr); - - int const oldIdx = recentlyOpened.indexOf(recentlyOpened.getChildWithProperty("Path", path.getFullPathName())); + if (recentlyOpened.getChildWithProperty("Path", path).isValid()) { + auto existing = recentlyOpened.getChildWithProperty("Path", path); + existing.setProperty("Time", Time::getCurrentTime().toMilliseconds(), nullptr); +#if JUCE_IOS + auto bookmarkData = url.getBookmarkData(); + if(bookmarkData.isNotEmpty()) { + existing.setProperty("Bookmark", bookmarkData, nullptr); + } +#endif + int const oldIdx = recentlyOpened.indexOf(existing); recentlyOpened.moveChild(oldIdx, 0, nullptr); } else { ValueTree subTree("Path"); - subTree.setProperty("Path", path.getFullPathName(), nullptr); + subTree.setProperty("Path", path, nullptr); subTree.setProperty("Time", Time::getCurrentTime().toMilliseconds(), nullptr); +#if JUCE_IOS + // Store iOS bookmark so that we can recover file permissions later + auto bookmarkData = url.getBookmarkData(); + if(bookmarkData.isNotEmpty()) { + subTree.setProperty("Bookmark", bookmarkData, nullptr); + } +#endif + #if JUCE_MAC || JUCE_WINDOWS - if (path.isOnRemovableDrive()) + if (url.getLocalFile().isOnRemovableDrive()) subTree.setProperty("Removable", var(1), nullptr); #endif recentlyOpened.addChild(subTree, 0, nullptr); @@ -403,7 +417,7 @@ bool SettingsFile::wantsNativeDialog() const return true; } - return static_cast(settingsTree.getProperty("NativeDialog")); + return settingsTree.getProperty("NativeDialog"); } void SettingsFile::initialiseThemesTree() @@ -529,13 +543,13 @@ void SettingsFile::initialiseOverlayTree() bool SettingsFile::acquireFileLock() { - auto startTime = Time::getCurrentTime().toMilliseconds(); - + auto const startTime = Time::getCurrentTime().toMilliseconds(); + while (Time::getCurrentTime().toMilliseconds() - startTime < lockTimeoutMs) { if (!lockFile.exists()) { // Try to create lock file with our process info auto processInfo = String(Time::getCurrentTime().toMilliseconds()); - + if (lockFile.replaceWithText(processInfo)) { // Double-check we successfully created it (atomic operation) Thread::sleep(1); // Brief pause to ensure file system consistency @@ -545,15 +559,15 @@ bool SettingsFile::acquireFileLock() } } else { auto lockAge = Time::getCurrentTime().toMilliseconds() - lockFile.loadFileAsString().getLargeIntValue(); - if (lockAge > lockTimeoutMs * 2) { - lockFile.deleteFile(); - continue; // Try to acquire again + if (lockAge > lockTimeoutMs * 2) { + lockFile.deleteFile(); + continue; // Try to acquire again } } - + Thread::sleep(10); // Brief wait before retry } - + return false; // Timeout } @@ -562,33 +576,31 @@ void SettingsFile::releaseFileLock() lockFile.deleteFile(); } - void SettingsFile::reloadSettings() { jassert(isInitialised); - if(acquireFileLock()) { + if (acquireFileLock()) { auto const newSettings = settingsFile.loadFileAsString(); - auto contentHash = newSettings.hashCode64(); - if(contentHash == lastContentHash) { + auto const contentHash = newSettings.hashCode64(); + if (contentHash == lastContentHash) { releaseFileLock(); return; } - + auto const newTree = ValueTree::fromXml(newSettings); - if(!newTree.isValid()) - { + if (!newTree.isValid()) { releaseFileLock(); return; } - + // Children shouldn't be overwritten as that would break some valueTree links for (auto child : settingsTree) { child.copyPropertiesAndChildrenFrom(newTree.getChildWithName(child.getType()), nullptr); } - + settingsTree.copyPropertiesFrom(newTree, nullptr); - + for (auto* listener : listeners) { listener->settingsFileReloaded(); } @@ -642,17 +654,17 @@ void SettingsFile::setGlobalScale(float const newScale) void SettingsFile::saveSettings() { jassert(isInitialised); - + saveCommandHistory(); - + // Check if content actually changed auto const xml = settingsTree.toXmlString(); - auto contentHash = xml.hashCode64(); - + auto const contentHash = xml.hashCode64(); + if (contentHash == lastContentHash) { return; // No changes to save } - + // Attempt to acquire file lock if (acquireFileLock()) { if (settingsFile.replaceWithText(xml)) { diff --git a/Source/Utility/SettingsFile.h b/Source/Utility/SettingsFile.h index ae7c5b0b89..871b3266d6 100644 --- a/Source/Utility/SettingsFile.h +++ b/Source/Utility/SettingsFile.h @@ -39,10 +39,10 @@ class SettingsFile final : public ValueTree::Listener ValueTree getTheme(String const& name) const; ValueTree getCurrentTheme() const; - void setLastBrowserPathForId(String const& identifier, File& path); + void setLastBrowserPathForId(String const& identifier, File const& path); File getLastBrowserPathForId(String const& identifier) const; - void addToRecentlyOpened(File const& path); + void addToRecentlyOpened(URL const& path); void saveCommandHistory(); void initialiseCommandHistory(); @@ -98,10 +98,9 @@ class SettingsFile final : public ValueTree::Listener void resetSettingsState(); private: - bool acquireFileLock(); void releaseFileLock(); - + static bool verify(XmlElement const* settings); void backupCorruptSettings(); @@ -117,11 +116,11 @@ class SettingsFile final : public ValueTree::Listener File settingsFile = ProjectInfo::appDataDir.getChildFile(".settings"); File lockFile = settingsFile.getSiblingFile(settingsFile.getFileNameWithoutExtension() + ".lock"); - + ValueTree settingsTree = ValueTree("SettingsTree"); bool settingsChangedInternally = false; bool settingsChangedExternally = false; - int64 lastContentHash; + int64 lastContentHash = 0; static constexpr int64 saveTimeoutMs = 100; static constexpr int64 lockTimeoutMs = 5000; diff --git a/Source/Utility/StackDropShadower.h b/Source/Utility/StackDropShadower.h index 3a8b874ea6..809b0a272b 100644 --- a/Source/Utility/StackDropShadower.h +++ b/Source/Utility/StackDropShadower.h @@ -216,7 +216,8 @@ class StackDropShadower final : private ComponentListener { setSize(1, 1); // to keep the OS happy by not having zero-size windows addToDesktop(ComponentPeer::windowIsTemporary | ComponentPeer::windowIgnoresMouseClicks - | ComponentPeer::windowIgnoresKeyPresses, OSUtils::getDesktopParentPeer(comp)); + | ComponentPeer::windowIgnoresKeyPresses, + OSUtils::getDesktopParentPeer(comp)); } else if (Component* const parent = comp->getParentComponent()) { parent->addChildComponent(this); } diff --git a/Source/Utility/StackShadow.cpp b/Source/Utility/StackShadow.cpp index b7aeaa7339..95dc37b2b9 100644 --- a/Source/Utility/StackShadow.cpp +++ b/Source/Utility/StackShadow.cpp @@ -8,7 +8,7 @@ StackShadow::~StackShadow() clearSingletonInstance(); } -void StackShadow::renderDropShadow(hash32 const id, juce::Graphics& g, juce::Path const& path, juce::Colour color, int const radius, juce::Point const offset, int const spread) +void StackShadow::renderDropShadow(hash32 const id, juce::Graphics& g, juce::Path const& path, juce::Colour const color, int const radius, juce::Point const offset, int const spread) { auto& dropShadow = StackShadow::getInstance()->dropShadows[id]; if (!dropShadow) diff --git a/Source/Utility/UnorderedMap.h b/Source/Utility/UnorderedMap.h index 8c293e4f3d..db946942ab 100644 --- a/Source/Utility/UnorderedMap.h +++ b/Source/Utility/UnorderedMap.h @@ -350,11 +350,11 @@ struct hash> { }; template -struct hash::value>::type> { +struct hash>> { using is_avalanching = void; auto operator()(Enum e) const noexcept -> uint64_t { - using underlying = typename std::underlying_type_t; + using underlying = std::underlying_type_t; return detail::wyhash::hash(static_cast(e)); } }; @@ -578,7 +578,7 @@ class segmented_vector { */ template class iter_t { - using ptr_t = typename std::conditional_t; + using ptr_t = std::conditional_t; ptr_t m_data {}; size_t m_idx {}; @@ -588,13 +588,13 @@ class segmented_vector { public: using difference_type = segmented_vector::difference_type; using value_type = T; - using reference = typename std::conditional_t; - using pointer = typename std::conditional_t; + using reference = std::conditional_t; + using pointer = std::conditional_t; using iterator_category = std::forward_iterator_tag; iter_t() noexcept = default; - template::type> + template> // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) constexpr iter_t(iter_t const& other) noexcept : m_data(other.m_data) @@ -608,7 +608,7 @@ class segmented_vector { { } - template::type> + template> constexpr auto operator=(iter_t const& other) noexcept -> iter_t& { m_data = other.m_data; @@ -883,7 +883,7 @@ template class table : public std::conditional_t, base_table_type_map, base_table_type_set> { - using underlying_value_type = typename std::conditional_t, std::pair, Key>; + using underlying_value_type = std::conditional_t, std::pair, Key>; using underlying_container_type = std::conditional_t, std::vector>; diff --git a/Source/Utility/ValueTreeViewer.h b/Source/Utility/ValueTreeViewer.h index 517e8c4f69..697407ce59 100644 --- a/Source/Utility/ValueTreeViewer.h +++ b/Source/Utility/ValueTreeViewer.h @@ -63,6 +63,9 @@ class ValueTreeNodeComponent final : public Component { void mouseUp(MouseEvent const& e) override { + if (!e.mods.isLeftButtonDown()) + return; + // double click to collapse directory / node if (e.getNumberOfClicks() == 2) { node->toggleNodeOpenClosed(); @@ -159,7 +162,7 @@ class ValueTreeNodeComponent final : public Component { } } - void paintOpenCloseButton(Graphics& g, Rectangle const& area) + void paintOpenCloseButton(Graphics& g, Rectangle const& area) const { auto const arrowArea = area.reduced(5, 9).translated(4, 0).toFloat(); Path p; @@ -206,6 +209,9 @@ class ValueTreeNodeComponent final : public Component { void mouseUp(MouseEvent const& e) override { + if (!e.mods.isLeftButtonDown()) + return; + isDragging = false; if (e.eventComponent == this && e.mods.isLeftButtonDown() && getScreenBounds().contains(e.getScreenPosition())) { @@ -308,11 +314,11 @@ class ValueTreeNodeComponent final : public Component { g.setColour(recColour.withAlpha(0.2f)); auto tagBounds = itemBounds.removeFromLeft(length).translated(4, 0).reduced(0, 5).expanded(2, 0).toFloat(); Path flag; - Point const a = tagBounds.getTopLeft().toFloat(); - Point const b = Point(tagBounds.getX() + tagBounds.getHeight() * 0.5f, tagBounds.getY()); - Point const c = Point(tagBounds.getX() + tagBounds.getHeight() * 0.5f, tagBounds.getCentreY()); - Point const d = Point(tagBounds.getX() + tagBounds.getHeight() * 0.5f, tagBounds.getBottom()); - Point const e = tagBounds.getBottomLeft().toFloat(); + auto const a = tagBounds.getTopLeft().toFloat(); + auto const b = Point(tagBounds.getX() + tagBounds.getHeight() * 0.5f, tagBounds.getY()); + auto const c = Point(tagBounds.getX() + tagBounds.getHeight() * 0.5f, tagBounds.getCentreY()); + auto const d = Point(tagBounds.getX() + tagBounds.getHeight() * 0.5f, tagBounds.getBottom()); + auto const e = tagBounds.getBottomLeft().toFloat(); flag.startNewSubPath(a); flag.lineTo(b); flag.lineTo(c); @@ -339,9 +345,9 @@ class ValueTreeNodeComponent final : public Component { g.setColour(sendColour.withAlpha(0.2f)); auto tagBounds = itemBounds.removeFromLeft(length).translated(4, 0).reduced(0, 5).expanded(2, 0).toFloat(); Path flag; - Point const a = tagBounds.getTopRight().toFloat(); - Point const b = Point(tagBounds.getRight() + tagBounds.getHeight() * 0.5f, tagBounds.getCentreY()); - Point const c = tagBounds.getBottomRight().toFloat(); + auto const a = tagBounds.getTopRight().toFloat(); + auto const b = Point(tagBounds.getRight() + tagBounds.getHeight() * 0.5f, tagBounds.getCentreY()); + auto const c = tagBounds.getBottomRight().toFloat(); flag.startNewSubPath(a); flag.lineTo(b); flag.lineTo(c); @@ -481,7 +487,7 @@ class ValueTreeViewerComponent final : public Component addAndMakeVisible(viewport); } - void makeNodeActive(void* objPtr) + void makeNodeActive(void const* objPtr) { for (auto* node : nodes) { if (reinterpret_cast(static_cast(node->valueTreeNode.getProperty("Object"))) == objPtr) { @@ -512,7 +518,7 @@ class ValueTreeViewerComponent final : public Component } } - void setSelectedNode(void* obj) + void setSelectedNode(void const* obj) { // Locate the object in the value tree, and set it as the selected node if (obj) { @@ -699,7 +705,8 @@ class ValueTreeViewerComponent final : public Component contentComponent.selectedNode = previousSel; } return true; - } else if (key.getKeyCode() == KeyPress::downKey) { + } + if (key.getKeyCode() == KeyPress::downKey) { // Traverse next nodes while (contentComponent.selectedNode && contentComponent.selectedNode->next) { contentComponent.selectedNode = contentComponent.selectedNode->next; diff --git a/Source/Utility/WindowDragger.h b/Source/Utility/WindowDragger.h index 6db857677b..2db65ce8e0 100644 --- a/Source/Utility/WindowDragger.h +++ b/Source/Utility/WindowDragger.h @@ -27,9 +27,8 @@ class WindowDragger { #endif } - void dragWindow(Component* componentToDrag, - MouseEvent const& e, - ComponentBoundsConstrainer* constrainer) + void dragWindow(Component const* componentToDrag, + MouseEvent const& e) { jassert(componentToDrag != nullptr); jassert(e.mods.isAnyMouseButtonDown()); // The event has to be a drag event! diff --git a/Source/Utility/ZoomableDragAndDropContainer.cpp b/Source/Utility/ZoomableDragAndDropContainer.cpp index 83f4b94278..cda4454033 100644 --- a/Source/Utility/ZoomableDragAndDropContainer.cpp +++ b/Source/Utility/ZoomableDragAndDropContainer.cpp @@ -40,7 +40,7 @@ class ZoomableDragAndDropContainer::DragImageComponent final : public Component Component* const sourceComponent, MouseInputSource const* draggingSource, ZoomableDragAndDropContainer& ddc, - Point offset, + Point const offset, bool const canZoom) : sourceDetails(desc, sourceComponent, Point()) , image(im) @@ -168,7 +168,7 @@ class ZoomableDragAndDropContainer::DragImageComponent final : public Component } } - void updateLocation(bool const canDoExternalDrag, Point screenPos) + void updateLocation(bool const canDoExternalDrag, Point const screenPos) { auto details = sourceDetails; @@ -334,7 +334,7 @@ class ZoomableDragAndDropContainer::DragImageComponent final : public Component return dynamic_cast(currentlyOverComp.get()); } - static Component* findDesktopComponentBelow(Point screenPos) + static Component* findDesktopComponentBelow(Point const screenPos) { auto const& desktop = Desktop::getInstance(); @@ -353,12 +353,12 @@ class ZoomableDragAndDropContainer::DragImageComponent final : public Component return nullptr; } - Point transformOffsetCoordinates(Component const* const sourceComponent, Point offsetInSource) const + Point transformOffsetCoordinates(Component const* const sourceComponent, Point const offsetInSource) const { return getLocalPoint(sourceComponent, offsetInSource) - getLocalPoint(sourceComponent, Point()); } - DragAndDropTarget* findTarget(Point screenPos, Point& relativePos, + DragAndDropTarget* findTarget(Point const screenPos, Point& relativePos, Component*& resultComponent) const { // if the source DnD is from the Add Object Menu, deal with it differently @@ -401,7 +401,7 @@ class ZoomableDragAndDropContainer::DragImageComponent final : public Component return nullptr; } - void setNewScreenPos(Point currentPos) + void setNewScreenPos(Point const currentPos) { auto newPos = currentPos - (isZoomable ? Point() : imageOffset); @@ -414,14 +414,14 @@ class ZoomableDragAndDropContainer::DragImageComponent final : public Component setTopLeftPosition(newPos); } - void sendDragMove(DragAndDropTarget::SourceDetails& details) const + void sendDragMove(DragAndDropTarget::SourceDetails const& details) const { if (auto* target = getCurrentlyOver()) if (target->isInterestedInDragSource(details)) target->itemDragMove(details); } - void checkForExternalDrag(DragAndDropTarget::SourceDetails& details, Point screenPos) + void checkForExternalDrag(DragAndDropTarget::SourceDetails const& details, Point const screenPos) { if (!hasCheckedForExternalDrag) { if (Desktop::getInstance().findComponentAt(screenPos) == nullptr) { @@ -447,8 +447,6 @@ class ZoomableDragAndDropContainer::DragImageComponent final : public Component DragAndDropContainer::performExternalDragDropOfText(text); deleteSelf(); // Delete asynchronously so the stack can unwind }); - - return; } } } @@ -522,7 +520,7 @@ void ZoomableDragAndDropContainer::startDragging(var const& sourceDescription, return { inputImage, imageOffsetFromMouse != nullptr ? dragImage.getScaledBounds().getConstrainedPoint(-imageOffsetFromMouse->toDouble()) : dragImage.getScaledBounds().getCentre() }; constexpr auto scaleFactor = 2.0; - auto image = sourceComponent->createComponentSnapshot(sourceComponent->getLocalBounds(), true, static_cast(scaleFactor)) + auto image = sourceComponent->createComponentSnapshot(sourceComponent->getLocalBounds(), true, scaleFactor) .convertedToFormat(Image::ARGB); image.multiplyAllAlphas(0.6f); @@ -558,7 +556,7 @@ void ZoomableDragAndDropContainer::startDragging(var const& sourceDescription, if (!Desktop::canUseSemiTransparentWindows()) { dragImageComponent->setOpaque(true); } - + dragImageComponent->addToDesktop(ComponentPeer::windowIgnoresMouseClicks | ComponentPeer::windowIsTemporary, OSUtils::getDesktopParentPeer(sourceComponent->getTopLevelComponent())); dragImageComponent->sourceDetails.localPosition = sourceComponent->getLocalPoint(nullptr, lastMouseDown).toInt(); @@ -587,7 +585,7 @@ bool ZoomableDragAndDropContainer::isDragAndDropActive() const return dragImageComponents.size() > 0; } -ZoomableDragAndDropContainer* ZoomableDragAndDropContainer::findParentDragContainerFor(Component* c) +ZoomableDragAndDropContainer* ZoomableDragAndDropContainer::findParentDragContainerFor(Component const* c) { return c != nullptr ? c->findParentComponentOfClass() : nullptr; } @@ -608,7 +606,7 @@ void ZoomableDragAndDropContainer::dragOperationEnded(DragAndDropTarget::SourceD { } -MouseInputSource const* ZoomableDragAndDropContainer::getMouseInputSourceForDrag(Component* sourceComponent, +MouseInputSource const* ZoomableDragAndDropContainer::getMouseInputSourceForDrag(Component const* sourceComponent, MouseInputSource const* inputSourceCausingDrag) { if (inputSourceCausingDrag == nullptr) { @@ -641,7 +639,7 @@ DragAndDropTarget* ZoomableDragAndDropContainer::findNextDragAndDropTarget(Point return nullptr; } -bool ZoomableDragAndDropContainer::isAlreadyDragging(Component* component) const noexcept +bool ZoomableDragAndDropContainer::isAlreadyDragging(Component const* component) const noexcept { for (auto const* dragImageComp : dragImageComponents) { if (dragImageComp->sourceDetails.sourceComponent == component) diff --git a/Source/Utility/ZoomableDragAndDropContainer.h b/Source/Utility/ZoomableDragAndDropContainer.h index 667f73e667..e46e01ea06 100644 --- a/Source/Utility/ZoomableDragAndDropContainer.h +++ b/Source/Utility/ZoomableDragAndDropContainer.h @@ -101,7 +101,6 @@ class ZoomableDragAndDropContainer { Component* sourceComponent, Image dragImage, Image dragInvalidImage, - bool allowDraggingToOtherJuceWindows = false, Point const* imageOffsetFromMouse = nullptr, MouseInputSource const* inputSourceCausingDrag = nullptr, bool const canZoom = false) @@ -118,7 +117,7 @@ class ZoomableDragAndDropContainer { /** Returns true if something is currently being dragged. */ bool isDragAndDropActive() const; - static ZoomableDragAndDropContainer* findParentDragContainerFor(Component* childComponent); + static ZoomableDragAndDropContainer* findParentDragContainerFor(Component const* childComponent); virtual TabComponent& getTabComponent() = 0; @@ -167,8 +166,8 @@ class ZoomableDragAndDropContainer { float zoomScale; - static MouseInputSource const* getMouseInputSourceForDrag(Component* sourceComponent, MouseInputSource const* inputSourceCausingDrag); - bool isAlreadyDragging(Component* sourceComponent) const noexcept; + static MouseInputSource const* getMouseInputSourceForDrag(Component const* sourceComponent, MouseInputSource const* inputSourceCausingDrag); + bool isAlreadyDragging(Component const* sourceComponent) const noexcept; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ZoomableDragAndDropContainer) }; diff --git a/Tests/HelpfileErrorTest.h b/Tests/HelpfileErrorTest.h index 9d108fa014..11482faf34 100644 --- a/Tests/HelpfileErrorTest.h +++ b/Tests/HelpfileErrorTest.h @@ -38,7 +38,8 @@ class HelpFileErrorTest : public PlugDataUnitTest beginTest(String(helpFiles.size()) + " -> " + helpFile.getFullPathName()); - auto* cnv = tabbar.openPatch(URL(helpFile)); + tabbar.openPatch(URL(helpFile)); + auto* cnv = tabbar.getCurrentCanvas(); auto* pd = cnv->pd; auto* editor = cnv->editor; diff --git a/Tests/HelpfileFuzzTest.h b/Tests/HelpfileFuzzTest.h index d4d39d0ed6..62c164884f 100644 --- a/Tests/HelpfileFuzzTest.h +++ b/Tests/HelpfileFuzzTest.h @@ -43,7 +43,8 @@ class HelpFileFuzzTest : public PlugDataUnitTest beginTest(String(helpFiles.size()) + " -> " + helpFile.getFullPathName()); - auto* cnv = tabbar.openPatch(URL(helpFile)); + tabbar.openPatch(URL(helpFile)); + auto* cnv = tabbar.getCurrentCanvas(); auto* editor = cnv->editor; // Click everything @@ -90,7 +91,6 @@ class HelpFileFuzzTest : public PlugDataUnitTest return; } - if(sys_trylock()) Timer::callAfterDelay(2, [cnv, objects, done]() mutable { if(cnv) { clickNextObject(cnv, objects, done);