diff --git a/.github/workflows/build-checks.yml b/.github/workflows/build-checks.yml index 3ad418c4b7fe..b0ad580d061c 100644 --- a/.github/workflows/build-checks.yml +++ b/.github/workflows/build-checks.yml @@ -5,6 +5,7 @@ name: build-checks on: push: pull_request: + types: [opened, synchronize, reopened, edited] permissions: contents: read # to fetch code (actions/checkout) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0376ef7220e9..402570a35584 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,6 +5,7 @@ name: Build on: push: pull_request: + types: [opened, synchronize, reopened, edited] permissions: contents: read # to fetch code (actions/checkout) @@ -88,15 +89,13 @@ jobs: artifacts_path: build/*.dmg artifacts_slug: macos-macosarm qt_qpa_platform: offscreen - - name: Windows 2019 (MSVC) - os: windows-2019 - # TODO: Re-enable FFmpeg after licensing issues have been clarified + - name: Windows 2022 (MSVC) + os: windows-2022 # Attention: If you change the cmake_args for the Windows CI build, # also adjust the for the local Windows build setup in # ./tools/windows_buildenv.bat cmake_args: >- -DBULK=ON - -DFFMPEG=OFF -DHSS1394=ON -DLOCALECOMPARE=ON -DMAD=ON @@ -175,7 +174,7 @@ jobs: - name: "[macOS] install ccache and make" if: runner.os == 'macOS' run: | - brew install ccache ninja + brew install ccache - name: "[macOS/Windows] Get build environment name" if: runner.os != 'Linux' @@ -492,7 +491,7 @@ jobs: - name: "Upload GitHub Actions artifacts" if: matrix.artifacts_path != null - uses: actions/upload-artifact@v4.6.1 + uses: actions/upload-artifact@v4.6.2 with: name: ${{ matrix.artifacts_name }} path: ${{ matrix.artifacts_path }} diff --git a/.github/workflows/download_cleanup.yml b/.github/workflows/download_cleanup.yml index d117eb01e01c..5e79705e9e01 100644 --- a/.github/workflows/download_cleanup.yml +++ b/.github/workflows/download_cleanup.yml @@ -25,254 +25,72 @@ jobs: if: env.SSH_AUTH_SOCK != null run: | mkdir empty_folder - echo 2.3 >> include_file.txt - echo 2.3/Windows >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-166ec959cf3d* >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-19e666415160* >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-368ae69a78cf* >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-39398482e110* >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-3b0444ac8407* >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-3eaba950a58a* >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-4ad4b301cff6* >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-4c326bb6ae14* >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-680eee15a156* >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-6a9e1c818629* >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-732303e0085e* >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-73b11c608f6b* >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-76e3c70661c2* >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-7e8d81317c40* >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-9a1c03c8ce8b* >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-a00a544c3bac* >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-a344b0e65600* >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-afda5a4c9fd7* >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-b2858329cd22* >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-e3f158c5fe54* >> include_file.txt - echo 2.3/Windows/mixxx-dependencies-2.3-x64-windows-ff5c5797f07f* >> include_file.txt - echo 2.3/Windows/mixxx-deps-2.3-x64-windows-1fd22d8* >> include_file.txt - echo 2.4-rel >> include_file.txt - echo 2.4-rel/macOS >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-2.4-arm64-osx-min1100-8f8342a* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-2.4-arm64-osx-min1100-release-00a63e* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-2.4-arm64-osx-min1100-release-498634* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-2.4-arm64-osx-min1100-release-67e51e* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-2.4-arm64-osx-min1100-release-eadc77* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-2.4-x64-osx-min1012-53cbf38* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-2.4-x64-osx-min1012-5f1e01d* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-2.4-x64-osx-min1012-8f8342a* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-2.4-x64-osx-min1012-release-00a63eb* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-2.4-x64-osx-min1012-release-498634a* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-2.4-x64-osx-min1012-release-67e51ef* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-2.4-x64-osx-min1012-release-eadc77c* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-rel-2.4-arm64-osx-min1100-14128e6* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-rel-2.4-arm64-osx-min1100-569c3c7* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-rel-2.4-arm64-osx-min1100-869da91* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-rel-2.4-arm64-osx-min1100-8cc0543* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-rel-2.4-arm64-osx-min1100-9348e40* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-rel-2.4-arm64-osx-min1100-a47f1d2* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-rel-2.4-arm64-osx-min1100-d854f94* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-rel-2.4-arm64-osx-min1100-ef81bbb* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-rel-2.4-arm64-osx-min1100-release-c0* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-rel-2.4-x64-osx-min1012-14128e6* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-rel-2.4-x64-osx-min1012-569c3c7* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-rel-2.4-x64-osx-min1012-869da91* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-rel-2.4-x64-osx-min1012-8cc0543* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-rel-2.4-x64-osx-min1012-9348e40* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-rel-2.4-x64-osx-min1012-a47f1d2* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-rel-2.4-x64-osx-min1012-d854f94* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-rel-2.4-x64-osx-min1012-ef81bbb* >> include_file.txt - echo 2.4-rel/macOS/mixxx-deps-rel-2.4-x64-osx-min1012-release-c06f* >> include_file.txt - echo 2.4 >> include_file.txt - echo 2.4/Windows >> include_file.txt - echo 2.4/Windows/mixxx-deps-2.4-x64-windows-00e79d1* >> include_file.txt - echo 2.4/Windows/mixxx-deps-2.4-x64-windows-39d1b21* >> include_file.txt - echo 2.4/Windows/mixxx-deps-2.4-x64-windows-50ff351* >> include_file.txt - echo 2.4/Windows/mixxx-deps-2.4-x64-windows-6e308f8* >> include_file.txt - echo 2.4/Windows/mixxx-deps-2.4-x64-windows-85942ea* >> include_file.txt - echo 2.4/Windows/mixxx-deps-2.4-x64-windows-86eed74* >> include_file.txt - echo 2.4/Windows/mixxx-deps-2.4-x64-windows-8cd543b* >> include_file.txt - echo 2.4/Windows/mixxx-deps-2.4-x64-windows-933ff62* >> include_file.txt - echo 2.4/Windows/mixxx-deps-2.4-x64-windows-a1480f7* >> include_file.txt - echo 2.4/Windows/mixxx-deps-2.4-x64-windows-a714f5b* >> include_file.txt - echo 2.4/Windows/mixxx-deps-2.4-x64-windows-c9ebc36* >> include_file.txt - echo 2.4/Windows/mixxx-deps-2.4-x64-windows-cf9ad27* >> include_file.txt - echo 2.4/Windows/mixxx-deps-2.4-x64-windows-db02b82* >> include_file.txt - echo 2.4/Windows/mixxx-deps-2.4-x64-windows-e2a51b1* >> include_file.txt - echo 2.4/Windows/mixxx-deps-2.4-x64-windows-e6cc98b* >> include_file.txt - echo 2.4/Windows/mixxx-deps-2.4-x64-windows-fa517d5* >> include_file.txt - echo 2.4/macOS >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-arm64-osx-min1100-00e79d1* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-arm64-osx-min1100-39d1b21* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-arm64-osx-min1100-450a843* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-arm64-osx-min1100-50ff351* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-arm64-osx-min1100-6e308f8* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-arm64-osx-min1100-86eed74* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-arm64-osx-min1100-8cd543b* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-arm64-osx-min1100-933ff62* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-arm64-osx-min1100-985aed5* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-arm64-osx-min1100-9e85dfd* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-arm64-osx-min1100-a1480f7* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-arm64-osx-min1100-c971ee2* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-arm64-osx-min1100-cf9ad27* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-arm64-osx-min1100-e2a51b1* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-arm64-osx-min1100-fa517d5* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-x64-osx-min1012-00e79d1* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-x64-osx-min1012-39d1b21* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-x64-osx-min1012-450a843* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-x64-osx-min1012-50ff351* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-x64-osx-min1012-6e308f8* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-x64-osx-min1012-85942ea* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-x64-osx-min1012-86eed74* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-x64-osx-min1012-8cd543b* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-x64-osx-min1012-933ff62* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-x64-osx-min1012-985aed5* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-x64-osx-min1012-a1480f7* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-x64-osx-min1012-c971ee2* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-x64-osx-min1012-c9ebc36* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-x64-osx-min1012-cf9ad27* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-x64-osx-min1012-db02b82* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-x64-osx-min1012-e2a51b1* >> include_file.txt - echo 2.4/macOS/mixxx-deps-2.4-x64-osx-min1012-fa517d5* >> include_file.txt - echo 2.4-rel/Windows >> include_file.txt - echo 2.4-rel/Windows/mixxx-deps-2.4-x64-windows-53cbf38* >> include_file.txt - echo 2.4-rel/Windows/mixxx-deps-2.4-x64-windows-5f1e01d* >> include_file.txt - echo 2.4-rel/Windows/mixxx-deps-2.4-x64-windows-8f8342a* >> include_file.txt - echo 2.4-rel/Windows/mixxx-deps-2.4-x64-windows-release-00a63eb* >> include_file.txt - echo 2.4-rel/Windows/mixxx-deps-2.4-x64-windows-release-498634a* >> include_file.txt - echo 2.4-rel/Windows/mixxx-deps-2.4-x64-windows-release-67e51ef* >> include_file.txt - echo 2.4-rel/Windows/mixxx-deps-2.4-x64-windows-release-eadc77c* >> include_file.txt - echo 2.4-rel/Windows/mixxx-deps-rel-2.4-x64-windows-14128e6* >> include_file.txt - echo 2.4-rel/Windows/mixxx-deps-rel-2.4-x64-windows-3cb806a* >> include_file.txt - echo 2.4-rel/Windows/mixxx-deps-rel-2.4-x64-windows-569c3c7* >> include_file.txt - echo 2.4-rel/Windows/mixxx-deps-rel-2.4-x64-windows-869da91* >> include_file.txt - echo 2.4-rel/Windows/mixxx-deps-rel-2.4-x64-windows-9348e40* >> include_file.txt - echo 2.4-rel/Windows/mixxx-deps-rel-2.4-x64-windows-a47f1d2* >> include_file.txt - echo 2.4-rel/Windows/mixxx-deps-rel-2.4-x64-windows-ef81bbb* >> include_file.txt - echo 2.5 >> include_file.txt - echo 2.5/Windows >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.4-x64-windows-ee6a820* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-025e451* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-0d6dec6* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-2223b1d* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-22e5547* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-27ceb62* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-2df9e55* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-54e92cb* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-556a389* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-5e1a10e* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-5f1e4ab* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-6fe4441* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-875f13b* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-89f011a* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-8b4aa07* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-8f4f065* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-9d10a04* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-a00d1f7* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-a04d7bc* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-a52e10e* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-bcf8ed3* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-bffc647* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-cbc158a* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-da7c834* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-dd80a94* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-eddd0f2* >> include_file.txt - echo 2.5/Windows/mixxx-deps-2.5-x64-windows-fbeeab6* >> include_file.txt - echo 2.5-rel >> include_file.txt - echo 2.5-rel/Windows >> include_file.txt - echo 2.5-rel/Windows/mixxx-deps-2.5-x64-windows-release-6e7a346* >> include_file.txt - echo 2.5-rel/Windows/mixxx-deps-2.5-x64-windows-release-70c6975* >> include_file.txt - echo 2.5-rel/Windows/mixxx-deps-2.5-x64-windows-release-99f3970* >> include_file.txt - echo 2.5-rel/Windows/mixxx-deps-2.5-x64-windows-release-a65f0d2* >> include_file.txt - echo 2.5-rel/Windows/mixxx-deps-2.5-x64-windows-release-da4c4b8* >> include_file.txt - echo 2.5-rel/Windows/mixxx-deps-2.5-x64-windows-release-de723f4* >> include_file.txt - echo 2.5-rel/Windows/mixxx-deps-rel-2.4-x64-windows-8c12f3f* >> include_file.txt - echo 2.5-rel/Windows/mixxx-deps-rel-2.5-x64-windows-0048787* >> include_file.txt - echo 2.5-rel/Windows/mixxx-deps-rel-2.5-x64-windows-104c751* >> include_file.txt - echo 2.5-rel/Windows/mixxx-deps-rel-2.5-x64-windows-10c57bf* >> include_file.txt - echo 2.5-rel/Windows/mixxx-deps-rel-2.5-x64-windows-2e1b6b1* >> include_file.txt - echo 2.5-rel/Windows/mixxx-deps-rel-2.5-x64-windows-5cf5341* >> include_file.txt - echo 2.5-rel/Windows/mixxx-deps-rel-2.5-x64-windows-6c90be2* >> include_file.txt - echo 2.5-rel/Windows/mixxx-deps-rel-2.5-x64-windows-9b0995f* >> include_file.txt - echo 2.5-rel/Windows/mixxx-deps-rel-2.5-x64-windows-dacaacf* >> include_file.txt - echo 2.5-rel/Windows/mixxx-deps-rel-2.5-x64-windows-release-2fa319b* >> include_file.txt - echo 2.5-rel/Windows/mixxx-deps-rel-2.5-x64-windows-release-cefa504* >> include_file.txt - echo 2.5/macOS >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.4-arm64-osx-min1100-ee6a820* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.4-x64-osx-min1012-ee6a820* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-arm64-osx-min1100-release-0d6dec* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-arm64-osx-min1100-release-1d013e* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-arm64-osx-min1100-release-22e554* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-arm64-osx-min1100-release-2df9e5* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-arm64-osx-min1100-release-54e92c* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-arm64-osx-min1100-release-556a38* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-arm64-osx-min1100-release-5e1a10* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-arm64-osx-min1100-release-875f13* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-arm64-osx-min1100-release-89f011* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-arm64-osx-min1100-release-8f4f06* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-arm64-osx-min1100-release-b20ad3* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-arm64-osx-min1100-release-b94019* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-arm64-osx-min1100-release-bffc64* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-arm64-osx-min1100-release-eddd0f* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-arm64-osx-min1100-release-fbeeab* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-025e451* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-1d013e9* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-1d618f7* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-2223b1d* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-22e5547* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-27ceb62* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-342fe99* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-54e92cb* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-556a389* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-5889ac5* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-5f1e4ab* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-6bd376f* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-6fe4441* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-89f011a* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-8b4aa07* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-8f4f065* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-9d10a04* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-a00d1f7* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-a04d7bc* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-a52e10e* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-b20ad39* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-b94019b* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-bcf8ed3* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-bef9399* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-c318c2d* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-cbc158a* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-da7c834* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-dd80a94* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-e54cec7* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1015-fbeeab6* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1100-0d6dec6* >> include_file.txt - echo 2.5/macOS/mixxx-deps-2.5-x64-osx-min1100-875f13b* >> include_file.txt - echo 2.5-rel/macOS >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-2.5-arm64-osx-min1100-release-6e7a34* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-2.5-arm64-osx-min1100-release-70c697* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-2.5-arm64-osx-min1100-release-99f397* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-2.5-arm64-osx-min1100-release-a65f0d* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-2.5-arm64-osx-min1100-release-de723f* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-2.5-x64-osx-min1015-release-99f3970* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-2.5-x64-osx-min1015-release-a65f0d2* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-2.5-x64-osx-min1015-release-de723f4* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-2.5-x64-osx-min1100-release-6e7a346* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-rel-2.4-arm64-osx-min1100-8c12f3f* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-rel-2.4-x64-osx-min1012-8c12f3f* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-rel-2.5-arm64-osx-min1100-104c751* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-rel-2.5-arm64-osx-min1100-10c57bf* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-rel-2.5-arm64-osx-min1100-dacaacf* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-rel-2.5-arm64-osx-min1100-release-2f* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-rel-2.5-arm64-osx-min1100-release-ce* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-rel-2.5-x64-osx-min1015-104c751* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-rel-2.5-x64-osx-min1015-10c57bf* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-rel-2.5-x64-osx-min1015-2e1b6b1* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-rel-2.5-x64-osx-min1015-5cf5341* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-rel-2.5-x64-osx-min1015-9b0995f* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-rel-2.5-x64-osx-min1015-c7cf485* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-rel-2.5-x64-osx-min1015-d28ff38* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-rel-2.5-x64-osx-min1015-dacaacf* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-rel-2.5-x64-osx-min1015-release-2fa3* >> include_file.txt - echo 2.5-rel/macOS/mixxx-deps-rel-2.5-x64-osx-min1015-release-cefa* >> include_file.txt - rsync --verbose --archive --times --recursive --delete --include-from=include_file.txt --exclude=* "empty_folder/" "${SSH_USER}@${SSH_HOST}:${DESTDIR}/dependencies/" + echo 2.5.1 >> include_file.txt + echo 2.5.1/manifest.json >> include_file.txt + echo 2.5.1/mixxx-2.6-alpha-174-g2c2dda9781-win64* >> include_file.txt + echo AzureCodeSigning >> include_file.txt + echo AzureCodeSigning/manifest.json >> include_file.txt + echo AzureCodeSigning/mixxx-2.6-alpha-* >> include_file.txt + echo CAStreamBasicDescription >> include_file.txt + echo CAStreamBasicDescription/manifest.json >> include_file.txt + echo CAStreamBasicDescription/mixxx-2.4.1-46-* >> include_file.txt + echo PR_13709 >> include_file.txt + echo PR_13709/manifest.json >> include_file.txt + echo PR_13709/mixxx-2.4.1-81-* >> include_file.txt + echo azure_signing_update >> include_file.txt + echo azure_signing_update/manifest.json >> include_file.txt + echo azure_signing_update/mixxx-2.4.1-61-* >> include_file.txt + echo chore >> include_file.txt + echo chore/upgrade-macos13-xcode15.2 >> include_file.txt + echo chore/upgrade-macos13-xcode15.2/manifest.json >> include_file.txt + echo chore/upgrade-macos13-xcode15.2/mixxx-2.6-alpha-76-* >> include_file.txt + echo daschuer-patch-1 >> include_file.txt + echo daschuer-patch-1/manifest.json >> include_file.txt + echo daschuer-patch-1/mixxx-2.6-alpha-284-* >> include_file.txt + echo dependabot >> include_file.txt + echo dependabot/github_actions >> include_file.txt + echo dependabot/github_actions/actions >> include_file.txt + echo dependabot/github_actions/actions/stale-6 >> include_file.txt + echo dependabot/github_actions/actions/stale-6/manifest.json >> include_file.txt + echo dependabot/github_actions/actions/stale-6/mixxx-2.4-alpha-1318-* >> include_file.txt + echo dependabot/github_actions/actions/upload-artifact-3.1.2 >> include_file.txt + echo dependabot/github_actions/actions/upload-artifact-3.1.2/manifest.json >> include_file.txt + echo dependabot/github_actions/actions/upload-artifact-3.1.2/mixxx-2.3.3-117-* >> include_file.txt + echo fix-14326 >> include_file.txt + echo fix-14326/manifest.json >> include_file.txt + echo fix-14326/mixxx-2.5.0-68-* >> include_file.txt + echo inpulse >> include_file.txt + echo inpulse/manifest.json >> include_file.txt + echo inpulse/mixxx-2.* >> include_file.txt + echo pr >> include_file.txt + echo pr/13709 >> include_file.txt + echo pr/13709/manifest.json >> include_file.txt + echo pr/13709/mixxx-2.4.1-81-* >> include_file.txt + echo resolve-from-urls-2.5 >> include_file.txt + echo resolve-from-urls-2.5/manifest.json >> include_file.txt + echo resolve-from-urls-2.5/mixxx-2.5-beta-100-* >> include_file.txt + echo revert-13208-gh13206 >> include_file.txt + echo revert-13208-gh13206/mixxx-2.4.1-5-gc71a48b76e-* >> include_file.txt + echo revert-13271-revert-13208-gh13206 >> include_file.txt + echo revert-13271-revert-13208-gh13206/mixxx-2.4.1-6-* >> include_file.txt + echo rg-use-opengl-node-and-add-shaders >> include_file.txt + echo rg-use-opengl-node-and-add-shaders/manifest.json >> include_file.txt + echo rg-use-opengl-node-and-add-shaders/mixxx-2.6-alpha-* >> include_file.txt + echo traktor-s3-updates >> include_file.txt + echo traktor-s3-updates/manifest.json >> include_file.txt + echo traktor-s3-updates/mixxx-2.6-alpha-* >> include_file.txt + echo ts_source_copy_check >> include_file.txt + echo ts_source_copy_check/manifest.json >> include_file.txt + echo ts_source_copy_check/mixxx-2.4.1-42-* >> include_file.txt + echo tsan-fix-13893 >> include_file.txt + echo tsan-fix-13893/manifest.json >> include_file.txt + echo tsan-fix-13893/mixxx-2.5-beta-83-* >> include_file.txt + echo tsan-fix-13895 >> include_file.txt + echo tsan-fix-13895/manifest.json >> include_file.txt + echo tsan-fix-13895/mixxx-2.5-beta-83-* >> include_file.txt + echo waveformwidgetinfo >> include_file.txt + echo waveformwidgetinfo/mixxx-2.5-alpha-3* >> include_file.txt + rsync --verbose --archive --times --recursive --delete --include-from=include_file.txt --exclude=* "empty_folder/" "${SSH_USER}@${SSH_HOST}:${DESTDIR}/snapshots/" env: DESTDIR: public_html/downloads SSH_HOST: downloads-hostgator.mixxx.org diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 4108268d0607..622f04aa5e46 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -69,14 +69,14 @@ jobs: - name: "Upload patch artifact" if: failure() && env.UPLOAD_PATCH_FILE != null - uses: actions/upload-artifact@v4.6.1 + uses: actions/upload-artifact@v4.6.2 with: name: ${{ env.UPLOAD_PATCH_FILE }} path: ${{ env.UPLOAD_PATCH_FILE }} - name: "Upload pre-commit.log" if: failure() && env.UPLOAD_PATCH_FILE == null - uses: actions/upload-artifact@v4.6.1 + uses: actions/upload-artifact@v4.6.2 with: name: pre-commit.log path: /github/home/.cache/pre-commit/pre-commit.log diff --git a/.github/workflows/sync_branches.yml b/.github/workflows/sync_branches.yml index 6f1137b4ec91..81420dfe513f 100644 --- a/.github/workflows/sync_branches.yml +++ b/.github/workflows/sync_branches.yml @@ -1,13 +1,19 @@ name: Sync Branches on: push: - branches: - - "2.4" - - "2.5" + # branches: + # - "2.4" + # - "2.5" + # temporary disabled for all branches + branches-ignore: + - "*" workflow_dispatch: permissions: {} +env: + SYNC_COMMITTER_EMAIL: bot@mixxx.org + SYNC_COMMITTER_NAME: Mixxx Bot jobs: sync-branches: strategy: @@ -21,12 +27,25 @@ jobs: permissions: contents: write pull-requests: write - env: - SYNC_COMMITTER: github-actions[bot] steps: + # Using an app is the recommended way to allow recursive operations. + # Apps offer a way to have reduced scoped permissions (like GITHUB_TOKEN) + # and short lifespan, unlike PAT. + # As we are trialing this approach tho, it is fine to play with a PAT and not spend the + # extra time setting up an app + # + # - name: Generate App Token + # id: generate-token-key-pair + # uses: actions/create-github-app-token@v1 + # with: + # private-key: ${{ secrets.MIXXX_BOT_APP_PRIVATE_KEY }} + # app-id: ${{ vars.MIXXX_BOT_APP_ID }} + - name: "Check out repository" uses: actions/checkout@v4.1.7 with: + # PAT setup with content:write and pull_request:write + token: ${{ secrets.MIXXX_BRANCH_SYNC_PAT }} persist-credentials: true - name: "Configure Git" @@ -39,12 +58,12 @@ jobs: run: | if git fetch origin "${SYNC_BRANCH}"; then echo "Branch ${SYNC_BRANCH} already exists, checking if the branch was modified..." - COMMITTER="$(git show --pretty=format:"%cn <%ce>" --no-patch "origin/${SYNC_BRANCH}")" - if [ "${COMMITTER}" = "${SYNC_COMMITTER}" ]; then - echo "Branch ${SYNC_BRANCH} was not modified." + echo "branch_exists=true" >> $GITHUB_OUTPUT + COMMITTER_EMAIL="$(git show --pretty=format:"%ce" --no-patch "origin/${SYNC_BRANCH}")" + if [ "${COMMITTER_EMAIL}" = "${SYNC_COMMITTER_EMAIL}" ]; then + echo "Branch ${SYNC_BRANCH} was NOT modified." else - echo "Branch ${SYNC_BRANCH} was modified, either delete it or update it yourself." - echo "skip_sync=true" >> $GITHUB_OUTPUT + echo "Branch ${SYNC_BRANCH} was modified." fi else echo "Branch ${SYNC_BRANCH} does not exist yet." @@ -53,26 +72,38 @@ jobs: SYNC_BRANCH: sync-branch-${{ matrix.from_branch}}-to-${{ matrix.to_branch }} - name: "Merge Changes" - if: steps.check_sync.outputs.skip_sync != 'true' run: | - git checkout -b "${SYNC_BRANCH}" - git fetch origin "${TO_BRANCH}" - git reset --hard "origin/${TO_BRANCH}" - git pull --no-edit origin "${FROM_BRANCH}" + echo "::group::Fetching and merging branches" + if [ "${BRANCH_EXISTS}" = true ]; then + git fetch origin "${SYNC_BRANCH}" "${FROM_BRANCH}" + git checkout "${SYNC_BRANCH}" + else + git fetch origin "${TO_BRANCH}" "${FROM_BRANCH}" + git checkout "origin/${TO_BRANCH}" + git checkout -b "${SYNC_BRANCH}" + fi + git config --global user.email "${SYNC_COMMITTER_EMAIL}" + git config --global user.name "${SYNC_COMMITTER_NAME}" + git pull --no-edit origin "${FROM_BRANCH}" || true COMMIT_ORIGINAL="$(git show --no-patch --format="%h" "origin/${TO_BRANCH}")" - COMMIT_MERGE="$(git show --no-patch --format="%h" "origin/${TO_BRANCH}")" + COMMIT_MERGE="$(git show --no-patch --format="%h" "origin/${SYNC_BRANCH}")" + git status + echo "::endgroup::" if [ "${COMMIT_ORIGINAL}" = "${COMMIT_MERGE}" ]; then - git status - echo "No changes (or merge conflict), skipping push and PR creation." + echo "::warning:: No changes (or merge conflict), skipping push and PR creation." else - git push --force origin "${SYNC_BRANCH}" - gh pr create -B "${FROM_BRANCH}" -H "${SYNC_BRANCH}" --title "${PULL_REQUEST_TITLE}" --body "${PULL_REQUEST_BODY}" --labels "sync-branches" + git push origin "${SYNC_BRANCH}" + if [ ! "${BRANCH_EXISTS}" = true ]; then + gh pr create -B "${FROM_BRANCH}" -H "${SYNC_BRANCH}" --title "${PULL_REQUEST_TITLE}" --body "${PULL_REQUEST_BODY}" --labels "sync-branches" + fi fi env: + BRANCH_EXISTS: ${{ steps.check_sync.outputs.branch_exists }} FROM_BRANCH: ${{ matrix.from_branch}} TO_BRANCH: ${{ matrix.to_branch }} SYNC_BRANCH: sync-branch-${{ matrix.from_branch}}-to-${{ matrix.to_branch }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # PAT setup with content:write and pull_request:write + GITHUB_TOKEN: ${{ secrets.MIXXX_BRANCH_SYNC_PAT }} PULL_REQUEST_TITLE: Merge changes from `${{ matrix.from_branch }}` into `${{ matrix.to_branch }}` PULL_REQUEST_BODY: | New content has landed in the `${{ matrix.from_branch }}` branch, so let's merge the changes into `${{ matrix.to_branch }}`. diff --git a/.tx/config b/.tx/config index 2fec46c1f484..7b5c8801a000 100644 --- a/.tx/config +++ b/.tx/config @@ -1,7 +1,7 @@ [main] host = https://www.transifex.com -[o:mixxx-dj-software:p:mixxxdj:r:mixxx2-6] +[o:mixxx-dj-software:p:mixxxdj:r:mixxx2-7] file_filter = res/translations/mixxx_.ts source_file = res/translations/mixxx.ts source_lang = en diff --git a/CHANGELOG.md b/CHANGELOG.md index d11c13b7c1d9..d8aab3227c48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [2.7.0](https://github.com/mixxxdj/mixxx/milestone/47) (Unreleased) + ## [2.6.0](https://github.com/mixxxdj/mixxx/milestone/44) (Unreleased) ### Controller Mappings @@ -41,6 +43,7 @@ * fix: sync rate using the current BPM instead of the file one [#13671](https://github.com/mixxxdj/mixxx/pull/13671) [#12738](https://github.com/mixxxdj/mixxx/issues/12738) +* sync: prefer playing inaudible decks over stopped non-sync decks [#14580](https://github.com/mixxxdj/mixxx/pull/14580) * fix: prevent null CO access when cloning sampler or preview [#13740](https://github.com/mixxxdj/mixxx/pull/13740) * Tooltips: Fix cue mode setting location [#14045](https://github.com/mixxxdj/mixxx/pull/14045) * Use correct detected channel count on CoreAudio [#14372](https://github.com/mixxxdj/mixxx/pull/14372) @@ -57,6 +60,8 @@ [#14354](https://github.com/mixxxdj/mixxx/pull/14354) * Waveform Preferences: Group options, adjust tabstops, reorder ui file [#13615](https://github.com/mixxxdj/mixxx/pull/13615) * Preferences Effects: left/right key in effect lists trigger hide/unhide [#14205](https://github.com/mixxxdj/mixxx/pull/14205) +* Open sound preferences with sprecific I/O tab selected [#14346](https://github.com/mixxxdj/mixxx/pull/14346) +* Show 'real' xfader configuration [#14124](https://github.com/mixxxdj/mixxx/pull/14124) ### Skins @@ -77,6 +82,7 @@ ### Library * Add color coding for key column [#13390](https://github.com/mixxxdj/mixxx/pull/13390) +* Add overview column with small waveform [#14140](https://github.com/mixxxdj/mixxx/pull/14140) * Elide key text form the right [#13475](https://github.com/mixxxdj/mixxx/pull/13475) * Add Key Color Palettes [#13497](https://github.com/mixxxdj/mixxx/pull/13497) * Fix BPM and Bitrate columns were wider than normal [#13571](https://github.com/mixxxdj/mixxx/pull/13571) @@ -87,6 +93,12 @@ * iTunes: Add iOS importer using the Media Player framework [#12690](https://github.com/mixxxdj/mixxx/pull/12690) * Add Shuffle action to track table header menu [#13392](https://github.com/mixxxdj/mixxx/pull/13392) * Track File Export: add 'Apply to all' checkbox, remove ".. All" buttons [#13614](https://github.com/mixxxdj/mixxx/pull/13614) +* Library scan: log summary and show popup + [#13427](https://github.com/mixxxdj/mixxx/pull/13427) + [#10720](https://github.com/mixxxdj/mixxx/issues/10720) +* Search: add BPM lock filter `bpm:locked` + [#14590](https://github.com/mixxxdj/mixxx/pull/14590) + [#14583](https://github.com/mixxxdj/mixxx/issues/14583) ### Effects @@ -138,6 +150,9 @@ [#14331](https://github.com/mixxxdj/mixxx/pull/14331) * Fix invalid slip render marker [#13422](https://github.com/mixxxdj/mixxx/pull/13422) * Add slip waveform to Textured/'High details' type [#14039](https://github.com/mixxxdj/mixxx/pull/14039) +* Fix waveform marker image alignment + [#14656](https://github.com/mixxxdj/mixxx/pull/14656) + [#14037](https://github.com/mixxxdj/mixxx/issues/14037) ### STEM file support @@ -161,6 +176,8 @@ * Fix build with -DSTEM=OFF [#13948](https://github.com/mixxxdj/mixxx/pull/13948) * Stem control test fix [#13960](https://github.com/mixxxdj/mixxx/pull/13960) * Solves problem with special characters in path to stems [#13784](https://github.com/mixxxdj/mixxx/pull/13784) +* Enable FFmpeg (free) on Windows. [#14695](https://github.com/mixxxdj/mixxx/pull/14695) +* FFmpeg: Use internal aac decoder. If not available give a hint. [#14645](https://github.com/mixxxdj/mixxx/pull/14645) ### Auto-DJ @@ -193,6 +210,9 @@ * Show translator file path in debug message [#14209](https://github.com/mixxxdj/mixxx/pull/14209) * Building without tests-tools [#14268](https://github.com/mixxxdj/mixxx/pull/14268) * Remove unmaintained shell.nix [#14300](https://github.com/mixxxdj/mixxx/pull/14300) +* add QGLES2 option for UNIX [#14489](https://github.com/mixxxdj/mixxx/pull/14489) +* Don't set GL_BGRA if QT_OPENGL_ES_2 [#14488](https://github.com/mixxxdj/mixxx/pull/14488) +* Windows and macOS: Update to Qt 6.8.3 (requires MSVC 2022) [#14655](https://github.com/mixxxdj/mixxx/pull/14655) ### Misc Refactorings @@ -298,18 +318,39 @@ * Use std::shared_ptr in controller settings to fix memory leak [#14413](https://github.com/mixxxdj/mixxx/pull/14413) * github labeler: add Dev Tools to `developer experience` [#14475](https://github.com/mixxxdj/mixxx/pull/14475) * chore: clean up README.md [#14471](https://github.com/mixxxdj/mixxx/pull/14471) +* Fix type safety warnings [#14613](https://github.com/mixxxdj/mixxx/pull/14613) +* CMake: Join project() with enable_language() [#14577](https://github.com/mixxxdj/mixxx/pull/14577) +* fix warning when building without STEM support [#14551](https://github.com/mixxxdj/mixxx/pull/14551) +* scenegraph is conditioned to QML=ON [#14487](https://github.com/mixxxdj/mixxx/pull/14487) +* Fix building with Qt 6.9 [#14678](https://github.com/mixxxdj/mixxx/pull/14678) +* fix: import proper QtQml.Models module instead of qmllabs [#14675](https://github.com/mixxxdj/mixxx/pull/14675) +* qmlwaveform: Fix moc in Qt 6.9.0 [#14649](https://github.com/mixxxdj/mixxx/pull/14649) -## [2.5.1](https://github.com/mixxxdj/mixxx/milestone/45) (unreleased) +## [2.5.1](https://github.com/mixxxdj/mixxx/milestone/45) (2025-04-27) ### Controller Mappings * Behringer DDM4000 & BCR2000: Update mappings to 2.5 [#14232](https://github.com/mixxxdj/mixxx/pull/14232) [#14349](https://github.com/mixxxdj/mixxx/pull/14349) -* Hercules Inpulse 300: add toneplay, slicer, and beatmatch functionalities +* DJ TechTools MIDI Fighter Spectra: Add controller mapping + [#14559](https://github.com/mixxxdj/mixxx/pull/14559) +* Hercules DJControl Inpulse 300: add toneplay, slicer, and beatmatch functionalities [#14051](https://github.com/mixxxdj/mixxx/pull/14051) [#14057](https://github.com/mixxxdj/mixxx/pull/14057) -* M-Vave SMC-Mixer: Add controller mapping [#14411](https://github.com/mixxxdj/mixxx/pull/14411) +* Hercules DJControl Inpulse 500: New mapping + [#14491](https://github.com/mixxxdj/mixxx/pull/14491) + [#14510](https://github.com/mixxxdj/mixxx/pull/14510) +* Hercules DJ Console Mk1: Fix pitch bend buttons [#14447](https://github.com/mixxxdj/mixxx/pull/14447) +* M-Vave SMC-Mixer: Add controller mapping + [#14411](https://github.com/mixxxdj/mixxx/pull/14411) + [#14448](https://github.com/mixxxdj/mixxx/pull/14448) + [#14457](https://github.com/mixxxdj/mixxx/pull/14457) + [#14458](https://github.com/mixxxdj/mixxx/pull/14458) +* M-Vave SMK-25 II: Piano keyboard mapping + [#14412](https://github.com/mixxxdj/mixxx/pull/14412) + [#14484](https://github.com/mixxxdj/mixxx/pull/14484) +* Numark Mixtrack Platinum: Fix VU Meters [#14575](https://github.com/mixxxdj/mixxx/pull/14575) * Numark NS6II: New mapping [#11075](https://github.com/mixxxdj/mixxx/pull/11075) * Numark Platinum FX: New mapping [#12872](https://github.com/mixxxdj/mixxx/pull/12872) * Pioneer-DDJ-SB3: Fixes slip mode and adds missing knob controls [#11307](https://github.com/mixxxdj/mixxx/pull/11307) @@ -326,6 +367,9 @@ [#14028](https://github.com/mixxxdj/mixxx/pull/14028) [#13995](https://github.com/mixxxdj/mixxx/issues/13995) * Traktor Kontrol S3: Use pitch absolute mode as described in the manual [#14123](https://github.com/mixxxdj/mixxx/pull/14123) +* Stanton SCS.1m/d; Keith McMillen QuNeo; EKS Otus: use `playposition` instead of non-existent `visual_playposition` + [#14609](https://github.com/mixxxdj/mixxx/pull/14609) + [#14603](https://github.com/mixxxdj/mixxx/issues/14603) ### Controller Backend @@ -362,12 +406,14 @@ * Fix for `TypeError` in `midi-components-0.0.js` [#14203](https://github.com/mixxxdj/mixxx/pull/14203) [#14197](https://github.com/mixxxdj/mixxx/issues/14197) +* Fix crash due to concurrent access in MidiController [#14159](https://github.com/mixxxdj/mixxx/pull/14159) ### Skins -* Deere (64 samplers): Bring back library in regular view +* Deere/LateNight (64 samplers): Bring back library in regular view [#14101](https://github.com/mixxxdj/mixxx/pull/14101) [#14097](https://github.com/mixxxdj/mixxx/issues/14097) + [#14700](https://github.com/mixxxdj/mixxx/issues/14700) * Fix crash when hiding waveforms in Deere [#14170](https://github.com/mixxxdj/mixxx/pull/14170) * Waveform Overview: Abort play pos dragging if cursor is released outside the valid area @@ -378,6 +424,10 @@ * Key Wheel: Move to View menu and make it a floating tool window [#14256](https://github.com/mixxxdj/mixxx/pull/14256) [#14239](https://github.com/mixxxdj/mixxx/pull/14239) +* Center effect parameter names [#14598](https://github.com/mixxxdj/mixxx/pull/14598) +* Track menu: highlight row when hovering checkbox + [#14636](https://github.com/mixxxdj/mixxx/pull/14636) + [#14680](https://github.com/mixxxdj/mixxx/pull/14680) ### Library @@ -392,7 +442,9 @@ [#14172](https://github.com/mixxxdj/mixxx/pull/14172) [#14289](https://github.com/mixxxdj/mixxx/pull/14289) * Fix writing metadata via symlink [#13711](https://github.com/mixxxdj/mixxx/pull/13711) -* Library menu: change "Engine DJ Prime" to "Engine DJ" [#14248](https://github.com/mixxxdj/mixxx/pull/14248) +* Library menu: change "Engine DJ Prime" to "Engine DJ" + [#14248](https://github.com/mixxxdj/mixxx/pull/14248) + [#14682](https://github.com/mixxxdj/mixxx/pull/14682) * Fix file extension handling during playlist export [#14381](https://github.com/mixxxdj/mixxx/pull/14381) * Fix manual key metadata editing in track properties dialog [#14022](https://github.com/mixxxdj/mixxx/pull/14022) @@ -402,10 +454,31 @@ * History: Don't allow joining with locked previous playlist [#14401](https://github.com/mixxxdj/mixxx/pull/14401) [#14399](https://github.com/mixxxdj/mixxx/issues/14399) +* Track info dialog: fixed cover label (max) size [#14418](https://github.com/mixxxdj/mixxx/pull/14418) * Track Menu: Reset `eject` after moving track file to trash [#14402](https://github.com/mixxxdj/mixxx/pull/14402) * Fix AutoDJ "Remove Crate" action [#14426](https://github.com/mixxxdj/mixxx/pull/14426) [#14425](https://github.com/mixxxdj/mixxx/issues/14425) +* Fix scrolling issue with coverart columns visible + [#13719](https://github.com/mixxxdj/mixxx/pull/13719) + [#14631](https://github.com/mixxxdj/mixxx/pull/14631) +* Developer Tools: multi-word search, no Tab navigation in controls table [#14474](https://github.com/mixxxdj/mixxx/pull/14474) +* Analyze feature: respect New / All selection when searching + [#14660](https://github.com/mixxxdj/mixxx/pull/14660) + [#14659](https://github.com/mixxxdj/mixxx/issues/14659) +* Stop populating Computer library feature when Mixxx should close [#14573](https://github.com/mixxxdj/mixxx/pull/14573) +* Tracks: apply played/missing text color also to selected tracks [#13583](https://github.com/mixxxdj/mixxx/pull/13583) +* Tracks: `show_track_menu` at index position [#14385](https://github.com/mixxxdj/mixxx/pull/14385) +* Search related menu: improve checkbox click UX [#14637](https://github.com/mixxxdj/mixxx/pull/14637) +* Avoid false missing tracks due to db inconsistency + [#14615](https://github.com/mixxxdj/mixxx/pull/14615) + [#14513](https://github.com/mixxxdj/mixxx/issues/14513) +* Fix automatic trimming of search bar text + [#14497](https://github.com/mixxxdj/mixxx/pull/14497) + [#14486](https://github.com/mixxxdj/mixxx/issues/14486) +* Avoid crash after removing Quick Link + [#14556](https://github.com/mixxxdj/mixxx/pull/14556) + [#8270](https://github.com/mixxxdj/mixxx/issues/8270) ### Other Fixes @@ -423,6 +496,12 @@ * Allow seeking to a hotcue during waveform scratching [#14357](https://github.com/mixxxdj/mixxx/pull/14357) [#13981](https://github.com/mixxxdj/mixxx/issues/13981) +* Reset saved loop when toggling off after switching cue type + [#14661](https://github.com/mixxxdj/mixxx/pull/14661) + [#14657](https://github.com/mixxxdj/mixxx/issues/14657) +* Fix leaks from fid_design() + [#14567](https://github.com/mixxxdj/mixxx/pull/14567) + [#9470](https://github.com/mixxxdj/mixxx/issues/9470) ### Target support @@ -434,6 +513,8 @@ [#14071](https://github.com/mixxxdj/mixxx/issues/14071) [#14200](https://github.com/mixxxdj/mixxx/pull/14200) [#14204](https://github.com/mixxxdj/mixxx/pull/14204) + [#14440](https://github.com/mixxxdj/mixxx/pull/14440) + [#14518](https://github.com/mixxxdj/mixxx/pull/14518) * Welcome Ubuntu Plucky Puffin; Good bye Mantic Minotaur [#14148](https://github.com/mixxxdj/mixxx/pull/14148) [#14158](https://github.com/mixxxdj/mixxx/pull/14158) @@ -447,7 +528,13 @@ * Allow building without tests-tools via new CMake options BUILD_TESTING and BUILD_BENCH [#14269](https://github.com/mixxxdj/mixxx/pull/14269) * Fix and improve "missing env" error message [#14321](https://github.com/mixxxdj/mixxx/pull/14321) -* Qt6.8: Ensure Mixxx uses "windowsvista" Qt style on Windows [#14228](https://github.com/mixxxdj/mixxx/pull/14228) +* Qt 6.8: Ensure Mixxx uses "windowsvista" Qt style on Windows [#14228](https://github.com/mixxxdj/mixxx/pull/14228) +* Raise macOS target version to 11 (Qt 6.5 requirement). [#14440](https://github.com/mixxxdj/mixxx/pull/14440) +* Fail early when building on WSL [#14481](https://github.com/mixxxdj/mixxx/pull/14481) +* Remove useless udev rule [#14630](https://github.com/mixxxdj/mixxx/pull/14630) +* Handle new " / " from taglib 2.0 + [#12854](https://github.com/mixxxdj/mixxx/pull/12854) + [#12790](https://github.com/mixxxdj/mixxx/issues/12790) ## [2.5.0](https://github.com/mixxxdj/mixxx/issues?q=milestone%3A2.5.0) (2024-12-24) @@ -2606,8 +2693,7 @@ announcements. First, if you are using Windows, you will have to uninstall any old versions of Mixxx before you can install 2.1. How to uninstall Mixxx varies on different versions of Windows: -* Windows Vista, 7, and 8: [Start > Control Panel > Programs > Uninstall a - Program](https://support.microsoft.com/en-us/help/2601726) +* Windows Vista, 7, and 8: Start > Control Panel > Programs > Uninstall a Program * Windows 10: [Start > Control Panel > Programs > Programs And Features > look for Mixxx > Uninstall](https://support.microsoft.com/en-gb/help/4028054/windows-repair-or-remove-programs-in-windows-10) diff --git a/CMakeLists.txt b/CMakeLists.txt index 683c860b0273..36e2f97a9605 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -338,7 +338,7 @@ elseif(APPLE) endif() endif() -project(mixxx VERSION 2.6.0 LANGUAGES C CXX) +project(mixxx VERSION 2.7.0 LANGUAGES C CXX) # Work around missing version suffixes support https://gitlab.kitware.com/cmake/cmake/-/issues/16716 set(MIXXX_VERSION_PRERELEASE "alpha") # set to "alpha" "beta" or "" @@ -410,6 +410,16 @@ if(MSVC) # Remove unreferenced code and data # Since c++11 they can safely be removed to speed up linking. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:inline") + + if(NOT DEFINED MSVC_TOOLSET_VERSION OR MSVC_TOOLSET_VERSION VERSION_LESS 143) + message( + FATAL_ERROR + "MSVC_TOOLSET_VERSION is ${MSVC_TOOLSET_VERSION}.\n" + "Mixxx requires the Microsoft Visual C++ Redistributable toolset of version 143 (VS2022) or greater, " + "as the VCPKG buildenv is built with this version.\n" + "Please use the Visual Studio 2022 toolset therefore!" + ) + endif() endif() # Speed up builds on HDDs and prevent wearing of SDDs @@ -1218,6 +1228,7 @@ add_library( src/library/dao/settingsdao.cpp src/library/dao/trackdao.cpp src/library/dao/trackschema.cpp + src/library/tabledelegates/defaultdelegate.cpp src/library/dlgcoverartfullsize.cpp src/library/dlgcoverartfullsize.ui src/library/dlgtagfetcher.cpp @@ -1251,6 +1262,7 @@ add_library( src/library/missing_hidden/hiddentablemodel.cpp src/library/missing_hidden/missingtablemodel.cpp src/library/mixxxlibraryfeature.cpp + src/library/overviewcache.cpp src/library/parser.cpp src/library/parsercsv.cpp src/library/parserm3u.cpp @@ -1280,6 +1292,7 @@ add_library( src/library/tabledelegates/keydelegate.cpp src/library/tabledelegates/locationdelegate.cpp src/library/tabledelegates/multilineeditdelegate.cpp + src/library/tabledelegates/overviewdelegate.cpp src/library/tabledelegates/previewbuttondelegate.cpp src/library/tabledelegates/stardelegate.cpp src/library/tabledelegates/stareditor.cpp @@ -1502,11 +1515,13 @@ add_library( src/util/workerthreadscheduler.cpp src/util/xml.cpp src/waveform/guitick.cpp + src/waveform/overviewtype.cpp src/waveform/renderers/glwaveformrenderbackground.cpp src/waveform/renderers/glvsynctestrenderer.cpp src/waveform/renderers/waveformmark.cpp src/waveform/renderers/waveformmarkrange.cpp src/waveform/renderers/waveformmarkset.cpp + src/waveform/renderers/waveformoverviewrenderer.cpp src/waveform/renderers/waveformrenderbackground.cpp src/waveform/renderers/waveformrenderbeat.cpp src/waveform/renderers/waveformrendererabstract.cpp @@ -1581,6 +1596,7 @@ add_library( src/widget/wlibrarytableview.cpp src/widget/wlibrarytextbrowser.cpp src/widget/wmainmenubar.cpp + src/widget/wmenucheckbox.cpp src/widget/wnumber.cpp src/widget/wnumberdb.cpp src/widget/wnumberpos.cpp @@ -2236,7 +2252,9 @@ endif() if(WIN32) set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION "${MIXXX_INSTALL_BINDIR}") if(MSVC AND CMAKE_BUILD_TYPE STREQUAL "Debug") + set(CMAKE_INSTALL_UCRT_LIBRARIES true) set(CMAKE_INSTALL_DEBUG_LIBRARIES true) + set(CMAKE_INSTALL_DEBUG_LIBRARIES_ONLY true) endif() include(InstallRequiredSystemLibraries) endif() @@ -2479,6 +2497,7 @@ if(BUILD_TESTING) src/test/colormapperjsproxy_test.cpp src/test/colorpalette_test.cpp src/test/configobject_test.cpp + src/test/controller_hid_reportdescriptor_test.cpp src/test/controller_mapping_validation_test.cpp src/test/controller_mapping_settings_test.cpp src/test/controllers/controller_columnid_regression_test.cpp @@ -2771,7 +2790,11 @@ if(WIN32) endif() # Chromaprint -find_package(Chromaprint REQUIRED) +find_package(Chromaprint) +if(NOT Chromaprint_FOUND) + # Fail verbose, because with Qt Creator, the verbose error message for Qt is bypassed. + fatal_error_missing_env() +endif() target_link_libraries(mixxx-lib PRIVATE Chromaprint::Chromaprint) # Locale Aware Compare for SQLite @@ -3296,8 +3319,17 @@ if(QML) list(APPEND QT_EXTRA_COMPONENTS "QuickShapesPrivate") list(APPEND QT_EXTRA_COMPONENTS "QuickTemplates2") list(APPEND QT_EXTRA_COMPONENTS "QuickWidgets") - list(APPEND QT_EXTRA_COMPONENTS "QmlWorkerScript") list(APPEND QT_EXTRA_COMPONENTS "ShaderTools") + if(QT_VERSION VERSION_GREATER_EQUAL 6.7) + list(APPEND QT_EXTRA_COMPONENTS "QuickControls2Basic") + list(APPEND QT_EXTRA_COMPONENTS "QuickControls2BasicStyleImpl") + list(APPEND QT_EXTRA_COMPONENTS "QuickControls2Fusion") + list(APPEND QT_EXTRA_COMPONENTS "QuickControls2FusionStyleImpl") + endif() + if(QT_VERSION VERSION_LESS 6.8) + # From Qt 6.8 integrated in Qml via QmlMeta + list(APPEND QT_EXTRA_COMPONENTS "QmlWorkerScript") + endif() endif() find_package( Qt${QT_VERSION_MAJOR} @@ -3638,6 +3670,32 @@ else() applocal ) + if(QT_VERSION VERSION_GREATER_EQUAL 6.7) + install( + IMPORTED_RUNTIME_ARTIFACTS + Qt${QT_VERSION_MAJOR}::QuickControls2Basic + Qt${QT_VERSION_MAJOR}::QuickControls2BasicStyleImpl + Qt${QT_VERSION_MAJOR}::QuickControls2Fusion + Qt${QT_VERSION_MAJOR}::QuickControls2FusionStyleImpl + DESTINATION + "${MIXXX_INSTALL_DATADIR}" + COMPONENT + applocal + ) + endif() + + if(QT_VERSION VERSION_LESS 6.8) + # From Qt 6.8 integrated in Qml via QmlMeta + install( + IMPORTED_RUNTIME_ARTIFACTS + Qt${QT_VERSION_MAJOR}::QmlWorkerScript + DESTINATION + "${MIXXX_INSTALL_DATADIR}" + COMPONENT + applocal + ) + endif() + install( IMPORTED_RUNTIME_ARTIFACTS Qt${QT_VERSION_MAJOR}::QuickLayouts @@ -3656,14 +3714,17 @@ else() applocal ) - install( - IMPORTED_RUNTIME_ARTIFACTS - Qt${QT_VERSION_MAJOR}::QmlWorkerScript - DESTINATION - "${MIXXX_INSTALL_DATADIR}" - COMPONENT - applocal - ) + if(QT_VERSION VERSION_LESS 6.4) + # From Qt 6.4 integrated in QuickControls2 + install( + IMPORTED_RUNTIME_ARTIFACTS + Qt${QT_VERSION_MAJOR}::QuickTemplates2 + DESTINATION + "${MIXXX_INSTALL_DATADIR}" + COMPONENT + applocal + ) + endif() install( IMPORTED_RUNTIME_ARTIFACTS @@ -4082,12 +4143,6 @@ target_link_libraries(mixxx-lib PRIVATE SoundTouch::SoundTouch) # TagLib find_package(TagLib 1.11 REQUIRED) -if(NOT TagLib_VERSION VERSION_LESS 2.0.0) - message( - WARNING - "Installed Taglib ${TagLib_VERSION} is not supported and might lead to data loss (https://github.com/mixxxdj/mixxx/issues/12708). Use version >= 1.11 and < 2.0 instead." - ) -endif() target_link_libraries(mixxx-lib PUBLIC TagLib::TagLib) if(QML) target_link_libraries(mixxx-qml-lib PUBLIC TagLib::TagLib) @@ -4662,12 +4717,14 @@ if(HID) target_sources( mixxx-lib PRIVATE + src/controllers/controllerhidreporttabsmanager.cpp src/controllers/hid/hidcontroller.cpp src/controllers/hid/hidiothread.cpp src/controllers/hid/hidioglobaloutputreportfifo.cpp src/controllers/hid/hidiooutputreport.cpp src/controllers/hid/hiddevice.cpp src/controllers/hid/hidenumerator.cpp + src/controllers/hid/hidreportdescriptor.cpp src/controllers/hid/hidusagetables.cpp src/controllers/hid/legacyhidcontrollermapping.cpp src/controllers/hid/legacyhidcontrollermappingfilehandler.cpp @@ -4893,7 +4950,14 @@ if(NOT CPACK_DEBIAN_PACKAGE_RELEASE) set(CPACK_DEBIAN_PACKAGE_RELEASE 1) endif() -set(CPACK_DEBIAN_DISTRIBUTION_RELEASES jammy noble oracular plucky) +set( + CPACK_DEBIAN_DISTRIBUTION_RELEASES + jammy + noble + oracular + plucky + questing +) set(CPACK_DEBIAN_SOURCE_DIR ${CMAKE_SOURCE_DIR}) set( CPACK_DEBIAN_UPLOAD_PPA_SCRIPT @@ -4949,7 +5013,9 @@ endif() if(APPLE AND MACOS_BUNDLE) set(BUNDLE_NAME "${MIXXX_INSTALL_PREFIX}") - set(BUNDLE_DIRS "${CMAKE_PREFIX_PATH}/lib") + foreach(PREFIX ${CMAKE_PREFIX_PATH}) + list(APPEND BUNDLE_DIRS "${PREFIX}/lib") + endforeach() set( APPLE_CODESIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/packaging/macos/Mixxx.entitlements" diff --git a/LICENSE b/LICENSE index a3ff5f714ce5..1bfecb19b6d8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Mixxx 2.6-alpha, Digital DJ'ing software. +Mixxx 2.7-alpha, Digital DJ'ing software. Copyright (C) 2001-2025 Mixxx Development Team Mixxx is free software; you can redistribute it and/or modify diff --git a/README.md b/README.md index 3d40a864cd3a..b1398634b5fe 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ Read [CONTRIBUTING](CONTRIBUTING.md) for more information. ## Building Mixxx -First, open a terminal (on Windows, use "x64 Native Tools Command Prompt for -[VS 2019][visualstudio2019]"), download the mixxx +First, open a terminal (on Windows, use "**x64 Native Tools Command Prompt for +[VS 2022][visualstudio2022]**"), download the mixxx source code and navigate to it: $ git clone https://github.com/mixxxdj/mixxx.git @@ -102,7 +102,7 @@ license. [blog]: https://mixxx.org/news/ [manual]: https://manual.mixxx.org/ [wiki]: https://github.com/mixxxdj/mixxx/wiki -[visualstudio2019]: https://docs.microsoft.com/visualstudio/install/install-visual-studio?view=vs-2019 +[visualstudio2022]: https://docs.microsoft.com/visualstudio/install/install-visual-studio?view=vs-2022 [easybugs]: https://github.com/mixxxdj/mixxx/issues?q=is%3Aopen+is%3Aissue+label%3Aeasy [creating skins]: https://mixxx.org/wiki/doku.php/Creating-Skins [help translate content]: https://www.transifex.com/projects/p/mixxxdj diff --git a/cmake/modules/FindChromaprint.cmake b/cmake/modules/FindChromaprint.cmake index 3852d6751db6..13f8e599ac3d 100644 --- a/cmake/modules/FindChromaprint.cmake +++ b/cmake/modules/FindChromaprint.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindChromaprint --------------- diff --git a/cmake/modules/FindDjInterop.cmake b/cmake/modules/FindDjInterop.cmake index 5eec0a11c8da..2de5a7eb0bda 100644 --- a/cmake/modules/FindDjInterop.cmake +++ b/cmake/modules/FindDjInterop.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindDjInterop --------------- diff --git a/cmake/modules/FindEbur128.cmake b/cmake/modules/FindEbur128.cmake index 09934058377e..2661a9fda3e6 100644 --- a/cmake/modules/FindEbur128.cmake +++ b/cmake/modules/FindEbur128.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindEbur128 ----------- diff --git a/cmake/modules/FindFLAC.cmake b/cmake/modules/FindFLAC.cmake index 74c6f266cced..fa8088d06281 100644 --- a/cmake/modules/FindFLAC.cmake +++ b/cmake/modules/FindFLAC.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindFLAC -------- diff --git a/cmake/modules/FindG72X.cmake b/cmake/modules/FindG72X.cmake index 117e4f95d5f1..1e94ee0f5c60 100644 --- a/cmake/modules/FindG72X.cmake +++ b/cmake/modules/FindG72X.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindG72X -------- diff --git a/cmake/modules/FindGPerfTools.cmake b/cmake/modules/FindGPerfTools.cmake index e201a8b2137d..94e18cb3550a 100644 --- a/cmake/modules/FindGPerfTools.cmake +++ b/cmake/modules/FindGPerfTools.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindGPerfTools -------------- diff --git a/cmake/modules/FindHSS1394.cmake b/cmake/modules/FindHSS1394.cmake index b349d996b285..53800951ec08 100644 --- a/cmake/modules/FindHSS1394.cmake +++ b/cmake/modules/FindHSS1394.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindHSS1394 ----------- diff --git a/cmake/modules/FindID3Tag.cmake b/cmake/modules/FindID3Tag.cmake index 566c40e953ab..3e168fb6858f 100644 --- a/cmake/modules/FindID3Tag.cmake +++ b/cmake/modules/FindID3Tag.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindID3Tag ---------- diff --git a/cmake/modules/FindKeyFinder.cmake b/cmake/modules/FindKeyFinder.cmake index 717a414fef76..75269fe3f2e9 100644 --- a/cmake/modules/FindKeyFinder.cmake +++ b/cmake/modules/FindKeyFinder.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindKeyFinder -------- diff --git a/cmake/modules/FindLibUSB.cmake b/cmake/modules/FindLibUSB.cmake index c0e8ad307777..c67cfb6470d4 100644 --- a/cmake/modules/FindLibUSB.cmake +++ b/cmake/modules/FindLibUSB.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindLibUSB ---------- diff --git a/cmake/modules/FindLibudev.cmake b/cmake/modules/FindLibudev.cmake index f87ccb322043..2021b8b489d2 100644 --- a/cmake/modules/FindLibudev.cmake +++ b/cmake/modules/FindLibudev.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindLibudev -------- diff --git a/cmake/modules/FindMAD.cmake b/cmake/modules/FindMAD.cmake index 2cfa029825db..7e26ef41e1ef 100644 --- a/cmake/modules/FindMAD.cmake +++ b/cmake/modules/FindMAD.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindMAD ------- diff --git a/cmake/modules/FindMP4.cmake b/cmake/modules/FindMP4.cmake index 7f813009e8e2..3f161bd7999b 100644 --- a/cmake/modules/FindMP4.cmake +++ b/cmake/modules/FindMP4.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindMP4 ------- diff --git a/cmake/modules/FindMP4v2.cmake b/cmake/modules/FindMP4v2.cmake index 041fcc4ad0ad..8d78cb1aff3e 100644 --- a/cmake/modules/FindMP4v2.cmake +++ b/cmake/modules/FindMP4v2.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindMP4v2 --------- diff --git a/cmake/modules/FindModplug.cmake b/cmake/modules/FindModplug.cmake index 5e7ad5ab0bf3..72fa64028517 100644 --- a/cmake/modules/FindModplug.cmake +++ b/cmake/modules/FindModplug.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindModplug ----------- diff --git a/cmake/modules/FindOgg.cmake b/cmake/modules/FindOgg.cmake index ccce92a4010a..8b721b57d8d6 100644 --- a/cmake/modules/FindOgg.cmake +++ b/cmake/modules/FindOgg.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindOgg ------- diff --git a/cmake/modules/FindOpus.cmake b/cmake/modules/FindOpus.cmake index c56ac38a7190..2805f7cff4e3 100644 --- a/cmake/modules/FindOpus.cmake +++ b/cmake/modules/FindOpus.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindOpus -------- diff --git a/cmake/modules/FindOpusFile.cmake b/cmake/modules/FindOpusFile.cmake index 1abe9df8cb8f..ddc0210946e8 100644 --- a/cmake/modules/FindOpusFile.cmake +++ b/cmake/modules/FindOpusFile.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindOpus -------- diff --git a/cmake/modules/FindPortAudio.cmake b/cmake/modules/FindPortAudio.cmake index bc4508352a1c..cbd5377f51b1 100644 --- a/cmake/modules/FindPortAudio.cmake +++ b/cmake/modules/FindPortAudio.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindPortAudio -------- diff --git a/cmake/modules/FindPortMidi.cmake b/cmake/modules/FindPortMidi.cmake index aa2392c07e62..905fd236c90b 100644 --- a/cmake/modules/FindPortMidi.cmake +++ b/cmake/modules/FindPortMidi.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindPortMidi --------------- diff --git a/cmake/modules/FindShoutidjc.cmake b/cmake/modules/FindShoutidjc.cmake index 3109a56c1b5a..8d27d078074b 100644 --- a/cmake/modules/FindShoutidjc.cmake +++ b/cmake/modules/FindShoutidjc.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindShoutidjc --------- diff --git a/cmake/modules/FindSleef.cmake b/cmake/modules/FindSleef.cmake index 6b573d81a5bc..491d8b7dfd9c 100644 --- a/cmake/modules/FindSleef.cmake +++ b/cmake/modules/FindSleef.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindSleef -------------- diff --git a/cmake/modules/FindSndFile.cmake b/cmake/modules/FindSndFile.cmake index 712da34c77f4..dd17820d6efc 100644 --- a/cmake/modules/FindSndFile.cmake +++ b/cmake/modules/FindSndFile.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindSndFile ----------- diff --git a/cmake/modules/FindSoundTouch.cmake b/cmake/modules/FindSoundTouch.cmake index e25e76c73103..b95d2a8c8b9c 100644 --- a/cmake/modules/FindSoundTouch.cmake +++ b/cmake/modules/FindSoundTouch.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindSoundTouch -------------- diff --git a/cmake/modules/FindTagLib.cmake b/cmake/modules/FindTagLib.cmake index 4314fcaef42f..884669001bd9 100644 --- a/cmake/modules/FindTagLib.cmake +++ b/cmake/modules/FindTagLib.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindTagLib ----------- diff --git a/cmake/modules/FindUpower.cmake b/cmake/modules/FindUpower.cmake index d6b3134a7828..2921672aa209 100644 --- a/cmake/modules/FindUpower.cmake +++ b/cmake/modules/FindUpower.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindUpower ---------- diff --git a/cmake/modules/FindVorbis.cmake b/cmake/modules/FindVorbis.cmake index 6380db1bb8c1..c7d975a7fb02 100644 --- a/cmake/modules/FindVorbis.cmake +++ b/cmake/modules/FindVorbis.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: FindVorbis ---------- diff --git a/cmake/modules/Findhidapi.cmake b/cmake/modules/Findhidapi.cmake index f934684a8a92..881b524477af 100644 --- a/cmake/modules/Findhidapi.cmake +++ b/cmake/modules/Findhidapi.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: Findhidapi ---------- diff --git a/cmake/modules/Findlilv.cmake b/cmake/modules/Findlilv.cmake index 4365fd5df274..34e02e7f705d 100644 --- a/cmake/modules/Findlilv.cmake +++ b/cmake/modules/Findlilv.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: Findlilv -------- @@ -94,7 +89,10 @@ if(lilv_FOUND) ) is_static_library(lilv_IS_STATIC lilv::lilv) if(lilv_IS_STATIC) - find_package(sord CONFIG REQUIRED) + find_package(sord CONFIG) + if(NOT sord_FOUND) + find_package(sord REQUIRED) + endif() set_property( TARGET lilv::lilv APPEND diff --git a/cmake/modules/Findmp3lame.cmake b/cmake/modules/Findmp3lame.cmake index e6e2467bd353..2a46361c0073 100644 --- a/cmake/modules/Findmp3lame.cmake +++ b/cmake/modules/Findmp3lame.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: Findmp3lame ----------- diff --git a/cmake/modules/Findrubberband.cmake b/cmake/modules/Findrubberband.cmake index 101ab00bcb4d..a6212baf2c70 100644 --- a/cmake/modules/Findrubberband.cmake +++ b/cmake/modules/Findrubberband.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: Findrubberband -------------- diff --git a/cmake/modules/Findserd.cmake b/cmake/modules/Findserd.cmake new file mode 100644 index 000000000000..e90acaeb586c --- /dev/null +++ b/cmake/modules/Findserd.cmake @@ -0,0 +1,90 @@ +#[=======================================================================[.rst: +Findserd +-------- + +Finds the serd library and its development headers. + +Imported Targets +^^^^^^^^^^^^^^^^ + +This module provides the following imported targets, if found: + +``serd::serd`` + The serd library + +Result Variables +^^^^^^^^^^^^^^^^ + +This will define the following variables: + +``serd_FOUND`` + True if the system has the serd library. +``serd_INCLUDE_DIRS`` + Include directories needed to use serd. +``serd_LIBRARIES`` + Libraries needed to link to serd. +``serd_DEFINITIONS`` + Compile definitions needed to use serd. + +Cache Variables +^^^^^^^^^^^^^^^ + +The following cache variables may also be set: + +``serd_INCLUDE_DIR`` + The directory containing ``serd/serd.h``. +``serd_LIBRARY`` + The path to the serd library. + +#]=======================================================================] + +include(IsStaticLibrary) + +find_package(PkgConfig QUIET) +if(PkgConfig_FOUND) + pkg_check_modules(PC_serd QUIET serd-0) +endif() + +find_path( + serd_INCLUDE_DIR + NAMES serd/serd.h + HINTS ${PC_serd_INCLUDE_DIRS} + DOC "serd include directory" +) +mark_as_advanced(serd_INCLUDE_DIR) + +find_library( + serd_LIBRARY + NAMES serd-0 + HINTS ${PC_serd_LIBRARY_DIRS} + DOC "serd library" +) +mark_as_advanced(serd_LIBRARY) + +if(DEFINED PC_serd_VERSION AND NOT PC_serd_VERSION STREQUAL "") + set(serd_VERSION "${PC_serd_VERSION}") +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + serd + REQUIRED_VARS serd_LIBRARY serd_INCLUDE_DIR + VERSION_VAR serd_VERSION +) + +if(serd_FOUND) + set(serd_LIBRARIES "${serd_LIBRARY}") + set(serd_INCLUDE_DIRS "${serd_INCLUDE_DIR}") + set(serd_DEFINITIONS ${PC_serd_CFLAGS_OTHER}) + + if(NOT TARGET serd::serd) + add_library(serd::serd UNKNOWN IMPORTED) + set_target_properties( + serd::serd + PROPERTIES + IMPORTED_LOCATION "${serd_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${PC_serd_CFLAGS_OTHER}" + INTERFACE_INCLUDE_DIRECTORIES "${serd_INCLUDE_DIR}" + ) + endif() +endif() diff --git a/cmake/modules/Findsord.cmake b/cmake/modules/Findsord.cmake new file mode 100644 index 000000000000..1bbe2b5c922c --- /dev/null +++ b/cmake/modules/Findsord.cmake @@ -0,0 +1,107 @@ +#[=======================================================================[.rst: +Findsord +-------- + +Finds the sord library and its development headers. + +Imported Targets +^^^^^^^^^^^^^^^^ + +This module provides the following imported targets, if found: + +``sord::sord`` + The sord library + +Result Variables +^^^^^^^^^^^^^^^^ + +This will define the following variables: + +``sord_FOUND`` + True if the system has the sord library. +``sord_INCLUDE_DIRS`` + Include directories needed to use sord. +``sord_LIBRARIES`` + Libraries needed to link to sord. +``sord_DEFINITIONS`` + Compile definitions needed to use sord. + +Cache Variables +^^^^^^^^^^^^^^^ + +The following cache variables may also be set: + +``sord_INCLUDE_DIR`` + The directory containing ``sord/sord.h``. +``sord_LIBRARY`` + The path to the sord library. + +#]=======================================================================] + +include(IsStaticLibrary) + +find_package(PkgConfig QUIET) +if(PkgConfig_FOUND) + pkg_check_modules(PC_sord QUIET sord-0) +endif() + +find_path( + sord_INCLUDE_DIR + NAMES sord/sord.h + HINTS ${PC_sord_INCLUDE_DIRS} + DOC "sord include directory" +) +mark_as_advanced(sord_INCLUDE_DIR) + +find_library( + sord_LIBRARY + NAMES sord-0 + HINTS ${PC_sord_LIBRARY_DIRS} + DOC "sord library" +) +mark_as_advanced(sord_LIBRARY) + +if(DEFINED PC_sord_VERSION AND NOT PC_sord_VERSION STREQUAL "") + set(sord_VERSION "${PC_sord_VERSION}") +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + sord + REQUIRED_VARS sord_LIBRARY sord_INCLUDE_DIR + VERSION_VAR sord_VERSION +) + +if(sord_FOUND) + set(sord_LIBRARIES "${sord_LIBRARY}") + set(sord_INCLUDE_DIRS "${sord_INCLUDE_DIR}") + set(sord_DEFINITIONS ${PC_sord_CFLAGS_OTHER}) + + if(NOT TARGET sord::sord) + add_library(sord::sord UNKNOWN IMPORTED) + set_target_properties( + sord::sord + PROPERTIES + IMPORTED_LOCATION "${sord_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${PC_sord_CFLAGS_OTHER}" + INTERFACE_INCLUDE_DIRECTORIES "${sord_INCLUDE_DIR}" + ) + is_static_library(sord_IS_STATIC sord::sord) + if(sord_IS_STATIC) + find_package(serd REQUIRED) + set_property( + TARGET sord::sord + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES serd::serd + ) + if(sord_VERSION VERSION_GREATER_EQUAL "0.16.16") + find_package(zix REQUIRED) + set_property( + TARGET sord::sord + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES zix::zix + ) + endif() + endif() + endif() +endif() diff --git a/cmake/modules/Findwavpack.cmake b/cmake/modules/Findwavpack.cmake index 187d27178035..15d647c483b4 100644 --- a/cmake/modules/Findwavpack.cmake +++ b/cmake/modules/Findwavpack.cmake @@ -1,8 +1,3 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2025 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - #[=======================================================================[.rst: Findwavpack ----------- diff --git a/cmake/modules/Findzix.cmake b/cmake/modules/Findzix.cmake new file mode 100644 index 000000000000..c03940502192 --- /dev/null +++ b/cmake/modules/Findzix.cmake @@ -0,0 +1,90 @@ +#[=======================================================================[.rst: +Findzix +-------- + +Finds the zix library and its development headers. + +Imported Targets +^^^^^^^^^^^^^^^^ + +This module provides the following imported targets, if found: + +``zix::zix`` + The zix library + +Result Variables +^^^^^^^^^^^^^^^^ + +This will define the following variables: + +``zix_FOUND`` + True if the system has the zix library. +``zix_INCLUDE_DIRS`` + Include directories needed to use zix. +``zix_LIBRARIES`` + Libraries needed to link to zix. +``zix_DEFINITIONS`` + Compile definitions needed to use zix. + +Cache Variables +^^^^^^^^^^^^^^^ + +The following cache variables may also be set: + +``zix_INCLUDE_DIR`` + The directory containing ``zix/zix.h``. +``zix_LIBRARY`` + The path to the zix library. + +#]=======================================================================] + +include(IsStaticLibrary) + +find_package(PkgConfig QUIET) +if(PkgConfig_FOUND) + pkg_check_modules(PC_zix QUIET zix-0) +endif() + +find_path( + zix_INCLUDE_DIR + NAMES zix/zix.h + HINTS ${PC_zix_INCLUDE_DIRS} + DOC "zix include directory" +) +mark_as_advanced(zix_INCLUDE_DIR) + +find_library( + zix_LIBRARY + NAMES zix-0 + HINTS ${PC_zix_LIBRARY_DIRS} + DOC "zix library" +) +mark_as_advanced(zix_LIBRARY) + +if(DEFINED PC_zix_VERSION AND NOT PC_zix_VERSION STREQUAL "") + set(zix_VERSION "${PC_zix_VERSION}") +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + zix + REQUIRED_VARS zix_LIBRARY zix_INCLUDE_DIR + VERSION_VAR zix_VERSION +) + +if(zix_FOUND) + set(zix_LIBRARIES "${zix_LIBRARY}") + set(zix_INCLUDE_DIRS "${zix_INCLUDE_DIR}") + set(zix_DEFINITIONS ${PC_zix_CFLAGS_OTHER}) + + if(NOT TARGET zix::zix) + add_library(zix::zix UNKNOWN IMPORTED) + set_target_properties( + zix::zix + PROPERTIES + IMPORTED_LOCATION "${zix_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${PC_zix_CFLAGS_OTHER}" + INTERFACE_INCLUDE_DIRECTORIES "${zix_INCLUDE_DIR}" + ) + endif() +endif() diff --git a/packaging/CPackConfig.cmake b/packaging/CPackConfig.cmake index 2ccabc533c55..aa9fe029e3ff 100644 --- a/packaging/CPackConfig.cmake +++ b/packaging/CPackConfig.cmake @@ -23,7 +23,14 @@ if(PACKAGE_VERSION MATCHES "^[0-9]+\\.[0-9]+[A-Za-z0-9.+~-]*$") if(PACKAGE_VERSION MATCHES "(alpha|beta)") string(REPLACE "-" "~" CPACK_DEBIAN_PACKAGE_VERSION "${PACKAGE_VERSION}") else() - string(REPLACE "-" "+" CPACK_DEBIAN_PACKAGE_VERSION "${PACKAGE_VERSION}") + string(REPLACE "-g" "+g" CPACK_DEBIAN_PACKAGE_VERSION "${PACKAGE_VERSION}") + string( + REPLACE + "-" + "+r" + CPACK_DEBIAN_PACKAGE_VERSION + "${CPACK_DEBIAN_PACKAGE_VERSION}" + ) endif() else() string(REPLACE "-" "~" CPACK_DEBIAN_PACKAGE_VERSION "${CPACK_MIXXX_VERSION}") diff --git a/packaging/debian/changelog b/packaging/debian/changelog index 48d0c3e2c927..e8ffcbe33abc 100644 --- a/packaging/debian/changelog +++ b/packaging/debian/changelog @@ -1,3 +1,9 @@ +mixxx (2.5.0-1~jammy) jammy; urgency=medium + + * Build of 2.5.0 + + -- RJ Skerry-Ryan Tue, 24 Dec 2024 02:09:16 +0000 + mixxx (2.4.2-1~focal) focal; urgency=medium * Build of 2.4.2 diff --git a/res/controllers/DJ TechTools MIDI Fighter Spectra.midi.xml b/res/controllers/DJ TechTools MIDI Fighter Spectra.midi.xml new file mode 100644 index 000000000000..d7caff82cf6a --- /dev/null +++ b/res/controllers/DJ TechTools MIDI Fighter Spectra.midi.xml @@ -0,0 +1,861 @@ + + + + DJ TechTools MIDI Fighter Spectra + Sam Whited + Multi-layer mappings for the Spectra. + dj_techtools_midi_fighter_spectra + https://mixxx.discourse.group/t/dj-techtools-midi-fighter-spectra/31554 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [Channel3] + MidiFighterSpectra.controller.eqLayer[0].input + High cutoff. + 0x92 + 0x30 + + + + + + [Channel1] + MidiFighterSpectra.controller.eqLayer[1].input + High cutoff. + 0x92 + 0x31 + + + + + + [Channel2] + MidiFighterSpectra.controller.eqLayer[2].input + High cutoff. + 0x92 + 0x32 + + + + + + [Channel4] + MidiFighterSpectra.controller.eqLayer[3].input + High cutoff. + 0x92 + 0x33 + + + + + + + [Channel3] + MidiFighterSpectra.controller.eqLayer[4].input + Mid cutoff. + 0x92 + 0x2C + + + + + + [Channel1] + MidiFighterSpectra.controller.eqLayer[5].input + Mid cutoff. + 0x92 + 0x2D + + + + + + [Channel2] + MidiFighterSpectra.controller.eqLayer[6].input + Mid cutoff. + 0x92 + 0x2E + + + + + + [Channel4] + MidiFighterSpectra.controller.eqLayer[7].input + Mid cutoff. + 0x92 + 0x2F + + + + + + + [Channel3] + MidiFighterSpectra.controller.eqLayer[8].input + Low cutoff. + 0x92 + 0x28 + + + + + + [Channel1] + MidiFighterSpectra.controller.eqLayer[9].input + Low cutoff. + 0x92 + 0x29 + + + + + + [Channel2] + MidiFighterSpectra.controller.eqLayer[10].input + Low cutoff. + 0x92 + 0x2A + + + + + + [Channel4] + MidiFighterSpectra.controller.eqLayer[11].input + Low cutoff. + 0x92 + 0x2B + + + + + + + [Channel3] + MidiFighterSpectra.controller.eqLayer[12].input + Quick effect cutoff. + 0x92 + 0x24 + + + + + + [Channel1] + MidiFighterSpectra.controller.eqLayer[13].input + Quick effect cutoff. + 0x92 + 0x25 + + + + + + [Channel2] + MidiFighterSpectra.controller.eqLayer[14].input + Quick effect cutoff. + 0x92 + 0x26 + + + + + + [Channel4] + MidiFighterSpectra.controller.eqLayer[15].input + Quick effect cutoff. + 0x92 + 0x27 + + + + + + + + [Channel1] + MidiFighterSpectra.controller.activeDeck.cueLayer[0].input + Set intro start marker. + 0x92 + 0x40 + + + + + + [Channel1] + MidiFighterSpectra.controller.activeDeck.cueLayer[1].input + Set intro end marker. + 0x92 + 0x41 + + + + + + [Channel1] + MidiFighterSpectra.controller.activeDeck.cueLayer[2].input + Set outro start marker. + 0x92 + 0x42 + + + + + + [Channel1] + MidiFighterSpectra.controller.activeDeck.cueLayer[3].input + Set outro end marker. + 0x92 + 0x43 + + + + + + + [Channel1] + MidiFighterSpectra.controller.activeDeck.cueLayer[4].input + Set/activate hotcue 1. + 0x92 + 0x3C + + + + + + [Channel1] + MidiFighterSpectra.controller.activeDeck.cueLayer[5].input + Set/activate hotcue 2. + 0x92 + 0x3D + + + + + + [Channel1] + MidiFighterSpectra.controller.activeDeck.cueLayer[6].input + Set/activate hotcue 3. + 0x92 + 0x3E + + + + + + [Channel1] + MidiFighterSpectra.controller.activeDeck.cueLayer[7].input + Set/activate hotcue 4. + 0x92 + 0x3F + + + + + + [Channel1] + MidiFighterSpectra.controller.activeDeck.cueLayer[8].input + Set/activate hotcue 5. + 0x92 + 0x38 + + + + + + [Channel1] + MidiFighterSpectra.controller.activeDeck.cueLayer[9].input + Set/activate hotcue 6. + 0x92 + 0x39 + + + + + + [Channel1] + MidiFighterSpectra.controller.activeDeck.cueLayer[10].input + Set/activate hotcue 7. + 0x92 + 0x3A + + + + + + [Channel1] + MidiFighterSpectra.controller.activeDeck.cueLayer[11].input + Set/activate hotcue 8. + 0x92 + 0x3B + + + + + + + [Channel1] + MidiFighterSpectra.controller.activeDeck.cueLayer[4].input + Release hotcue 1. + 0x82 + 0x3C + + + + + + [Channel1] + MidiFighterSpectra.controller.activeDeck.cueLayer[5].input + Release hotcue 2. + 0x82 + 0x3D + + + + + + [Channel1] + MidiFighterSpectra.controller.activeDeck.cueLayer[6].input + Release hotcue 3. + 0x82 + 0x3E + + + + + + [Channel1] + MidiFighterSpectra.controller.activeDeck.cueLayer[7].input + Release hotcue 4. + 0x82 + 0x3F + + + + + + [Channel1] + MidiFighterSpectra.controller.activeDeck.cueLayer[8].input + Release hotcue 5. + 0x82 + 0x38 + + + + + + [Channel1] + MidiFighterSpectra.controller.activeDeck.cueLayer[9].input + Release hotcue 6. + 0x82 + 0x39 + + + + + + [Channel1] + MidiFighterSpectra.controller.activeDeck.cueLayer[10].input + Release hotcue 7. + 0x82 + 0x3A + + + + + + [Channel1] + MidiFighterSpectra.controller.activeDeck.cueLayer[11].input + Release hotcue 8. + 0x82 + 0x3B + + + + + + + [Channel1] + MidiFighterSpectra.controller.selectCueDeck.input + Select deck 1. + 0x92 + 0x34 + + + + + + [Channel2] + MidiFighterSpectra.controller.selectCueDeck.input + Select deck 2. + 0x92 + 0x35 + + + + + + [Channel3] + MidiFighterSpectra.controller.selectCueDeck.input + Select deck 3. + 0x92 + 0x36 + + + + + + [Channel4] + MidiFighterSpectra.controller.selectCueDeck.input + Select deck 4. + 0x92 + 0x37 + + + + + + + [Sampler1] + MidiFighterSpectra.controller.samplerLayer[0].input + Load or play sampler. + 0x92 + 0x50 + + + + + + [Sampler2] + MidiFighterSpectra.controller.samplerLayer[1].input + Load or play sampler. + 0x92 + 0x51 + + + + + + [Sampler3] + MidiFighterSpectra.controller.samplerLayer[2].input + Load or play sampler. + 0x92 + 0x52 + + + + + + [Sampler4] + MidiFighterSpectra.controller.samplerLayer[3].input + Load or play sampler. + 0x92 + 0x53 + + + + + + [Sampler5] + MidiFighterSpectra.controller.samplerLayer[4].input + Load or play sampler. + 0x92 + 0x4C + + + + + + [Sampler6] + MidiFighterSpectra.controller.samplerLayer[5].input + Load or play sampler. + 0x92 + 0x4D + + + + + + [Sampler7] + MidiFighterSpectra.controller.samplerLayer[6].input + Load or play sampler. + 0x92 + 0x4E + + + + + + [Sampler8] + MidiFighterSpectra.controller.samplerLayer[7].input + Load or play sampler. + 0x92 + 0x4F + + + + + + [Sampler9] + MidiFighterSpectra.controller.samplerLayer[8].input + Load or play sampler. + 0x92 + 0x48 + + + + + + [Sampler10] + MidiFighterSpectra.controller.samplerLayer[9].input + Load or play sampler. + 0x92 + 0x49 + + + + + + [Sampler11] + MidiFighterSpectra.controller.samplerLayer[10].input + Load or play sampler. + 0x92 + 0x4A + + + + + + [Sampler12] + MidiFighterSpectra.controller.samplerLayer[11].input + Load or play sampler. + 0x92 + 0x4B + + + + + + [Sampler13] + MidiFighterSpectra.controller.samplerLayer[12].input + Load or play sampler. + 0x92 + 0x44 + + + + + + [Sampler14] + MidiFighterSpectra.controller.samplerLayer[13].input + Load or play sampler. + 0x92 + 0x45 + + + + + + [Sampler15] + MidiFighterSpectra.controller.samplerLayer[14].input + Load or play sampler. + 0x92 + 0x46 + + + + + + [Sampler16] + MidiFighterSpectra.controller.samplerLayer[15].input + Load or play sampler. + 0x92 + 0x47 + + + + + + + + diff --git a/res/controllers/DJ TechTools-MIDI Fighter Spectra-scripts.js b/res/controllers/DJ TechTools-MIDI Fighter Spectra-scripts.js new file mode 100644 index 000000000000..89902e6b2457 --- /dev/null +++ b/res/controllers/DJ TechTools-MIDI Fighter Spectra-scripts.js @@ -0,0 +1,284 @@ +"use strict"; + +// eslint-disable-next-line no-var +var MidiFighterSpectra; +(function(MidiFighterSpectra) { + const mapIndexToChannel = function(index) { + switch (Math.abs(index) % 4) { + case 0: return 3; + case 1: return 1; + case 2: return 2; + case 3: return 4; + } + }; + + class Deck extends components.Deck { + constructor() { + super([1, 2, 3, 4]); + + this.cueLayer = [ + // Intro/outro markers + new components.Button({ + group: "[Channel1]", + midi: [0x92, 0x40], + inKey: "intro_start_activate", + outKey: "intro_start_enabled", + on: engine.getSetting("introOutroColor"), + off: engine.getSetting("unsetIntroOutroColor"), + }), + new components.Button({ + group: "[Channel1]", + midi: [0x92, 0x41], + inKey: "intro_end_activate", + outKey: "intro_end_enabled", + on: engine.getSetting("introOutroColor"), + off: engine.getSetting("unsetIntroOutroColor"), + }), + new components.Button({ + group: "[Channel1]", + midi: [0x92, 0x42], + inKey: "outro_start_activate", + outKey: "outro_start_enabled", + on: engine.getSetting("introOutroColor"), + off: engine.getSetting("unsetIntroOutroColor"), + }), + new components.Button({ + group: "[Channel1]", + midi: [0x92, 0x43], + inKey: "outro_end_activate", + outKey: "outro_end_enabled", + on: engine.getSetting("introOutroColor"), + off: engine.getSetting("unsetIntroOutroColor"), + }), + ]; + for (let i = 0; i < 8; i++) { + this.cueLayer[i + 4] = new components.HotcueButton({ + number: i + 1, + group: "[Channel1]", + midi: [0x92, [ + 0x3C, 0x3D, 0x3E, 0x3F, + 0x38, 0x39, 0x3A, 0x3B + ][i]], + colorMapper: new ColorMapper({ + // These colors don't always appear to match what the + // manual says they should be. The "bright" version of + // the color is what the manual says should be the + // "dull" version, and "white" is actually purple (and + // the invalid value, 121, provided is actually white). + 0x000000: 1, // Black (Off) + 0xC50A08: 19, // Dim Red + 0xF07800: 31, // Dim Orange + 0xF8D200: 43, // Dim Yellow + 0xC4D82E: 55, // Dim Lime + 0x32BE44: 67, // Dim Green + 0x42D4F4: 79, // Dim Bianchi/Celeste + 0x0044FF: 91, // Dim Blue + 0xAF00CC: 103, // Dim Purple + 0xFCA6D7: 115, // Dim Pink + 0xF2F2FF: 122, // White + }), + input: function(channel, control, value, status, group) { + // If this is a note release event, swap the value as if + // this were a normal button release. + if (status === 0x82) { + value = value ? this.off : this.on; + status = 0x92; + } + components.HotcueButton.prototype.input.bind(this)(channel, control, value, status, group); + }, + outputColor: function(colorCode) { + const enabled = engine.getValue(this.group, `hotcue_${this.number}_status`) === 2; + // If the loop is enabled or the hotcue is previewing, + // set the brighter variant of the closest color. + if (enabled) { + const nearestColorValue = this.colorMapper.getValueForNearestColor(colorCode); + this.send(nearestColorValue - (enabled ? 1 : 0)); + return; + } + + // otherwise set the "dim" variant. + components.HotcueButton.prototype.outputColor.bind(this)(colorCode); + }, + }); + } + } + } + + class Controller extends components.ComponentContainer { + constructor() { + super({}); + + this.eqLayer = []; + for (let i = 0; i < 4; i++) { + this.eqLayer[i] = new components.Button({ + group: `[EqualizerRack1_[Channel${mapIndexToChannel(i)}]_Effect1]`, + midi: [0x92, 0x30 + i], + key: "button_parameter3", + type: components.Button.prototype.types.toggle, + off: engine.getSetting("eqOnColor"), + on: engine.getSetting("eqOffColor"), + }); + this.eqLayer[i + 4] = new components.Button({ + group: `[EqualizerRack1_[Channel${mapIndexToChannel(i)}]_Effect1]`, + midi: [0x92, 0x2C + i], + key: "button_parameter2", + type: components.Button.prototype.types.toggle, + off: engine.getSetting("eqOnColor"), + on: engine.getSetting("eqOffColor"), + }); + this.eqLayer[i + 8] = new components.Button({ + group: `[EqualizerRack1_[Channel${mapIndexToChannel(i)}]_Effect1]`, + midi: [0x92, 0x28 + i], + key: "button_parameter1", + type: components.Button.prototype.types.toggle, + off: engine.getSetting("eqOnColor"), + on: engine.getSetting("eqOffColor"), + }); + this.eqLayer[i + 12] = new components.Button({ + group: `[QuickEffectRack1_[Channel${mapIndexToChannel(i)}]]`, + midi: [0x92, 0x24 + i], + key: "enabled", + type: components.Button.prototype.types.toggle, + off: engine.getSetting("superOnColor"), + on: engine.getSetting("superOffColor"), + }); + } + + this.samplerLayer = []; + for (let i = 0; i < 16; i++) { + this.samplerLayer[i] = new components.SamplerButton({ + number: i + 1, + midi: [0x92, [ + 0x50, 0x51, 0x52, 0x53, + 0x4C, 0x4D, 0x4E, 0x4F, + 0x48, 0x49, 0x4A, 0x4B, + 0x44, 0x45, 0x46, 0x47, + ][i]], + off: engine.getSetting("samplerEmptyColor"), + on: engine.getSetting("samplerLoadedColor"), + }); + } + + this.activeDeck = new Deck(); + + // Since this is a radio button set, just have one button that + // handles setting and unsetting all the LEDs. + this.selectCueDeck = new components.Button({ + group: "[Channel1]", + key: "end_of_track", + midi: [0x92, 0x34], + input: function(_channel, control, value, _status, group) { + MidiFighterSpectra.controller.activeDeck.setCurrentDeck(group); + this.output(value, group, control); + }, + output: function(_value, _group, control) { + for (let i = 0; i < 4; i++) { + midi.sendShortMsg(this.midi[0], this.midi[1] + i, (control === this.midi[1] + i) ? this.on : this.off); + } + }, + pulse: function(value, group, _control) { + const deckNum = parseInt(script.channelRegEx.exec(group)[1]); + if (value) { + midi.sendShortMsg(this.midi[0] + 1, this.midi[1] + deckNum - 1, 47); + } else { + midi.sendShortMsg(this.midi[0] - 0xF, this.midi[1] + deckNum - 1, 33); + } + }, + on: engine.getSetting("deckSelectedColor"), + off: engine.getSetting("deckUnselectedColor"), + connect: function() { + if (engine.getSetting("pulseDeckSelect")) { + for (let i = 0; i < 4; i++) { + this.connections[i] = engine.makeConnection(`[Channel${i + 1}]`, "end_of_track", this.pulse.bind(this)); + } + } + }, + }); + this.selectCueDeck.output(0x7F, "[Channel1]", 0x34); + } + } + + // Functions for scaling a signed byte value to the scale expected to toggle + // an effect. Each value is a function except for "off", which always + const groundEffect = { + off: 18, // 18 is always off + on: function(value) { return (value / 127 * 15) + 19; }, // 19-33 brightness values + gate: function(value) { return (value / 127 * 7) + 34; }, // 34-41 sets the strobe rate + pulse: function(value) { return (value / 127 * 7) + 42; }, // 42-49 sets the pulse rate + }; + + MidiFighterSpectra.init = function() { + MidiFighterSpectra.controller = new Controller(); + + // Blink the ground effect LEDs when a track is ending. + MidiFighterSpectra.connections = []; + switch (engine.getSetting("groundEffectLed")) { + case "off": + break; + case "end_of_track": + MidiFighterSpectra.connections = MidiFighterSpectra.connections.concat([ + engine.makeConnection("[Channel1]", "end_of_track", function(value) { + midi.sendShortMsg(0x93, 0x13, (value === 0) ? groundEffect.off : groundEffect.pulse(75)); + }), + engine.makeConnection("[Channel2]", "end_of_track", function(value) { + midi.sendShortMsg(0x93, 0x11, (value === 0) ? groundEffect.off : groundEffect.pulse(75)); + }), + engine.makeConnection("[Channel3]", "end_of_track", function(value) { + midi.sendShortMsg(0x93, 0x12, (value === 0) ? groundEffect.off : groundEffect.pulse(75)); + }), + engine.makeConnection("[Channel4]", "end_of_track", function(value) { + midi.sendShortMsg(0x93, 0x10, (value === 0) ? groundEffect.off : groundEffect.pulse(75)); + }), + ]); + break; + case "beat_active": + MidiFighterSpectra.connections = MidiFighterSpectra.connections.concat([ + engine.makeConnection("[Channel1]", "beat_active", function(value) { + midi.sendShortMsg(0x93, 0x13, (!value) ? groundEffect.off : groundEffect.on(127)); + }), + engine.makeConnection("[Channel2]", "beat_active", function(value) { + midi.sendShortMsg(0x93, 0x11, (!value) ? groundEffect.off : groundEffect.on(127)); + }), + engine.makeConnection("[Channel3]", "beat_active", function(value) { + midi.sendShortMsg(0x93, 0x12, (!value) ? groundEffect.off : groundEffect.on(127)); + }), + engine.makeConnection("[Channel4]", "beat_active", function(value) { + midi.sendShortMsg(0x93, 0x10, (!value) ? groundEffect.off : groundEffect.on(127)); + }), + ]); + break; + }; + + // The manual says to send a C1-D#1 (ie. 0x18-0x1B), but it appears that + // you have to send 0x00-0x03 instead. + switch (engine.getSetting("defaultLayer")) { + case "eq": + midi.sendShortMsg(0x93, 0x00, 127); + break; + case "hotcues": + midi.sendShortMsg(0x93, 0x01, 127); + break; + case "samplers": + midi.sendShortMsg(0x93, 0x02, 127); + break; + } + }; + + MidiFighterSpectra.shutdown = function() { + MidiFighterSpectra.controller.shutdown(); + + for (const connection of MidiFighterSpectra.connections) { + connection.disconnect(); + } + + // Make sure we turn ground effect LEDs off when we shut down. + for (let i = 0x10; i <= 0x13; i++) { + midi.sendShortMsg(0x93, i, groundEffect.off); + midi.sendShortMsg(0x93, i, groundEffect.off); + midi.sendShortMsg(0x93, i, groundEffect.off); + midi.sendShortMsg(0x93, i, groundEffect.off); + } + }; +})(MidiFighterSpectra || (MidiFighterSpectra = {})); + +// vim:expandtab:tabstop=4:shiftwidth=4 diff --git a/res/controllers/EKS-Otus.js b/res/controllers/EKS-Otus.js index 1ae93195ea30..d9c65f6ffba6 100644 --- a/res/controllers/EKS-Otus.js +++ b/res/controllers/EKS-Otus.js @@ -731,7 +731,7 @@ EksOtus.enableSpinningPlatterLEDs = function() { return; engine.connectControl( EksOtus.activeSpinningPlatterGroup, - "visual_playposition", + "playposition", "EksOtus.circleLEDs" ); engine.connectControl( @@ -748,7 +748,7 @@ EksOtus.disableSpinningPlatterLEDs = function() { return; engine.connectControl( EksOtus.activeSpinningPlatterGroup, - "visual_playposition", + "playposition", "EksOtus.circleLEDs", true ); diff --git a/res/controllers/Hercules-DJControl-Inpulse-200-script.js b/res/controllers/Hercules-DJControl-Inpulse-200-script.js index 556ac69b6136..44af50094234 100644 --- a/res/controllers/Hercules-DJControl-Inpulse-200-script.js +++ b/res/controllers/Hercules-DJControl-Inpulse-200-script.js @@ -25,7 +25,8 @@ // //************************************************************************* -var DJCi200 = {}; +var DJCi200 = {}; // eslint-disable-line + /////////////////////////////////////////////////////////////// // USER OPTIONS // /////////////////////////////////////////////////////////////// diff --git a/res/controllers/KANE_QuNeo_scripts.js b/res/controllers/KANE_QuNeo_scripts.js index e206570deb08..bd00b91679cb 100644 --- a/res/controllers/KANE_QuNeo_scripts.js +++ b/res/controllers/KANE_QuNeo_scripts.js @@ -339,13 +339,11 @@ KANE_QuNeo.jumpHoldTimers = KANE_QuNeo.makeVar([]) // hold timers for continued KANE_QuNeo.init = function (id) { // called when the device is opened & set up - // NOTE: the 2 following controls are called each time the music updates, - // which means ~every 0.02 seconds. Everything that needs consistent updates - // should branch from these functions so we don't eat the cpu. - // Visual playposition is updated roughly 4x more often - // than playposition. - engine.connectControl("[Channel1]","visual_playposition","KANE_QuNeo.time1Keeper"); - engine.connectControl("[Channel2]","visual_playposition","KANE_QuNeo.time2Keeper"); + // NOTE: the 2 following controls are called each time the music updates, + // which means ~every 0.02 seconds. Everything that needs consistent updates + // should branch from these functions so we don't eat the cpu. + engine.connectControl("[Channel1]", "playposition", "KANE_QuNeo.time1Keeper"); + engine.connectControl("[Channel2]", "playposition", "KANE_QuNeo.time2Keeper"); // led controls for the master / flanger channels engine.connectControl("[Main]", "vu_meter", "KANE_QuNeo.masterVuMeter"); @@ -509,7 +507,7 @@ KANE_QuNeo.toggleRecord = function (channel, control, value, status, group) { for (var deck = 1; deck <= 2; deck++) { var channelName = KANE_QuNeo.getChannelName(deck); var samples = engine.getValue(channelName,"track_samples"); - var position = engine.getValue(channelName,"visual_playposition"); + const position = engine.getValue(channelName, "playposition"); var samplePosition = samples * position; print("Recording started with deck "+deck+ " at sample: "+samplePosition) } @@ -541,7 +539,7 @@ KANE_QuNeo.jumpLoop = function (deck, numBeats) { var spb = samplerate * 60 * 2 / bpm // samples per beat, not sure on the 2. // calculate the new position - var oldPosition = engine.getValue(channelName, "visual_playposition"); + const oldPosition = engine.getValue(channelName, "playposition"); var direction = KANE_QuNeo.trackJump[channel]; var beatsVector = numBeats * direction; // vectors have magnitude and direction var newPosition = oldPosition + (beatsVector*spb/samples); @@ -899,8 +897,7 @@ KANE_QuNeo.deckCursor = function (deck, value) { var channelName = KANE_QuNeo.getChannelName(deck) var normalized = 1 - (value / 127); // adjust play positions - engine.setValue(channelName,"visual_playposition", normalized) - engine.setValue(channelName,"playposition", normalized) + engine.setValue(channelName, "playposition", normalized); } /***** (VN) Visual Nudging *****/ @@ -938,12 +935,11 @@ KANE_QuNeo.doVisualNudge = function (deck, direction) { var channel = deck - 1; var channelName = KANE_QuNeo.getChannelName(deck) // calculate new position - var newPosition = engine.getValue(channelName,"visual_playposition") + const newPosition = engine.getValue(channelName, "playposition") + (KANE_QuNeo.visualNudgeDist * direction); // now apply to both visual and actual position - engine.setValue(channelName,"playposition",newPosition) - engine.setValue(channelName,"visual_playposition",newPosition) + engine.setValue(channelName, "playposition", newPosition); } KANE_QuNeo.visualNudgeOff = function (deck, direction) { @@ -1163,7 +1159,7 @@ KANE_QuNeo.quantizeCues = function (deck, LEDControl) { // find out our current position, assume it's on the beatgrid var trackSamples = engine.getValue(channelName,"track_samples") - var position = engine.getValue(channelName,"visual_playposition") + const position = engine.getValue(channelName, "playposition") * trackSamples; // track position in samples // determine bpm and spb @@ -1259,7 +1255,7 @@ KANE_QuNeo.handleBeat = function (deck) { var lastWholeBeat = KANE_QuNeo.wholeBeat[channel]; // now see which beat this one is - var value = engine.getValue(channelName,"visual_playposition") + const value = engine.getValue(channelName, "playposition"); var samplerate = engine.getValue(channelName,"track_samplerate"); var samples = engine.getValue(channelName,"track_samples"); var bpm = engine.getValue(channelName,"file_bpm"); @@ -1814,7 +1810,7 @@ KANE_QuNeo.assertHotcueActivateLEDs = function (deck) { var channel = deck - 1; var channelName = KANE_QuNeo.getChannelName(deck); var closest = [undefined,2]; // next hotcue wrt current playpos as [control,pos] - var position = engine.getValue(channelName,"visual_playposition"); // song position + const position = engine.getValue(channelName, "playposition"); // song position var trackSamples = engine.getValue(channelName,"track_samples") var on = [], LEDs = [[0x71,0x73,0x75,0x77, 0x61,0x63,0x65,0x67, diff --git a/res/controllers/Numark-Mixtrack-Platinum-scripts.js b/res/controllers/Numark-Mixtrack-Platinum-scripts.js index f19a41e8742d..39ba3a322e26 100644 --- a/res/controllers/Numark-Mixtrack-Platinum-scripts.js +++ b/res/controllers/Numark-Mixtrack-Platinum-scripts.js @@ -1413,13 +1413,13 @@ MixtrackPlatinum.vuCallback = function(value, group, control) { midi.sendShortMsg(0xBF, 0x45, level); } } - else if (group == '[Master]' && control == 'VuMeterL') { + else if (group === "[Main]" && control === "vu_meter_left") { if (engine.getValue(group, "peak_indicator_left")) { level = 81; } midi.sendShortMsg(0xBF, 0x44, level); } - else if (group == '[Master]' && control == 'VuMeterR') { + else if (group === "[Main]" && control === "vu_meter_right") { if (engine.getValue(group, "peak_indicator_right")) { level = 81; } diff --git a/res/controllers/Stanton-SCS1d-scripts.js b/res/controllers/Stanton-SCS1d-scripts.js index b69d73b1f5bd..5050a19f885e 100644 --- a/res/controllers/Stanton-SCS1d-scripts.js +++ b/res/controllers/Stanton-SCS1d-scripts.js @@ -88,7 +88,7 @@ StantonSCS1d.deckSignals = [ ["CurrentChannel", "rateRange", "StantonSCS1d.pi ["CurrentChannel", "loop_enabled", "StantonSCS1d.loopEnabled"], ["CurrentChannel", "reloop_exit", "StantonSCS1d.activateLoop"], ["CurrentChannel", "bpm_tap", "StantonSCS1d.bpmLED"], - ["CurrentChannel", "visual_playposition", "StantonSCS1d.circleBars"], + ["CurrentChannel", "playposition", "StantonSCS1d.circleBars"], ["CurrentChannel", "duration","StantonSCS1d.durationChange"] ]; // When Mixxx gets multiple loop capability, just change the key values here for the respective buttons @@ -851,9 +851,9 @@ StantonSCS1d.DeckChangeFinish = function(channel) { else midi.sendShortMsg(byte1,64,32); // Deck select button green StantonSCS1d.connectDeckSignals(channel); // Connect static signals // Update jog circle - StantonSCS1d.durationChange(engine.getValue("[Channel"+StantonSCS1d.deck+"]","duration")); + StantonSCS1d.durationChange(engine.getValue(`[Channel${StantonSCS1d.deck}]`, "duration")); StantonSCS1d.lastLight[StantonSCS1d.deck]=-1; - StantonSCS1d.circleBars(engine.getValue("[Channel"+StantonSCS1d.deck+"]","visual_playposition")); + StantonSCS1d.circleBars(engine.getValue(`[Channel${StantonSCS1d.deck}]`, "playposition")); } StantonSCS1d.DeckChangeP2 = function (channel, value) { diff --git a/res/controllers/Stanton-SCS1m-scripts.js b/res/controllers/Stanton-SCS1m-scripts.js index 06a6c1bbd9de..ea24691d7828 100644 --- a/res/controllers/Stanton-SCS1m-scripts.js +++ b/res/controllers/Stanton-SCS1m-scripts.js @@ -92,11 +92,11 @@ StantonSCS1m.init = function (id) { // called when the MIDI device is opened engine.connectControl("[Channel1]","rateRange","StantonSCS1m.pitchColor1"); engine.connectControl("[Channel2]","rateRange","StantonSCS1m.pitchColor2"); - // Virtual platter LEDs & time displays - engine.connectControl("[Channel1]","visual_playposition","StantonSCS1m.positionUpdates1"); - engine.connectControl("[Channel2]","visual_playposition","StantonSCS1m.positionUpdates2"); - engine.connectControl("[Channel1]","duration","StantonSCS1m.durationChange1"); - engine.connectControl("[Channel2]","duration","StantonSCS1m.durationChange2"); + // Virtual platter LEDs & time displays + engine.connectControl("[Channel1]", "playposition", "StantonSCS1m.positionUpdates1"); + engine.connectControl("[Channel2]", "playposition", "StantonSCS1m.positionUpdates2"); + engine.connectControl("[Channel1]", "duration", "StantonSCS1m.durationChange1"); + engine.connectControl("[Channel2]", "duration", "StantonSCS1m.durationChange2"); // Faders engine.connectControl("[Master]","crossfader","StantonSCS1m.crossFaderStart"); diff --git a/res/keyboard/cs_CZ.kbd.cfg b/res/keyboard/cs_CZ.kbd.cfg index d12ee77db1bc..d84ede8a4b6b 100644 --- a/res/keyboard/cs_CZ.kbd.cfg +++ b/res/keyboard/cs_CZ.kbd.cfg @@ -141,6 +141,8 @@ vinylcontrol_cueing Ctrl+Alt+U FileMenu_LoadDeck1 Ctrl+o FileMenu_LoadDeck2 Ctrl+Shift+O FileMenu_Quit Ctrl+q +LibraryMenu_SearchInCurrentView Ctrl+f +LibraryMenu_SearchInAllTracks Ctrl+Shift+F LibraryMenu_NewPlaylist Ctrl+n LibraryMenu_NewCrate Ctrl+Shift+N ViewMenu_ShowSkinSettings Ctrl+1 diff --git a/res/keyboard/da_DK.kbd.cfg b/res/keyboard/da_DK.kbd.cfg index 9255c56f1bf8..9bfcbd54e86a 100644 --- a/res/keyboard/da_DK.kbd.cfg +++ b/res/keyboard/da_DK.kbd.cfg @@ -141,6 +141,8 @@ vinylcontrol_cueing Ctrl+Alt+U FileMenu_LoadDeck1 Ctrl+o FileMenu_LoadDeck2 Ctrl+Shift+O FileMenu_Quit Ctrl+q +LibraryMenu_SearchInCurrentView Ctrl+f +LibraryMenu_SearchInAllTracks Ctrl+Shift+F LibraryMenu_NewPlaylist Ctrl+n LibraryMenu_NewCrate Ctrl+Shift+N ViewMenu_ShowSkinSettings Ctrl+1 diff --git a/res/keyboard/de_CH.kbd.cfg b/res/keyboard/de_CH.kbd.cfg index b4da055bdfe1..d080b43be88a 100644 --- a/res/keyboard/de_CH.kbd.cfg +++ b/res/keyboard/de_CH.kbd.cfg @@ -141,6 +141,8 @@ vinylcontrol_cueing Ctrl+Alt+U FileMenu_LoadDeck1 Ctrl+o FileMenu_LoadDeck2 Ctrl+Shift+O FileMenu_Quit Ctrl+q +LibraryMenu_SearchInCurrentView Ctrl+f +LibraryMenu_SearchInAllTracks Ctrl+Shift+F LibraryMenu_NewPlaylist Ctrl+n LibraryMenu_NewCrate Ctrl+Shift+N ViewMenu_ShowSkinSettings Ctrl+1 diff --git a/res/keyboard/de_DE.kbd.cfg b/res/keyboard/de_DE.kbd.cfg index b912eef63e6b..aa5a976d4c79 100644 --- a/res/keyboard/de_DE.kbd.cfg +++ b/res/keyboard/de_DE.kbd.cfg @@ -141,6 +141,8 @@ vinylcontrol_cueing Ctrl+Alt+U FileMenu_LoadDeck1 Ctrl+o FileMenu_LoadDeck2 Ctrl+Shift+O FileMenu_Quit Ctrl+q +LibraryMenu_SearchInCurrentView Ctrl+f +LibraryMenu_SearchInAllTracks Ctrl+Shift+F LibraryMenu_NewPlaylist Ctrl+n LibraryMenu_NewCrate Ctrl+Shift+N ViewMenu_ShowSkinSettings Ctrl+1 diff --git a/res/keyboard/el_GR.kbd.cfg b/res/keyboard/el_GR.kbd.cfg index 4674298283f4..3571c934a130 100644 --- a/res/keyboard/el_GR.kbd.cfg +++ b/res/keyboard/el_GR.kbd.cfg @@ -145,6 +145,8 @@ vinylcontrol_cueing Ctrl+Alt+Θ FileMenu_LoadDeck1 Ctrl+o FileMenu_LoadDeck2 Ctrl+Shift+O FileMenu_Quit Ctrl+q +LibraryMenu_SearchInCurrentView Ctrl+f +LibraryMenu_SearchInAllTracks Ctrl+Shift+F LibraryMenu_NewPlaylist Ctrl+n LibraryMenu_NewCrate Ctrl+Shift+N ViewMenu_ShowSkinSettings Ctrl+1 diff --git a/res/keyboard/en_US.kbd.cfg b/res/keyboard/en_US.kbd.cfg index 3b8050bfe722..d14ab1b04797 100644 --- a/res/keyboard/en_US.kbd.cfg +++ b/res/keyboard/en_US.kbd.cfg @@ -141,6 +141,8 @@ vinylcontrol_cueing Ctrl+Alt+U FileMenu_LoadDeck1 Ctrl+o FileMenu_LoadDeck2 Ctrl+Shift+O FileMenu_Quit Ctrl+q +LibraryMenu_SearchInCurrentView Ctrl+f +LibraryMenu_SearchInAllTracks Ctrl+Shift+F LibraryMenu_NewPlaylist Ctrl+n LibraryMenu_NewCrate Ctrl+Shift+N ViewMenu_ShowSkinSettings Ctrl+1 diff --git a/res/keyboard/es_ES.kbd.cfg b/res/keyboard/es_ES.kbd.cfg index cfe492491ec9..dc13861bd505 100644 --- a/res/keyboard/es_ES.kbd.cfg +++ b/res/keyboard/es_ES.kbd.cfg @@ -141,6 +141,8 @@ vinylcontrol_cueing Ctrl+Alt+U FileMenu_LoadDeck1 Ctrl+o FileMenu_LoadDeck2 Ctrl+Shift+O FileMenu_Quit Ctrl+q +LibraryMenu_SearchInCurrentView Ctrl+f +LibraryMenu_SearchInAllTracks Ctrl+Shift+F LibraryMenu_NewPlaylist Ctrl+n LibraryMenu_NewCrate Ctrl+Shift+N ViewMenu_ShowSkinSettings Ctrl+1 diff --git a/res/keyboard/fi_FI.kbd.cfg b/res/keyboard/fi_FI.kbd.cfg index 9ec9721d1382..c8c6fc6bed2d 100644 --- a/res/keyboard/fi_FI.kbd.cfg +++ b/res/keyboard/fi_FI.kbd.cfg @@ -141,6 +141,8 @@ vinylcontrol_cueing Ctrl+Alt+U FileMenu_LoadDeck1 Ctrl+o FileMenu_LoadDeck2 Ctrl+Shift+O FileMenu_Quit Ctrl+q +LibraryMenu_SearchInCurrentView Ctrl+f +LibraryMenu_SearchInAllTracks Ctrl+Shift+F LibraryMenu_NewPlaylist Ctrl+n LibraryMenu_NewCrate Ctrl+Shift+N ViewMenu_ShowSkinSettings Ctrl+1 diff --git a/res/keyboard/fr_CH.kbd.cfg b/res/keyboard/fr_CH.kbd.cfg index ed65a7de8a4a..5f3cbfd398a4 100644 --- a/res/keyboard/fr_CH.kbd.cfg +++ b/res/keyboard/fr_CH.kbd.cfg @@ -141,6 +141,8 @@ vinylcontrol_cueing Ctrl+Alt+U FileMenu_LoadDeck1 Ctrl+o FileMenu_LoadDeck2 Ctrl+Shift+O FileMenu_Quit Ctrl+q +LibraryMenu_SearchInCurrentView Ctrl+f +LibraryMenu_SearchInAllTracks Ctrl+Shift+F LibraryMenu_NewPlaylist Ctrl+n LibraryMenu_NewCrate Ctrl+Shift+N ViewMenu_ShowSkinSettings Ctrl+1 diff --git a/res/keyboard/fr_FR.kbd.cfg b/res/keyboard/fr_FR.kbd.cfg index 7c1f50d2077c..dd16215ff720 100644 --- a/res/keyboard/fr_FR.kbd.cfg +++ b/res/keyboard/fr_FR.kbd.cfg @@ -141,6 +141,8 @@ vinylcontrol_cueing Ctrl+Alt+U FileMenu_LoadDeck1 Ctrl+o FileMenu_LoadDeck2 Ctrl+Shift+O FileMenu_Quit Ctrl+q +LibraryMenu_SearchInCurrentView Ctrl+f +LibraryMenu_SearchInAllTracks Ctrl+Shift+F LibraryMenu_NewPlaylist Ctrl+n LibraryMenu_NewCrate Ctrl+Shift+N ViewMenu_ShowSkinSettings Ctrl+1 diff --git a/res/keyboard/it_IT.kbd.cfg b/res/keyboard/it_IT.kbd.cfg index 46063ceb6c3e..3df1bda30e19 100644 --- a/res/keyboard/it_IT.kbd.cfg +++ b/res/keyboard/it_IT.kbd.cfg @@ -141,6 +141,8 @@ vinylcontrol_cueing Ctrl+Alt+U FileMenu_LoadDeck1 Ctrl+o FileMenu_LoadDeck2 Ctrl+Shift+O FileMenu_Quit Ctrl+q +LibraryMenu_SearchInCurrentView Ctrl+f +LibraryMenu_SearchInAllTracks Ctrl+Shift+F LibraryMenu_NewPlaylist Ctrl+n LibraryMenu_NewCrate Ctrl+Shift+N ViewMenu_ShowSkinSettings Ctrl+1 diff --git a/res/keyboard/ru_RU.kbd.cfg b/res/keyboard/ru_RU.kbd.cfg index f506f422c515..38263d70fa35 100644 --- a/res/keyboard/ru_RU.kbd.cfg +++ b/res/keyboard/ru_RU.kbd.cfg @@ -141,6 +141,8 @@ vinylcontrol_cueing Ctrl+Alt+Г FileMenu_LoadDeck1 Ctrl+o FileMenu_LoadDeck2 Ctrl+Shift+O FileMenu_Quit Ctrl+q +LibraryMenu_SearchInCurrentView Ctrl+f +LibraryMenu_SearchInAllTracks Ctrl+Shift+F LibraryMenu_NewPlaylist Ctrl+n LibraryMenu_NewCrate Ctrl+Shift+N ViewMenu_ShowSkinSettings Ctrl+1 diff --git a/res/linux/mixxx-usb-uaccess.rules b/res/linux/mixxx-usb-uaccess.rules index 52700f942ce9..63d07fdc449e 100644 --- a/res/linux/mixxx-usb-uaccess.rules +++ b/res/linux/mixxx-usb-uaccess.rules @@ -56,6 +56,3 @@ KERNEL=="hidraw*", ATTRS{idVendor}=="054c", TAG+="uaccess" # Missing: # - American Musical Supply (AMS/Mixars) - -# Only some distribuions require the below -KERNEL=="hiddev*", NAME="usb/%k", GROUP="uaccess" diff --git a/res/linux/org.mixxx.Mixxx.metainfo.xml b/res/linux/org.mixxx.Mixxx.metainfo.xml index 39919db96601..5752aff313df 100644 --- a/res/linux/org.mixxx.Mixxx.metainfo.xml +++ b/res/linux/org.mixxx.Mixxx.metainfo.xml @@ -96,7 +96,11 @@ Do not edit it manually. --> - + + + + +

Controller Mappings @@ -208,6 +212,10 @@ #13671 #12738 +

  • + sync: prefer playing inaudible decks over stopped non-sync decks + #14580 +
  • fix: prevent null CO access when cloning sampler or preview #13740 @@ -254,6 +262,14 @@ Preferences Effects: left/right key in effect lists trigger hide/unhide #14205
  • +
  • + Open sound preferences with sprecific I/O tab selected + #14346 +
  • +
  • + Show 'real' xfader configuration + #14124 +
  • Skins @@ -300,6 +316,10 @@ Add color coding for key column #13390 +

  • + Add overview column with small waveform + #14140 +
  • Elide key text form the right #13475 @@ -342,6 +362,17 @@ Track File Export: add 'Apply to all' checkbox, remove ".. All" buttons #13614
  • +
  • + Library scan: log summary and show popup + #13427 + #10720 +
  • +
  • + Search: add BPM lock filter + bpm:locked + #14590 + #14583 +
  • Effects @@ -444,6 +475,11 @@ Add slip waveform to Textured/'High details' type #14039 +

  • + Fix waveform marker image alignment + #14656 + #14037 +
  • STEM file support @@ -508,6 +544,14 @@ Solves problem with special characters in path to stems #13784 +

  • + Enable FFmpeg (free) on Windows. + #14695 +
  • +
  • + FFmpeg: Use internal aac decoder. If not available give a hint. + #14645 +
  • Auto-DJ @@ -598,6 +642,18 @@ Remove unmaintained shell.nix #14300 +

  • + add QGLES2 option for UNIX + #14489 +
  • +
  • + Don't set GL_BGRA if QT_OPENGL_ES_2 + #14488 +
  • +
  • + Windows and macOS: Update to Qt 6.8.3 (requires MSVC 2022) + #14655 +
  • Misc Refactorings @@ -990,10 +1046,38 @@ chore: clean up README.md #14471 +

  • + Fix type safety warnings + #14613 +
  • +
  • + CMake: Join project() with enable_language() + #14577 +
  • +
  • + fix warning when building without STEM support + #14551 +
  • +
  • + scenegraph is conditioned to QML=ON + #14487 +
  • +
  • + Fix building with Qt 6.9 + #14678 +
  • +
  • + fix: import proper QtQml.Models module instead of qmllabs + #14675 +
  • +
  • + qmlwaveform: Fix moc in Qt 6.9.0 + #14649 +
  • - +

    Controller Mappings @@ -1005,13 +1089,38 @@ #14349

  • - Hercules Inpulse 300: add toneplay, slicer, and beatmatch functionalities + DJ TechTools MIDI Fighter Spectra: Add controller mapping + #14559 +
  • +
  • + Hercules DJControl Inpulse 300: add toneplay, slicer, and beatmatch functionalities #14051 #14057
  • +
  • + Hercules DJControl Inpulse 500: New mapping + #14491 + #14510 +
  • +
  • + Hercules DJ Console Mk1: Fix pitch bend buttons + #14447 +
  • M-Vave SMC-Mixer: Add controller mapping #14411 + #14448 + #14457 + #14458 +
  • +
  • + M-Vave SMK-25 II: Piano keyboard mapping + #14412 + #14484 +
  • +
  • + Numark Mixtrack Platinum: Fix VU Meters + #14575
  • Numark NS6II: New mapping @@ -1049,6 +1158,14 @@ Traktor Kontrol S3: Use pitch absolute mode as described in the manual #14123
  • +
  • + Stanton SCS.1m/d; Keith McMillen QuNeo; EKS Otus: use + playposition + instead of non-existent + visual_playposition + #14609 + #14603 +
  • Controller Backend @@ -1132,15 +1249,20 @@ #14203 #14197 +

  • + Fix crash due to concurrent access in MidiController + #14159 +
  • Skins

    • - Deere (64 samplers): Bring back library in regular view + Deere/LateNight (64 samplers): Bring back library in regular view #14101 #14097 + #14700
    • Fix crash when hiding waveforms in Deere @@ -1164,6 +1286,15 @@ #14256 #14239
    • +
    • + Center effect parameter names + #14598 +
    • +
    • + Track menu: highlight row when hovering checkbox + #14636 + #14680 +

    Library @@ -1198,6 +1329,7 @@

  • Library menu: change "Engine DJ Prime" to "Engine DJ" #14248 + #14682
  • Fix file extension handling during playlist export @@ -1215,6 +1347,10 @@ #14401 #14399
  • +
  • + Track info dialog: fixed cover label (max) size + #14418 +
  • Track Menu: Reset eject @@ -1226,6 +1362,53 @@ #14426 #14425
  • +
  • + Fix scrolling issue with coverart columns visible + #13719 + #14631 +
  • +
  • + Developer Tools: multi-word search, no Tab navigation in controls table + #14474 +
  • +
  • + Analyze feature: respect New / All selection when searching + #14660 + #14659 +
  • +
  • + Stop populating Computer library feature when Mixxx should close + #14573 +
  • +
  • + Tracks: apply played/missing text color also to selected tracks + #13583 +
  • +
  • + Tracks: + show_track_menu + at index position + #14385 +
  • +
  • + Search related menu: improve checkbox click UX + #14637 +
  • +
  • + Avoid false missing tracks due to db inconsistency + #14615 + #14513 +
  • +
  • + Fix automatic trimming of search bar text + #14497 + #14486 +
  • +
  • + Avoid crash after removing Quick Link + #14556 + #8270 +
  • Other Fixes @@ -1266,6 +1449,16 @@ #14357 #13981 +

  • + Reset saved loop when toggling off after switching cue type + #14661 + #14657 +
  • +
  • + Fix leaks from fid_design() + #14567 + #9470 +
  • Target support @@ -1282,6 +1475,8 @@ #14071 #14200 #14204 + #14440 + #14518

  • Welcome Ubuntu Plucky Puffin; Good bye Mantic Minotaur @@ -1311,9 +1506,26 @@ #14321
  • - Qt6.8: Ensure Mixxx uses "windowsvista" Qt style on Windows + Qt 6.8: Ensure Mixxx uses "windowsvista" Qt style on Windows #14228
  • +
  • + Raise macOS target version to 11 (Qt 6.5 requirement). + #14440 +
  • +
  • + Fail early when building on WSL + #14481 +
  • +
  • + Remove useless udev rule + #14630 +
  • +
  • + Handle new " / " from taglib 2.0 + #12854 + #12790 +
  • @@ -6899,9 +7111,7 @@ varies on different versions of Windows:

    • - Windows Vista, 7, and 8: - Start > Control Panel > Programs > Uninstall a - Program + Windows Vista, 7, and 8: Start > Control Panel > Programs > Uninstall a Program
    • Windows 10: diff --git a/res/qml/DeveloperToolsWindow.qml b/res/qml/DeveloperToolsWindow.qml index cbf895ba851f..d56b7ed7139b 100644 --- a/res/qml/DeveloperToolsWindow.qml +++ b/res/qml/DeveloperToolsWindow.qml @@ -1,6 +1,7 @@ import "." as Skin import Mixxx 1.0 as Mixxx -import Qt.labs.qmlmodels 1.0 +import QtQml.Models // DelegateChoice for Qt >= 6.9 +import Qt.labs.qmlmodels // DelegateChooser import QtQuick 2.12 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.12 diff --git a/res/skins/Deere/effect_parameter_button.xml b/res/skins/Deere/effect_parameter_button.xml index 0a65466d64ee..b77dd032b070 100644 --- a/res/skins/Deere/effect_parameter_button.xml +++ b/res/skins/Deere/effect_parameter_button.xml @@ -44,6 +44,7 @@ 34,15 58,15 me,f + center right EffectButtonLabel diff --git a/res/skins/Deere/effect_parameter_knob.xml b/res/skins/Deere/effect_parameter_knob.xml index 2f73f9b44541..ffd4d506603f 100644 --- a/res/skins/Deere/effect_parameter_knob.xml +++ b/res/skins/Deere/effect_parameter_knob.xml @@ -46,6 +46,7 @@ + center right diff --git a/res/skins/Deere/library.xml b/res/skins/Deere/library.xml index 1f08cc11345c..235578a2796a 100644 --- a/res/skins/Deere/library.xml +++ b/res/skins/Deere/library.xml @@ -72,6 +72,10 @@ false 0.175 + + + diff --git a/res/skins/Deere/preview_deck.xml b/res/skins/Deere/preview_deck.xml index 1806cb669f5e..7437022b8161 100644 --- a/res/skins/Deere/preview_deck.xml +++ b/res/skins/Deere/preview_deck.xml @@ -100,7 +100,7 @@ - #FF8000 + #00FF00 false diff --git a/res/skins/Deere/sampler_controls_row.xml b/res/skins/Deere/sampler_controls_row.xml index a56c178839a1..62e5b8d6a880 100644 --- a/res/skins/Deere/sampler_controls_row.xml +++ b/res/skins/Deere/sampler_controls_row.xml @@ -42,7 +42,7 @@ - #FF8000 + #00FF00 false diff --git a/res/skins/Deere/skin.xml b/res/skins/Deere/skin.xml index 74f7ad973c87..0cbd46f1bc97 100644 --- a/res/skins/Deere/skin.xml +++ b/res/skins/Deere/skin.xml @@ -135,6 +135,9 @@ 22,-1 f,me + + #FF8000 + + + diff --git a/res/skins/LateNight/style_classic.qss b/res/skins/LateNight/style_classic.qss index e27b4f5ed187..f1f2428844f3 100644 --- a/res/skins/LateNight/style_classic.qss +++ b/res/skins/LateNight/style_classic.qss @@ -2187,10 +2187,22 @@ WLibraryTextBrowser:focus { WLibrarySidebar::item:selected, WTrackTableView::item:selected, #LibraryBPMButton::item:selected, -#LibraryPlayedCheckbox::item:selected { - color: #fff; +#LibraryPlayedCheckbox::item:selected { /* disabled since it interferes with selection-color + which we use in DefaultDelegate and TableItemDelegate to + blend the highlight color with the played/missing color. + color: #fff;*/ + /* This is not required for painting the background, + but it is for applying the focus outline. */ background-color: #5e4507; -} + } + /* Highlight currentIndex which can be moved with Ctrl + arrow keys */ + WTrackTableView::item:!selected:focus { + /* For outline to be applied, background-color must be set, too ... + Use a value between #0f0f0f and #0a0a0a (alternate-background-color) */ + background-color: #0c0c0c; + outline: 1px solid #fff; + color: #fff; + } WLibrarySidebar::item:selected:focus { outline: none; } diff --git a/res/skins/LateNight/style_palemoon.qss b/res/skins/LateNight/style_palemoon.qss index f3caf48b9a6d..edccee2626f7 100644 --- a/res/skins/LateNight/style_palemoon.qss +++ b/res/skins/LateNight/style_palemoon.qss @@ -2649,9 +2649,23 @@ WLibrarySidebar::item:selected, WTrackTableView::item:selected, #LibraryBPMButton::item:selected, #LibraryPlayedCheckbox::item:selected { - color: #fff; + /* disabled since it interferes with selection-color + which we use in DefaultDelegate and TableItemDelegate to + blend the highlight color with the played/missing color. + color: #fff;*/ + /* This is not required for painting the background, + but it is for applying the focus outline. */ background-color: #2c454f; -} + } + /* Highlight currentIndex which can be moved with Ctrl + arrow keys */ + WTrackTableView::item:!selected:focus { + /* For outline to be applied, background-color must be set, too ... + Use a value between #0f0f0f and #0a0a0a (alternate-background-color) */ + background-color: #0c0c0c; + outline: 1px solid #fff; + color: #fff; + } + WLibrarySidebar::item:selected:focus { outline: none; } @@ -3181,6 +3195,10 @@ QPlainTextEdit QMenu::item:disabled { border-top: 1px solid #000; border-bottom: 1px solid #333; } + /* use the same color for the vertical separator in Search related menu */ + WSearchRelatedCheckBox { + qproperty-separatorColor: #333; + } #MainMenu QMenu::right-arrow, WTrackMenu::right-arrow, diff --git a/res/skins/Shade/deck_overview.xml b/res/skins/Shade/deck_overview.xml index 5d7ad987fc03..044a455346e8 100644 --- a/res/skins/Shade/deck_overview.xml +++ b/res/skins/Shade/deck_overview.xml @@ -9,7 +9,7 @@ - #191F24 + #00FF00 #EA0000 diff --git a/res/skins/Shade/preview_deck.xml b/res/skins/Shade/preview_deck.xml index aa132122d68a..c260cdc88adc 100644 --- a/res/skins/Shade/preview_deck.xml +++ b/res/skins/Shade/preview_deck.xml @@ -171,7 +171,7 @@ - #191F24 + #00FF00 false diff --git a/res/skins/Shade/sampler.xml b/res/skins/Shade/sampler.xml index c3d1a7fd098a..43ea2371efd6 100644 --- a/res/skins/Shade/sampler.xml +++ b/res/skins/Shade/sampler.xml @@ -177,7 +177,7 @@ - #191F24 + #00FF00 false diff --git a/res/skins/Shade/skin.xml b/res/skins/Shade/skin.xml index 57584106cfdc..a87cae5b918b 100644 --- a/res/skins/Shade/skin.xml +++ b/res/skins/Shade/skin.xml @@ -273,6 +273,17 @@ vertical + + + #191F24 + #55F764 + + #8d98a3 + + + - #55F764 - - #8d98a3 - - Overview1 diff --git a/res/skins/Shade/style.qss b/res/skins/Shade/style.qss index f0a14387f898..626a744c1ddb 100644 --- a/res/skins/Shade/style.qss +++ b/res/skins/Shade/style.qss @@ -523,9 +523,22 @@ WLibrarySidebar::item:selected, WLibrarySidebar::branch:selected, #LibraryBPMButton::item:selected, #LibraryPlayedCheckbox::item:selected { - color: #fff; + /* disabled since it interferes with selection-color + which we use in DefaultDelegate and TableItemDelegate to + blend the highlight color with the played/missing color. + color: #fff; */ + /* This is not required for painting the background, + but it is for applying the focus outline. */ background-color: #656d75; } + /* Highlight currentIndex which can be moved with Ctrl + arrow keys */ + WTrackTableView::item:!selected:focus { + /* For outline to be applied, background-color must be set, too ... + Use a value between #0f0f0f and #1a1a1a (alternate-background-color) */ + background-color: #151515; + outline: 1px solid #fff; + color: #fff; + } WSearchLineEdit::item:checked:!selected { color: #fff; background-color: #32363a; diff --git a/res/skins/Shade/style_dark.qss b/res/skins/Shade/style_dark.qss index 1b152656cd25..7d1b48ae8f0d 100644 --- a/res/skins/Shade/style_dark.qss +++ b/res/skins/Shade/style_dark.qss @@ -186,7 +186,12 @@ WTrackTableView::item:selected, WLibrarySidebar::branch:selected, #LibraryBPMButton::item:selected, #LibraryPlayedCheckbox::item:selected { - color: #000; + /* disabled since it interferes with selection-color + which we use in DefaultDelegate and TableItemDelegate to + blend the highlight color with the played/missing color. + color: #000; */ + /* This is not required for painting the background, + but it is for applying the focus outline. */ selection-color: #000; background-color: #666; } diff --git a/res/skins/Tango/decks/preview_deck.xml b/res/skins/Tango/decks/preview_deck.xml index f31cd98321ff..9d156a3a4290 100644 --- a/res/skins/Tango/decks/preview_deck.xml +++ b/res/skins/Tango/decks/preview_deck.xml @@ -114,7 +114,7 @@ Variables: 1me,34f #151515 - #bababa + #FF4300 #00FF00 diff --git a/res/skins/Tango/fx/parameter_knob.xml b/res/skins/Tango/fx/parameter_knob.xml index 0c5583f9a145..ed9a77e86e7e 100644 --- a/res/skins/Tango/fx/parameter_knob.xml +++ b/res/skins/Tango/fx/parameter_knob.xml @@ -60,6 +60,7 @@ Variables: + center right diff --git a/res/skins/Tango/library.xml b/res/skins/Tango/library.xml index 090bea247281..3add726203bf 100644 --- a/res/skins/Tango/library.xml +++ b/res/skins/Tango/library.xml @@ -118,6 +118,10 @@ Description: #eece33 false 0.2 + + + diff --git a/res/skins/Tango/mic_aux_sampler/sampler.xml b/res/skins/Tango/mic_aux_sampler/sampler.xml index 305776cdbf80..83ff7c842d39 100644 --- a/res/skins/Tango/mic_aux_sampler/sampler.xml +++ b/res/skins/Tango/mic_aux_sampler/sampler.xml @@ -200,7 +200,7 @@ Variables: me,min #151515 - #bababa + #FF4300 false diff --git a/res/skins/Tango/skin.xml b/res/skins/Tango/skin.xml index 052b60090d3a..d2cd9c342c0f 100644 --- a/res/skins/Tango/skin.xml +++ b/res/skins/Tango/skin.xml @@ -182,6 +182,8 @@ #19260B purple green + + #bababa empty pixmap, add to ignore list"; + m_tracksWithoutOverview.insert(res.trackId); + } + m_currentlyLoading.remove(res.trackId); + + emit overviewReady(res.requester, res.trackId, !pixmap.isNull()); +} diff --git a/src/library/overviewcache.h b/src/library/overviewcache.h new file mode 100644 index 000000000000..c95448c7f91b --- /dev/null +++ b/src/library/overviewcache.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include "analyzer/analyzerprogress.h" +#include "preferences/usersettings.h" +#include "track/trackid.h" +#include "util/db/dbconnectionpool.h" +#include "util/singleton.h" +#include "waveform/overviewtype.h" + +class WaveformSignalColors; + +class OverviewCache : public QObject, public Singleton { + Q_OBJECT + public: + void onTrackSummaryChanged(TrackId); + + QPixmap requestCachedOverview( + mixxx::OverviewType type, + TrackId trackId, + const QObject* pRequester, + QSize desiredSize); + QPixmap requestUncachedOverview( + mixxx::OverviewType type, + const WaveformSignalColors& signalColors, + TrackId trackId, + const QObject* pRequester, + QSize desiredSize); + + struct FutureResult { + FutureResult() + : requester(nullptr) { + } + + TrackId trackId; + mixxx::OverviewType type; + QImage image; + QSize resizedToSize; + const QObject* requester; + }; + + public slots: + void onNormalizeOrVisualGainChanged(); + void overviewPrepared(); + void onTrackAnalysisProgress(TrackId trackId, AnalyzerProgress analyzerProgress); + + signals: + void overviewReady( + const QObject* pRequester, + TrackId trackId, + bool pixmapValid); + + void overviewChanged(TrackId); + + protected: + OverviewCache(UserSettingsPointer pConfig, + mixxx::DbConnectionPoolPtr m_pDbConnectionPool); + virtual ~OverviewCache() override = default; + friend class Singleton; + + static FutureResult prepareOverview( + UserSettingsPointer pConfig, + mixxx::DbConnectionPoolPtr pDbConnectionPool, + mixxx::OverviewType type, + const WaveformSignalColors& signalColors, + TrackId trackId, + const QObject* pRequester, + QSize desiredSize); + + private: + UserSettingsPointer m_pConfig; + mixxx::DbConnectionPoolPtr m_pDbConnectionPool; + + QSet m_currentlyLoading; + QSet m_tracksWithoutOverview; + QMultiHash m_cacheKeysByTrackId; + bool m_clearingCache; + bool m_stopClearing; +}; diff --git a/src/library/scanner/libraryscanner.cpp b/src/library/scanner/libraryscanner.cpp index 978a2bea0a24..c3ee7c575aa4 100644 --- a/src/library/scanner/libraryscanner.cpp +++ b/src/library/scanner/libraryscanner.cpp @@ -1,6 +1,7 @@ #include "library/scanner/libraryscanner.h" #include "library/coverartutils.h" +#include "library/library_decl.h" #include "library/queryutil.h" #include "library/scanner/libraryscannerdlg.h" #include "library/scanner/recursivescandirectorytask.h" @@ -99,11 +100,10 @@ LibraryScanner::LibraryScanner( const UserSettingsPointer& pConfig) : m_pDbConnectionPool(std::move(pDbConnectionPool)), m_analysisDao(pConfig), - m_trackDao(m_cueDao, m_playlistDao, - m_analysisDao, m_libraryHashDao, - pConfig), + m_trackDao(m_cueDao, m_playlistDao, m_analysisDao, m_libraryHashDao, pConfig), m_stateSema(1), // only one transaction is possible at a time - m_state(IDLE) { + m_state(IDLE), + m_manualScan(true) { // Move LibraryScanner to its own thread so that our signals/slots will // queue to our event loop. moveToThread(this); @@ -199,12 +199,17 @@ void LibraryScanner::slotStartScan() { changeScannerState(SCANNING); QSet trackLocations = m_trackDao.getAllTrackLocations(); + // Store number of existing tracks so we can calculate the number + // of missing tracks in slotFinishUnhashedScan(). + m_previouslyMissingTracks = m_trackDao.getAllMissingTrackLocations(); + m_numPreviouslyExistingTracks = m_trackDao.getAllExistingTrackLocations().size(); QHash directoryHashes = m_libraryHashDao.getDirectoryHashes(); QRegularExpression extensionFilter(SoundSourceProxy::getSupportedFileNamesRegex()); QRegularExpression coverExtensionFilter = QRegularExpression(CoverArtUtils::supportedCoverArtExtensionsRegex(), QRegularExpression::CaseInsensitiveOption); QStringList directoryBlacklist = ScannerUtil::getDirectoryBlacklist(); + m_numRelocatedTracks = 0; m_scannerGlobal = ScannerGlobalPointer( new ScannerGlobal(trackLocations, directoryHashes, extensionFilter, @@ -220,6 +225,11 @@ void LibraryScanner::slotStartScan() { // verified. m_libraryHashDao.invalidateAllDirectories(); + // Make sure that `directory` in in track_locations table is indeed a + // directory path. This works around / removes residues of a bug where tracks + // are falsely marked missing because `directory` == `location`. + m_trackDao.cleanupTrackLocationsDirectory(); + // Mark all the tracks in the library as needing verification of their // existence. (ie. we want to check they're still on your hard drive where // we think they are) @@ -301,6 +311,7 @@ void LibraryScanner::slotFinishHashedScan() { pWatcher->taskDone(); } +// Quick hack: return number of relocated tracks void LibraryScanner::cleanUpScan() { // At the end of a scan, mark all tracks and directories that weren't // "verified" as "deleted" (as long as the scan wasn't canceled half way @@ -358,9 +369,10 @@ void LibraryScanner::cleanUpScan() { return; } if (!relocatedTracks.isEmpty()) { + m_numRelocatedTracks = relocatedTracks.size(); kLogger.info() << "Found" - << relocatedTracks.size() + << m_numRelocatedTracks << "moved track(s)"; emit tracksRelocated(relocatedTracks); } @@ -385,7 +397,6 @@ void LibraryScanner::cleanUpScan() { } } - // is called when all tasks of the second stage are done (threads are finished) void LibraryScanner::slotFinishUnhashedScan() { kLogger.debug() << "slotFinishUnhashedScan"; @@ -422,27 +433,73 @@ void LibraryScanner::slotFinishUnhashedScan() { kLogger.debug() << "Scan cancelled"; } - // TODO(XXX) doesn't take into account verifyRemainingTracks. - qDebug("Scan took: %s. " - "%d unchanged directories. " - "%d changed/added directories. " - "%d tracks verified from changed/added directories. " - "%d new tracks.", - m_scannerGlobal->timerElapsed().formatNanosWithUnit().toLocal8Bit().constData(), - static_cast(m_scannerGlobal->verifiedDirectories().size()), - m_scannerGlobal->numScannedDirectories(), - static_cast(m_scannerGlobal->verifiedTracks().size()), - static_cast(m_scannerGlobal->addedTracks().size())); + const auto duration = m_scannerGlobal->timerElapsed(); + double seconds = duration.toDoubleSeconds(); + QString durationString; + // Pick a comfortable format for the duration display + if (seconds < 2.0) { // 812 ms + durationString = duration.formatMillisWithUnit(); + } else if (seconds < 60) { // 12 s + durationString = duration.formatSecondsWithUnit(); + } else { // 3:48 + durationString = mixxx::Duration::formatTime(seconds); + } + const int numVerifiedDirs = static_cast(m_scannerGlobal->verifiedDirectories().size()); + const int numScannedDirs = m_scannerGlobal->numScannedDirectories(); + const int numVerifiedTracks = static_cast(m_scannerGlobal->verifiedTracks().size()); + const int numNewTracks = m_scannerGlobal->addedTracks().size() - m_numRelocatedTracks; + + const QSet existingTracks = m_trackDao.getAllExistingTrackLocations(); + int numRediscoveredTracks = 0; + for (const QString& loc : std::as_const(m_previouslyMissingTracks)) { + if (existingTracks.contains(loc)) { + numRediscoveredTracks++; + } + } + const auto missingTracks = m_trackDao.getAllMissingTrackLocations(); + const int numMissingTracks = missingTracks.size(); + int numNewMissingTracks = 0; + for (const QString& loc : std::as_const(missingTracks)) { + if (!m_previouslyMissingTracks.contains(loc)) { + numNewMissingTracks++; + } + } + const int tracksTotal = existingTracks.size(); + + qInfo() << "-------------------------------------------------------"; + qInfo("Library scan finished after %s", durationString.toLocal8Bit().constData()); + qInfo(" %d unchanged directories", numVerifiedDirs); + qInfo(" %d scanned directories", numScannedDirs); + qInfo(" %d tracks verified from changed/added directories", numVerifiedTracks); + qInfo(" %d new tracks", numNewTracks); + qInfo(" %d moved tracks", m_numRelocatedTracks); + qInfo(" %d new missing tracks", numNewMissingTracks); + qInfo(" %d missing tracks total", numMissingTracks); + qInfo(" %d rediscovered tracks", numRediscoveredTracks); + qInfo(" %d tracks total", tracksTotal); + qInfo() << "-------------------------------------------------------"; + + LibraryScanResultSummary result; + result.durationString = durationString; + result.numNewTracks = numNewTracks; + result.numMovedTracks = m_numRelocatedTracks; + result.numNewMissingTracks = numNewMissingTracks; + result.numMissingTracks = numMissingTracks; + result.numRediscoveredTracks = numRediscoveredTracks; + result.tracksTotal = tracksTotal; + result.autoscan = m_manualScan; m_scannerGlobal.clear(); changeScannerState(FINISHED); // now we may accept new scan commands emit scanFinished(); + emit scanSummary(result); } -void LibraryScanner::scan() { +void LibraryScanner::scan(bool autoscan) { if (changeScannerState(STARTING)) { + m_manualScan = autoscan; emit startScan(); } } @@ -566,6 +623,7 @@ void LibraryScanner::slotTrackExists(const QString& trackPath) { } } +// triggered by ScannerTask::addNewTrack / in ImportFilesTask::run() void LibraryScanner::slotAddNewTrack(const QString& trackPath) { //kLogger.debug() << "slotAddNewTrack" << trackPath; ScopedTimer timer(QStringLiteral("LibraryScanner::addNewTrack")); diff --git a/src/library/scanner/libraryscanner.h b/src/library/scanner/libraryscanner.h index 9cda0d72d0cc..b0a8281a3e27 100644 --- a/src/library/scanner/libraryscanner.h +++ b/src/library/scanner/libraryscanner.h @@ -21,6 +21,7 @@ class ScannerTask; class LibraryScannerDlg; class QString; +struct LibraryScanResultSummary; class LibraryScanner : public QThread { FRIEND_TEST(LibraryScannerTest, ScannerRoundtrip); @@ -34,7 +35,10 @@ class LibraryScanner : public QThread { public slots: // Call from any thread to start a scan. Does nothing if a scan is already // in progress. - void scan(); + // The autoscan flag is used for the summary report. Receivers of scanSummary() + // can use this to decide whether to show the summary dialog, for example hide + // it for the automatic scan during startup. + void scan(bool autoscan = false); // Call from any thread to cancel the scan. void slotCancel(); @@ -42,6 +46,7 @@ class LibraryScanner : public QThread { signals: void scanStarted(); void scanFinished(); + void scanSummary(const LibraryScanResultSummary& result); void progressHashing(const QString&); void progressLoading(const QString& path); void progressCoverArt(const QString& file); @@ -118,6 +123,12 @@ class LibraryScanner : public QThread { // this is accessed main and LibraryScanner thread volatile ScannerState m_state; + QSet m_previouslyMissingTracks; + int m_numPreviouslyExistingTracks; + int m_numRelocatedTracks; + QList m_libraryRootDirs; QScopedPointer m_pProgressDlg; + + bool m_manualScan; }; diff --git a/src/library/scanner/scannerglobal.h b/src/library/scanner/scannerglobal.h index e1f4c64203d5..9f22d1f14514 100644 --- a/src/library/scanner/scannerglobal.h +++ b/src/library/scanner/scannerglobal.h @@ -29,7 +29,8 @@ class ScannerGlobal { // Unless marked un-clean, we assume it will finish cleanly. m_scanFinishedCleanly(true), m_shouldCancel(false), - m_numScannedDirectories(0) { + m_numScannedDirectories(0), + m_numRelocatedTracks(0) { } TaskWatcher& getTaskWatcher() { @@ -153,6 +154,13 @@ class ScannerGlobal { m_numScannedDirectories++; } + int numRelocatedTracks() const { + return m_numRelocatedTracks; + } + void addRelocatedTracks(int numTracks) { + m_numRelocatedTracks += numTracks; + } + private: TaskWatcher m_watcher; @@ -197,6 +205,7 @@ class ScannerGlobal { // Stats tracking. PerformanceTimer m_timer; int m_numScannedDirectories; + int m_numRelocatedTracks; }; typedef QSharedPointer ScannerGlobalPointer; diff --git a/src/library/searchquery.cpp b/src/library/searchquery.cpp index 2cf21488faad..55ff34db3c44 100644 --- a/src/library/searchquery.cpp +++ b/src/library/searchquery.cpp @@ -550,6 +550,11 @@ BpmFilterNode::BpmFilterNode(QString& argument, bool fuzzy, bool negate) return; } + if (argument == QStringLiteral("locked")) { + m_matchMode = MatchMode::Locked; + return; + } + QRegularExpressionMatch opMatch = kNumericOperatorRegex.match(argument); if (opMatch.hasMatch()) { if (fuzzy) { @@ -688,6 +693,10 @@ BpmFilterNode::BpmFilterNode(QString& argument, bool fuzzy, bool negate) } bool BpmFilterNode::match(const TrackPointer& pTrack) const { + if (m_matchMode == MatchMode::Locked) { + return pTrack->isBpmLocked(); + } + double value = pTrack->getBpm(); switch (m_matchMode) { @@ -728,8 +737,11 @@ bool BpmFilterNode::match(const TrackPointer& pTrack) const { QString BpmFilterNode::toSql() const { switch (m_matchMode) { + case MatchMode::Locked: { + return QStringLiteral("%1 IS 1").arg(LIBRARYTABLE_BPM_LOCK); + } case MatchMode::Null: { - return QString("bpm IS 0"); + return QStringLiteral("bpm IS 0"); } case MatchMode::Explicit: { return QStringLiteral("bpm >= %1 AND bpm < %2") @@ -756,10 +768,10 @@ QString BpmFilterNode::toSql() const { return concatSqlClauses(searchClauses, "OR"); } case MatchMode::Operator: { - return QString("bpm %1 %2").arg(m_operator, QString::number(m_bpm)); + return QStringLiteral("bpm %1 %2").arg(m_operator, QString::number(m_bpm)); } default: // MatchMode::Invalid - return QString("bpm IS NULL"); + return QStringLiteral("bpm IS NULL"); } } diff --git a/src/library/searchquery.h b/src/library/searchquery.h index b6a553055727..ec7c9701f953 100644 --- a/src/library/searchquery.h +++ b/src/library/searchquery.h @@ -205,6 +205,7 @@ class BpmFilterNode : public QueryNode { HalveDouble, // bpm:120 HalveDoubleStrict, // bpm:120.0 Operator, // bpm:<=120 + Locked, // bpm:locked }; // Allows WSearchRelatedTracksMenu to construct the QAction title diff --git a/src/library/tabledelegates/checkboxdelegate.cpp b/src/library/tabledelegates/checkboxdelegate.cpp index ca6889321fac..70f6492271f1 100644 --- a/src/library/tabledelegates/checkboxdelegate.cpp +++ b/src/library/tabledelegates/checkboxdelegate.cpp @@ -60,16 +60,13 @@ void CheckboxDelegate::paintItem(QPainter* painter, // BaseTrackTableModel::data(). // Enforce it with an explicit stylesheet. Note: the stylesheet persists so // we need to reset it to normal/highlighted. + // By now, we have already changed the palette's highlight color in + // TableItemDelegate::paint(), so we can pick that here. QColor textColor; if (option.state & QStyle::State_Selected) { - textColor = option.palette.color(QPalette::Normal, QPalette::HighlightedText); + textColor = option.palette.highlightedText().color(); } else { - auto colorData = index.data(Qt::ForegroundRole); - if (colorData.canConvert()) { - textColor = colorData.value(); - } else { - textColor = option.palette.color(QPalette::Normal, QPalette::Text); - } + textColor = option.palette.text().color(); } if (textColor.isValid() && textColor != m_cachedTextColor) { diff --git a/src/library/tabledelegates/defaultdelegate.cpp b/src/library/tabledelegates/defaultdelegate.cpp new file mode 100644 index 000000000000..070e359bf0d1 --- /dev/null +++ b/src/library/tabledelegates/defaultdelegate.cpp @@ -0,0 +1,46 @@ +#include "library/tabledelegates/defaultdelegate.h" + +#include + +#include "moc_defaultdelegate.cpp" + +DefaultDelegate::DefaultDelegate(QTableView* pTableView) + : QStyledItemDelegate(pTableView) { +} + +void DefaultDelegate::paint( + QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index) const { + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + + if (opt.state & QStyle::State_Selected) { + setHighlightedTextColor(opt, index); + } + // TODO Guarantee font/bg contrast with ALL track colors + + QStyledItemDelegate::paint(painter, opt, index); +} + +void DefaultDelegate::setHighlightedTextColor( + QStyleOptionViewItem& option, + const QModelIndex& index) const { + // Get the palette's selected text color + QColor hlColor = option.palette.highlightedText().color(); + // Get the 'played' or 'missing' color from the model. + auto colorData = index.data(Qt::ForegroundRole); + if (colorData.canConvert()) { + const QColor fgColor = colorData.value(); + if (fgColor == hlColor) { + return; + } + // Blend the colors 50/50 + hlColor = QColor( + static_cast((fgColor.red() + hlColor.red()) / 2), + static_cast((fgColor.green() + hlColor.green()) / 2), + static_cast((fgColor.blue() + hlColor.blue()) / 2)); + option.palette.setColor(QPalette::Normal, QPalette::HighlightedText, hlColor); + option.palette.setColor(QPalette::Inactive, QPalette::HighlightedText, hlColor); + } +} diff --git a/src/library/tabledelegates/defaultdelegate.h b/src/library/tabledelegates/defaultdelegate.h new file mode 100644 index 000000000000..4f6fc85437c3 --- /dev/null +++ b/src/library/tabledelegates/defaultdelegate.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +class DefaultDelegate : public QStyledItemDelegate { + Q_OBJECT + public: + explicit DefaultDelegate(QTableView* pTableView); + ~DefaultDelegate() override = default; + + void paint( + QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index) const override; + + void setHighlightedTextColor( + QStyleOptionViewItem& option, + const QModelIndex& index) const; +}; diff --git a/src/library/tabledelegates/overviewdelegate.cpp b/src/library/tabledelegates/overviewdelegate.cpp new file mode 100644 index 000000000000..ec6f93fb6892 --- /dev/null +++ b/src/library/tabledelegates/overviewdelegate.cpp @@ -0,0 +1,172 @@ +#include "library/tabledelegates/overviewdelegate.h" + +#include + +#include "control/controlproxy.h" +#include "library/dao/trackdao.h" +#include "library/overviewcache.h" +#include "library/trackmodel.h" +#include "moc_overviewdelegate.cpp" +#include "util/logger.h" +#include "util/make_const_iterator.h" +#include "widget/wlibrary.h" + +namespace { + +const mixxx::Logger kLogger("OverviewDelegate"); + +inline TrackModel* asTrackModel( + QTableView* pTableView) { + auto* pTrackModel = + dynamic_cast(pTableView->model()); + DEBUG_ASSERT(pTrackModel); + return pTrackModel; +} + +inline WLibrary* findLibraryWidgetParent(QWidget* pWidget) { + while (pWidget) { + WLibrary* pLibrary = qobject_cast(pWidget); + if (pLibrary) { + return pLibrary; + } + + pWidget = pWidget->parentWidget(); + } + + return nullptr; +} + +} // anonymous namespace + +OverviewDelegate::OverviewDelegate(QTableView* pTableView) + : TableItemDelegate(pTableView), + m_pTrackModel(asTrackModel(pTableView)), + m_pCache(OverviewCache::instance()), + m_type(mixxx::OverviewType::RGB), + m_inhibitLazyLoading(false) { + WLibrary* pLibrary = findLibraryWidgetParent(pTableView); + if (pLibrary) { + m_signalColors = pLibrary->getOverviewSignalColors(); + } + + connect(m_pCache, + &OverviewCache::overviewReady, + this, + &OverviewDelegate::slotOverviewReady); + + connect(m_pCache, + &OverviewCache::overviewChanged, + this, + &OverviewDelegate::slotOverviewChanged); + + m_pTypeControl = make_parented( + QStringLiteral("[Waveform]"), + QStringLiteral("WaveformOverviewType"), + this); + m_pTypeControl->connectValueChanged(this, &OverviewDelegate::slotTypeControlChanged); + slotTypeControlChanged(m_pTypeControl->get()); +} + +void OverviewDelegate::slotTypeControlChanged(double v) { + // Assert that v is in enum range to prevent UB. + DEBUG_ASSERT(v >= 0 && v < QMetaEnum::fromType().keyCount()); + mixxx::OverviewType type = static_cast(static_cast(v)); + if (type == m_type) { + return; + } + + m_type = type; + // Instantly update visible overviews so we get a live preview + // when changing the type in the preferences. + m_pTableView->update(); +} + +void OverviewDelegate::emitOverviewRowsChanged(QSet&& trackIds) { + if (trackIds.isEmpty()) { + return; + } + + QList rows; + for (auto id : trackIds) { + const QList idRows = m_pTrackModel->getTrackRows(id); + rows.append(idRows); + } + if (rows.isEmpty()) { + // For some reason this can happen during startup + return; + } + // Sort in ascending order... + std::sort(rows.begin(), rows.end()); + // ...and then deduplicate... + constErase( + &rows, + make_const_iterator( + rows, std::unique(rows.begin(), rows.end())), + rows.constEnd()); + // ...before emitting the signal. + DEBUG_ASSERT(!rows.isEmpty()); + emit overviewRowsChanged(std::move(rows)); +} + +void OverviewDelegate::slotInhibitLazyLoading(bool inhibitLazyLoading) { + m_inhibitLazyLoading = inhibitLazyLoading; + if (m_inhibitLazyLoading || m_cacheMissIds.isEmpty()) { + return; + } + // If we can request non-cache covers now, request updates + // for all rows that were cache misses since the last time. + // Reset the member variable before mutating the aggregated + // rows list (-> implicit sharing) and emitting a signal that + // in turn may trigger new signals for CoverArtDelegate! + QSet staleIds = std::move(m_cacheMissIds); + DEBUG_ASSERT(m_cacheMissIds.isEmpty()); + emitOverviewRowsChanged(std::move(staleIds)); +} + +/// Maybe request repaint via dataChanged() by BaseTrackTableModel +void OverviewDelegate::slotOverviewReady(const QObject* pRequester, + const TrackId trackId, + bool pixmapValid) { + // kLogger.info() << "slotOverviewReady()" << trackId << "pixmap valid:" << pixmapValid; + + if (pRequester == this && pixmapValid) { + emitOverviewRowsChanged(QSet{trackId}); + } +} + +/// Maybe request repaint via dataChanged() by BaseTrackTableModel +void OverviewDelegate::slotOverviewChanged(const TrackId trackId) { + // kLogger.info() << "slotOverviewChanged()" << trackId; + emitOverviewRowsChanged(QSet{trackId}); +} + +void OverviewDelegate::paintItem(QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index) const { + const TrackId trackId(m_pTrackModel->getTrackId(index)); + const double scaleFactor = m_pTableView->devicePixelRatioF(); + QPixmap pixmap = m_pCache->requestCachedOverview(m_type, + trackId, + this, + option.rect.size() * scaleFactor); + if (pixmap.isNull()) { + // Cache miss + if (m_inhibitLazyLoading) { + // We are requesting cache-only covers and got a cache + // miss. Record this row so that when we switch to requesting + // non-cache we can request an update. + m_cacheMissIds.insert(trackId); + } else { + pixmap = m_pCache->requestUncachedOverview(m_type, + m_signalColors, + trackId, + this, + option.rect.size() * scaleFactor); + } + paintItemBackground(painter, option, index); + } else { + // We have a cached pixmap, paint it + pixmap.setDevicePixelRatio(scaleFactor); + painter->drawPixmap(option.rect, pixmap); + } +} diff --git a/src/library/tabledelegates/overviewdelegate.h b/src/library/tabledelegates/overviewdelegate.h new file mode 100644 index 000000000000..219ccba1d75d --- /dev/null +++ b/src/library/tabledelegates/overviewdelegate.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include + +#include "library/tabledelegates/tableitemdelegate.h" +#include "track/trackid.h" +#include "util/parented_ptr.h" +#include "waveform/overviewtype.h" +#include "waveform/renderers/waveformsignalcolors.h" + +class ControlProxy; +class OverviewCache; +class TrackModel; + +class OverviewDelegate : public TableItemDelegate { + Q_OBJECT + + public: + explicit OverviewDelegate(QTableView* parent); + ~OverviewDelegate() override = default; + + void paintItem(QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index) const final; + + signals: + void overviewRowsChanged(const QList& rows); + + public slots: + // Advise the delegate to temporarily inhibit lazy loading + // of overview images and to only display those images + // that have already been cached. + // + // It is useful to handle cases when the user scroll down + // very fast or when they hold an arrow key. In this case + // it is NOT desirable to start multiple expensive file + // system operations for loading and scaling images that + // are not even displayed after scrolling beyond them. + void slotInhibitLazyLoading(bool inhibitLazyLoading); + + private slots: + void slotTypeControlChanged(double v); + void slotOverviewReady(const QObject* pRequester, + const TrackId trackId, + bool pixmapValid); + void slotOverviewChanged(const TrackId trackId); + + protected: + TrackModel* const m_pTrackModel; + + private: + void emitOverviewRowsChanged(QSet&& trackIds); + OverviewCache* const m_pCache; + mixxx::OverviewType m_type; + bool m_inhibitLazyLoading; + parented_ptr m_pTypeControl; + WaveformSignalColors m_signalColors; + + mutable QSet m_cacheMissIds; +}; diff --git a/src/library/tabledelegates/stardelegate.cpp b/src/library/tabledelegates/stardelegate.cpp index 62743016af85..6d9f5e64b248 100644 --- a/src/library/tabledelegates/stardelegate.cpp +++ b/src/library/tabledelegates/stardelegate.cpp @@ -43,6 +43,13 @@ QWidget* StarDelegate::createEditor(QWidget* parent, QStyleOptionViewItem newOption = option; initStyleOption(&newOption, index); + // Set the color for the star polygons. + // Only remaining very minor issue: if the editor is active while the played + // state changes, the polygon color will not be updated (though that's a + // confusing situation anyway since changed index (row) data will trigger a + // paint event which resets the pending (unsaved) rating anyway. + setHighlightedTextColor(newOption, index); + StarEditor* editor = new StarEditor(parent, m_pTableView, index, newOption, m_focusBorderColor); connect(editor, diff --git a/src/library/tabledelegates/stareditor.cpp b/src/library/tabledelegates/stareditor.cpp index a4e1ba78a6c0..e3ce0c65198e 100644 --- a/src/library/tabledelegates/stareditor.cpp +++ b/src/library/tabledelegates/stareditor.cpp @@ -73,20 +73,10 @@ void StarEditor::paintEvent(QPaintEvent*) { style->drawControl(QStyle::CE_ItemViewItem, &m_styleOption, &painter, m_pTableView); } - // Set the palette appropriately based on whether the row is selected or - // not. We also have to check if it is inactive or not and use the - // appropriate ColorGroup. - QPalette::ColorGroup cg = m_styleOption.state & QStyle::State_Enabled - ? QPalette::Normal - : QPalette::Disabled; - if (cg == QPalette::Normal && !(m_styleOption.state & QStyle::State_Active)) { - cg = QPalette::Inactive; - } - if (m_styleOption.state & QStyle::State_Selected) { - painter.setBrush(m_styleOption.palette.color(cg, QPalette::HighlightedText)); + painter.setBrush(m_styleOption.palette.highlightedText().color()); } else { - painter.setBrush(m_styleOption.palette.color(cg, QPalette::Text)); + painter.setBrush(m_styleOption.palette.text().color()); } m_starRating.paint(&painter, m_styleOption.rect); diff --git a/src/library/tabledelegates/tableitemdelegate.cpp b/src/library/tabledelegates/tableitemdelegate.cpp index 114c4bd6b15f..a92512c2c42c 100644 --- a/src/library/tabledelegates/tableitemdelegate.cpp +++ b/src/library/tabledelegates/tableitemdelegate.cpp @@ -7,7 +7,7 @@ #include "widget/wtracktableview.h" TableItemDelegate::TableItemDelegate(QTableView* pTableView) - : QStyledItemDelegate(pTableView), + : DefaultDelegate(pTableView), m_pTableView(pTableView) { DEBUG_ASSERT(m_pTableView); auto* pTrackTableView = qobject_cast(m_pTableView); @@ -36,45 +36,40 @@ void TableItemDelegate::paint( PainterScope painterScope(painter); painter->setClipRect(option.rect); - // Set the palette appropriately based on whether the row is selected or - // not. We also have to check if it is inactive or not and use the - // appropriate ColorGroup. - QPalette::ColorGroup cg = QPalette::Disabled; - if ((option.state & QStyle::State_Enabled) && - (option.state & QStyle::State_Active)) { - cg = QPalette::Normal; + // Clone the const option so we can change palette colors. + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + + if (opt.state & QStyle::State_Selected) { + setHighlightedTextColor(opt, index); } - if (option.state & QStyle::State_Selected) { - painter->setBrush(option.palette.color(cg, QPalette::HighlightedText)); + QBrush brush; + if (opt.state & QStyle::State_Selected) { + brush = opt.palette.highlightedText(); } else { - // This gets the custom 'missing' or played text color from BaseTrackTableModel - // depending on check state of the (hidden) 'missing' (fs_deleted) - // or 'played' columns. - // Note that we need to do this again in BPMDelegate which uses the - // style of the TableView. - auto customColorData = index.data(Qt::ForegroundRole); - if (customColorData.canConvert()) { - QColor customColor = customColorData.value(); - // for the star rating polygons - painter->setBrush(customColor); - // for the 'location' text - painter->setPen(customColor); - } else { - painter->setBrush(option.palette.color(cg, QPalette::Text)); - } + brush = opt.palette.text(); } + // Brush color for the star rating polygons: + painter->setBrush(brush); + // Pen for the 'location' text + // Note: seems not to be required anymore (Qt 6.2.3) +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + painter->setPen(brush.color()); +#endif QStyle* style = m_pTableView->style(); if (style) { style->drawControl( QStyle::CE_ItemViewItem, + // Use the original option here to not screw up + // the Key and Preview delegates. &option, painter, m_pTableView); } - paintItem(painter, option, index); + paintItem(painter, opt, index); } int TableItemDelegate::columnWidth(const QModelIndex &index) const { @@ -124,5 +119,8 @@ void TableItemDelegate::paintItem( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { - QStyledItemDelegate::paint(painter, option, index); + // We don't want to call DefaultDelegate::paint() because that would again + // try to set the 'selected' color (which we already did in paint() above). + // Relevant only for derived classes that don't implement paintItem() + QStyledItemDelegate::paint(painter, option, index); // clazy:exclude=skipped-base-method } diff --git a/src/library/tabledelegates/tableitemdelegate.h b/src/library/tabledelegates/tableitemdelegate.h index 36973761c7c5..78311e239bad 100644 --- a/src/library/tabledelegates/tableitemdelegate.h +++ b/src/library/tabledelegates/tableitemdelegate.h @@ -1,14 +1,11 @@ #pragma once -#include +#include "library/tabledelegates/defaultdelegate.h" -class QTableView; - -class TableItemDelegate : public QStyledItemDelegate { +class TableItemDelegate : public DefaultDelegate { Q_OBJECT public: - explicit TableItemDelegate( - QTableView* pTableView); + explicit TableItemDelegate(QTableView* pTableView); ~TableItemDelegate() override = default; void paint( @@ -36,6 +33,6 @@ class TableItemDelegate : public QStyledItemDelegate { // Having this here avoids including QTableView there. int columnWidth(const QModelIndex &index) const; - QColor m_focusBorderColor; QTableView* m_pTableView; + QColor m_focusBorderColor; }; diff --git a/src/library/trackcollection.cpp b/src/library/trackcollection.cpp index 5f9abb156e7d..55a85efe6f13 100644 --- a/src/library/trackcollection.cpp +++ b/src/library/trackcollection.cpp @@ -23,11 +23,6 @@ TrackCollection::TrackCollection( m_trackDao(m_cueDao, m_playlistDao, m_analysisDao, m_libraryHashDao, pConfig) { // Forward signals from TrackDAO - connect(&m_trackDao, - &TrackDAO::trackClean, - this, - &TrackCollection::trackClean, - /*signal-to-signal*/ Qt::DirectConnection); connect(&m_trackDao, &TrackDAO::trackDirty, this, diff --git a/src/library/trackcollection.h b/src/library/trackcollection.h index 00ee7463963a..94cebb99cd63 100644 --- a/src/library/trackcollection.h +++ b/src/library/trackcollection.h @@ -90,7 +90,6 @@ class TrackCollection : public QObject, void scanTrackAdded(TrackPointer pTrack); // Forwarded signals from TrackDAO - void trackClean(TrackId trackId); void trackDirty(TrackId trackId); void tracksAdded(const QSet& trackIds); void tracksChanged(const QSet& trackIds); diff --git a/src/library/trackcollectionmanager.cpp b/src/library/trackcollectionmanager.cpp index 397b87bac746..9fbac0527cb6 100644 --- a/src/library/trackcollectionmanager.cpp +++ b/src/library/trackcollectionmanager.cpp @@ -3,6 +3,7 @@ #include #include "library/externaltrackcollection.h" +#include "library/library_decl.h" #include "library/library_prefs.h" #include "library/scanner/libraryscanner.h" #include "library/trackcollection.h" @@ -84,6 +85,11 @@ TrackCollectionManager::TrackCollectionManager( this, &TrackCollectionManager::libraryScanFinished, /*signal-to-signal*/ Qt::DirectConnection); + connect(m_pScanner.get(), + &LibraryScanner::scanSummary, + this, + &TrackCollectionManager::libraryScanSummary, + /*signal-to-signal*/ Qt::DirectConnection); // Handle signals // NOTE: The receiver's thread context `this` is required to enforce @@ -166,6 +172,13 @@ TrackCollectionManager::~TrackCollectionManager() { GlobalTrackCache::destroyInstance(); } +void TrackCollectionManager::startLibraryAutoScan() { + VERIFY_OR_DEBUG_ASSERT(m_pScanner) { + return; + } + m_pScanner->scan(true); +} + void TrackCollectionManager::startLibraryScan() { VERIFY_OR_DEBUG_ASSERT(m_pScanner) { return; diff --git a/src/library/trackcollectionmanager.h b/src/library/trackcollectionmanager.h index edf48753e052..8f88f571f78f 100644 --- a/src/library/trackcollectionmanager.h +++ b/src/library/trackcollectionmanager.h @@ -16,6 +16,7 @@ class LibraryScanner; class TrackCollection; class ExternalTrackCollection; class RelocatedTrack; +struct LibraryScanResultSummary; // Manages Mixxx's internal database of tracks as well as external track collections. // @@ -91,10 +92,13 @@ class TrackCollectionManager: public QObject, Failed, }; SaveTrackResult saveTrack(const TrackPointer& pTrack) const; + // Same as startLibraryScan() but don't emit the scan summary. + void startLibraryAutoScan(); signals: void libraryScanStarted(); void libraryScanFinished(); + void libraryScanSummary(const LibraryScanResultSummary& result); public slots: void startLibraryScan(); diff --git a/src/library/trackset/crate/cratefeature.cpp b/src/library/trackset/crate/cratefeature.cpp index 7b60150c110f..a4d5ad9a48c7 100644 --- a/src/library/trackset/crate/cratefeature.cpp +++ b/src/library/trackset/crate/cratefeature.cpp @@ -128,12 +128,12 @@ void CrateFeature::initActions() { this, &CrateFeature::slotExportTrackFiles); #ifdef __ENGINEPRIME__ - m_pExportAllCratesAction = make_parented(tr("Export to Engine Prime"), this); + m_pExportAllCratesAction = make_parented(tr("Export to Engine DJ"), this); connect(m_pExportAllCratesAction.get(), &QAction::triggered, this, &CrateFeature::exportAllCrates); - m_pExportCrateAction = make_parented(tr("Export to Engine Prime"), this); + m_pExportCrateAction = make_parented(tr("Export to Engine DJ"), this); connect(m_pExportCrateAction.get(), &QAction::triggered, this, diff --git a/src/library/trackset/playlistfeature.cpp b/src/library/trackset/playlistfeature.cpp index c5f0258ec7dd..d1cacdd67eb9 100644 --- a/src/library/trackset/playlistfeature.cpp +++ b/src/library/trackset/playlistfeature.cpp @@ -308,8 +308,7 @@ void PlaylistFeature::slotPlaylistTableChanged(int playlistId) { // Else (root item was selected or for some reason no index could be created) // there's nothing to do: either no child was selected earlier, or the root // was selected and will remain selected after the child model was rebuilt. - activateChild(newIndex); - emit featureSelect(this, newIndex); + selectAndActivate(newIndex); } } diff --git a/src/library/trackset/setlogfeature.cpp b/src/library/trackset/setlogfeature.cpp index 093ff3703eb3..1f3582ad8f63 100644 --- a/src/library/trackset/setlogfeature.cpp +++ b/src/library/trackset/setlogfeature.cpp @@ -712,13 +712,8 @@ void SetlogFeature::slotPlaylistTableChanged(int playlistId) { newIndex = m_pSidebarModel->index(selectedYearIndexRow - 1, 0); } } - if (newIndex.isValid()) { - emit featureSelect(this, newIndex); - activateChild(newIndex); - } else if (rootWasSelected) { - // calling featureSelect with invalid index will select the root item - emit featureSelect(this, newIndex); - activate(); // to reload the new current playlist + if (newIndex.isValid() || rootWasSelected) { + selectAndActivate(newIndex); } } diff --git a/src/library/treeitemmodel.cpp b/src/library/treeitemmodel.cpp index d2b06230ebd8..4a088aba3e94 100644 --- a/src/library/treeitemmodel.cpp +++ b/src/library/treeitemmodel.cpp @@ -143,13 +143,13 @@ int TreeItemModel::rowCount(const QModelIndex& parent) const { return 0; } - TreeItem* parentItem; + TreeItem* pParentItem; if (parent.isValid()) { - parentItem = static_cast(parent.internalPointer()); + pParentItem = static_cast(parent.internalPointer()); } else { - parentItem = getRootItem(); + pParentItem = getRootItem(); } - return parentItem->childRows(); + return pParentItem->childRows(); } // Populates the model and notifies the view. diff --git a/src/mixxxmainwindow.cpp b/src/mixxxmainwindow.cpp index 0f08dcb964f9..4cda0aa7a57d 100644 --- a/src/mixxxmainwindow.cpp +++ b/src/mixxxmainwindow.cpp @@ -34,10 +34,12 @@ #endif #include "control/controlindicatortimer.h" #include "library/library.h" +#include "library/library_decl.h" #include "library/library_prefs.h" #ifdef __ENGINEPRIME__ #include "library/export/libraryexporter.h" #endif +#include "library/overviewcache.h" #include "library/trackcollectionmanager.h" #include "mixer/playerinfo.h" #include "mixer/playermanager.h" @@ -120,6 +122,12 @@ MixxxMainWindow::MixxxMainWindow(std::shared_ptr pCoreServi CmdlineArgs::Instance().getStartInFullscreen() || fullscreenPref); } #endif // __LINUX__ + + connect(m_pCoreServices.get(), + &mixxx::CoreServices::libraryScanSummary, + this, + &MixxxMainWindow::slotLibraryScanSummaryDlg); + createMenuBar(); m_pMenuBar->hide(); @@ -275,6 +283,18 @@ void MixxxMainWindow::initialize() { WaveformWidgetFactory::createInstance(); // takes a long time WaveformWidgetFactory::instance()->setConfig(m_pCoreServices->getSettings()); WaveformWidgetFactory::instance()->startVSync(m_pGuiTick, m_pVisualsManager, false); + // Connect OverviewCache so we can clear and re-render overviews in the library + // when "OverviewNormalized" or "VisualGain_0" (all) have been changed in the + // preferences. + auto* pOverviewCache = OverviewCache::instance(); + connect(WaveformWidgetFactory::instance(), + &WaveformWidgetFactory::visualGainChanged, + pOverviewCache, + &OverviewCache::onNormalizeOrVisualGainChanged); + connect(WaveformWidgetFactory::instance(), + &WaveformWidgetFactory::overviewNormalizeChanged, + pOverviewCache, + &OverviewCache::onNormalizeOrVisualGainChanged); connect(this, &MixxxMainWindow::skinLoaded, @@ -740,6 +760,7 @@ QDialog::DialogCode MixxxMainWindow::noOutputDlg(bool* continueClicked) { // This way of opening the dialog allows us to use it synchronously m_pPrefDlg->setWindowModality(Qt::ApplicationModal); + m_pPrefDlg->showSoundHardwarePage(mixxx::preferences::SoundHardwareTab::Output); m_pPrefDlg->exec(); if (m_pPrefDlg->result() == QDialog::Accepted) { return QDialog::Accepted; @@ -935,6 +956,16 @@ void MixxxMainWindow::connectMenuBar() { } if (m_pCoreServices->getLibrary()) { + connect(m_pMenuBar, + &WMainMenuBar::searchInCurrentView, + m_pCoreServices->getLibrary().get(), + &Library::slotSearchInCurrentView, + Qt::UniqueConnection); + connect(m_pMenuBar, + &WMainMenuBar::searchInAllTracks, + m_pCoreServices->getLibrary().get(), + &Library::slotSearchInAllTracks, + Qt::UniqueConnection); connect(m_pMenuBar, &WMainMenuBar::createCrate, m_pCoreServices->getLibrary().get(), @@ -1084,7 +1115,7 @@ void MixxxMainWindow::slotNoVinylControlInputConfigured() { QMessageBox::Cancel); if (btn == QMessageBox::Ok) { m_pPrefDlg->show(); - m_pPrefDlg->showSoundHardwarePage(); + m_pPrefDlg->showSoundHardwarePage(mixxx::preferences::SoundHardwareTab::Input); } } @@ -1098,7 +1129,7 @@ void MixxxMainWindow::slotNoDeckPassthroughInputConfigured() { QMessageBox::Cancel); if (btn == QMessageBox::Ok) { m_pPrefDlg->show(); - m_pPrefDlg->showSoundHardwarePage(); + m_pPrefDlg->showSoundHardwarePage(mixxx::preferences::SoundHardwareTab::Input); } } @@ -1112,7 +1143,7 @@ void MixxxMainWindow::slotNoMicrophoneInputConfigured() { QMessageBox::Cancel); if (btn == QMessageBox::Ok) { m_pPrefDlg->show(); - m_pPrefDlg->showSoundHardwarePage(); + m_pPrefDlg->showSoundHardwarePage(mixxx::preferences::SoundHardwareTab::Input); } } @@ -1126,7 +1157,7 @@ void MixxxMainWindow::slotNoAuxiliaryInputConfigured() { QMessageBox::Cancel); if (btn == QMessageBox::Ok) { m_pPrefDlg->show(); - m_pPrefDlg->showSoundHardwarePage(); + m_pPrefDlg->showSoundHardwarePage(mixxx::preferences::SoundHardwareTab::Input); } } @@ -1135,6 +1166,53 @@ void MixxxMainWindow::slotHelpAbout() { about->show(); } +void MixxxMainWindow::slotLibraryScanSummaryDlg(const LibraryScanResultSummary& result) { + // Don't show the report dialog when the scan is run during startup and no + // noteworthy changes have been detected. + if (result.autoscan && + result.numNewTracks == 0 && + result.numNewMissingTracks == 0 && + result.numRediscoveredTracks == 0) { + return; + } + + QString summary = + tr("Scan took %1").arg(result.durationString) + QStringLiteral("

      "); + if (result.numNewTracks == 0 && result.numMovedTracks == 0 && result.numNewMissingTracks == 0) { + summary += tr("No changes detected.") + + QStringLiteral("
      ") + + tr("%1 tracks in total").arg(QString::number(result.tracksTotal)) + + QStringLiteral(""); + } else { + if (result.numNewTracks != 0) { + summary += tr("%1 new tracks found").arg(QString::number(result.numNewTracks)) + + QStringLiteral("
      "); + } + if (result.numMovedTracks != 0) { + summary += tr("%1 moved tracks detected").arg(QString::number(result.numMovedTracks)) + + QStringLiteral("
      "); + } + if (result.numNewMissingTracks != 0) { + summary += tr("%1 tracks are missing (%2 total)") + .arg(QString::number(result.numNewMissingTracks), + result.numMissingTracks); + } + if (result.numRediscoveredTracks != 0) { + summary += QStringLiteral("
      ") + + tr("%1 tracks have been rediscovered") + .arg(QString::number(result.numRediscoveredTracks)); + } + summary += QStringLiteral("

      ") + + tr("%1 tracks in total").arg(QString::number(result.tracksTotal)) + + QStringLiteral(""); + } + QMessageBox* pMsg = new QMessageBox(); + pMsg->setTextFormat(Qt::RichText); // required to get bold text with tags + pMsg->setWindowTitle(tr("Library scan finished")); + pMsg->setText(summary); + pMsg->show(); +} + void MixxxMainWindow::slotShowKeywheel(bool toggle) { if (!m_pKeywheel) { m_pKeywheel = make_parented(this, m_pCoreServices->getSettings()); diff --git a/src/mixxxmainwindow.h b/src/mixxxmainwindow.h index fd7f1cbb38ec..da66b1e57e58 100644 --- a/src/mixxxmainwindow.h +++ b/src/mixxxmainwindow.h @@ -17,6 +17,7 @@ class GuiTick; class LaunchImage; class VisualsManager; class WMainMenuBar; +struct LibraryScanResultSummary; namespace mixxx { @@ -63,7 +64,9 @@ class MixxxMainWindow : public QMainWindow { void slotOptionsPreferences(); /// show the about dialog void slotHelpAbout(); - // show keywheel + /// show popup with library scan results + void slotLibraryScanSummaryDlg(const LibraryScanResultSummary& result); + /// show keywheel void slotShowKeywheel(bool toggle); /// toggle full screen mode void slotViewFullScreen(bool toggle); diff --git a/src/preferences/constants.h b/src/preferences/constants.h index 389e9f1df46b..d3eae052a3fd 100644 --- a/src/preferences/constants.h +++ b/src/preferences/constants.h @@ -39,6 +39,12 @@ enum class MultiSamplingMode { }; Q_ENUM_NS(MultiSamplingMode); +enum class SoundHardwareTab { + Output, + Input +}; +Q_ENUM_NS(SoundHardwareTab); + } // namespace constants } // namespace preferences } // namespace mixxx diff --git a/src/preferences/dialog/dlgpreferences.cpp b/src/preferences/dialog/dlgpreferences.cpp index 47b96b9fa06d..c491941a62a4 100644 --- a/src/preferences/dialog/dlgpreferences.cpp +++ b/src/preferences/dialog/dlgpreferences.cpp @@ -97,9 +97,10 @@ DlgPreferences::DlgPreferences( m_iconsPath.setPath(":/images/preferences/dark/"); } - // Construct widgets for use in tabs. + // Construct page widgets and associated sidebar items + m_pSoundDlg = std::make_unique(this, pSoundManager, m_pConfig); m_soundPage = PreferencesPage( - new DlgPrefSound(this, pSoundManager, m_pConfig), + m_pSoundDlg.get(), new QTreeWidgetItem(contentsTreeWidget, QTreeWidgetItem::Type)); addPageWidget(m_soundPage, tr("Sound Hardware"), @@ -301,9 +302,13 @@ void DlgPreferences::changePage(QTreeWidgetItem* pCurrent, QTreeWidgetItem* pPre } } -void DlgPreferences::showSoundHardwarePage() { +void DlgPreferences::showSoundHardwarePage( + std::optional tab) { switchToPage(m_soundPage.pTreeItem->text(0), m_soundPage.pDlg); contentsTreeWidget->setCurrentItem(m_soundPage.pTreeItem); + if (tab.has_value()) { + m_pSoundDlg->selectIOTab(*tab); + } } bool DlgPreferences::eventFilter(QObject* o, QEvent* e) { diff --git a/src/preferences/dialog/dlgpreferences.h b/src/preferences/dialog/dlgpreferences.h index ad5f2185662a..3fe85c1d2d6b 100644 --- a/src/preferences/dialog/dlgpreferences.h +++ b/src/preferences/dialog/dlgpreferences.h @@ -14,12 +14,13 @@ #include "preferences/settingsmanager.h" #include "preferences/usersettings.h" -class SoundManager; class ControllerManager; +class DlgPrefControllers; +class DlgPrefSound; class EffectsManager; class Library; +class SoundManager; class VinylControlManager; -class DlgPrefControllers; namespace mixxx { class ScreensaverManager; @@ -62,7 +63,9 @@ class DlgPreferences : public QDialog, public Ui::DlgPreferencesDlg { public slots: void changePage(QTreeWidgetItem* pCurrent, QTreeWidgetItem* pPrevious); - void showSoundHardwarePage(); + void showSoundHardwarePage( + std::optional tab = + std::nullopt); void slotButtonPressed(QAbstractButton* pButton); signals: void closeDlg(); @@ -97,6 +100,7 @@ class DlgPreferences : public QDialog, public Ui::DlgPreferencesDlg { QStringList m_geometry; UserSettingsPointer m_pConfig; + std::unique_ptr m_pSoundDlg; PreferencesPage m_soundPage; DlgPrefControllers* m_pControllersDlg; diff --git a/src/preferences/dialog/dlgprefmixer.cpp b/src/preferences/dialog/dlgprefmixer.cpp index 297e826fd5fa..681e7c897775 100644 --- a/src/preferences/dialog/dlgprefmixer.cpp +++ b/src/preferences/dialog/dlgprefmixer.cpp @@ -72,13 +72,13 @@ DlgPrefMixer::DlgPrefMixer( : DlgPreferencePage(pParent), m_pConfig(pConfig), m_xFaderMode(MIXXX_XFADER_ADDITIVE), - m_transform(EngineXfader::kTransformDefault), - m_cal(0.0), - m_mode(kXfaderModeKey), - m_curve(kXfaderCurveKey), - m_calibration(kXfaderCalibrationKey), - m_reverse(kXfaderReverseKey), - m_crossfader("[Master]", "crossfader"), + m_xFaderCurve(EngineXfader::kTransformDefault), + m_xFaderCal(0.0), + m_xfModeCO(make_parented(kXfaderModeKey, this)), + m_xfCurveCO(make_parented(kXfaderCurveKey, this)), + m_xfReverseCO(make_parented(kXfaderReverseKey, this)), + m_xfCalibrationCO(make_parented(kXfaderCalibrationKey, this)), + m_crossfader(QStringLiteral("[Master]"), QStringLiteral("crossfader")), m_xFaderReverse(false), m_COLoFreq(kLowEqFreqKey), m_COHiFreq(kHighEqFreqKey), @@ -110,14 +110,30 @@ DlgPrefMixer::DlgPrefMixer( connect(SliderXFader, QOverload::of(&QSlider::valueChanged), this, - &DlgPrefMixer::slotUpdateXFader); - connect(SliderXFader, &QSlider::sliderMoved, this, &DlgPrefMixer::slotUpdateXFader); - connect(SliderXFader, &QSlider::sliderReleased, this, &DlgPrefMixer::slotUpdateXFader); - connect(radioButtonAdditive, &QRadioButton::clicked, this, &DlgPrefMixer::slotUpdateXFader); - connect(radioButtonConstantPower, - &QRadioButton::clicked, + &DlgPrefMixer::slotXFaderSliderChanged); + connect(SliderXFader, &QSlider::sliderMoved, this, &DlgPrefMixer::slotXFaderSliderChanged); + connect(SliderXFader, &QSlider::sliderReleased, this, &DlgPrefMixer::slotXFaderSliderChanged); + connect(buttonGroupCrossfaderModes, + &QButtonGroup::buttonClicked, this, - &DlgPrefMixer::slotUpdateXFader); + &DlgPrefMixer::slotXFaderModeBoxToggled); + connect(checkBoxReverse, +#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) + &QCheckBox::checkStateChanged, +#else + &QCheckBox::stateChanged, +#endif + this, + &DlgPrefMixer::slotXFaderReverseBoxToggled); + + m_xfModeCO->connectValueChanged( + this, &DlgPrefMixer::slotXFaderModeControlChanged); + m_xfCurveCO->connectValueChanged( + this, &DlgPrefMixer::slotXFaderCurveControlChanged); + m_xfCalibrationCO->connectValueChanged( + this, &DlgPrefMixer::slotXFaderCalibrationControlChanged); + m_xfReverseCO->connectValueChanged( + this, &DlgPrefMixer::slotXFaderReverseControlChanged); // Don't allow the xfader graph getting keyboard focus graphicsViewXfader->setFocusPolicy(Qt::NoFocus); @@ -132,33 +148,57 @@ DlgPrefMixer::DlgPrefMixer( connect(SliderLoEQ, &QSlider::sliderReleased, this, &DlgPrefMixer::slotLoEqSliderChanged); connect(CheckBoxEqAutoReset, - &QCheckBox::toggled, +#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) + &QCheckBox::checkStateChanged, +#else + &QCheckBox::stateChanged, +#endif this, &DlgPrefMixer::slotEqAutoResetToggled); connect(CheckBoxGainAutoReset, - &QCheckBox::toggled, +#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) + &QCheckBox::checkStateChanged, +#else + &QCheckBox::stateChanged, +#endif this, &DlgPrefMixer::slotGainAutoResetToggled); #ifdef __STEM__ connect(CheckBoxStemAutoReset, - &QCheckBox::toggled, +#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) + &QCheckBox::checkStateChanged, +#else + &QCheckBox::stateChanged, +#endif this, &DlgPrefMixer::slotStemAutoResetToggled); #else CheckBoxStemAutoReset->hide(); #endif connect(CheckBoxBypass, - &QCheckBox::toggled, +#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) + &QCheckBox::checkStateChanged, +#else + &QCheckBox::stateChanged, +#endif this, &DlgPrefMixer::slotBypassEqToggled); connect(CheckBoxEqOnly, - &QCheckBox::toggled, +#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) + &QCheckBox::checkStateChanged, +#else + &QCheckBox::stateChanged, +#endif this, &DlgPrefMixer::slotEqOnlyToggled); connect(CheckBoxSingleEqEffect, - &QCheckBox::toggled, +#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) + &QCheckBox::checkStateChanged, +#else + &QCheckBox::stateChanged, +#endif this, &DlgPrefMixer::slotSingleEqToggled); @@ -192,7 +232,6 @@ DlgPrefMixer::DlgPrefMixer( m_initializing = false; } -// Create EQ & QuickEffect selectors and deck label for each added deck void DlgPrefMixer::slotNumDecksChanged(double numDecks) { while (m_deckEqEffectSelectors.size() < static_cast(numDecks)) { // 1-based for display @@ -688,19 +727,7 @@ int DlgPrefMixer::getSliderPosition(double eqFreq, int minValue, int maxValue) { } void DlgPrefMixer::slotApply() { - // xfader ////////////////////////////////////////////////////////////////// - m_mode.set(m_xFaderMode); - m_curve.set(m_transform); - m_calibration.set(m_cal); - if (checkBoxReverse->isChecked() != m_xFaderReverse) { - m_reverse.set(checkBoxReverse->isChecked()); - double position = m_crossfader.get(); - m_crossfader.set(0.0 - position); - m_xFaderReverse = checkBoxReverse->isChecked(); - } - m_pConfig->set(kXfaderModeKey, ConfigValue(m_xFaderMode)); - m_pConfig->set(kXfaderCurveKey, ConfigValue(QString::number(m_transform))); - m_pConfig->set(kXfaderReverseKey, ConfigValue(checkBoxReverse->isChecked() ? 1 : 0)); + applyXFader(); // EQ & QuickEffect settings /////////////////////////////////////////////// m_pConfig->set(kEnableEqsKey, ConfigValue(m_eqBypass ? 0 : 1)); @@ -717,6 +744,21 @@ void DlgPrefMixer::slotApply() { storeEqShelves(); } +void DlgPrefMixer::applyXFader() { + m_xfModeCO->set(m_xFaderMode); + m_xfCurveCO->set(m_xFaderCurve); + m_xfCalibrationCO->set(m_xFaderCal); + if (m_xFaderReverse != m_xfReverseCO->toBool()) { + double position = m_crossfader.get(); + m_crossfader.set(0.0 - position); + } + m_xfReverseCO->set(m_xFaderReverse ? 1.0 : 0.0); + + m_pConfig->setValue(kXfaderModeKey, m_xFaderMode); + m_pConfig->setValue(kXfaderCurveKey, m_xFaderCurve); + m_pConfig->setValue(kXfaderReverseKey, m_xFaderReverse); +} + void DlgPrefMixer::storeEqShelves() { if (m_initializing) { return; @@ -726,29 +768,7 @@ void DlgPrefMixer::storeEqShelves() { m_pConfig->set(kLowEqFreqPreciseKey, ConfigValue(QString::number(m_lowEqFreq, 'f'))); } -// Update the widgets with values from config / EffectsManager void DlgPrefMixer::slotUpdate() { - // xfader ////////////////////////////////////////////////////////////////// - m_transform = m_pConfig->getValue(kXfaderCurveKey, EngineXfader::kTransformDefault); - - // Range SliderXFader 0 .. 100 - double sliderVal = RescalerUtils::oneByXToLinear( - m_transform - EngineXfader::kTransformMin + 1, - EngineXfader::kTransformMax - EngineXfader::kTransformMin + 1, - SliderXFader->minimum(), - SliderXFader->maximum()); - SliderXFader->setValue(static_cast(std::round(sliderVal))); - - m_xFaderMode = m_pConfig->getValueString(kXfaderModeKey).toInt(); - if (m_xFaderMode == MIXXX_XFADER_CONSTPWR) { - radioButtonConstantPower->setChecked(true); - } else { - radioButtonAdditive->setChecked(true); - } - - m_xFaderReverse = m_pConfig->getValueString(kXfaderReverseKey).toInt() == 1; - checkBoxReverse->setChecked(m_xFaderReverse); - slotUpdateXFader(); // EQs & QuickEffects ////////////////////////////////////////////////////// @@ -815,8 +835,62 @@ void DlgPrefMixer::slotUpdate() { updateMainEQ(); } -// Draw the crossfader curve graph. Only needs to get drawn when a change -// has been made. +void DlgPrefMixer::slotUpdateXFader() { + // Read values from config only on first update if the xfader curve controls + // are still at their default values. This should detect if controller mappings + // (or skin attributes) have changed the xfader controls. + // Else and on later calls, always read the current state from controls. + if (m_initializing && + m_xfCurveCO->get() == m_xfCurveCO->getDefault() && + m_xfCalibrationCO->get() == m_xfCalibrationCO->getDefault() && + m_xfModeCO->get() == m_xfModeCO->getDefault() && + m_xfReverseCO->get() == m_xfReverseCO->getDefault()) { + m_xFaderCurve = m_pConfig->getValue(kXfaderCurveKey, EngineXfader::kTransformDefault); + // "xFaderCalibration" is not stored in the config and it's not expsoed + // with a slider here. Each time the slider is touched it's calculated + // to get us a smooth curve for ConstPower mode. And hos no effect for + // Additive mode. + // TODO This also means custom values set by controller mappings are + // wiped on shutdown. + m_xFaderCal = EngineXfader::getPowerCalibration(m_xFaderCurve); + m_xFaderMode = m_pConfig->getValue(kXfaderModeKey); + m_xFaderReverse = m_pConfig->getValue(kXfaderReverseKey); + } else { + // Update xfader from controls + // deactivated for now. resolve dupe debug etc. + // slotXFaderControlChanged(); + m_xFaderCurve = m_xfCurveCO->get(); + m_xFaderCal = m_xfCalibrationCO->get(); + m_xFaderMode = static_cast(m_xfModeCO->get()); + m_xFaderReverse = static_cast(m_xfReverseCO->get()); + } + + updateXFaderWidgets(); +} + +void DlgPrefMixer::updateXFaderWidgets() { + const QSignalBlocker signalBlocker(this); + + // Range SliderXFader 0 .. 100 + double sliderVal = RescalerUtils::oneByXToLinear( + m_xFaderCurve - EngineXfader::kTransformMin + 1, + EngineXfader::kTransformMax - EngineXfader::kTransformMin + 1, + SliderXFader->minimum(), + SliderXFader->maximum()); + SliderXFader->setValue(static_cast(std::round(sliderVal))); + + // Same here + if (m_xFaderMode == MIXXX_XFADER_CONSTPWR) { + radioButtonConstantPower->setChecked(true); + } else { + radioButtonAdditive->setChecked(true); + } + + checkBoxReverse->setChecked(m_xFaderReverse); + + drawXfaderDisplay(); +} + void DlgPrefMixer::drawXfaderDisplay() { // Initialize or clear scene if (m_pxfScene) { @@ -878,10 +952,10 @@ void DlgPrefMixer::drawXfaderDisplay() { for (int x = 1; x <= pointCount + 1; x++) { CSAMPLE_GAIN gainL, gainR; EngineXfader::getXfadeGains((-1. + (xfadeStep * (x - 1))), - m_transform, - m_cal, + m_xFaderCurve, + m_xFaderCal, m_xFaderMode, - checkBoxReverse->isChecked(), + m_xFaderReverse, &gainL, &gainR); @@ -908,28 +982,70 @@ void DlgPrefMixer::drawXfaderDisplay() { graphicsViewXfader->repaint(); } -void DlgPrefMixer::slotUpdateXFader() { - if (radioButtonAdditive->isChecked()) { - m_xFaderMode = MIXXX_XFADER_ADDITIVE; - } else { - m_xFaderMode = MIXXX_XFADER_CONSTPWR; - } +void DlgPrefMixer::slotXFaderReverseBoxToggled() { + m_xFaderReverse = checkBoxReverse->isChecked(); +} - // m_transform is in the range of 1 to 1000 while 50 % slider results +void DlgPrefMixer::slotXFaderSliderChanged() { + // m_xFaderCurve is in the range of 1 to 1000 while 50 % slider results // to ~2, which represents a medium rounded fader curve. - double transform = RescalerUtils::linearToOneByX( - SliderXFader->value(), - SliderXFader->minimum(), - SliderXFader->maximum(), - EngineXfader::kTransformMax) - + double curve = RescalerUtils::linearToOneByX( + SliderXFader->value(), + SliderXFader->minimum(), + SliderXFader->maximum(), + EngineXfader::kTransformMax) - 1 + EngineXfader::kTransformMin; // Round to 4 decimal places to avoid round-trip offsets with default 1.0 - m_transform = std::round(transform * 10000) / 10000; - m_cal = EngineXfader::getPowerCalibration(m_transform); + m_xFaderCurve = std::round(curve * 10000) / 10000; + // If the curve has been changed in the GUI we fetch the engine value for + // calibration which gives us a smooth curve. + // This wipes any previous value set by controller mappings for example. + m_xFaderCal = EngineXfader::getPowerCalibration(m_xFaderCurve); + drawXfaderDisplay(); +} + +void DlgPrefMixer::slotXFaderModeBoxToggled() { + m_xFaderMode = radioButtonConstantPower->isChecked() + ? MIXXX_XFADER_CONSTPWR + : MIXXX_XFADER_ADDITIVE; drawXfaderDisplay(); } +void DlgPrefMixer::slotXFaderCurveControlChanged(double v) { + if (v == m_xFaderCurve) { + return; + } + m_xFaderCurve = v; + updateXFaderWidgets(); +} + +void DlgPrefMixer::slotXFaderCalibrationControlChanged(double v) { + if (v == m_xFaderCal) { + return; + } + m_xFaderCal = v; + updateXFaderWidgets(); +} + +void DlgPrefMixer::slotXFaderModeControlChanged(double v) { + int mode = static_cast(v); + if (mode == m_xFaderMode) { + return; + } + m_xFaderMode = mode; + updateXFaderWidgets(); +} + +void DlgPrefMixer::slotXFaderReverseControlChanged(double v) { + bool reverse = v > 0; + if (reverse == m_xFaderReverse) { + return; + } + m_xFaderReverse = reverse; + updateXFaderWidgets(); +} + void DlgPrefMixer::slotEqAutoResetToggled(bool checked) { m_eqAutoReset = checked; } diff --git a/src/preferences/dialog/dlgprefmixer.h b/src/preferences/dialog/dlgprefmixer.h index 2b907be93d66..092ca9f1433e 100644 --- a/src/preferences/dialog/dlgprefmixer.h +++ b/src/preferences/dialog/dlgprefmixer.h @@ -26,10 +26,12 @@ class DlgPrefMixer : public DlgPreferencePage, public Ui::DlgPrefMixerDlg { public slots: void slotApply() override; + /// Update the widgets with values from config / EffectsManager void slotUpdate() override; void slotResetToDefaults() override; private slots: + /// Create EQ & QuickEffect selectors and deck label for each added deck void slotNumDecksChanged(double numDecks); void slotEQEffectSelectionChanged(int effectIndex); void slotQuickEffectSelectionChanged(int effectIndex); @@ -41,16 +43,27 @@ class DlgPrefMixer : public DlgPreferencePage, public Ui::DlgPrefMixerDlg { void slotStemAutoResetToggled(bool checked); #endif void slotBypassEqToggled(bool checked); - // Create, populate and show/hide EQ & QuickEffect selectors, considering the - // number of decks and the 'Single EQ' checkbox + /// Create, populate and show/hide EQ & QuickEffect selectors, considering the + /// number of decks and the 'Single EQ' checkbox void slotPopulateDeckEqSelectors(); void slotPopulateQuickEffectSelectors(); void slotUpdateXFader(); + void updateXFaderWidgets(); + + void slotXFaderReverseBoxToggled(); + void slotXFaderModeBoxToggled(); + void slotXFaderSliderChanged(); + + void slotXFaderCurveControlChanged(double v); + void slotXFaderCalibrationControlChanged(double v); + void slotXFaderModeControlChanged(double v); + void slotXFaderReverseControlChanged(double v); + void slotHiEqSliderChanged(); void slotLoEqSliderChanged(); - // Update the Main EQ + /// Update the Main EQ void slotMainEQParameterSliderChanged(int value); void slotMainEQToDefault(); void slotMainEqEffectChanged(int effectIndex); @@ -63,6 +76,8 @@ class DlgPrefMixer : public DlgPreferencePage, public Ui::DlgPrefMixerDlg { int getSliderPosition(double eqFreq, int minimum, int maximum); void validateEQShelves(); + void applyXFader(); + void applyDeckEQs(); void applyQuickEffects(); @@ -79,12 +94,12 @@ class DlgPrefMixer : public DlgPreferencePage, public Ui::DlgPrefMixerDlg { // X-fader values int m_xFaderMode; - double m_transform, m_cal; + double m_xFaderCurve, m_xFaderCal; - PollingControlProxy m_mode; - PollingControlProxy m_curve; - PollingControlProxy m_calibration; - PollingControlProxy m_reverse; + parented_ptr m_xfModeCO; + parented_ptr m_xfCurveCO; + parented_ptr m_xfReverseCO; + parented_ptr m_xfCalibrationCO; PollingControlProxy m_crossfader; bool m_xFaderReverse; diff --git a/src/preferences/dialog/dlgprefmixerdlg.ui b/src/preferences/dialog/dlgprefmixerdlg.ui index 22ce6e0798eb..48a7a3cf5214 100644 --- a/src/preferences/dialog/dlgprefmixerdlg.ui +++ b/src/preferences/dialog/dlgprefmixerdlg.ui @@ -22,7 +22,7 @@ - + Crossfader Curve diff --git a/src/preferences/dialog/dlgprefsound.cpp b/src/preferences/dialog/dlgprefsound.cpp index b785fb115835..b8080c1491ce 100644 --- a/src/preferences/dialog/dlgprefsound.cpp +++ b/src/preferences/dialog/dlgprefsound.cpp @@ -383,6 +383,16 @@ QUrl DlgPrefSound::helpUrl() const { return QUrl(MIXXX_MANUAL_SOUND_URL); } +void DlgPrefSound::selectIOTab(mixxx::preferences::SoundHardwareTab tab) { + switch (tab) { + case mixxx::preferences::SoundHardwareTab::Input: + ioTabs->setCurrentWidget(inputTab); + return; + case mixxx::preferences::SoundHardwareTab::Output: + ioTabs->setCurrentWidget(outputTab); + return; + } +} /// Initializes (and creates) all the path items. Each path item widget allows /// the user to input a sound device name and channel number given a description /// of what will be done with that info. Inputs and outputs are grouped by tab, diff --git a/src/preferences/dialog/dlgprefsound.h b/src/preferences/dialog/dlgprefsound.h index ef8b5f0c81c2..322ac4c25b24 100644 --- a/src/preferences/dialog/dlgprefsound.h +++ b/src/preferences/dialog/dlgprefsound.h @@ -4,6 +4,7 @@ #include "control/pollingcontrolproxy.h" #include "defs_urls.h" +#include "preferences/constants.h" #include "preferences/dialog/dlgpreferencepage.h" #include "preferences/dialog/ui_dlgprefsounddlg.h" #include "preferences/usersettings.h" @@ -12,13 +13,13 @@ #include "soundio/soundmanagerconfig.h" #include "util/parented_ptr.h" -class SoundManager; -class PlayerManager; class ControlObject; +class ControlProxy; +class DlgPrefSoundItem; +class PlayerManager; class SoundDevice; class SoundDeviceId; -class DlgPrefSoundItem; -class ControlProxy; +class SoundManager; // TODO(bkgood) (n-decks) establish a signal/slot connection with a signal // on EngineMaster that emits every time a channel is added, and a slot here @@ -31,6 +32,8 @@ class DlgPrefSound : public DlgPreferencePage, public Ui::DlgPrefSoundDlg { std::shared_ptr soundManager, UserSettingsPointer pSettings); + void selectIOTab(mixxx::preferences::SoundHardwareTab tab); + QUrl helpUrl() const override; signals: diff --git a/src/preferences/dialog/dlgprefwaveform.cpp b/src/preferences/dialog/dlgprefwaveform.cpp index 8b0c4bff9f11..d770f10e1249 100644 --- a/src/preferences/dialog/dlgprefwaveform.cpp +++ b/src/preferences/dialog/dlgprefwaveform.cpp @@ -8,9 +8,9 @@ #include "moc_dlgprefwaveform.cpp" #include "preferences/waveformsettings.h" #include "util/db/dbconnectionpooled.h" +#include "waveform/overviewtype.h" #include "waveform/renderers/waveformwidgetrenderer.h" #include "waveform/waveformwidgetfactory.h" -#include "widget/woverview.h" namespace { const ConfigKey kOverviewTypeCfgKey(QStringLiteral("[Waveform]"), @@ -28,20 +28,20 @@ DlgPrefWaveform::DlgPrefWaveform( // Waveform overview init waveformOverviewComboBox->addItem( - tr("Filtered"), QVariant::fromValue(WOverview::Type::Filtered)); - waveformOverviewComboBox->addItem(tr("HSV"), QVariant::fromValue(WOverview::Type::HSV)); - waveformOverviewComboBox->addItem(tr("RGB"), QVariant::fromValue(WOverview::Type::RGB)); + tr("Filtered"), QVariant::fromValue(mixxx::OverviewType::Filtered)); + waveformOverviewComboBox->addItem(tr("HSV"), QVariant::fromValue(mixxx::OverviewType::HSV)); + waveformOverviewComboBox->addItem(tr("RGB"), QVariant::fromValue(mixxx::OverviewType::RGB)); m_pTypeControl = std::make_unique(kOverviewTypeCfgKey); - m_pTypeControl->setStates(QMetaEnum::fromType().keyCount()); + m_pTypeControl->setStates(QMetaEnum::fromType().keyCount()); m_pTypeControl->setReadOnly(); // Update the control with the config value - WOverview::Type overviewType = - m_pConfig->getValue(kOverviewTypeCfgKey, WOverview::Type::RGB); + mixxx::OverviewType overviewType = + m_pConfig->getValue(kOverviewTypeCfgKey, mixxx::OverviewType::RGB); int cfgTypeIndex = waveformOverviewComboBox->findData(QVariant::fromValue(overviewType)); if (cfgTypeIndex == -1) { // Invalid config value, set default type RGB and write it to config waveformOverviewComboBox->setCurrentIndex( - waveformOverviewComboBox->findData(QVariant::fromValue(WOverview::Type::RGB))); + waveformOverviewComboBox->findData(QVariant::fromValue(mixxx::OverviewType::RGB))); m_pConfig->setValue(kOverviewTypeCfgKey, cfgTypeIndex); } else { waveformOverviewComboBox->setCurrentIndex(cfgTypeIndex); @@ -306,10 +306,10 @@ void DlgPrefWaveform::slotUpdate() { WaveformWidgetFactory::toUntilMarkTextHeightLimitIndex( factory->getUntilMarkTextHeightLimit())); - WOverview::Type cfgOverviewType = - m_pConfig->getValue(kOverviewTypeCfgKey, WOverview::Type::RGB); + mixxx::OverviewType cfgOverviewType = + m_pConfig->getValue(kOverviewTypeCfgKey, mixxx::OverviewType::RGB); // Assumes the combobox index is in sync with the ControlPushButton - if (cfgOverviewType != waveformOverviewComboBox->currentData().value()) { + if (cfgOverviewType != waveformOverviewComboBox->currentData().value()) { int cfgOverviewTypeIndex = waveformOverviewComboBox->findData(QVariant::fromValue(cfgOverviewType)); waveformOverviewComboBox->setCurrentIndex(cfgOverviewTypeIndex); @@ -372,7 +372,7 @@ void DlgPrefWaveform::slotResetToDefaults() { // RGB overview. waveformOverviewComboBox->setCurrentIndex( - waveformOverviewComboBox->findData(QVariant::fromValue(WOverview::Type::RGB))); + waveformOverviewComboBox->findData(QVariant::fromValue(mixxx::OverviewType::RGB))); // Don't normalize overview. normalizeOverviewCheckBox->setChecked(false); @@ -583,8 +583,8 @@ void DlgPrefWaveform::updateWaveformGainEnabled() { void DlgPrefWaveform::slotSetWaveformOverviewType() { // Apply immediately QVariant comboboxData = waveformOverviewComboBox->currentData(); - DEBUG_ASSERT(comboboxData.canConvert()); - auto type = comboboxData.value(); + DEBUG_ASSERT(comboboxData.canConvert()); + auto type = comboboxData.value(); m_pConfig->setValue(kOverviewTypeCfgKey, type); m_pTypeControl->forceSet(static_cast(type)); } diff --git a/src/qml/qmlwaveformoverview.h b/src/qml/qmlwaveformoverview.h index 0c0ecb7c9095..4b51bb83747d 100644 --- a/src/qml/qmlwaveformoverview.h +++ b/src/qml/qmlwaveformoverview.h @@ -58,7 +58,11 @@ class QmlWaveformOverview : public QQuickPaintedItem { signals: void playerChanged(); void channelsChanged(mixxx::qml::QmlWaveformOverview::Channels channels); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + void rendererChanged(Renderer renderer); +#else void rendererChanged(mixxx::qml::QmlWaveformOverview::Renderer renderer); +#endif void colorHighChanged(const QColor& color); void colorMidChanged(const QColor& color); void colorLowChanged(const QColor& color); diff --git a/src/qml/qmlwaveformrenderer.h b/src/qml/qmlwaveformrenderer.h index 1c081966fc23..361691ea48a3 100644 --- a/src/qml/qmlwaveformrenderer.h +++ b/src/qml/qmlwaveformrenderer.h @@ -397,7 +397,11 @@ class QmlWaveformRendererMark signals: void playMarkerColorChanged(const QColor&); void playMarkerBackgroundChanged(const QColor&); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + void defaultMarkChanged(QmlWaveformMark*); +#else void defaultMarkChanged(mixxx::qml::QmlWaveformMark*); +#endif private: QColor m_playMarkerColor; diff --git a/src/skin/legacy/legacyskinparser.cpp b/src/skin/legacy/legacyskinparser.cpp index 994db6f8635c..18ab904a062b 100644 --- a/src/skin/legacy/legacyskinparser.cpp +++ b/src/skin/legacy/legacyskinparser.cpp @@ -1535,6 +1535,19 @@ QWidget* LegacySkinParser::parseSearchBox(const QDomElement& node) { commonWidgetSetup(node, pLineEditSearch, false); pLineEditSearch->setup(node, *m_pContext); + // Translate shortcuts to native text + QString searchInCurrentViewShortcut = + localizeShortcutKeys(m_pKeyboard->getKeyboardConfig()->getValue( + ConfigKey("[KeyboardShortcuts]", + "LibraryMenu_SearchInCurrentView"), + "Ctrl+f")); + QString searchInAllTracksShortcut = + localizeShortcutKeys(m_pKeyboard->getKeyboardConfig()->getValue( + ConfigKey("[KeyboardShortcuts]", + "LibraryMenu_SearchInAllTracks"), + "Ctrl+Shift+F")); + pLineEditSearch->setupToolTip(searchInCurrentViewShortcut, searchInAllTracksShortcut); + m_pLibrary->bindSearchboxWidget(pLineEditSearch); return pLineEditSearch; @@ -2609,9 +2622,6 @@ void LegacySkinParser::addShortcutToToolTip(WBaseWidget* pWidget, QString tooltip; - // translate shortcut to native text - QString nativeShortcut = QKeySequence(shortcut, QKeySequence::PortableText).toString(QKeySequence::NativeText); - tooltip += "\n"; tooltip += tr("Shortcut"); if (!cmd.isEmpty()) { @@ -2619,10 +2629,16 @@ void LegacySkinParser::addShortcutToToolTip(WBaseWidget* pWidget, tooltip += cmd; } tooltip += ": "; - tooltip += nativeShortcut; + tooltip += localizeShortcutKeys(shortcut); pWidget->appendBaseTooltip(tooltip); } +QString LegacySkinParser::localizeShortcutKeys(const QString& shortcut) { + // Translate shortcut to native text + return QKeySequence(shortcut, QKeySequence::PortableText) + .toString(QKeySequence::NativeText); +} + QString LegacySkinParser::parseLaunchImageStyle(const QDomNode& skinDoc) { QString schemeLaunchImageStyle; // Check if the skins has color schemes diff --git a/src/skin/legacy/legacyskinparser.h b/src/skin/legacy/legacyskinparser.h index 5a6c26bf08ff..a271b5ee45bb 100644 --- a/src/skin/legacy/legacyskinparser.h +++ b/src/skin/legacy/legacyskinparser.h @@ -142,6 +142,7 @@ class LegacySkinParser : public QObject, public SkinParser { bool setupPosition=true); void setupConnections(const QDomNode& node, WBaseWidget* pWidget); void addShortcutToToolTip(WBaseWidget* pWidget, const QString& shortcut, const QString& cmd); + QString localizeShortcutKeys(const QString& shortcut); QString getLibraryStyle(const QDomNode& node); QString lookupNodeGroup(const QDomElement& node); diff --git a/src/sources/soundsourceffmpeg.cpp b/src/sources/soundsourceffmpeg.cpp index 942ef9e85897..e019d4669c1b 100644 --- a/src/sources/soundsourceffmpeg.cpp +++ b/src/sources/soundsourceffmpeg.cpp @@ -548,9 +548,11 @@ SoundSource::OpenResult SoundSourceFFmpeg::tryOpen( // Find the best stream #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 0, 100) // FFmpeg 5.0 const AVCodec* pDecoder = nullptr; + const AVCodec* pAacDecoder = nullptr; #else // https://github.com/FFmpeg/FFmpeg/blob/dd17c86aa11feae2b86de054dd0679cc5f88ebab/doc/APIchanges#L175 AVCodec* pDecoder = nullptr; + AVCodec* pAacDecoder = nullptr; #endif const int av_find_best_stream_result = av_find_best_stream( m_pavInputFormatContext, @@ -578,6 +580,29 @@ SoundSource::OpenResult SoundSourceFFmpeg::tryOpen( } DEBUG_ASSERT(pDecoder); + if (pDecoder->id == AV_CODEC_ID_AAC || + pDecoder->id == AV_CODEC_ID_AAC_LATM) { + // We only allow AAC decoders that pass our seeking tests + if (std::strcmp(pDecoder->name, "aac") != 0 && std::strcmp(pDecoder->name, "aac_at") != 0) { + pAacDecoder = avcodec_find_decoder_by_name("aac"); + if (pAacDecoder) { + pDecoder = pAacDecoder; + } else { + kLogger.warning() + << "Internal aac decoder not found in your FFmpeg " + "build." + << "To enable AAC support, please install an FFmpeg " + "version with the internal aac decoder enabled." + "Note 1: The libfdk_aac decoder is no working properly " + "with Mixxx, FFmpeg's internal AAC decoder does." + << "Note 2: AAC decoding may be subject to patent " + "restrictions, depending on your country."; + } + } + } + + kLogger.debug() << "using decoder:" << pDecoder->long_name; + // Select audio stream for decoding AVStream* pavStream = m_pavInputFormatContext->streams[av_find_best_stream_result]; DEBUG_ASSERT(pavStream != nullptr); diff --git a/src/sources/soundsourceproxy.cpp b/src/sources/soundsourceproxy.cpp index 738db69b16ec..6e9e0bd8a1fb 100644 --- a/src/sources/soundsourceproxy.cpp +++ b/src/sources/soundsourceproxy.cpp @@ -303,7 +303,7 @@ bool SoundSourceProxy::isUrlSupported(const QUrl& url) { bool SoundSourceProxy::isFileSupported(const mixxx::FileInfo& fileInfo) { const QString fileType = mixxx::SoundSource::getTypeFromFile(fileInfo.asQFileInfo()); - qDebug() << "isFileSupported" << fileType; + // qDebug() << "isFileSupported" << fileType; return isFileTypeSupported(fileType); } diff --git a/src/sources/soundsourcestem.cpp b/src/sources/soundsourcestem.cpp index a70924c23697..990cd1055b34 100644 --- a/src/sources/soundsourcestem.cpp +++ b/src/sources/soundsourcestem.cpp @@ -115,6 +115,29 @@ SoundSource::OpenResult SoundSourceSingleSTEM::tryOpen( DEBUG_ASSERT(pDecoder); + if (pDecoder->id == AV_CODEC_ID_AAC || + pDecoder->id == AV_CODEC_ID_AAC_LATM) { + // We only allow AAC decoders that pass our seeking tests + if (std::strcmp(pDecoder->name, "aac") != 0 && std::strcmp(pDecoder->name, "aac_at") != 0) { + const AVCodec* pAacDecoder = avcodec_find_decoder_by_name("aac"); + if (pAacDecoder) { + pDecoder = pAacDecoder; + } else { + kLogger.warning() + << "Internal aac decoder not found in your FFmpeg " + "build." + << "To enable AAC support, please install an FFmpeg " + "version with the internal aac decoder enabled." + "Note 1: The libfdk_aac decoder is no working properly " + "with Mixxx, FFmpeg's internal AAC decoder does." + << "Note 2: AAC decoding may be subject to patent " + "restrictions, depending on your country."; + } + } + } + + kLogger.debug() << "using FFmpeg decoder:" << pDecoder->long_name; + // Select the main mix stream for decoding AVStream* pavStream = selectedAudioStream; DEBUG_ASSERT(pavStream != nullptr); diff --git a/src/test/controller_hid_reportdescriptor_test.cpp b/src/test/controller_hid_reportdescriptor_test.cpp new file mode 100644 index 000000000000..e83c8145f4fe --- /dev/null +++ b/src/test/controller_hid_reportdescriptor_test.cpp @@ -0,0 +1,281 @@ +#include + +#include + +#include "controllers/hid/hidreportdescriptor.h" + +using namespace hid::reportDescriptor; + +// Example HID report descriptor data + +// clang-format off +uint8_t reportDescriptor[] = { + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x02, // Usage (Mouse) + 0xA1, 0x01, // Collection (Application) + 0x09, 0x01, // Usage (Pointer) + 0xA1, 0x00, // Collection (Physical) + 0x05, 0x09, // Usage Page (Button) + 0x19, 0x01, // Usage Minimum (1) + 0x29, 0x03, // Usage Maximum (3) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x95, 0x03, // Report Count (3) + 0x75, 0x01, // Report Size (1) + 0x81, 0x02, // Input (Data, Variable, Absolute) + 0x95, 0x01, // Report Count (1) + 0x75, 0x05, // Report Size (5) + 0x81, 0x01, // Input (Constant) + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x15, 0x81, // Logical Minimum (-127) + 0x25, 0x7F, // Logical Maximum (127) + 0x75, 0x08, // Report Size (8) + 0x95, 0x02, // Report Count (2) + 0x81, 0x06, // Input (Data, Variable, Relative) + 0xC0, // End Collection + 0xC0 // End Collection +}; +// clang-format on + +TEST(HidReportDescriptorParserTest, ParseReportDescriptor) { + HIDReportDescriptor parser(reportDescriptor, sizeof(reportDescriptor)); + Collection collection = parser.parse(); + + // Use getListOfReports to get the list of reports + auto reportsList = parser.getListOfReports(); + ASSERT_EQ(reportsList.size(), 1); + + auto [collectionIdx, reportType, reportId] = reportsList[0]; + ASSERT_EQ(collectionIdx, 0); + + // Use getReport to get the report + const Report* report = parser.getReport(reportType, reportId); + ASSERT_NE(report, nullptr); + + // Validate Report fields + ASSERT_EQ(report->m_reportType, reportType); + ASSERT_EQ(report->m_reportId, reportId); + + // Validate all Control fields + const std::vector& controls = report->getControls(); + ASSERT_EQ(controls.size(), 5); + + // Mouse Button 1 + ASSERT_EQ(controls[0].m_usage, 0x0009'0001); + ASSERT_EQ(controls[0].m_logicalMinimum, 0); + ASSERT_EQ(controls[0].m_logicalMaximum, 1); + ASSERT_EQ(controls[0].m_physicalMinimum, 0); + ASSERT_EQ(controls[0].m_physicalMaximum, 1); + ASSERT_EQ(controls[0].m_unitExponent, 0); + ASSERT_EQ(controls[0].m_unit, 0); + ASSERT_EQ(controls[0].m_bytePosition, 0); + ASSERT_EQ(controls[0].m_bitPosition, 0); + ASSERT_EQ(controls[0].m_bitSize, 1); + + // Mouse Button 2 + ASSERT_EQ(controls[1].m_usage, 0x0009'0002); + ASSERT_EQ(controls[1].m_logicalMinimum, 0); + ASSERT_EQ(controls[1].m_logicalMaximum, 1); + ASSERT_EQ(controls[1].m_physicalMinimum, 0); + ASSERT_EQ(controls[1].m_physicalMaximum, 1); + ASSERT_EQ(controls[1].m_unitExponent, 0); + ASSERT_EQ(controls[1].m_unit, 0); + ASSERT_EQ(controls[1].m_bytePosition, 0); + ASSERT_EQ(controls[1].m_bitPosition, 1); + ASSERT_EQ(controls[1].m_bitSize, 1); + + // Mouse Button 3 + ASSERT_EQ(controls[2].m_usage, 0x0009'0003); + ASSERT_EQ(controls[2].m_logicalMinimum, 0); + ASSERT_EQ(controls[2].m_logicalMaximum, 1); + ASSERT_EQ(controls[2].m_physicalMinimum, 0); + ASSERT_EQ(controls[2].m_physicalMaximum, 1); + ASSERT_EQ(controls[2].m_unitExponent, 0); + ASSERT_EQ(controls[2].m_unit, 0); + ASSERT_EQ(controls[2].m_bytePosition, 0); + ASSERT_EQ(controls[2].m_bitPosition, 2); + ASSERT_EQ(controls[2].m_bitSize, 1); + + // Mouse Movement X + ASSERT_EQ(controls[3].m_usage, 0x0001'0030); + ASSERT_EQ(controls[3].m_logicalMinimum, -127); + ASSERT_EQ(controls[3].m_logicalMaximum, 127); + ASSERT_EQ(controls[3].m_physicalMinimum, -127); + ASSERT_EQ(controls[3].m_physicalMaximum, 127); + ASSERT_EQ(controls[3].m_unitExponent, 0); + ASSERT_EQ(controls[3].m_unit, 0); + ASSERT_EQ(controls[3].m_bitSize, 8); + ASSERT_EQ(controls[3].m_bytePosition, 1); + ASSERT_EQ(controls[3].m_bitPosition, 0); + + // Mouse Movement Y + ASSERT_EQ(controls[4].m_usage, 0x0001'0031); + ASSERT_EQ(controls[4].m_logicalMinimum, -127); + ASSERT_EQ(controls[4].m_logicalMaximum, 127); + ASSERT_EQ(controls[4].m_physicalMinimum, -127); + ASSERT_EQ(controls[4].m_physicalMaximum, 127); + ASSERT_EQ(controls[4].m_unitExponent, 0); + ASSERT_EQ(controls[4].m_unit, 0); + ASSERT_EQ(controls[4].m_bitSize, 8); + ASSERT_EQ(controls[4].m_bytePosition, 2); + ASSERT_EQ(controls[4].m_bitPosition, 0); +} + +TEST(HIDReportDescriptorTest, ControlValue_1Bit) { + auto reportData = QByteArray::fromHex("81'00'00'FF'01"); + Control control({{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, // Flags + 0x0009'0001, // UsagePage/Usage + 0, // LogicalMinimum + 1, // LogicalMaximum + 0, // PhysicalMinimum + 1, // PhysicalMaximum + 0, // UnitExponent + 0, // Unit + 3, // BytePosition + 0, // BitPosition + 1); // BitSize + + int32_t value = extractLogicalValue(reportData, control); + EXPECT_EQ(value, 0x1); + + bool result = applyLogicalValue(reportData, control, 0); + EXPECT_TRUE(result); + EXPECT_EQ(reportData, QByteArray::fromHex("81'00'00'FE'01")); + + int32_t value2 = extractLogicalValue(reportData, control); + EXPECT_EQ(value2, 0x0); +} + +TEST(HIDReportDescriptorTest, ControlValue_unsigned11Bits) { + auto reportData = QByteArray::fromHex("81'30'46'00'01"); + Control control({{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, // Flags + 0x0009'0001, // UsagePage/Usage + 0, // LogicalMinimum + 2047, // LogicalMaximum + 0, // PhysicalMinimum + 2047, // PhysicalMaximum + 0, // UnitExponent + 0, // Unit + 1, // BytePosition + 2, // BitPosition + 11); // BitSize + + int32_t value = extractLogicalValue(reportData, control); + EXPECT_EQ(value, 0b001'1000'1100); + + bool result = applyLogicalValue(reportData, control, 0b010'1010'1010); + EXPECT_TRUE(result); + EXPECT_EQ(reportData, QByteArray::fromHex("81'A8'4A'00'01")); + + int32_t value2 = extractLogicalValue(reportData, control); + EXPECT_EQ(value2, 0b010'1010'1010); +} + +TEST(HIDReportDescriptorTest, ControlValue_signed11Bits) { + auto reportData = QByteArray::fromHex("AA'BB'CC'DD'EE"); + Control control({{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, // Flags + 0x0009'0001, // UsagePage/Usage + -1000, // LogicalMinimum + 1000, // LogicalMaximum + -10, // PhysicalMinimum + 10, // PhysicalMaximum + 0, // UnitExponent + 0, // Unit + 2, // BytePosition + 0, // BitPosition + 11); // BitSize + + int32_t value = extractLogicalValue(reportData, control); + EXPECT_EQ(value, -564); + + bool result = applyLogicalValue(reportData, control, +200); + EXPECT_TRUE(result); + EXPECT_EQ(reportData, QByteArray::fromHex("AA'BB'C8'D8'EE")); + + int32_t value2 = extractLogicalValue(reportData, control); + EXPECT_EQ(value2, +200); + + bool result2 = applyLogicalValue(reportData, control, -200); + EXPECT_TRUE(result2); + EXPECT_EQ(reportData, QByteArray::fromHex("AA'BB'38'DF'EE")); + + int32_t value3 = extractLogicalValue(reportData, control); + EXPECT_EQ(value3, -200); +} + +TEST(HIDReportDescriptorTest, ControlValue_unsigned32Bits) { + auto reportData = QByteArray::fromHex("0A'21'43'65'B7"); + Control control({{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, // Flags + 0x0009'0001, // UsagePage/Usage + 0, // LogicalMinimum + 0x7FFFFFFF, // LogicalMaximum + 0, // PhysicalMinimum + 0x7FFFFFFF, // PhysicalMaximum + 0, // UnitExponent + 0, // Unit + 0, // BytePosition + 4, // BitPosition + 32); // BitSize + + int32_t value = extractLogicalValue(reportData, control); + EXPECT_EQ(value, 0x76'54'32'10); + + bool result = applyLogicalValue(reportData, control, 0x01'23'45'67); + EXPECT_TRUE(result); + EXPECT_EQ(reportData, QByteArray::fromHex("7A'56'34'12'B0")); + + int32_t value2 = extractLogicalValue(reportData, control); + EXPECT_EQ(value2, 0x01'23'45'67); +} + +TEST(HIDReportDescriptorTest, ControlValue_signed32Bits) { + auto reportData = QByteArray::fromHex("0A'21'43'65'B7"); + Control control({{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, // Flags + 0x0009'0001, // UsagePage/Usage + std::numeric_limits::min(), // LogicalMinimum + std::numeric_limits::max(), // LogicalMaximum + 10, // PhysicalMinimum + 10, // PhysicalMaximum + 0, // UnitExponent + 0, // Unit + 0, // BytePosition + 4, // BitPosition + 32); // BitSize + + int32_t value = extractLogicalValue(reportData, control); + EXPECT_EQ(value, 0x76'54'32'10); + + bool result = applyLogicalValue(reportData, control, std::numeric_limits::min()); + EXPECT_TRUE(result); + EXPECT_EQ(reportData, QByteArray::fromHex("0A'00'00'00'B8")); + + int32_t value2 = extractLogicalValue(reportData, control); + EXPECT_EQ(value2, std::numeric_limits::min()); + + bool result2 = applyLogicalValue(reportData, control, std::numeric_limits::max()); + EXPECT_TRUE(result2); + EXPECT_EQ(reportData, QByteArray::fromHex("FA'FF'FF'FF'B7")); + + int32_t value3 = extractLogicalValue(reportData, control); + EXPECT_EQ(value3, std::numeric_limits::max()); +} + +TEST(HIDReportDescriptorTest, SetControlValue_OutOfRange) { + auto reportData = QByteArray::fromHex("81'00'00'00'01"); + Control control({{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, // Flags + 0x0009'0001, // UsagePage/Usage + 0, // LogicalMinimum + 2047, // LogicalMaximum + 0, // PhysicalMinimum + 2047, // PhysicalMaximum + 0, // UnitExponent + 0, // Unit + 0, // BytePosition + 0, // BitPosition + 11); // BitSize + + bool result = applyLogicalValue(reportData, control, 3000); + EXPECT_FALSE(result); +} diff --git a/src/test/enginefilterbiquadtest.cpp b/src/test/enginefilterbiquadtest.cpp index 7ba4c06e8b82..7c85fa68a8d2 100644 --- a/src/test/enginefilterbiquadtest.cpp +++ b/src/test/enginefilterbiquadtest.cpp @@ -26,6 +26,8 @@ TEST_F(EngineFilterBiquadTest, analysisPkBq) { char* pDesc = nullptr; FidFilter* filt = fid_design(spec_d, 44100, 1000, 0, 0, &pDesc); EXPECT_NE(pDesc, nullptr); + free(pDesc); + int delay = fid_calc_delay(filt); EXPECT_EQ(delay, 0); @@ -41,6 +43,8 @@ TEST_F(EngineFilterBiquadTest, analysisLpBe4) { char* pDesc = nullptr; FidFilter* filt = fid_design("LpBe4", 44100, 600, 0, 0, &pDesc); EXPECT_NE(pDesc, nullptr); + free(pDesc); + int delay = fid_calc_delay(filt); EXPECT_EQ(delay, 24); diff --git a/src/test/soundproxy_test.cpp b/src/test/soundproxy_test.cpp index 6fb72bef8bc0..cefb976059ad 100644 --- a/src/test/soundproxy_test.cpp +++ b/src/test/soundproxy_test.cpp @@ -50,8 +50,13 @@ class SoundSourceProxyTest : public MixxxTest, SoundSourceProviderRegistration { // was not correctly handled. The actual FFmpeg version // that fixed this bug is unknown. << "-itunes-12.3.0-aac.m4a" +#ifndef __WINDOWS__ + // These tests always fail on Windows11/Windows Server 2022, + // due to a bug in the MediaFoundation AAC decoder shipped with Windows. + // See https://bugs.mixxx.org/issues/11094 << "-itunes-12.7.0-aac.m4a" << "-ffmpeg-aac.m4a" +#endif #if defined(__FFMPEG__) || defined(__COREAUDIO__) << "-itunes-12.7.0-alac.m4a" #endif diff --git a/src/track/globaltrackcache.cpp b/src/track/globaltrackcache.cpp index 4d382cd33e12..5ab6887d66a1 100644 --- a/src/track/globaltrackcache.cpp +++ b/src/track/globaltrackcache.cpp @@ -776,8 +776,7 @@ TrackRef GlobalTrackCache::initTrackId( DEBUG_ASSERT(pDel); DEBUG_ASSERT(strongPtr == m_incompleteTrack); - m_incompleteTrack = nullptr; - m_isTrackCompleted.wakeAll(); + discardIncompleteTrack(); // Insert item by id DEBUG_ASSERT(m_tracksById.find(trackId) == m_tracksById.end()); diff --git a/src/track/serato/markers.cpp b/src/track/serato/markers.cpp index 8c12a24ec7c0..1b42ad06a0f2 100644 --- a/src/track/serato/markers.cpp +++ b/src/track/serato/markers.cpp @@ -635,7 +635,7 @@ QByteArray SeratoMarkers::dumpMP4() const { } QList SeratoMarkers::getCues() const { - qDebug() << "Reading cues from 'Serato Markers_' tag data..."; + // qDebug() << "Reading cues from 'Serato Markers_' tag data..."; QList cueInfos; int cueIndex = 0; diff --git a/src/track/serato/markers2.cpp b/src/track/serato/markers2.cpp index 554b8f3a382f..18dc9edf643b 100644 --- a/src/track/serato/markers2.cpp +++ b/src/track/serato/markers2.cpp @@ -592,7 +592,7 @@ SeratoMarkers2EntryPointer SeratoMarkers2::findEntryByType( } QList SeratoMarkers2::getCues() const { - qDebug() << "Reading cues from 'Serato Markers2' tag data..."; + // qDebug() << "Reading cues from 'Serato Markers2' tag data..."; QList cueInfos; @@ -795,7 +795,7 @@ QByteArray SeratoMarkers2::dumpFLAC() const { } std::optional SeratoMarkers2::getTrackColor() const { - kLogger.debug() << "Reading track color from 'Serato Markers2' tag data..."; + // kLogger.debug() << "Reading track color from 'Serato Markers2' tag data..."; for (const auto& pEntry : std::as_const(m_entries)) { VERIFY_OR_DEBUG_ASSERT(pEntry) { @@ -845,7 +845,7 @@ void SeratoMarkers2::setTrackColor(SeratoStoredTrackColor color) { } bool SeratoMarkers2::isBpmLocked() const { - kLogger.debug() << "Reading bpmlock state from 'Serato Markers2' tag data..."; + // kLogger.debug() << "Reading bpmlock state from 'Serato Markers2' tag data..."; for (const auto& pEntry : std::as_const(m_entries)) { VERIFY_OR_DEBUG_ASSERT(pEntry) { diff --git a/src/track/serato/tags.cpp b/src/track/serato/tags.cpp index 15e55fb85e15..ed01711d06c0 100644 --- a/src/track/serato/tags.cpp +++ b/src/track/serato/tags.cpp @@ -280,7 +280,7 @@ QList SeratoTags::getCueInfos() const { } const QList cueInfos = cueMap.values(); - qDebug() << "SeratoTags::getCueInfos()"; + // qDebug() << "SeratoTags::getCueInfos()"; for (const CueInfo& cueInfo : cueInfos) { qDebug() << cueInfo; } diff --git a/src/track/taglib/trackmetadata_common.cpp b/src/track/taglib/trackmetadata_common.cpp index 77c84f41bbd5..62bb583f5a2e 100644 --- a/src/track/taglib/trackmetadata_common.cpp +++ b/src/track/taglib/trackmetadata_common.cpp @@ -268,28 +268,36 @@ void importTrackMetadataFromTag( } } +bool isMultiValueTagEqual(const TagLib::String& taglibVal, QString mixxxVal) { + // Taglib 2 uses " / " instead of " " as a multi value separator. + // We may have read or write with either TagLib 1 or 2. + QString taglibValStripped = toQString(taglibVal).remove(" /"); + QString mixxxValStripped = mixxxVal.remove(" /"); + return taglibValStripped == mixxxValStripped; +} + void exportTrackMetadataIntoTag( TagLib::Tag* pTag, const TrackMetadata& trackMetadata, WriteTagMask writeMask) { DEBUG_ASSERT(pTag); // already validated before - pTag->setTitle(toTString(trackMetadata.getTrackInfo().getTitle())); - pTag->setAlbum(toTString(trackMetadata.getAlbumInfo().getTitle())); - // The mapping of multi-valued fields in TagLib is not bijective. // We don't want to overwrite existing values if the corresponding - // field has not been modified in Mixxx. This workaround only covers - // the most common multi-valued fields. + // field has not been modified in Mixxx. // // See also: - const auto artist = toTString(trackMetadata.getTrackInfo().getArtist()); - if (artist != pTag->artist()) { - pTag->setArtist(artist); + if (!isMultiValueTagEqual(pTag->title(), trackMetadata.getTrackInfo().getTitle())) { + pTag->setTitle(toTString(trackMetadata.getTrackInfo().getTitle())); } - const auto genre = toTString(trackMetadata.getTrackInfo().getGenre()); - if (genre != pTag->genre()) { - pTag->setGenre(genre); + if (!isMultiValueTagEqual(pTag->album(), trackMetadata.getAlbumInfo().getTitle())) { + pTag->setAlbum(toTString(trackMetadata.getAlbumInfo().getTitle())); + } + if (!isMultiValueTagEqual(pTag->artist(), trackMetadata.getTrackInfo().getArtist())) { + pTag->setArtist(toTString(trackMetadata.getTrackInfo().getArtist())); + } + if (!isMultiValueTagEqual(pTag->genre(), trackMetadata.getTrackInfo().getGenre())) { + pTag->setGenre(toTString(trackMetadata.getTrackInfo().getGenre())); } // Using setComment() from TagLib::Tag might have undesirable @@ -297,7 +305,9 @@ void exportTrackMetadataIntoTag( // different purposes, e.g. ID3v2. In this case setting the // comment here should be omitted. if (0 == (writeMask & WriteTagFlag::OmitComment)) { - pTag->setComment(toTString(trackMetadata.getTrackInfo().getComment())); + if (!isMultiValueTagEqual(pTag->comment(), trackMetadata.getTrackInfo().getComment())) { + pTag->setComment(toTString(trackMetadata.getTrackInfo().getComment())); + } } // Specialized write functions for tags derived from Taglib::Tag might diff --git a/src/waveform/overviewtype.cpp b/src/waveform/overviewtype.cpp new file mode 100644 index 000000000000..5077ce71b684 --- /dev/null +++ b/src/waveform/overviewtype.cpp @@ -0,0 +1,4 @@ +// just a stub so the MOC file can be included somewhere +#include "overviewtype.h" + +#include "moc_overviewtype.cpp" diff --git a/src/waveform/overviewtype.h b/src/waveform/overviewtype.h new file mode 100644 index 000000000000..811c9a3a2a12 --- /dev/null +++ b/src/waveform/overviewtype.h @@ -0,0 +1,16 @@ +#pragma once + +// required for Qt-Macros +#include + +namespace mixxx { +Q_NAMESPACE + +enum class OverviewType { + Filtered, + HSV, + RGB, +}; +Q_ENUM_NS(OverviewType); + +} // namespace mixxx diff --git a/src/waveform/renderers/waveformmark.cpp b/src/waveform/renderers/waveformmark.cpp index be701859f3fd..ae4e09e41593 100644 --- a/src/waveform/renderers/waveformmark.cpp +++ b/src/waveform/renderers/waveformmark.cpp @@ -420,16 +420,31 @@ QImage WaveformMark::generateImage(float devicePixelRatio) { QString path = m_pixmapPath; // Use devicePixelRatio to properly scale the image QImage image = *WImageStore::getImage(path, devicePixelRatio); - // If loading the image didn't fail, then we're done. Otherwise fall - // through and render a label. + // If loading the image didn't fail, then we're done. Otherwise fall + // through and render a label. if (!image.isNull()) { image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); - // Set the pixel/device ratio AFTER loading the image in order to get - // a truly scaled source image. - // See https://doc.qt.io/qt-5/qimage.html#setDevicePixelRatio - // Also, without this some Qt-internal issue results in an offset - // image when calculating the center line of pixmaps in draw(). + // Set the pixel/device ratio AFTER loading the image in order to get + // a truly scaled source image. + // See https://doc.qt.io/qt-5/qimage.html#setDevicePixelRatio + // Also, without this some Qt-internal issue results in an offset + // image when calculating the center line of pixmaps in draw(). image.setDevicePixelRatio(devicePixelRatio); + // Calculate the offset + const float imgw = image.width(); + const Qt::Alignment alignH = m_align & Qt::AlignHorizontal_Mask; + switch (alignH) { + case Qt::AlignHCenter: + m_offset = -(imgw - 1.f) / 2.f; + break; + case Qt::AlignLeft: + m_offset = -imgw + 2.f; + break; + case Qt::AlignRight: + default: + m_offset = -1.f; + break; + } return image; } } diff --git a/src/waveform/renderers/waveformoverviewrenderer.cpp b/src/waveform/renderers/waveformoverviewrenderer.cpp new file mode 100644 index 000000000000..1519a6289bad --- /dev/null +++ b/src/waveform/renderers/waveformoverviewrenderer.cpp @@ -0,0 +1,336 @@ +#include "waveformoverviewrenderer.h" + +#include + +#include "util/colorcomponents.h" +#include "util/math.h" +#include "util/timer.h" +#include "waveform/renderers/waveformsignalcolors.h" +#include "waveform/waveformwidgetfactory.h" + +namespace waveformOverviewRenderer { + +QImage render(ConstWaveformPointer pWaveform, + mixxx::OverviewType type, + const WaveformSignalColors& signalColors, + bool mono) { + const int dataSize = pWaveform->getDataSize(); + if (dataSize <= 0) { + return QImage(); + } + + QImage image(dataSize / 2, 2 * 255, QImage::Format_ARGB32_Premultiplied); + image.fill(QColor(0, 0, 0, 0).value()); + + QPainter painter(&image); + painter.translate(0.0, static_cast(image.height()) / 2.0); + + if (type == mixxx::OverviewType::HSV) { + drawWaveformPartHSV(&painter, + pWaveform, + nullptr, + dataSize, + signalColors, + mono); + } else if (type == mixxx::OverviewType::Filtered) { + drawWaveformPartLMH(&painter, + pWaveform, + nullptr, + dataSize, + signalColors, + mono); + } else { + drawWaveformPartRGB(&painter, + pWaveform, + nullptr, + dataSize, + signalColors, + mono); + } + + // Evaluate waveform ratio peak + float peak = 1; + for (int i = 0; i < dataSize; i += 2) { + peak = math_max3( + peak, + static_cast(pWaveform->getAll(i)), + static_cast(pWaveform->getAll(i + 1))); + } + // Normalize + WaveformWidgetFactory* widgetFactory = WaveformWidgetFactory::instance(); + float diffGain = 0; + bool normalize = widgetFactory->isOverviewNormalized(); + if (normalize && peak > 1) { + diffGain = 255 - peak - 1; + } else { + const auto visualGain = static_cast( + widgetFactory->getVisualGain(BandIndex::AllBand)); + diffGain = 255.0f - (255.0f / visualGain); + } + + const int topLeft = static_cast(mono ? diffGain * 2 : diffGain); + const QRect sourceRect(0, + topLeft, + image.width(), + image.height() - + 2 * static_cast(diffGain)); + QImage croppedImage = image.copy(sourceRect); + // Copy image, otherwise QPainter crashes when we alter it. + QImage normImage = croppedImage.scaled(image.size(), + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + + return normImage; +} + +void drawWaveformPartRGB( + QPainter* pPainter, + ConstWaveformPointer pWaveform, + int* start, + int end, + const WaveformSignalColors& signalColors, + bool mono) { + ScopedTimer t(QStringLiteral("waveformOverviewRenderer::drawNextPixmapPartRGB")); + int startVal = 0; + if (start) { + startVal = *start; + } + + const QColor lowColor = signalColors.getRgbLowColor(); + const QColor midColor = signalColors.getRgbMidColor(); + const QColor highColor = signalColors.getRgbHighColor(); + QColor color; + + float lowColor_r = 0, lowColor_g = 0, lowColor_b = 0, + midColor_r = 0, midColor_g = 0, midColor_b = 0, + highColor_r = 0, highColor_g = 0, highColor_b = 0, + all = 0, low = 0, mid = 0, high = 0, + red = 0, green = 0, blue = 0, max = 0; + + getRgbF(lowColor, &lowColor_r, &lowColor_g, &lowColor_b); + getRgbF(midColor, &midColor_r, &midColor_g, &midColor_b); + getRgbF(highColor, &highColor_r, &highColor_g, &highColor_b); + + if (mono) { + // Mono means we're going to paint from bottom to top with l+r. + const qreal dy = pPainter->deviceTransform().dy(); + pPainter->resetTransform(); + // shift y0 to bottom + pPainter->translate(0, 2 * dy); + // flip y-axis + pPainter->scale(1, -1); + for (int i = startVal, x = startVal / 2; i < end; i += 2, ++x) { + // Left + all = pWaveform->getAll(i) + pWaveform->getAll(i + 1); + low = pWaveform->getLow(i) + pWaveform->getLow(i + 1); + mid = pWaveform->getMid(i) + pWaveform->getMid(i + 1); + high = pWaveform->getHigh(i) + pWaveform->getHigh(i + 1); + + red = low * lowColor_r + mid * midColor_r + high * highColor_r; + green = low * lowColor_g + mid * midColor_g + high * highColor_g; + blue = low * lowColor_b + mid * midColor_b + high * highColor_b; + // Normalize + max = math_max3(red, green, blue); + // Draw + if (max > 0.0) { + color.setRgbF(static_cast(low / max), + static_cast(mid / max), + static_cast(high / max)); + pPainter->setPen(color); + pPainter->drawLine(x, static_cast(all), x, 0); + } + } + } else { // stereo + for (int i = startVal, x = startVal / 2; i < end; i += 2, ++x) { + // Left + all = pWaveform->getAll(i); + low = pWaveform->getLow(i); + mid = pWaveform->getMid(i); + high = pWaveform->getHigh(i); + + red = low * lowColor_r + mid * midColor_r + high * highColor_r; + green = low * lowColor_g + mid * midColor_g + high * highColor_g; + blue = low * lowColor_b + mid * midColor_b + high * highColor_b; + // Normalize + max = math_max3(red, green, blue); + // Draw + if (max > 0.0) { + color.setRgbF(static_cast(low / max), + static_cast(mid / max), + static_cast(high / max)); + pPainter->setPen(color); + pPainter->drawLine(x, static_cast(-all), x, 0); + } + + // Right + all = pWaveform->getAll(i + 1); + low = pWaveform->getLow(i + 1); + mid = pWaveform->getMid(i + 1); + high = pWaveform->getHigh(i + 1); + + red = low * lowColor_r + mid * midColor_r + high * highColor_r; + green = low * lowColor_g + mid * midColor_g + high * highColor_g; + blue = low * lowColor_b + mid * midColor_b + high * highColor_b; + + max = math_max3(red, green, blue); + + if (max > 0.0) { + color.setRgbF(static_cast(low / max), + static_cast(mid / max), + static_cast(high / max)); + pPainter->setPen(color); + pPainter->drawLine(x, 0, x, static_cast(all)); + } + } + } + + if (start) { + *start = end; + } +} + +void drawWaveformPartLMH( + QPainter* pPainter, + ConstWaveformPointer pWaveform, + int* start, + int end, + const WaveformSignalColors& signalColors, + bool mono) { + ScopedTimer t(QStringLiteral("waveformOverviewRenderer::drawNextPixmapPartLMH")); + const QColor lowColor = signalColors.getLowColor(); + const QColor midColor = signalColors.getMidColor(); + const QColor highColor = signalColors.getHighColor(); + int startVal = 0; + if (start) { + startVal = *start; + } + + if (mono) { + // Mono means we're going to paint from bottom to top with l+r. + const qreal dy = pPainter->deviceTransform().dy(); + pPainter->resetTransform(); + // shift y0 to bottom + pPainter->translate(0, 2 * dy); + // flip y-axis + pPainter->scale(1, -1); + + for (int i = startVal, x = startVal / 2; i < end; i += 2, ++x) { + x = i / 2; + pPainter->setPen(lowColor); + pPainter->drawLine(QPoint(x, 0), + QPoint(x, pWaveform->getLow(i) + pWaveform->getLow(i + 1))); + + pPainter->setPen(midColor); + pPainter->drawLine(QPoint(x, 0), + QPoint(x, pWaveform->getMid(i) + pWaveform->getMid(i + 1))); + + pPainter->setPen(highColor); + pPainter->drawLine(QPoint(x, 0), + QPoint(x, pWaveform->getHigh(i) + pWaveform->getHigh(i + 1))); + } + } else { // stereo + for (int i = startVal, x = startVal / 2; i < end; i += 2, ++x) { + x = i / 2; + pPainter->setPen(lowColor); + pPainter->drawLine(QPoint(x, -pWaveform->getLow(i)), + QPoint(x, pWaveform->getLow(i + 1))); + + pPainter->setPen(midColor); + pPainter->drawLine(QPoint(x, -pWaveform->getMid(i)), + QPoint(x, pWaveform->getMid(i + 1))); + + pPainter->setPen(highColor); + pPainter->drawLine(QPoint(x, -pWaveform->getHigh(i)), + QPoint(x, pWaveform->getHigh(i + 1))); + } + } + + if (start) { + *start = end; + } +} + +void drawWaveformPartHSV( + QPainter* pPainter, + ConstWaveformPointer pWaveform, + int* start, + int end, + const WaveformSignalColors& signalColors, + bool mono) { + ScopedTimer t(QStringLiteral("waveformOverviewRenderer::drawNextPixmapPartHSV")); + int startVal = 0; + if (start) { + startVal = *start; + } + + float h = 0, s = 0, v = 0, lo = 0, hi = 0, total = 0; + // Get HSV of low color. + const QColor lowColor = signalColors.getLowColor(); + getHsvF(lowColor, &h, &s, &v); + QColor color; + + unsigned char low[2] = {0, 0}; + unsigned char high[2] = {0, 0}; + unsigned char mid[2] = {0, 0}; + unsigned char all[2] = {0, 0}; + + if (mono) { + // Mono means we're going to paint from bottom to top with l+r. + const qreal dy = pPainter->deviceTransform().dy(); + pPainter->resetTransform(); + // shift y0 to bottom + pPainter->translate(0, 2 * dy); + // flip y-axis + pPainter->scale(1, -1); + } + + for (int i = startVal, x = startVal / 2; i < end; i += 2, ++x) { + x = i / 2; + all[0] = pWaveform->getAll(i); + all[1] = pWaveform->getAll(i + 1); + + if (!all[0] && !all[1]) { + continue; + } + + low[0] = pWaveform->getLow(i); + low[1] = pWaveform->getLow(i + 1); + mid[0] = pWaveform->getMid(i); + mid[1] = pWaveform->getMid(i + 1); + high[0] = pWaveform->getHigh(i); + high[1] = pWaveform->getHigh(i + 1); + + total = (low[0] + low[1] + mid[0] + mid[1] + + high[0] + high[1]) * + 1.2f; + + // Prevent division by zero + if (total > 0) { + // Normalize low and high + // (mid not need, because it not change the color) + lo = (low[0] + low[1]) / total; + hi = (high[0] + high[1]) / total; + } else { + lo = hi = 0.0; + } + + // Set color + color.setHsvF(h, 1.0f - hi, 1.0f - lo); + + if (mono) { + pPainter->setPen(color); + pPainter->drawLine(QPoint(i / 2, 0), + QPoint(i / 2, all[0] + all[1])); + } else { + pPainter->setPen(color); + pPainter->drawLine(QPoint(i / 2, -all[0]), + QPoint(i / 2, all[1])); + } + } + + if (start) { + *start = end; + } +} + +} // namespace waveformOverviewRenderer diff --git a/src/waveform/renderers/waveformoverviewrenderer.h b/src/waveform/renderers/waveformoverviewrenderer.h new file mode 100644 index 000000000000..8a234be91b58 --- /dev/null +++ b/src/waveform/renderers/waveformoverviewrenderer.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include "waveform/overviewtype.h" +#include "waveform/waveform.h" + +class QPainter; +class WaveformSignalColors; + +namespace waveformOverviewRenderer { +QImage render(ConstWaveformPointer pWaveform, + mixxx::OverviewType type, + const WaveformSignalColors& signalColors, + bool mono = false); +/// These paint methods allow "mono" rendering (mono-mixdown, bottom-aligned). +/// Note: Don't use mono = true with WOverview, it's not adjusted yet! It does some +/// additional scaling for normalization which will atm cut off the bottom part. +void drawWaveformPartRGB( + QPainter* pPainter, + ConstWaveformPointer pWaveform, + int* start, + int end, + const WaveformSignalColors& signalColors, + bool mono = false); +void drawWaveformPartLMH( + QPainter* pPainter, + ConstWaveformPointer pWaveform, + int* start, + int end, + const WaveformSignalColors& signalColors, + bool mono = false); +void drawWaveformPartHSV( + QPainter* pPainter, + ConstWaveformPointer pWaveform, + int* start, + int end, + const WaveformSignalColors& signalColors, + bool mono = false); +} // namespace waveformOverviewRenderer diff --git a/src/widget/wbattery.cpp b/src/widget/wbattery.cpp index c94fe0c80517..8955ba4baa54 100644 --- a/src/widget/wbattery.cpp +++ b/src/widget/wbattery.cpp @@ -145,12 +145,12 @@ void WBattery::setPixmap(PaintablePointer* ppPixmap, const PixmapSource& source, Paintable::DrawMode mode, double scaleFactor) { PaintablePointer pPixmap = WPixmapStore::getPaintable(source, mode, scaleFactor); if (!pPixmap || pPixmap->isNull()) { - qDebug() << this << "Error loading pixmap:" << source.getPath(); + qDebug() << "WBattery: Error loading pixmap:" << source.getPath(); } else { - *ppPixmap = pPixmap; - if (mode == Paintable::DrawMode::Fixed) { - setFixedSize(pPixmap->size()); - } + *ppPixmap = pPixmap; + if (mode == Paintable::DrawMode::Fixed) { + setFixedSize(pPixmap->size()); + } } } diff --git a/src/widget/wdisplay.cpp b/src/widget/wdisplay.cpp index bc0a28b86689..27dbda71a594 100644 --- a/src/widget/wdisplay.cpp +++ b/src/widget/wdisplay.cpp @@ -86,7 +86,7 @@ void WDisplay::setPixmapBackground(const PixmapSource& source, double scaleFactor) { m_pPixmapBack = WPixmapStore::getPaintable(source, mode, scaleFactor); if (!m_pPixmapBack || m_pPixmapBack->isNull()) { - qDebug() << metaObject()->className() + qDebug() << metaObject()->className() << objectName() << "Error loading background pixmap:" << source.getPath(); } } @@ -104,8 +104,8 @@ void WDisplay::setPixmap( PixmapSource source(filename); PaintablePointer pPixmap = WPixmapStore::getPaintable(source, mode, scaleFactor); if (!pPixmap || pPixmap->isNull()) { - qDebug() << metaObject()->className() - << "Error loading pixmap:" << filename; + qDebug() << metaObject()->className() << objectName() + << "Error loading pixmap:" << filename << "for" << iPos; } else { (*pPixmaps)[iPos] = pPixmap; if (mode == Paintable::DrawMode::Fixed) { diff --git a/src/widget/weffectchainpresetbutton.cpp b/src/widget/weffectchainpresetbutton.cpp index 3b7e7d694b86..402f3e081a80 100644 --- a/src/widget/weffectchainpresetbutton.cpp +++ b/src/widget/weffectchainpresetbutton.cpp @@ -1,6 +1,5 @@ #include "widget/weffectchainpresetbutton.h" -#include #include #include "effects/effectparameter.h" @@ -11,6 +10,7 @@ #include "moc_weffectchainpresetbutton.cpp" #include "util/parented_ptr.h" #include "widget/effectwidgetutils.h" +#include "widget/wmenucheckbox.h" WEffectChainPresetButton::WEffectChainPresetButton(QWidget* parent, EffectsManager* pEffectsManager) : QPushButton(parent), @@ -138,7 +138,7 @@ void WEffectChainPresetButton::populateMenu() { const auto& hiddenParameters = pEffectSlot->getHiddenParameters().value(parameterType); for (const auto& parameters : {loadedParameters, hiddenParameters}) { for (const auto& pParameter : parameters) { - auto pCheckbox = make_parented(pEffectMenu); + auto pCheckbox = make_parented(pEffectMenu); pCheckbox->setChecked(true); pCheckbox->setText(pParameter->manifest()->name()); #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) diff --git a/src/widget/wlibrary.cpp b/src/widget/wlibrary.cpp index 8787b3f04879..dcc0390dde33 100644 --- a/src/widget/wlibrary.cpp +++ b/src/widget/wlibrary.cpp @@ -30,6 +30,8 @@ void WLibrary::setup(const QDomNode& node, const SkinContext& context) { kDefaultTrackTableBackgroundColorOpacity), kMinTrackTableBackgroundColorOpacity, kMaxTrackTableBackgroundColorOpacity); + + m_overviewSignalColors.setup(node, context); } bool WLibrary::registerView(const QString& name, QWidget* pView) { diff --git a/src/widget/wlibrary.h b/src/widget/wlibrary.h index a017e9d823dd..64c68cfe6f38 100644 --- a/src/widget/wlibrary.h +++ b/src/widget/wlibrary.h @@ -8,6 +8,7 @@ #include "library/libraryview.h" #include "skin/legacy/skincontext.h" #include "util/compatibility/qmutex.h" +#include "waveform/renderers/waveformsignalcolors.h" #include "widget/wbasewidget.h" class LibraryView; @@ -55,8 +56,13 @@ class WLibrary : public QStackedWidget, public WBaseWidget { return m_bShowButtonText; } + WaveformSignalColors getOverviewSignalColors() const { + return m_overviewSignalColors; + } + signals: - FocusWidget setLibraryFocus(FocusWidget newFocus); + FocusWidget setLibraryFocus(FocusWidget newFocus, + Qt::FocusReason focusReason = Qt::OtherFocusReason); public slots: // Show the view registered with the given name. Does nothing if the current @@ -77,4 +83,5 @@ class WLibrary : public QStackedWidget, public WBaseWidget { QMap m_viewMap; double m_trackTableBackgroundColorOpacity; bool m_bShowButtonText; + WaveformSignalColors m_overviewSignalColors; }; diff --git a/src/widget/wlibrarysidebar.h b/src/widget/wlibrarysidebar.h index f74345c5f22a..2e092751ae66 100644 --- a/src/widget/wlibrarysidebar.h +++ b/src/widget/wlibrarysidebar.h @@ -38,7 +38,8 @@ class WLibrarySidebar : public QTreeView, public WBaseWidget { void rightClicked(const QPoint&, const QModelIndex&); void renameItem(const QModelIndex&); void deleteItem(const QModelIndex&); - FocusWidget setLibraryFocus(FocusWidget newFocus); + FocusWidget setLibraryFocus(FocusWidget newFocus, + Qt::FocusReason focusReason = Qt::OtherFocusReason); protected: bool event(QEvent* pEvent) override; diff --git a/src/widget/wlibrarytableview.h b/src/widget/wlibrarytableview.h index ad060872f2f4..fb915d2ce107 100644 --- a/src/widget/wlibrarytableview.h +++ b/src/widget/wlibrarytableview.h @@ -66,7 +66,7 @@ class WLibraryTableView : public QTableView, public virtual LibraryView { #endif bool play = false); void trackSelected(TrackPointer pTrack); - void onlyCachedCoverArt(bool); + void onlyCachedCoversAndOverviews(bool); void scrollValueChanged(int); FocusWidget setLibraryFocus(FocusWidget newFocus); diff --git a/src/widget/wmainmenubar.cpp b/src/widget/wmainmenubar.cpp index 0d07eaff3966..b22196815dfa 100644 --- a/src/widget/wmainmenubar.cpp +++ b/src/widget/wmainmenubar.cpp @@ -169,6 +169,34 @@ void WMainMenuBar::initialize() { pLibraryMenu->addSeparator(); + QString searchHereTitle = tr("Search in Current View..."); + QString searchHereText = tr("Search for tracks in the current library view"); + auto* pSearchHere = new QAction(searchHereTitle, this); + pSearchHere->setShortcut(QKeySequence(m_pKbdConfig->getValue( + ConfigKey("[KeyboardShortcuts]", "LibraryMenu_SearchInCurrentView"), + tr("Ctrl+f")))); + pSearchHere->setShortcutContext(Qt::ApplicationShortcut); + pSearchHere->setStatusTip(searchHereText); + pSearchHere->setWhatsThis(buildWhatsThis(searchHereTitle, searchHereText)); + connect(pSearchHere, &QAction::triggered, this, &WMainMenuBar::searchInCurrentView); + pLibraryMenu->addAction(pSearchHere); + + QString searchAllTitle = tr("Search in Tracks Library..."); + QString searchAllText = + tr("Search in the internal track collection under \"Tracks\" in " + "the library"); + auto* pSearchAll = new QAction(searchAllTitle, this); + pSearchAll->setShortcut(QKeySequence(m_pKbdConfig->getValue( + ConfigKey("[KeyboardShortcuts]", "LibraryMenu_SearchInAllTracks"), + tr("Ctrl+Shift+F")))); + pSearchAll->setShortcutContext(Qt::ApplicationShortcut); + pSearchAll->setStatusTip(searchAllText); + pSearchAll->setWhatsThis(buildWhatsThis(searchAllText, searchAllText)); + connect(pSearchAll, &QAction::triggered, this, &WMainMenuBar::searchInAllTracks); + pLibraryMenu->addAction(pSearchAll); + + pLibraryMenu->addSeparator(); + QString createPlaylistTitle = tr("Create &New Playlist"); QString createPlaylistText = tr("Create a new playlist"); auto* pLibraryCreatePlaylist = new QAction(createPlaylistTitle, this); diff --git a/src/widget/wmainmenubar.h b/src/widget/wmainmenubar.h index 98ec66c698f7..dd9fafadbcee 100644 --- a/src/widget/wmainmenubar.h +++ b/src/widget/wmainmenubar.h @@ -67,6 +67,8 @@ class WMainMenuBar : public QMenuBar { #ifdef __ENGINEPRIME__ void exportLibrary(); #endif + void searchInCurrentView(); + void searchInAllTracks(); void showAbout(); void showKeywheel(bool visible); void showPreferences(); diff --git a/src/widget/wmenucheckbox.cpp b/src/widget/wmenucheckbox.cpp new file mode 100644 index 000000000000..f3b387bb11c7 --- /dev/null +++ b/src/widget/wmenucheckbox.cpp @@ -0,0 +1,28 @@ +#include "widget/wmenucheckbox.h" + +#include + +#include "moc_wmenucheckbox.cpp" + +WMenuCheckBox::WMenuCheckBox(QWidget* pParent) + : WMenuCheckBox(QString(), pParent) { +} + +WMenuCheckBox::WMenuCheckBox(const QString& label, QWidget* pParent) + : QCheckBox(label, pParent) { + installEventFilter(this); +} + +bool WMenuCheckBox::eventFilter(QObject* pObj, QEvent* pEvent) { + if (isEnabled() && pEvent->type() == QEvent::HoverEnter) { + setFocus(Qt::MouseFocusReason); + } else if (pEvent->type() == QEvent::HoverLeave) { + // Also explicitly clear focus. This is required when we have other + // Q[Widget]Actions in the same menu which have a 'selected' but no + // 'focus' property. + clearFocus(); + } else if (pEvent->type() == QEvent::MouseButtonDblClick) { + return true; + } + return QCheckBox::eventFilter(pObj, pEvent); +} diff --git a/src/widget/wmenucheckbox.h b/src/widget/wmenucheckbox.h new file mode 100644 index 000000000000..237b34c528d4 --- /dev/null +++ b/src/widget/wmenucheckbox.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +/// This is a custom QCheckBox fixing some bugs/quirks that occur when QCheckBox +/// is packed into QWidgetAction for use in QMenu. +/// 1. fixed hover behavior: get focused (highlighted) on hover, no matter +/// where the pointer is. With original QCheckBox it'd only get focused +/// when the pointer hovers the label or the [ ] indicator -- hovering the +/// whitespace next to narrow labels had no effect. +/// 2. block double-clicks: originally, in conjunction with QWidgetAction, those +/// would first toggle the checkbox, then make the QWidgetAction emit the +/// 'activated' signal and thereby close the menu unexpectedly. +/// +/// Currently used in WTrackTableViewHeader menu, WTrackMenu's Crates menu and +/// WSearchrelatedMenu. +class WMenuCheckBox : public QCheckBox { + Q_OBJECT + public: + explicit WMenuCheckBox(QWidget* pParent = nullptr); + explicit WMenuCheckBox(const QString& label, QWidget* pParent = nullptr); + + bool eventFilter(QObject* pObj, QEvent* pEvent) override; +}; diff --git a/src/widget/woverview.cpp b/src/widget/woverview.cpp index 773cfbc12a6a..8dcce984d12c 100644 --- a/src/widget/woverview.cpp +++ b/src/widget/woverview.cpp @@ -21,6 +21,7 @@ #include "util/math.h" #include "util/painterscope.h" #include "util/timer.h" +#include "waveform/renderers/waveformoverviewrenderer.h" #include "waveform/waveform.h" #include "waveform/waveformwidgetfactory.h" #include "widget/controlwidgetconnection.h" @@ -40,7 +41,7 @@ WOverview::WOverview( : WWidget(parent), m_group(group), m_pConfig(pConfig), - m_type(Type::RGB), + m_type(mixxx::OverviewType::RGB), m_actualCompletion(0), m_pixmapDone(false), m_waveformPeak(-1.0), @@ -442,8 +443,8 @@ void WOverview::onPassthroughChange(double v) { void WOverview::slotTypeControlChanged(double v) { // Assert that v is in enum range to prevent UB. - DEBUG_ASSERT(v >= 0 && v < QMetaEnum::fromType().keyCount()); - Type type = static_cast(static_cast(v)); + DEBUG_ASSERT(v >= 0 && v < QMetaEnum::fromType().keyCount()); + mixxx::OverviewType type = static_cast(static_cast(v)); if (type == m_type) { return; } @@ -1024,9 +1025,11 @@ void WOverview::drawMarks(QPainter* pPainter, const float offset, const float ga float nextMarkPosition = -1.0f; for (auto m = std::next(it); m != m_marks.cend(); ++m) { const WaveformMarkPointer& otherMark = *m; - bool otherAtSameHeight = valign == (otherMark->m_align & Qt::AlignVertical_Mask); + bool otherAtSameHeight = + valign == (otherMark->m_align & Qt::AlignVertical_Mask); // Hotcues always show at least their number. - bool otherHasLabel = !otherMark->m_text.isEmpty() || otherMark->getHotCue() != Cue::kNoHotCue; + bool otherHasLabel = !otherMark->m_text.isEmpty() || + otherMark->getHotCue() != Cue::kNoHotCue; if (otherAtSameHeight && otherHasLabel) { nextMarkPosition = offset + static_cast( @@ -1121,7 +1124,7 @@ void WOverview::drawMarks(QPainter* pPainter, const float offset, const float ga double markTime = samplePositionToSeconds(markSamples); double markTimeRemaining = samplePositionToSeconds(trackSamples - markSamples); double markTimeDistance = samplePositionToSeconds(markSamples - currentPositionSamples); - QString cuePositionText = mixxx::Duration::formatTime(markTime) + " -" + + const QString cuePositionText = mixxx::Duration::formatTime(markTime) + " -" + mixxx::Duration::formatTime(markTimeRemaining); QString cueTimeDistanceText = mixxx::Duration::formatTime(fabs(markTimeDistance)); // Cast to int to avoid confusingly switching from -0:00 to 0:00 as @@ -1239,7 +1242,8 @@ void WOverview::drawTimeRuler(QPainter* pPainter) { qreal timeDistance = samplePositionToSeconds( (widgetPositionFraction - m_playpositionControl.get()) * trackSamples); - QString timeText = mixxx::Duration::formatTime(timePosition) + " -" + mixxx::Duration::formatTime(timePositionTillEnd); + const QString timeText = mixxx::Duration::formatTime(timePosition) + + " -" + mixxx::Duration::formatTime(timePositionTillEnd); m_timeRulerPositionLabel.prerender(textPoint, QPixmap(), @@ -1285,10 +1289,12 @@ void WOverview::drawMarkLabels(QPainter* pPainter, const float offset, const flo } } if (m_bShowCueTimes && - (pMark->m_label.intersects(m_cuePositionLabel) || pMark->m_label.intersects(m_cueTimeDistanceLabel))) { + (pMark->m_label.intersects(m_cuePositionLabel) || + pMark->m_label.intersects(m_cueTimeDistanceLabel))) { continue; } - if (pMark->m_label.intersects(m_timeRulerPositionLabel) || pMark->m_label.intersects(m_timeRulerDistanceLabel)) { + if (pMark->m_label.intersects(m_timeRulerPositionLabel) || + pMark->m_label.intersects(m_timeRulerDistanceLabel)) { continue; } @@ -1338,7 +1344,10 @@ void WOverview::drawMarkLabels(QPainter* pPainter, const float offset, const flo width(), devicePixelRatioF()); - if (!(markRange.m_durationLabel.intersects(m_cuePositionLabel) || markRange.m_durationLabel.intersects(m_cueTimeDistanceLabel) || markRange.m_durationLabel.intersects(m_timeRulerPositionLabel) || markRange.m_durationLabel.intersects(m_timeRulerDistanceLabel))) { + if (!(markRange.m_durationLabel.intersects(m_cuePositionLabel) || + markRange.m_durationLabel.intersects(m_cueTimeDistanceLabel) || + markRange.m_durationLabel.intersects(m_timeRulerPositionLabel) || + markRange.m_durationLabel.intersects(m_timeRulerDistanceLabel))) { markRange.m_durationLabel.draw(pPainter); } } @@ -1403,83 +1412,8 @@ bool WOverview::drawNextPixmapPart() { QPainter painter(&m_waveformSourceImage); painter.translate(0.0, static_cast(m_waveformSourceImage.height()) / 2.0); - if (m_type == Type::Filtered) { - drawNextPixmapPartLMH(&painter, pWaveform, nextCompletion); - } else if (m_type == Type::HSV) { - drawNextPixmapPartHSV(&painter, pWaveform, nextCompletion); - } else { // Type::RGB: - drawNextPixmapPartRGB(&painter, pWaveform, nextCompletion); - } - - m_waveformImageScaled = QImage(); - m_diffGain = 0; - - // Test if the complete waveform is done - if (m_actualCompletion >= dataSize - 2) { - m_pixmapDone = true; - // qDebug() << "m_waveformPeakRatio" << m_waveformPeak; - } - - return true; -} - -void WOverview::drawNextPixmapPartHSV(QPainter* pPainter, - ConstWaveformPointer pWaveform, - const int nextCompletion) { - DEBUG_ASSERT(!m_waveformSourceImage.isNull()); - ScopedTimer t(QStringLiteral("WOverview::drawNextPixmapPartHSV")); - - // Get HSV of low color. - float h, s, v; - getHsvF(m_signalColors.getLowColor(), &h, &s, &v); - - QColor color; - float lo, hi, total; - - unsigned char maxLow[2] = {0, 0}; - unsigned char maxHigh[2] = {0, 0}; - unsigned char maxMid[2] = {0, 0}; - unsigned char maxAll[2] = {0, 0}; - - int currentCompletion = 0; - for (int currentCompletion = m_actualCompletion; - currentCompletion < nextCompletion; - currentCompletion += 2) { - maxAll[0] = pWaveform->getAll(currentCompletion); - maxAll[1] = pWaveform->getAll(currentCompletion + 1); - if (maxAll[0] || maxAll[1]) { - maxLow[0] = pWaveform->getLow(currentCompletion); - maxLow[1] = pWaveform->getLow(currentCompletion + 1); - maxMid[0] = pWaveform->getMid(currentCompletion); - maxMid[1] = pWaveform->getMid(currentCompletion + 1); - maxHigh[0] = pWaveform->getHigh(currentCompletion); - maxHigh[1] = pWaveform->getHigh(currentCompletion + 1); - - total = (maxLow[0] + maxLow[1] + maxMid[0] + maxMid[1] + - maxHigh[0] + maxHigh[1]) * - 1.2f; - - // Prevent division by zero - if (total > 0) { - // Normalize low and high - // (mid not need, because it not change the color) - lo = (maxLow[0] + maxLow[1]) / total; - hi = (maxHigh[0] + maxHigh[1]) / total; - } else { - lo = hi = 0.0; - } - - // Set color - color.setHsvF(h, 1.0f - hi, 1.0f - lo); - - pPainter->setPen(color); - pPainter->drawLine(QPoint(currentCompletion / 2, -maxAll[0]), - QPoint(currentCompletion / 2, maxAll[1])); - } - } - // Evaluate waveform ratio peak - for (currentCompletion = m_actualCompletion; + for (int currentCompletion = m_actualCompletion; currentCompletion < nextCompletion; currentCompletion += 2) { m_waveformPeak = math_max3( @@ -1488,145 +1422,38 @@ void WOverview::drawNextPixmapPartHSV(QPainter* pPainter, static_cast(pWaveform->getAll(currentCompletion + 1))); } - m_actualCompletion = nextCompletion; -} - -void WOverview::drawNextPixmapPartLMH(QPainter* pPainter, - ConstWaveformPointer pWaveform, - const int nextCompletion) { - DEBUG_ASSERT(!m_waveformSourceImage.isNull()); - ScopedTimer t(QStringLiteral("WOverview::drawNextPixmapPartLMH")); - - QColor lowColor = m_signalColors.getLowColor(); - QPen lowColorPen(QBrush(lowColor), 1); - - QColor midColor = m_signalColors.getMidColor(); - QPen midColorPen(QBrush(midColor), 1); - - QColor highColor = m_signalColors.getHighColor(); - QPen highColorPen(QBrush(highColor), 1); - - int currentCompletion = 0; - for (currentCompletion = m_actualCompletion; - currentCompletion < nextCompletion; - currentCompletion += 2) { - unsigned char lowNeg = pWaveform->getLow(currentCompletion); - unsigned char lowPos = pWaveform->getLow(currentCompletion + 1); - if (lowPos || lowNeg) { - pPainter->setPen(lowColorPen); - pPainter->drawLine(QPoint(currentCompletion / 2, -lowNeg), - QPoint(currentCompletion / 2, lowPos)); - } - } - - for (currentCompletion = m_actualCompletion; - currentCompletion < nextCompletion; - currentCompletion += 2) { - pPainter->setPen(midColorPen); - pPainter->drawLine(QPoint(currentCompletion / 2, - -pWaveform->getMid(currentCompletion)), - QPoint(currentCompletion / 2, - pWaveform->getMid(currentCompletion + 1))); - } - - for (currentCompletion = m_actualCompletion; - currentCompletion < nextCompletion; - currentCompletion += 2) { - pPainter->setPen(highColorPen); - pPainter->drawLine(QPoint(currentCompletion / 2, - -pWaveform->getHigh(currentCompletion)), - QPoint(currentCompletion / 2, - pWaveform->getHigh(currentCompletion + 1))); + if (m_type == mixxx::OverviewType::Filtered) { + waveformOverviewRenderer::drawWaveformPartLMH( + &painter, + pWaveform, + &m_actualCompletion, + nextCompletion, + m_signalColors); + } else if (m_type == mixxx::OverviewType::HSV) { + waveformOverviewRenderer::drawWaveformPartHSV( + &painter, + pWaveform, + &m_actualCompletion, + nextCompletion, + m_signalColors); + } else { // mixxx::OverviewType::RGB: + waveformOverviewRenderer::drawWaveformPartRGB( + &painter, + pWaveform, + &m_actualCompletion, + nextCompletion, + m_signalColors); } - // Evaluate waveform ratio peak - - for (currentCompletion = m_actualCompletion; - currentCompletion < nextCompletion; - currentCompletion += 2) { - m_waveformPeak = math_max3( - m_waveformPeak, - static_cast(pWaveform->getAll(currentCompletion)), - static_cast(pWaveform->getAll(currentCompletion + 1))); - } - - m_actualCompletion = nextCompletion; -} - -void WOverview::drawNextPixmapPartRGB(QPainter* pPainter, - ConstWaveformPointer pWaveform, - const int nextCompletion) { - DEBUG_ASSERT(!m_waveformSourceImage.isNull()); - ScopedTimer t(QStringLiteral("WOverview::drawNextPixmapPartRGB")); - - QColor color; - - float lowColor_r, lowColor_g, lowColor_b; - getRgbF(m_signalColors.getRgbLowColor(), &lowColor_r, &lowColor_g, &lowColor_b); - - float midColor_r, midColor_g, midColor_b; - getRgbF(m_signalColors.getRgbMidColor(), &midColor_r, &midColor_g, &midColor_b); - - float highColor_r, highColor_g, highColor_b; - getRgbF(m_signalColors.getRgbHighColor(), &highColor_r, &highColor_g, &highColor_b); - - int currentCompletion = 0; - for (currentCompletion = m_actualCompletion; - currentCompletion < nextCompletion; - currentCompletion += 2) { - unsigned char left = pWaveform->getAll(currentCompletion); - unsigned char right = pWaveform->getAll(currentCompletion + 1); - - // Retrieve "raw" LMH values from waveform - float low = static_cast(pWaveform->getLow(currentCompletion)); - float mid = static_cast(pWaveform->getMid(currentCompletion)); - float high = static_cast(pWaveform->getHigh(currentCompletion)); - - // Do matrix multiplication - float red = low * lowColor_r + mid * midColor_r + high * highColor_r; - float green = low * lowColor_g + mid * midColor_g + high * highColor_g; - float blue = low * lowColor_b + mid * midColor_b + high * highColor_b; - - // Normalize and draw - float max = math_max3(red, green, blue); - if (max > 0.0) { - color.setRgbF(red / max, green / max, blue / max); - pPainter->setPen(color); - pPainter->drawLine(QPointF(currentCompletion / 2, -left), - QPointF(currentCompletion / 2, 0)); - } - - // Retrieve "raw" LMH values from waveform - low = static_cast(pWaveform->getLow(currentCompletion + 1)); - mid = static_cast(pWaveform->getMid(currentCompletion + 1)); - high = static_cast(pWaveform->getHigh(currentCompletion + 1)); - - // Do matrix multiplication - red = low * lowColor_r + mid * midColor_r + high * highColor_r; - green = low * lowColor_g + mid * midColor_g + high * highColor_g; - blue = low * lowColor_b + mid * midColor_b + high * highColor_b; - - // Normalize and draw - max = math_max3(red, green, blue); - if (max > 0.0) { - color.setRgbF(red / max, green / max, blue / max); - pPainter->setPen(color); - pPainter->drawLine(QPointF(currentCompletion / 2, 0), - QPointF(currentCompletion / 2, right)); - } - } + m_waveformImageScaled = QImage(); + m_diffGain = 0; - // Evaluate waveform ratio peak - for (currentCompletion = m_actualCompletion; - currentCompletion < nextCompletion; - currentCompletion += 2) { - m_waveformPeak = math_max3( - m_waveformPeak, - static_cast(pWaveform->getAll(currentCompletion)), - static_cast(pWaveform->getAll(currentCompletion + 1))); + // Test if the complete waveform is done + if (m_actualCompletion >= dataSize - 2) { + m_pixmapDone = true; } - m_actualCompletion = nextCompletion; + return true; } void WOverview::paintText(const QString& text, QPainter* pPainter) { diff --git a/src/widget/woverview.h b/src/widget/woverview.h index 373ed7b274de..75723d469da8 100644 --- a/src/widget/woverview.h +++ b/src/widget/woverview.h @@ -8,6 +8,7 @@ #include "track/track_decl.h" #include "track/trackid.h" #include "util/parented_ptr.h" +#include "waveform/overviewtype.h" #include "waveform/renderers/waveformmarkrange.h" #include "waveform/renderers/waveformmarkset.h" #include "waveform/renderers/waveformsignalcolors.h" @@ -32,13 +33,6 @@ class WOverview : public WWidget, public TrackDropTarget { void setup(const QDomNode& node, const SkinContext& context); virtual void initWithTrack(TrackPointer pTrack); - enum class Type { - Filtered, - HSV, - RGB, - }; - Q_ENUM(Type); - public slots: void onConnectedControlChanged(double dParameter, double dValue) override; void slotTrackLoaded(TrackPointer pTrack); @@ -152,7 +146,7 @@ class WOverview : public WWidget, public TrackDropTarget { const QString m_group; UserSettingsPointer m_pConfig; - Type m_type; + mixxx::OverviewType m_type; int m_actualCompletion; bool m_pixmapDone; float m_waveformPeak; diff --git a/src/widget/wpushbutton.cpp b/src/widget/wpushbutton.cpp index b12af08cc56c..30c8c38706f5 100644 --- a/src/widget/wpushbutton.cpp +++ b/src/widget/wpushbutton.cpp @@ -266,7 +266,9 @@ void WPushButton::setPixmap(int iState, if (!pPixmap || pPixmap->isNull()) { // Only log if it looks like the user tried to specify a pixmap. if (!source.isEmpty()) { - qDebug() << "WPushButton: Error loading pixmap:" << source.getPath(); + qDebug() << metaObject()->className() << objectName() + << "Error loading pixmap" << source.getPath() + << "for state" << iState; } } else if (mode == Paintable::DrawMode::Fixed) { // Set size of widget equal to pixmap size @@ -283,7 +285,8 @@ void WPushButton::setPixmapBackground(const PixmapSource& source, if (!source.isEmpty() && (!m_pPixmapBack || m_pPixmapBack->isNull())) { // Only log if it looks like the user tried to specify a pixmap. - qDebug() << "WPushButton: Error loading background pixmap:" << source.getPath(); + qDebug() << metaObject()->className() << objectName() + << "Error loading background pixmap:" << source.getPath(); } } diff --git a/src/widget/wsearchlineedit.cpp b/src/widget/wsearchlineedit.cpp index 1154fdf88e43..48fabe96348f 100644 --- a/src/widget/wsearchlineedit.cpp +++ b/src/widget/wsearchlineedit.cpp @@ -122,12 +122,6 @@ WSearchLineEdit::WSearchLineEdit(QWidget* pParent, UserSettingsPointer pConfig) this, &WSearchLineEdit::slotClearSearch); - QShortcut* setFocusShortcut = new QShortcut(QKeySequence(tr("Ctrl+F", "Search|Focus")), this); - connect(setFocusShortcut, - &QShortcut::activated, - this, - &WSearchLineEdit::slotSetShortcutFocus); - // Set up a timer to search after a few hundred milliseconds timeout. This // stops us from thrashing the database if you type really fast. m_debouncingTimer.setSingleShot(true); @@ -217,31 +211,35 @@ void WSearchLineEdit::setup(const QDomNode& node, const SkinContext& context) { setPalette(pal); m_clearButton->setToolTip(tr("Clear input") + "\n" + - tr("Clear the search bar input field") + "\n\n" + - - tr("Shortcut") + ": \n" + - tr("Ctrl+Backspace")); + tr("Clear the search bar input field")); +} +void WSearchLineEdit::setupToolTip(const QString& searchInCurrentViewShortcut, + const QString& searchInAllTracksShortcut) { setBaseTooltip(tr("Search", "noun") + "\n" + - tr("Enter a string to search for") + "\n" + - tr("Use operators like bpm:115-128, artist:BooFar, -year:1990") + - "\n" + tr("For more information see User Manual > Mixxx Library") + - "\n\n" + - tr("Shortcuts") + ": \n" + - tr("Ctrl+F") + " " + - tr("Focus", "Give search bar input focus") + "\n" + - tr("Return") + " " + - tr("Trigger search before search-as-you-type timeout or" - "jump to tracks view afterwards") + + tr("Enter a string to search for.") + " " + + tr("Use operators like bpm:115-128, artist:BooFar, -year:1990.") + + "\n" + tr("See User Manual > Mixxx Library for more information.") + + "\n\n" + searchInCurrentViewShortcut + ": " + + tr("Focus/Select All (Search in current view)", + "Give search bar input focus") + + "\n" + searchInAllTracksShortcut + ": " + + tr("Focus/Select All (Search in \'Tracks\' library view)") + + "\n\n" + tr("Additional Shortcuts When Focused:") + "\n" + + tr("Return") + ": " + + tr("Trigger search before search-as-you-type timeout or " + "focus tracks view afterwards") + "\n" + - tr("Ctrl+Backspace") + " " + - tr("Clear input", "Clear the search bar input field") + "\n" + - tr("Ctrl+Space") + " " + + tr("Esc or Ctrl+Return") + ": " + + tr("Immediately trigger search and focus tracks view", + "Exit search bar and leave focus") + + "\n" + tr("Ctrl+Space") + ": " + tr("Toggle search history", "Shows/hides the search history entries") + "\n" + - tr("Delete or Backspace") + " " + tr("Delete query from history") + "\n" + - tr("Esc") + " " + tr("Exit search", "Exit search bar and leave focus")); + tr("Delete or Backspace") + + " (" + tr("in search history") + "): " + + tr("Delete query from history")); } void WSearchLineEdit::loadQueriesFromConfig() { @@ -311,15 +309,12 @@ QString WSearchLineEdit::getSearchText() const { if (isEnabled()) { DEBUG_ASSERT(!currentText().isNull()); QString text = currentText(); - QCompleter* pCompleter = completer(); - if (pCompleter && hasSelectedText()) { - if (text.startsWith(pCompleter->completionPrefix()) && - pCompleter->completionPrefix().size() == lineEdit()->cursorPosition()) { - // Search for the entered text until the user has accepted the - // completion by pressing Enter or changed/deselected the selected - // completion text with Right or Left key - return pCompleter->completionPrefix(); - } + QString completionPrefix; + if (hasCompletionAvailable(&completionPrefix)) { + // Search for the entered text until the user has accepted the + // completion by pressing Enter or changed/deselected the selected + // completion text with Right or Left key + return completionPrefix; } return text; } else { @@ -402,7 +397,12 @@ void WSearchLineEdit::keyPressEvent(QKeyEvent* keyEvent) { if (slotClearSearchIfClearButtonHasFocus()) { return; } - if (hasSelectedText()) { + if (keyEvent->modifiers() & Qt::ControlModifier) { + // Esc and Ctrl+Enter should have the same effect + emit setLibraryFocus(FocusWidget::TracksTable); + return; + } + if (hasCompletionAvailable()) { QComboBox::keyPressEvent(keyEvent); slotTriggerSearch(); return; @@ -543,7 +543,8 @@ void WSearchLineEdit::slotTriggerSearch() { /// saves the current query as selection void WSearchLineEdit::slotSaveSearch() { m_saveTimer.stop(); - QString cText = currentText().trimmed(); + // Keep original text for UI, potentially with trailing spaces + QString cText = currentText(); int cIndex = findCurrentTextIndex(); #if ENABLE_TRACE_LOG kLogger.trace() @@ -562,13 +563,16 @@ void WSearchLineEdit::slotSaveSearch() { } if (cIndex > 0 || cIndex == -1) { // If the query doesn't exist yet or was not at top, insert it at the top - insertItem(0, cText); + insertItem(0, cText.trimmed()); } setCurrentIndex(0); while (count() > kMaxSearchEntries) { removeItem(kMaxSearchEntries); } + + // Set the text without spaces for UI + setTextBlockSignals(cText); } void WSearchLineEdit::slotMoveSelectedHistory(int steps) { @@ -789,11 +793,18 @@ void WSearchLineEdit::slotTextChanged(const QString& text) { m_saveTimer.start(kSaveTimeoutMillis); } -void WSearchLineEdit::slotSetShortcutFocus() { - if (hasFocus()) { +void WSearchLineEdit::setFocus(Qt::FocusReason focusReason) { + if (!hasFocus()) { + // selectAll will be called by setFocus - but only if hasFocus + // was false previously and focusReason is Tab, Backtab or Shortcut + QWidget::setFocus(focusReason); + } else if (focusReason == Qt::TabFocusReason || + focusReason == Qt::BacktabFocusReason || + focusReason == Qt::ShortcutFocusReason) { + // If this widget already had focus (which can happen when the user + // presses the shortcut key while already in the searchbox), + // we need to manually simulate this behavior instead. lineEdit()->selectAll(); - } else { - setFocus(Qt::ShortcutFocusReason); } } @@ -809,3 +820,17 @@ void WSearchLineEdit::slotSetFont(const QFont& font) { bool WSearchLineEdit::hasSelectedText() const { return lineEdit()->hasSelectedText(); } + +bool WSearchLineEdit::hasCompletionAvailable(QString* completionPrefix) const { + QCompleter* pCompleter = completer(); + QString prefix = pCompleter ? pCompleter->completionPrefix() : QString(); + if (!prefix.isEmpty() && hasSelectedText() && + lineEdit()->text().startsWith(prefix) && + prefix.size() == lineEdit()->cursorPosition()) { + if (completionPrefix) { + *completionPrefix = prefix; + } + return true; + } + return false; +} diff --git a/src/widget/wsearchlineedit.h b/src/widget/wsearchlineedit.h index 45d2d7910ca2..f83f76647f04 100644 --- a/src/widget/wsearchlineedit.h +++ b/src/widget/wsearchlineedit.h @@ -36,6 +36,10 @@ class WSearchLineEdit : public QComboBox, public WBaseWidget { ~WSearchLineEdit(); void setup(const QDomNode& node, const SkinContext& context); + void setupToolTip(const QString& searchInCurrentViewShortcut, + const QString& searchInAllTracksShortcut); + + void setFocus(Qt::FocusReason focusReason); protected: void resizeEvent(QResizeEvent*) override; @@ -47,7 +51,8 @@ class WSearchLineEdit : public QComboBox, public WBaseWidget { signals: void search(const QString& text); - FocusWidget setLibraryFocus(FocusWidget newFocusWidget); + FocusWidget setLibraryFocus(FocusWidget newFocusWidget, + Qt::FocusReason focusReason = Qt::OtherFocusReason); public slots: void slotSetFont(const QFont& font); @@ -65,7 +70,6 @@ class WSearchLineEdit : public QComboBox, public WBaseWidget { void slotDeleteCurrentItem(); private slots: - void slotSetShortcutFocus(); void slotTextChanged(const QString& text); void slotIndexChanged(int index); @@ -92,9 +96,10 @@ class WSearchLineEdit : public QComboBox, public WBaseWidget { void deleteSelectedListItem(); void triggerSearchDebounced(); bool hasSelectedText() const; + bool hasCompletionAvailable(QString* completionPrefix = nullptr) const; inline int findCurrentTextIndex() { - return findData(currentText(), Qt::DisplayRole); + return findData(currentText().trimmed(), Qt::DisplayRole); } QString getSearchText() const; diff --git a/src/widget/wsearchrelatedtracksmenu.cpp b/src/widget/wsearchrelatedtracksmenu.cpp index cd2a9dcb3813..11fa2ec9ed04 100644 --- a/src/widget/wsearchrelatedtracksmenu.cpp +++ b/src/widget/wsearchrelatedtracksmenu.cpp @@ -2,8 +2,10 @@ #include #include +#include #include #include +#include #include #include "library/searchquery.h" @@ -20,7 +22,7 @@ namespace { // a viable upper bound for the context menu. constexpr double kMaxMenuToAvailableScreenWidthRatio = 0.2; -const QString kActionTextPrefixSuffixSeparator = QStringLiteral(" | "); +const QString kActionTextPrefixSuffixSeparator = QStringLiteral(": "); inline QString quoteSearchQueryText(const QString& text) { return QChar('"') + text + QChar('"'); @@ -75,7 +77,7 @@ void WSearchRelatedTracksMenu::addTriggerSearchAction( actionTextPrefix, elidableTextSuffix); - auto pCheckBox = make_parented( + auto pCheckBox = make_parented( mixxx::escapeTextPropertyWithoutShortcuts(elidedActionText), this); pCheckBox->setProperty("query", searchQuery); @@ -130,7 +132,8 @@ QString WSearchRelatedTracksMenu::elideActionText( // TODO: Customize the suffix elision? Qt::ElideMiddle, maxWidthInPixels - prefixWidthInPixels); - return actionTextPrefixWithSeparator + elidedTextSuffix; + // Add some margin between the label and the separator bar (see paintEvent()) + return QStringLiteral(" ") + actionTextPrefixWithSeparator + elidedTextSuffix; } void WSearchRelatedTracksMenu::addActionsForTrack( @@ -358,7 +361,8 @@ void WSearchRelatedTracksMenu::addActionsForTrack( // Make the Search button a checkbox to simplify setting an icon via qss. // This is not possible with a QAction, and tedious with a QPushButton. - auto pCheckBox = make_parented(tr("&Search selected"), this); + // Use a custom QCheckBox with fixed hover behavior. + auto pCheckBox = make_parented(tr("&Search selected"), this); pCheckBox->setObjectName("SearchSelectedAction"); m_pSearchAction = make_parented(this); m_pSearchAction->setDefaultWidget(pCheckBox.get()); @@ -379,33 +383,35 @@ void WSearchRelatedTracksMenu::addActionsForTrack( bool WSearchRelatedTracksMenu::eventFilter(QObject* pObj, QEvent* e) { if (e->type() == QEvent::MouseButtonPress) { - // Clicking any spot in the checkbox that is not inside the indicator's - // 'click' rectangle triggers the search, ignoring other checked boxes. - // Clicks on the indicator are passed on to the event filter, hence - // toggling the checkbox happens as usual. + // Since we want tp provide a toggle function that allows to check multiple + // criteria (ie. don't auto-close the menu on first click) we need to + // figure the intended click target. + // Simply checking whether the click is inside the indicator's rectangle + // is not sufficient: the indicator's width & height is only about 60% + // of the item's total height, so there's top/left/bottom margin that + // would activate the action. + // Let's simply check if the click's x position is in the label region. + // If it is, trigger search ignoring other checked boxes. Else toggle it. QCheckBox* pBox = qobject_cast(pObj); if (pBox) { - QMouseEvent* pMe = static_cast(e); - VERIFY_OR_DEBUG_ASSERT(pMe) { - return true; - } + auto* pStyle = pBox->style(); QStyleOptionButton option; option.initFrom(pBox); - auto* pStyle = pBox->style(); - if (!pStyle) { - return true; - } - const QRect indicatorClickRect = pStyle->subElementRect(QStyle::SE_CheckBoxClickRect, + const QRect labelRect = pStyle->subElementRect(QStyle::SE_CheckBoxContents, &option, pBox); - if (!indicatorClickRect.contains(pMe->pos())) { - // Text ('border' ractangle) was clicked, trigger the search. + QMouseEvent* pMe = static_cast(e); + if (pMe->pos().x() > labelRect.left()) { + // Label region was clicked, trigger the search. const QString query = pBox->property("query").toString(); emit triggerSearch(query); // Note that this click will not emit QAction::triggered like // when pressing Return on a selected action, hence we need to // make sure WTrackMenu closes when receiving triggerSearch(). + } else { + pBox->toggle(); } + return true; } } return QObject::eventFilter(pObj, e); @@ -448,3 +454,31 @@ void WSearchRelatedTracksMenu::combineQueriesTriggerSearch() { emit triggerSearch(queryCombo); } } + +void WSearchRelatedCheckBox::paintEvent(QPaintEvent*) { + // start original QCheckBox implementation + QStylePainter painter(this); + QStyleOptionButton opt; + initStyleOption(&opt); + painter.drawControl(QStyle::CE_CheckBox, opt); + // end + + // Draw a vertical bar over the entire height at the left edge of the label + const QStyle* pStyle = style(); + const QRect labelRect = pStyle->subElementRect(QStyle::SE_CheckBoxContents, + &opt, + this); + const QRect frameRect = opt.rect; + const QPoint top(labelRect.left(), frameRect.top()); + const QPoint bottom(labelRect.left(), frameRect.bottom()); + // We draw with the separator color from qss or, if that's not set, + // with the palette's inactive text color. + const QPen linePen( + m_separatorColor.isValid() ? m_separatorColor + : opt.palette.color(QPalette::Disabled, QPalette::Text), + 1, + Qt::SolidLine, + Qt::SquareCap); + painter.setPen(linePen); + painter.drawLine(top, bottom); +} diff --git a/src/widget/wsearchrelatedtracksmenu.h b/src/widget/wsearchrelatedtracksmenu.h index 6efc344d424f..e3e1935a9c24 100644 --- a/src/widget/wsearchrelatedtracksmenu.h +++ b/src/widget/wsearchrelatedtracksmenu.h @@ -3,10 +3,32 @@ #include #include "util/parented_ptr.h" +#include "widget/wmenucheckbox.h" class Track; class QWidgetAction; +/// Extension of WMenuCheckBox with a vertical separator bar in between the +/// label and the indicator box. This is supposed to clarify the different +/// behavior of clicks in these two regions. +class WSearchRelatedCheckBox : public WMenuCheckBox { + Q_OBJECT + public: + explicit WSearchRelatedCheckBox(const QString& label, + QWidget* pParent = nullptr) + : WMenuCheckBox(label, pParent) { + } + + Q_PROPERTY(QColor separatorColor + MEMBER m_separatorColor + DESIGNABLE true); + + void paintEvent(QPaintEvent*) override; + + private: + QColor m_separatorColor; +}; + class WSearchRelatedTracksMenu : public QMenu { Q_OBJECT public: diff --git a/src/widget/wstatuslight.cpp b/src/widget/wstatuslight.cpp index 42af3c4c06bd..850e6f4cb6e9 100644 --- a/src/widget/wstatuslight.cpp +++ b/src/widget/wstatuslight.cpp @@ -68,7 +68,8 @@ void WStatusLight::setPixmap(int iState, setFixedSize(pPixmap->size()); } } else { - qDebug() << "WStatusLight: Error loading pixmap:" << source.getPath() << iState; + qDebug() << "WStatusLight" << objectName() << "Error loading pixmap" + << source.getPath() << "for state" << iState; m_pixmaps[iState].reset(); } } diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp index eeb7e7fb3dd4..4fbebf3b9eaa 100644 --- a/src/widget/wtrackmenu.cpp +++ b/src/widget/wtrackmenu.cpp @@ -46,6 +46,7 @@ #include "widget/wcoverartlabel.h" #include "widget/wcoverartmenu.h" #include "widget/wfindonwebmenu.h" +#include "widget/wmenucheckbox.h" #include "widget/wsearchrelatedtracksmenu.h" // WStarRating is required for DlgTrackInfo #include "widget/wstarrating.h" @@ -1630,7 +1631,8 @@ void WTrackMenu::slotPopulateCrateMenu() { while (allCrates.populateNext(&crate)) { auto pAction = make_parented( m_pCrateMenu); - auto pCheckBox = make_parented( + // Use a custom QCheckBox with fixed hover behavior. + auto pCheckBox = make_parented( mixxx::escapeTextPropertyWithoutShortcuts(crate.getName()), m_pCrateMenu); pCheckBox->setProperty("crateId", QVariant::fromValue(crate.getId())); diff --git a/src/widget/wtrackstemmenu.cpp b/src/widget/wtrackstemmenu.cpp index 5281ef6266ea..b4ff75035ebb 100644 --- a/src/widget/wtrackstemmenu.cpp +++ b/src/widget/wtrackstemmenu.cpp @@ -6,7 +6,6 @@ namespace { const QList stemTracks = { - mixxx::StemChannel::First, mixxx::StemChannel::Second, mixxx::StemChannel::Third, @@ -67,10 +66,12 @@ WTrackStemMenu::WTrackStemMenu(const QString& label, bool WTrackStemMenu::eventFilter(QObject* pObj, QEvent* e) { QInputEvent* pInputEvent = dynamic_cast(e); - if (pInputEvent != nullptr && - (pInputEvent->modifiers() & Qt::ControlModifier) != m_selectMode) { - m_selectMode = pInputEvent->modifiers() & Qt::ControlModifier; - updateActions(); + if (pInputEvent != nullptr) { + bool selectMode = pInputEvent->modifiers().testFlag(Qt::ControlModifier); + if (selectMode != m_selectMode) { + m_selectMode = selectMode; + updateActions(); + } } if (m_selectMode && (e->type() == QEvent::MouseButtonRelease)) { diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index b1f4a03557cf..271e86e82dcc 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -88,7 +88,7 @@ void WTrackTableView::enableCachedOnly() { if (!m_loadCachedOnly) { // don't try to load and search covers, drawing only // covers which are already in the QPixmapCache. - emit onlyCachedCoverArt(true); + emit onlyCachedCoversAndOverviews(true); m_loadCachedOnly = true; } m_lastUserAction = mixxx::Time::elapsed(); @@ -152,7 +152,7 @@ void WTrackTableView::slotGuiTick50ms(double /*unused*/) { // This allows CoverArtDelegate to request that we load covers from disk // (as opposed to only serving them from cache). - emit onlyCachedCoverArt(false); + emit onlyCachedCoversAndOverviews(false); m_loadCachedOnly = false; } } @@ -506,42 +506,85 @@ void WTrackTableView::slotUnhide() { void WTrackTableView::slotShowHideTrackMenu(bool show) { VERIFY_OR_DEBUG_ASSERT(m_pTrackMenu.get()) { - return; + initTrackMenu(); } if (show == m_pTrackMenu->isVisible()) { emit trackMenuVisible(show); return; } if (show) { - QContextMenuEvent event(QContextMenuEvent::Mouse, - mapFromGlobal(QCursor::pos()), - QCursor::pos()); - contextMenuEvent(&event); + const auto selectedIndices = selectionModel()->selectedIndexes(); + if (selectedIndices.isEmpty()) { + // If selection is empty, contextMenuEvent() won't work anyway. + return; + } + + // Show at current index if it's valid. When using only a controller for + // for track selection, there's only on row selected. + // If it's not part of the selection, like when Ctrl+click was used to + // deselect a row, we use the first selected row and the column of the + // current index. + // Else show at cursor position. + QPoint evPos; + const auto currIdx = currentIndex(); + if (currIdx.isValid()) { + if (selectedIndices.contains(currIdx)) { + evPos = visualRect(currIdx).center(); + } else { + // use first selected row and column of current index + const QList rows = getSelectedRowNumbers(); + const QModelIndex tempIdx = currIdx.siblingAtRow(rows.first()); + evPos = visualRect(tempIdx).center(); + } + // If the selected row is outside the table's viewport (above or below), + // let the menu unfold from the bottom center to hopefully clarify + // that the menu belongs to the library and not to some deck widget. + if (!viewport()->rect().contains(evPos)) { + evPos = QPoint(viewport()->rect().center().x(), + viewport()->rect().bottom()); + } else { + // The viewports start below the header, but mapToGlobal() uses + // the rect() as reference. Add header height to y and we're good. + // Assumes the view shows at least one row which is at least as tall + // as the header, else we'll end up below the viewport, then it's + // up to QMenu to find an adequate popup position. + evPos += QPoint(0, horizontalHeader()->height()); + } + evPos = mapToGlobal(evPos); + } else { + evPos = QCursor::pos(); + } + showTrackMenu(evPos, indexAt(evPos)); } else { m_pTrackMenu->close(); } } -void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { +void WTrackTableView::contextMenuEvent(QContextMenuEvent* pEvent) { VERIFY_OR_DEBUG_ASSERT(m_pTrackMenu.get()) { initTrackMenu(); } - event->accept(); + pEvent->accept(); + + showTrackMenu(pEvent->globalPos(), indexAt(pEvent->pos())); +} + +void WTrackTableView::showTrackMenu(const QPoint pos, const QModelIndex& index) { + VERIFY_OR_DEBUG_ASSERT(m_pTrackMenu.get()) { + return; + } // Update track indices in context menu const QModelIndexList indices = getSelectedRows(); if (indices.isEmpty()) { return; } - // TODO Also pass the index of the focused column so DlgTrackInfo/~Multi? - // They could then focus the respective edit field. m_pTrackMenu->loadTrackModelIndices(indices); - const QModelIndex clickedIdx = indexAt(event->pos()); - m_pTrackMenu->setTrackPropertyName(columnNameOfIndex(clickedIdx)); + m_pTrackMenu->setTrackPropertyName(columnNameOfIndex(index)); saveCurrentIndex(); - m_pTrackMenu->popup(event->globalPos()); - // WTrackmenu emits restoreCurrentViewStateOrIndex() if required + m_pTrackMenu->popup(pos); + // WTrackmenu emits restoreCurrentViewStateOrIndex() on hide if required } QString WTrackTableView::columnNameOfIndex(const QModelIndex& index) const { diff --git a/src/widget/wtracktableview.h b/src/widget/wtracktableview.h index 8ea734ba77a7..c6c4b7b19ec3 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -172,6 +172,7 @@ class WTrackTableView : public WLibraryTableView { TrackModel* getTrackModel() const; void initTrackMenu(); + void showTrackMenu(const QPoint pos, const QModelIndex& index); void hideOrRemoveSelectedTracks(); diff --git a/src/widget/wtracktableviewheader.cpp b/src/widget/wtracktableviewheader.cpp index 06fcb9104b92..af409b99891f 100644 --- a/src/widget/wtracktableviewheader.cpp +++ b/src/widget/wtracktableviewheader.cpp @@ -8,6 +8,7 @@ #include "moc_wtracktableviewheader.cpp" #include "util/math.h" #include "util/parented_ptr.h" +#include "widget/wmenucheckbox.h" #define WTTVH_MINIMUM_SECTION_SIZE 20 @@ -164,7 +165,8 @@ void WTrackTableViewHeader::setModel(QAbstractItemModel* model) { QString title = model->headerData(i, orientation()).toString(); - auto pCheckBox = make_parented(title, &m_menu); + // Custom QCheckBox with fixed hover behavior + auto pCheckBox = make_parented(title, &m_menu); // Keep a map of checkboxes and columns m_columnCheckBoxes.insert(i, pCheckBox.get()); connect(pCheckBox.get(), diff --git a/tools/macos_buildenv.sh b/tools/macos_buildenv.sh index caf6ce105713..4d2d19634e84 100755 --- a/tools/macos_buildenv.sh +++ b/tools/macos_buildenv.sh @@ -21,20 +21,20 @@ THIS_SCRIPT_NAME=${BASH_SOURCE[0]} if [ -n "${BUILDENV_ARM64}" ]; then VCPKG_TARGET_TRIPLET="arm64-osx-min1100-release" - BUILDENV_BRANCH="2.5-rel" - BUILDENV_NAME="mixxx-deps-2.5-arm64-osx-min1100-release-40c29ff" - BUILDENV_SHA256="b76685e77f681baf8fdc5037297b0f16d323a405d09ce276d8844304530278e1" + BUILDENV_BRANCH="2.6-rel" + BUILDENV_NAME="mixxx-deps-2.6-arm64-osx-min1100-release-98fd50c" + BUILDENV_SHA256="36f244abf251c98ce9a7a2cd56b825fbcc7d82f87fece0c182fcd1a0d5e9603b" else if [ -n "${BUILDENV_RELEASE}" ]; then VCPKG_TARGET_TRIPLET="x64-osx-min1100-release" - BUILDENV_BRANCH="2.5-rel" - BUILDENV_NAME="mixxx-deps-2.5-x64-osx-min1100-release-40c29ff" - BUILDENV_SHA256="a9b7dd2cb9ab00db6d05ac1f05aab933ed0ab2697f71db1a1bad70305befcf1b" + BUILDENV_BRANCH="2.6-rel" + BUILDENV_NAME="mixxx-deps-2.6-x64-osx-min1100-release-98fd50c" + BUILDENV_SHA256="fd3b27c06bad80c39d247763c30b7664bc3b1e15a0f145f5445e2b2942869001" else VCPKG_TARGET_TRIPLET="x64-osx-min1100" - BUILDENV_BRANCH="2.5" - BUILDENV_NAME="mixxx-deps-2.5-x64-osx-min1100-c15790e" - BUILDENV_SHA256="0252293436efed1b043d5c6ee384a9502ca0ade712eff95b2c0d2199d94598bb" + BUILDENV_BRANCH="2.6" + BUILDENV_NAME="mixxx-deps-2.6-x64-osx-min1100-12239ed" + BUILDENV_SHA256="0875cc8ad389aa859fc813eef10bb98dfc4ff94d6e9fbc9bfcd03ba5c1bfee59" fi fi diff --git a/tools/windows_buildenv.bat b/tools/windows_buildenv.bat index e74d89d76a86..991a31c1ee2c 100644 --- a/tools/windows_buildenv.bat +++ b/tools/windows_buildenv.bat @@ -21,16 +21,16 @@ IF NOT DEFINED INSTALL_ROOT ( ) IF DEFINED BUILDENV_RELEASE ( - SET BUILDENV_BRANCH=2.5-rel + SET BUILDENV_BRANCH=2.6-rel SET VCPKG_TARGET_TRIPLET=x64-windows-release vcpkg_update_main - SET BUILDENV_NAME=mixxx-deps-2.5-x64-windows-release-40c29ff - SET BUILDENV_SHA256=a9d809ae9c52d8a553af1bb8a58565649ced7b1f938d1d37c1c7d83ad53aacf3 + SET BUILDENV_NAME=mixxx-deps-2.6-x64-windows-release-98fd50c + SET BUILDENV_SHA256=dff7d5a8141ae2a4c13eb85fe45e6f2915b24c11329af52a11b38b430e6b1961 ) ELSE ( - SET BUILDENV_BRANCH=2.5 + SET BUILDENV_BRANCH=2.6 SET VCPKG_TARGET_TRIPLET=x64-windows - SET BUILDENV_NAME=mixxx-deps-2.5-x64-windows-c15790e - SET BUILDENV_SHA256=138e4685ec73c6a6a509f71f8573be581403b091e4ecea2314df2cc79f9720b9 + SET BUILDENV_NAME=mixxx-deps-2.6-x64-windows-12239ed + SET BUILDENV_SHA256=5c60b2c61d6448a99979d7cc997e92f2fd5b4b65f65e2439abfca3fa6fd30f8d ) IF "%~1"=="" ( @@ -218,7 +218,7 @@ REM Generate CMakeSettings.json which is read by MS Visual Studio to determine t REM Replace all \ by \\ in CMAKE_PREFIX_PATH REM CALL :AddCMakeVar2CMakeSettings_JSON "CMAKE_PREFIX_PATH" "STRING" "!CMAKE_PREFIX_PATH:\=\\!" CALL :AddCMakeVar2CMakeSettings_JSON "DEBUG_ASSERTIONS_FATAL" "BOOL" "True" - CALL :AddCMakeVar2CMakeSettings_JSON "FFMPEG" "BOOL" "False" + CALL :AddCMakeVar2CMakeSettings_JSON "FFMPEG" "BOOL" "True" CALL :AddCMakeVar2CMakeSettings_JSON "HID" "BOOL" "True" CALL :AddCMakeVar2CMakeSettings_JSON "HSS1394" "BOOL" "True" CALL :AddCMakeVar2CMakeSettings_JSON "KEYFINDER" "BOOL" "False"