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
+
+
+
+
+
+
+
+ Select the default layer.
+
+ eq
+ hotcues
+ samplers
+
+
+
+ Allows configuring the behavior of the ground effect LEDs.
+
+ off
+ end_of_track
+ beat_active
+
+
+
+ Pulse the appropriate deck selection button on the hotcue layer when the
+ track on that deck is ending.
+
+
+
+
+
+ The color of the selected deck button.
+ 1
+ 127
+ 18
+ 19
+ 30
+ 43
+ 42
+ 43
+ 54
+ 55
+ 66
+ 67
+ 78
+ 79
+ 90
+ 91
+ 102
+ 103
+ 114
+ 115
+ 121
+
+
+ The color of the non-active deck selection buttons.
+ 1
+ 127
+ 18
+ 19
+ 30
+ 43
+ 42
+ 43
+ 54
+ 55
+ 66
+ 67
+ 78
+ 79
+ 90
+ 91
+ 102
+ 103
+ 114
+ 115
+ 121
+
+
+ The color of the intro or outro buttons when set.
+ 1
+ 127
+ 18
+ 19
+ 30
+ 43
+ 42
+ 43
+ 54
+ 55
+ 66
+ 67
+ 78
+ 79
+ 90
+ 91
+ 102
+ 103
+ 114
+ 115
+ 121
+
+
+ The color of the intro or outro buttons when unset.
+ 1
+ 127
+ 18
+ 19
+ 30
+ 43
+ 42
+ 43
+ 54
+ 55
+ 66
+ 67
+ 78
+ 79
+ 90
+ 91
+ 102
+ 103
+ 114
+ 115
+ 121
+
+
+ The color of the activated EQ kill switch.
+ 1
+ 127
+ 18
+ 19
+ 30
+ 43
+ 42
+ 43
+ 54
+ 55
+ 66
+ 67
+ 78
+ 79
+ 90
+ 91
+ 102
+ 103
+ 114
+ 115
+ 121
+
+
+ The color of the inactive EQ kill switch.
+ 1
+ 127
+ 18
+ 19
+ 30
+ 43
+ 42
+ 43
+ 54
+ 55
+ 66
+ 67
+ 78
+ 79
+ 90
+ 91
+ 102
+ 103
+ 114
+ 115
+ 121
+
+
+ The color of the activated quick effect kill switch.
+ 1
+ 127
+ 18
+ 19
+ 30
+ 43
+ 42
+ 43
+ 54
+ 55
+ 66
+ 67
+ 78
+ 79
+ 90
+ 91
+ 102
+ 103
+ 114
+ 115
+ 121
+
+
+ The color of the inactive quick effect kill switch.
+ 1
+ 127
+ 18
+ 19
+ 30
+ 43
+ 42
+ 43
+ 54
+ 55
+ 66
+ 67
+ 78
+ 79
+ 90
+ 91
+ 102
+ 103
+ 114
+ 115
+ 121
+
+
+ The color of a sampler button with a loaded sound.
+ 1
+ 127
+ 18
+ 19
+ 30
+ 43
+ 42
+ 43
+ 54
+ 55
+ 66
+ 67
+ 78
+ 79
+ 90
+ 91
+ 102
+ 103
+ 114
+ 115
+ 121
+
+
+ The color of a sampler button with no loaded sound.
+ 1
+ 127
+ 18
+ 19
+ 30
+ 43
+ 42
+ 43
+ 54
+ 55
+ 66
+ 67
+ 78
+ 79
+ 90
+ 91
+ 102
+ 103
+ 114
+ 115
+ 121
+
+
+
+
+
+
+
+
+
+
+
+
+
+ [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"