Compare commits
142 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6607fef6e | ||
|
|
f815b2b699 | ||
|
|
247c0ae46a | ||
|
|
f62d22f94a | ||
|
|
78a9cb35a3 | ||
|
|
e72b001d02 | ||
|
|
5bdbb9f13f | ||
|
|
0e7ab1cab8 | ||
|
|
f11de5bfc2 | ||
|
|
6d68f7f008 | ||
|
|
2bed827eb6 | ||
|
|
98cfef3306 | ||
|
|
f44839137c | ||
|
|
0decbdc2fb | ||
|
|
23b5ceb6c9 | ||
|
|
4b36beec40 | ||
|
|
b60ca9f6fa | ||
|
|
718c24ee47 | ||
|
|
86dd2886d2 | ||
|
|
23fb4651be | ||
|
|
877c9ca41a | ||
|
|
99fb775e30 | ||
|
|
1bb764a2d2 | ||
|
|
a3eab902ff | ||
|
|
e9684cd1a1 | ||
|
|
9ae0afaaf7 | ||
|
|
48bb5a3e9f | ||
|
|
f642513587 | ||
|
|
255623bbfd | ||
|
|
4270b12cd1 | ||
|
|
e3e6a22172 | ||
|
|
eb30c654c5 | ||
|
|
3b925c24ad | ||
|
|
beefdabc64 | ||
|
|
91e06cadf3 | ||
|
|
4ea5eb8292 | ||
|
|
af06f8e70a | ||
|
|
3238e295ba | ||
|
|
26fbd5c144 | ||
|
|
e64a2b98c4 | ||
|
|
42417e5554 | ||
|
|
82e613206e | ||
|
|
f1038152e9 | ||
|
|
2597a8faed | ||
|
|
8008ec895a | ||
|
|
d85d25b931 | ||
|
|
de62552ad1 | ||
|
|
155485173b | ||
|
|
82079fcf70 | ||
|
|
3b2eea5292 | ||
|
|
1f2ad9c177 | ||
|
|
cc6e5a6c69 | ||
|
|
c77c7a247a | ||
|
|
552440f50e | ||
|
|
2a9ccd7480 | ||
|
|
f265c055a5 | ||
|
|
837e5388ea | ||
|
|
9883dc6925 | ||
|
|
e3ad00e930 | ||
|
|
6428ae8b3a | ||
|
|
07c182d5b8 | ||
|
|
64aa15842c | ||
|
|
19c8da06e6 | ||
|
|
7dd959f4a1 | ||
|
|
148ae530d8 | ||
|
|
e75698ee9a | ||
|
|
80bea31b98 | ||
|
|
4ea57d1181 | ||
|
|
854847ca8a | ||
|
|
bd39e7cb0d | ||
|
|
20ef621a20 | ||
|
|
145c276c97 | ||
|
|
1c5d0dceb1 | ||
|
|
359f320b06 | ||
|
|
108d522dcf | ||
|
|
96e746c508 | ||
|
|
b2c862e7d5 | ||
|
|
9334fe9f24 | ||
|
|
b964385024 | ||
|
|
3f3059c98b | ||
|
|
8da616491d | ||
|
|
cb0db8750f | ||
|
|
08224443e3 | ||
|
|
5c2989196f | ||
|
|
4c4c84e104 | ||
|
|
232399ea28 | ||
|
|
9d22e4ec07 | ||
|
|
ee5bc16e47 | ||
|
|
74f0f885b9 | ||
|
|
b914d9aaba | ||
|
|
136f150d67 | ||
|
|
5b50cbb61b | ||
|
|
cb3a9bf195 | ||
|
|
cb847951e6 | ||
|
|
11228f0634 | ||
|
|
a0889d60f1 | ||
|
|
0055ebe8a7 | ||
|
|
58c7a9b9cc | ||
|
|
6afc081ff0 | ||
|
|
2c0ad2fc88 | ||
|
|
77e934beab | ||
|
|
368022ec43 | ||
|
|
69f8ca95bc | ||
|
|
dde8661e93 | ||
|
|
2604e1a0ff | ||
|
|
e8471bcc66 | ||
|
|
d230dd7365 | ||
|
|
74dce24e91 | ||
|
|
bc667a6474 | ||
|
|
a2cae06582 | ||
|
|
5212587055 | ||
|
|
efd42bc68f | ||
|
|
ebaa2e7918 | ||
|
|
7ebcc73a49 | ||
|
|
be09011bb7 | ||
|
|
2778a55e8e | ||
|
|
9b5fe3bfd6 | ||
|
|
91eef0d695 | ||
|
|
88704efad8 | ||
|
|
f4e4483392 | ||
|
|
63102bce23 | ||
|
|
8890a3dd0f | ||
|
|
3d9dec2c27 | ||
|
|
c96ad61c80 | ||
|
|
acd74d5b54 | ||
|
|
4808188964 | ||
|
|
1bb045b3b0 | ||
|
|
bdca60c0ad | ||
|
|
8d9c135498 | ||
|
|
0f76482916 | ||
|
|
38eb86bdee | ||
|
|
cbce9892d5 | ||
|
|
f624b7a331 | ||
|
|
1ebcd61a75 | ||
|
|
358da72ffe | ||
|
|
9666feca37 | ||
|
|
03eb52eac8 | ||
|
|
6562cc710c | ||
|
|
222001bc13 | ||
|
|
e93f14248a | ||
|
|
7119f1bc81 | ||
|
|
548fa3f6ee |
114
.github/workflows/build.yml
vendored
114
.github/workflows/build.yml
vendored
@@ -16,13 +16,9 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
opensuse_version: [ 'tumbleweed', 'leap:15.6' ]
|
||||
qt_version: [ '5', '6' ]
|
||||
container:
|
||||
image: opensuse/${{matrix.opensuse_version}}
|
||||
steps:
|
||||
- name: Add tagparser repo
|
||||
if: matrix.opensuse_version == 'tumbleweed'
|
||||
run: zypper -n ar -c -f -n 'repo-tagparser' https://download.opensuse.org/repositories/home:/mkittler/openSUSE_Tumbleweed/ repo-tagparser
|
||||
- name: Refresh repositories
|
||||
run: zypper -n --gpg-auto-import-keys ref
|
||||
- name: Upgrade packages
|
||||
@@ -74,26 +70,6 @@ jobs:
|
||||
update-desktop-files
|
||||
appstream-glib
|
||||
hicolor-icon-theme
|
||||
- name: Install Qt 5
|
||||
if: matrix.qt_version == '5'
|
||||
run: >
|
||||
zypper -n --gpg-auto-import-keys in
|
||||
libQt5Core-devel
|
||||
libQt5Gui-devel
|
||||
libQt5Widgets-devel
|
||||
libQt5Concurrent-devel
|
||||
libQt5Network-devel
|
||||
libQt5Sql-devel
|
||||
libQt5DBus-devel
|
||||
libQt5Test-devel
|
||||
libqt5-qtbase-common-devel
|
||||
libQt5Sql5-sqlite
|
||||
libqt5-linguist-devel
|
||||
libqt5-qtx11extras-devel
|
||||
- name: Install Qt 6
|
||||
if: matrix.qt_version == '6'
|
||||
run: >
|
||||
zypper -n --gpg-auto-import-keys in
|
||||
qt6-core-devel
|
||||
qt6-gui-devel
|
||||
qt6-widgets-devel
|
||||
@@ -105,14 +81,8 @@ jobs:
|
||||
qt6-base-common-devel
|
||||
qt6-sql-sqlite
|
||||
qt6-linguist-devel
|
||||
- name: Install tagparser
|
||||
if: matrix.opensuse_version == 'tumbleweed'
|
||||
run: zypper -n --gpg-auto-import-keys in tagparser-devel
|
||||
- name: Install kdsingleapplication-devel
|
||||
if: matrix.opensuse_version == 'tumbleweed' && matrix.qt_version == '5'
|
||||
run: zypper -n --gpg-auto-import-keys in kdsingleapplication-devel
|
||||
- name: Install kdsingleapplication-qt6-devel
|
||||
if: matrix.opensuse_version == 'tumbleweed' && matrix.qt_version == '6'
|
||||
if: matrix.opensuse_version == 'tumbleweed'
|
||||
run: zypper -n --gpg-auto-import-keys in kdsingleapplication-qt6-devel
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -124,7 +94,7 @@ jobs:
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory build
|
||||
- name: Configure CMake
|
||||
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON -DUSE_TAGLIB=ON -DQT_VERSION_MAJOR=${{matrix.qt_version}}
|
||||
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON -DUSE_TAGLIB=ON
|
||||
- name: Create source tarball
|
||||
working-directory: build
|
||||
run: ../dist/scripts/maketarball.sh
|
||||
@@ -148,14 +118,14 @@ jobs:
|
||||
id: set-subdir
|
||||
run: echo "subdir=$(echo ${{matrix.opensuse_version}} | sed 's/leap:/lp/g' | sed 's/\.//g')" > $GITHUB_OUTPUT
|
||||
- name: Upload source
|
||||
if: matrix.opensuse_version == 'tumbleweed' && matrix.qt_version == '6'
|
||||
if: matrix.opensuse_version == 'tumbleweed'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: source
|
||||
path: |
|
||||
/usr/src/packages/SOURCES/*.xz
|
||||
- name: Upload rpm
|
||||
if: matrix.opensuse_version != 'tumbleweed' && matrix.qt_version == '6'
|
||||
if: matrix.opensuse_version != 'tumbleweed'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: opensuse-${{steps.set-subdir.outputs.subdir}}
|
||||
@@ -460,7 +430,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
debian_version: [ 'bullseye', 'bookworm', 'trixie' ]
|
||||
debian_version: [ 'bookworm', 'trixie' ]
|
||||
container:
|
||||
image: debian:${{matrix.debian_version}}
|
||||
steps:
|
||||
@@ -505,16 +475,11 @@ jobs:
|
||||
libcdio-dev
|
||||
libmtp-dev
|
||||
libgpod-dev
|
||||
- name: Install Qt 5
|
||||
if: matrix.debian_version == 'bullseye'
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: apt install -y qtbase5-dev qtbase5-dev-tools qttools5-dev qttools5-dev-tools libqt5x11extras5-dev
|
||||
- name: Install Qt 6
|
||||
if: matrix.debian_version != 'bullseye'
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: apt install -y qt6-base-dev qt6-base-dev-tools qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools
|
||||
qt6-base-dev
|
||||
qt6-base-dev-tools
|
||||
qt6-tools-dev
|
||||
qt6-tools-dev-tools
|
||||
qt6-l10n-tools
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -544,7 +509,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
ubuntu_version: [ 'focal', 'jammy', 'noble', 'oracular' ]
|
||||
ubuntu_version: [ 'noble', 'oracular' ]
|
||||
container:
|
||||
image: ubuntu:${{matrix.ubuntu_version}}
|
||||
steps:
|
||||
@@ -592,16 +557,11 @@ jobs:
|
||||
libcdio-dev
|
||||
libmtp-dev
|
||||
libgpod-dev
|
||||
- name: Install Qt 5
|
||||
if: matrix.ubuntu_version == 'focal'
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: apt install -y qtbase5-dev qtbase5-dev-tools qttools5-dev qttools5-dev-tools libqt5x11extras5-dev
|
||||
- name: Install Qt 6
|
||||
if: matrix.ubuntu_version != 'focal'
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: apt install -y qt6-base-dev qt6-base-dev-tools qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools
|
||||
qt6-base-dev
|
||||
qt6-base-dev-tools
|
||||
qt6-tools-dev
|
||||
qt6-tools-dev-tools
|
||||
qt6-l10n-tools
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -628,12 +588,12 @@ jobs:
|
||||
|
||||
upload-ubuntu-ppa:
|
||||
name: Upload Ubuntu PPA
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.event_name == 'release' || (github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')))
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.event_name == 'release' || (github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/1.1')))
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
ubuntu_version: [ 'focal', 'jammy', 'noble', 'oracular' ]
|
||||
ubuntu_version: [ 'noble', 'oracular' ]
|
||||
container:
|
||||
image: ubuntu:${{matrix.ubuntu_version}}
|
||||
steps:
|
||||
@@ -678,6 +638,11 @@ jobs:
|
||||
libcdio-dev
|
||||
libmtp-dev
|
||||
libgpod-dev
|
||||
qt6-base-dev
|
||||
qt6-base-dev-tools
|
||||
qt6-tools-dev
|
||||
qt6-tools-dev-tools
|
||||
qt6-l10n-tools
|
||||
gstreamer1.0-alsa
|
||||
gstreamer1.0-pulseaudio
|
||||
protobuf-compiler
|
||||
@@ -686,16 +651,6 @@ jobs:
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: apt install -y keyboxd
|
||||
- name: Install Qt 5
|
||||
if: matrix.ubuntu_version == 'focal'
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: apt install -y qtbase5-dev qtbase5-dev-tools qttools5-dev qttools5-dev-tools libqt5x11extras5-dev
|
||||
- name: Install Qt 6
|
||||
if: matrix.ubuntu_version != 'focal'
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: apt install -y qt6-base-dev qt6-base-dev-tools qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -807,7 +762,6 @@ jobs:
|
||||
-B build
|
||||
-DCMAKE_BUILD_TYPE="${{env.cmake_buildtype}}"
|
||||
-DCMAKE_PREFIX_PATH="${{env.prefix_path}}/lib/cmake"
|
||||
-DBUILD_WITH_QT6=ON
|
||||
-DBUILD_WERROR=OFF
|
||||
-DUSE_BUNDLE=ON
|
||||
-DENABLE_DBUS=OFF
|
||||
@@ -833,9 +787,14 @@ jobs:
|
||||
run: make deploy
|
||||
|
||||
- name: Manually Codesign
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.runner == 'macos-13'
|
||||
working-directory: build
|
||||
run: codesign -s 383J84DVB6 -f strawberry.app/Contents/Frameworks/{libsoup-3.0.0.dylib,libnghttp2.14.dylib,libpsl.5.dylib,libpcre2-8.0.dylib,libpcre2-16.0.dylib,libpng16.16.dylib,libzstd.1.dylib,libfreetype.6.dylib} strawberry.app/Contents/Frameworks/png.framework/png strawberry.app
|
||||
run: codesign -s 383J84DVB6 -f strawberry.app/Contents/Frameworks/{libpcre2-8.0.dylib,libpcre2-16.0.dylib,libpng16.16.dylib,libfreetype.6.dylib,libzstd.1.dylib,libutf8_validity.dylib} strawberry.app/Contents/Frameworks/png.framework/png strawberry.app
|
||||
|
||||
- name: Manually Codesign
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.runner == 'macos-14'
|
||||
working-directory: build
|
||||
run: codesign -s 383J84DVB6 -f strawberry.app/Contents/Frameworks/png.framework/png strawberry.app
|
||||
|
||||
- name: Deploy check
|
||||
working-directory: build
|
||||
@@ -943,7 +902,6 @@ jobs:
|
||||
-B build
|
||||
-DCMAKE_BUILD_TYPE="${{env.cmake_buildtype}}"
|
||||
-DCMAKE_PREFIX_PATH="${{env.prefix_path}}/lib/cmake"
|
||||
-DBUILD_WITH_QT6=ON
|
||||
-DBUILD_WERROR=OFF
|
||||
-DUSE_BUNDLE=ON
|
||||
-DENABLE_DBUS=OFF
|
||||
@@ -993,11 +951,11 @@ jobs:
|
||||
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/development_releases/macos" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Create server path
|
||||
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{steps.set-upload-path.outputs.upload_path}}
|
||||
#- name: Create server path
|
||||
#run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{steps.set-upload-path.outputs.upload_path}}
|
||||
|
||||
- name: rsync
|
||||
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var build/*.dmg ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{steps.set-upload-path.outputs.upload_path}}/
|
||||
#- name: rsync
|
||||
#run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var build/*.dmg ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{steps.set-upload-path.outputs.upload_path}}/
|
||||
|
||||
|
||||
build-windows-mingw:
|
||||
@@ -1042,7 +1000,6 @@ jobs:
|
||||
-DCMAKE_TOOLCHAIN_FILE="../cmake/Toolchain-${{matrix.arch}}-w64-mingw32-shared.cmake"
|
||||
-DCMAKE_BUILD_TYPE="${{env.cmake_buildtype}}"
|
||||
-DCMAKE_PREFIX_PATH="/strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/qt6"
|
||||
-DBUILD_WITH_QT6=ON
|
||||
-DBUILD_WERROR=OFF
|
||||
-DARCH="${{matrix.arch}}"
|
||||
-DENABLE_WIN32_CONSOLE=$(test "${{matrix.buildtype}}" = "debug" && echo "ON" || echo "OFF")
|
||||
@@ -1331,7 +1288,6 @@ jobs:
|
||||
-G "Ninja"
|
||||
-DCMAKE_BUILD_TYPE="${{env.cmake_buildtype}}"
|
||||
-DCMAKE_PREFIX_PATH="${{env.prefix_path_forwardslash}}/lib/cmake"
|
||||
-DBUILD_WITH_QT6=ON
|
||||
-DARCH="${{matrix.arch}}"
|
||||
-DENABLE_WIN32_CONSOLE=${{env.win32_console}}
|
||||
-DUSE_TAGLIB=ON
|
||||
@@ -1543,7 +1499,7 @@ jobs:
|
||||
upload_path="${{secrets.RELEASES_PATH}}/"
|
||||
else
|
||||
distro=$(echo "$i" | cut -d '/' -f 2)
|
||||
if [ "$(echo "$i" | grep '-' || true)" = "" ]; then
|
||||
if [ -z "$(echo "${distro}" | grep '-' || true)" ]; then
|
||||
upload_path="${{secrets.BUILDS_PATH}}/${distro}/"
|
||||
else
|
||||
distro_name=$(echo "${distro}" | cut -d '-' -f 1)
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -13,5 +13,5 @@
|
||||
/dist/scripts/maketarball.sh
|
||||
/dist/unix/strawberry.spec
|
||||
/dist/windows/strawberry.nsi
|
||||
/debian/control
|
||||
/debian/changelog
|
||||
/dist/macos/Info.plist
|
||||
|
||||
22
3rdparty/README.md
vendored
22
3rdparty/README.md
vendored
@@ -2,20 +2,28 @@
|
||||
============================================
|
||||
|
||||
KDSingleApplication
|
||||
-----------------
|
||||
This is a small static library used by Strawberry to prevent it from starting twice per user session.
|
||||
-------------------
|
||||
A small library used by Strawberry to prevent it from starting twice per user session.
|
||||
If the user tries to start strawberry twice, the main window will maximize instead of starting another instance.
|
||||
It is also used to pass command-line options through to the first instance.
|
||||
This 3rdparty copy is used only if KDSingleApplication 1.1 or higher is not found on the system.
|
||||
|
||||
URL: https://github.com/KDAB/KDSingleApplication/
|
||||
|
||||
|
||||
SPMediaKeyTap
|
||||
-------------
|
||||
Used on macOS to exclusively enable strawberry to grab global media shortcuts.
|
||||
Can safely be deleted on other platforms.
|
||||
A library used on macOS to exclusively grab global media shortcuts.
|
||||
|
||||
The library is no longer maintained by the original author.
|
||||
|
||||
The directory can safely be deleted on other platforms.
|
||||
|
||||
|
||||
getopt
|
||||
------
|
||||
getopt included only when compiling on Windows.
|
||||
gstfastspectrum
|
||||
---------------
|
||||
A GStreamer spectrum plugin using FFTW3.
|
||||
It is needed for moodbar support, and is currently not available
|
||||
in GStreamer.
|
||||
The plan is to submit it to GStreamer, or move it to
|
||||
a seperate repository outside of Strawberry.
|
||||
|
||||
3
3rdparty/getopt/CMakeLists.txt
vendored
3
3rdparty/getopt/CMakeLists.txt
vendored
@@ -1,3 +0,0 @@
|
||||
add_library(getopt STATIC getopt.cpp)
|
||||
target_compile_definitions(getopt PRIVATE -DSTATIC_GETOPT -D_UNICODE)
|
||||
target_include_directories(getopt PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
753
3rdparty/getopt/getopt.cpp
vendored
753
3rdparty/getopt/getopt.cpp
vendored
@@ -1,753 +0,0 @@
|
||||
/* Getopt for Microsoft C
|
||||
This code is a modification of the Free Software Foundation, Inc.
|
||||
Getopt library for parsing command line argument the purpose was
|
||||
to provide a Microsoft Visual C friendly derivative. This code
|
||||
provides functionality for both Unicode and Multibyte builds.
|
||||
|
||||
Date: 02/03/2011 - Ludvik Jerabek - Initial Release
|
||||
Version: 1.1
|
||||
Comment: Supports getopt, getopt_long, and getopt_long_only
|
||||
and POSIXLY_CORRECT environment flag
|
||||
License: LGPL
|
||||
|
||||
Revisions:
|
||||
|
||||
02/03/2011 - Ludvik Jerabek - Initial Release
|
||||
02/20/2011 - Ludvik Jerabek - Fixed compiler warnings at Level 4
|
||||
07/05/2011 - Ludvik Jerabek - Added no_argument, required_argument, optional_argument defs
|
||||
08/03/2011 - Ludvik Jerabek - Fixed non-argument runtime bug which caused runtime exception
|
||||
08/09/2011 - Ludvik Jerabek - Added code to export functions for DLL and LIB
|
||||
02/15/2012 - Ludvik Jerabek - Fixed _GETOPT_THROW definition missing in implementation file
|
||||
08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
|
||||
10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
|
||||
06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
|
||||
09/24/2022 - Ludvik Jerabek - Updated to match most recent getopt release
|
||||
09/25/2022 - Ludvik Jerabek - Fixed memory allocation (malloc call) issue for wchar_t*
|
||||
|
||||
**DISCLAIMER**
|
||||
THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||
EITHER EXPRESS OR IMPLIED, INCLUDING, BUT Not LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, OR NON-INFRINGEMENT. SOME JURISDICTIONS DO NOT ALLOW THE
|
||||
EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT
|
||||
APPLY TO YOU. IN NO EVENT WILL I BE LIABLE TO ANY PARTY FOR ANY
|
||||
DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY
|
||||
USE OF THIS MATERIAL INCLUDING, WITHOUT LIMITATION, ANY LOST
|
||||
PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON
|
||||
YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE
|
||||
EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
*/
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <malloc.h>
|
||||
#include "getopt.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
# define _GETOPT_THROW throw()
|
||||
#else
|
||||
# define _GETOPT_THROW
|
||||
#endif
|
||||
|
||||
int optind = 1;
|
||||
int opterr = 1;
|
||||
int optopt = '?';
|
||||
enum ENUM_ORDERING {
|
||||
REQUIRE_ORDER,
|
||||
PERMUTE,
|
||||
RETURN_IN_ORDER
|
||||
};
|
||||
|
||||
//
|
||||
//
|
||||
// Ansi structures and functions follow
|
||||
//
|
||||
//
|
||||
|
||||
static struct _getopt_data_a {
|
||||
int optind;
|
||||
int opterr;
|
||||
int optopt;
|
||||
char *optarg;
|
||||
int __initialized;
|
||||
char *__nextchar;
|
||||
enum ENUM_ORDERING __ordering;
|
||||
int __first_nonopt;
|
||||
int __last_nonopt;
|
||||
} getopt_data_a;
|
||||
char *optarg_a;
|
||||
|
||||
static void exchange_a(char **argv, struct _getopt_data_a *d) {
|
||||
int bottom = d->__first_nonopt;
|
||||
int middle = d->__last_nonopt;
|
||||
int top = d->optind;
|
||||
char *tem;
|
||||
while (top > middle && middle > bottom) {
|
||||
if (top - middle > middle - bottom) {
|
||||
int len = middle - bottom;
|
||||
int i;
|
||||
for (i = 0; i < len; i++) {
|
||||
tem = argv[bottom + i];
|
||||
argv[bottom + i] = argv[top - (middle - bottom) + i];
|
||||
argv[top - (middle - bottom) + i] = tem;
|
||||
}
|
||||
top -= len;
|
||||
}
|
||||
else {
|
||||
int len = top - middle;
|
||||
int i;
|
||||
for (i = 0; i < len; i++) {
|
||||
tem = argv[bottom + i];
|
||||
argv[bottom + i] = argv[middle + i];
|
||||
argv[middle + i] = tem;
|
||||
}
|
||||
bottom += len;
|
||||
}
|
||||
}
|
||||
d->__first_nonopt += (d->optind - d->__last_nonopt);
|
||||
d->__last_nonopt = d->optind;
|
||||
}
|
||||
|
||||
static int process_long_option_a(int argc, char **argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, struct _getopt_data_a *d, int print_errors, const char *prefix);
|
||||
static int process_long_option_a(int argc, char **argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, struct _getopt_data_a *d, int print_errors, const char *prefix) {
|
||||
assert(longopts != NULL);
|
||||
char *nameend;
|
||||
size_t namelen;
|
||||
const struct option_a *p;
|
||||
const struct option_a *pfound = NULL;
|
||||
int n_options;
|
||||
int option_index = 0;
|
||||
for (nameend = d->__nextchar; *nameend && *nameend != '='; nameend++)
|
||||
;
|
||||
namelen = nameend - d->__nextchar;
|
||||
for (p = longopts, n_options = 0; p->name; p++, n_options++)
|
||||
if (!strncmp(p->name, d->__nextchar, namelen) && namelen == strlen(p->name)) {
|
||||
pfound = p;
|
||||
option_index = n_options;
|
||||
break;
|
||||
}
|
||||
if (pfound == NULL) {
|
||||
unsigned char *ambig_set = NULL;
|
||||
int ambig_fallback = 0;
|
||||
int indfound = -1;
|
||||
for (p = longopts, option_index = 0; p->name; p++, option_index++)
|
||||
if (!strncmp(p->name, d->__nextchar, namelen)) {
|
||||
if (pfound == NULL) {
|
||||
pfound = p;
|
||||
indfound = option_index;
|
||||
}
|
||||
else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val) {
|
||||
if (!ambig_fallback) {
|
||||
if (!print_errors)
|
||||
ambig_fallback = 1;
|
||||
|
||||
else if (!ambig_set) {
|
||||
if ((ambig_set = reinterpret_cast<unsigned char *>(malloc(n_options * sizeof(char)))) == NULL)
|
||||
ambig_fallback = 1;
|
||||
|
||||
if (ambig_set) {
|
||||
memset(ambig_set, 0, n_options * sizeof(char));
|
||||
ambig_set[indfound] = 1;
|
||||
}
|
||||
}
|
||||
if (ambig_set)
|
||||
ambig_set[option_index] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ambig_set || ambig_fallback) {
|
||||
if (print_errors) {
|
||||
if (ambig_fallback)
|
||||
fprintf(stderr, "%s: option '%s%s' is ambiguous\n", argv[0], prefix, d->__nextchar);
|
||||
else {
|
||||
_lock_file(stderr);
|
||||
fprintf(stderr, "%s: option '%s%s' is ambiguous; possibilities:", argv[0], prefix, d->__nextchar);
|
||||
for (option_index = 0; option_index < n_options; option_index++)
|
||||
if (ambig_set[option_index])
|
||||
fprintf(stderr, " '%s%s'", prefix, longopts[option_index].name);
|
||||
fprintf(stderr, "\n");
|
||||
_unlock_file(stderr);
|
||||
}
|
||||
}
|
||||
free(ambig_set);
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
d->optind++;
|
||||
d->optopt = 0;
|
||||
return '?';
|
||||
}
|
||||
option_index = indfound;
|
||||
}
|
||||
if (pfound == NULL) {
|
||||
if (!long_only || argv[d->optind][1] == '-' || strchr(optstring, *d->__nextchar) == NULL) {
|
||||
if (print_errors)
|
||||
fprintf(stderr, "%s: unrecognized option '%s%s'\n", argv[0], prefix, d->__nextchar);
|
||||
d->__nextchar = NULL;
|
||||
d->optind++;
|
||||
d->optopt = 0;
|
||||
return '?';
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
d->optind++;
|
||||
d->__nextchar = NULL;
|
||||
if (*nameend) {
|
||||
if (pfound->has_arg)
|
||||
d->optarg = nameend + 1;
|
||||
else {
|
||||
if (print_errors)
|
||||
fprintf(stderr, "%s: option '%s%s' doesn't allow an argument\n", argv[0], prefix, pfound->name);
|
||||
d->optopt = pfound->val;
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
else if (pfound->has_arg == 1) {
|
||||
if (d->optind < argc)
|
||||
d->optarg = argv[d->optind++];
|
||||
else {
|
||||
if (print_errors)
|
||||
fprintf(stderr, "%s: option '%s%s' requires an argument\n", argv[0], prefix, pfound->name);
|
||||
d->optopt = pfound->val;
|
||||
return optstring[0] == ':' ? ':' : '?';
|
||||
}
|
||||
}
|
||||
if (longind != NULL)
|
||||
*longind = option_index;
|
||||
|
||||
if (pfound->flag) {
|
||||
*(pfound->flag) = pfound->val;
|
||||
return 0;
|
||||
}
|
||||
return pfound->val;
|
||||
}
|
||||
|
||||
static const char *_getopt_initialize_a(const char *optstring, struct _getopt_data_a *d, int posixly_correct) {
|
||||
if (d->optind == 0)
|
||||
d->optind = 1;
|
||||
|
||||
d->__first_nonopt = d->__last_nonopt = d->optind;
|
||||
d->__nextchar = NULL;
|
||||
|
||||
if (optstring[0] == '-') {
|
||||
d->__ordering = RETURN_IN_ORDER;
|
||||
++optstring;
|
||||
}
|
||||
else if (optstring[0] == '+') {
|
||||
d->__ordering = REQUIRE_ORDER;
|
||||
++optstring;
|
||||
}
|
||||
else if (posixly_correct | !!getenv("POSIXLY_CORRECT"))
|
||||
d->__ordering = REQUIRE_ORDER;
|
||||
else
|
||||
d->__ordering = PERMUTE;
|
||||
|
||||
d->__initialized = 1;
|
||||
return optstring;
|
||||
}
|
||||
|
||||
int _getopt_internal_r_a(int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, struct _getopt_data_a *d, int posixly_correct);
|
||||
int _getopt_internal_r_a(int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, struct _getopt_data_a *d, int posixly_correct) {
|
||||
int print_errors = d->opterr;
|
||||
if (argc < 1)
|
||||
return -1;
|
||||
d->optarg = NULL;
|
||||
if (d->optind == 0 || !d->__initialized)
|
||||
optstring = _getopt_initialize_a(optstring, d, posixly_correct);
|
||||
else if (optstring[0] == '-' || optstring[0] == '+')
|
||||
optstring++;
|
||||
if (optstring[0] == ':')
|
||||
print_errors = 0;
|
||||
|
||||
if (d->__nextchar == NULL || *d->__nextchar == '\0') {
|
||||
if (d->__last_nonopt > d->optind)
|
||||
d->__last_nonopt = d->optind;
|
||||
if (d->__first_nonopt > d->optind)
|
||||
d->__first_nonopt = d->optind;
|
||||
if (d->__ordering == PERMUTE) {
|
||||
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
||||
exchange_a(const_cast<char **>(argv), d);
|
||||
else if (d->__last_nonopt != d->optind)
|
||||
d->__first_nonopt = d->optind;
|
||||
while (d->optind < argc && (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0'))
|
||||
d->optind++;
|
||||
d->__last_nonopt = d->optind;
|
||||
}
|
||||
if (d->optind != argc && !strcmp(argv[d->optind], "--")) {
|
||||
d->optind++;
|
||||
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
||||
exchange_a(const_cast<char **>(argv), d);
|
||||
else if (d->__first_nonopt == d->__last_nonopt)
|
||||
d->__first_nonopt = d->optind;
|
||||
d->__last_nonopt = argc;
|
||||
d->optind = argc;
|
||||
}
|
||||
if (d->optind == argc) {
|
||||
if (d->__first_nonopt != d->__last_nonopt)
|
||||
d->optind = d->__first_nonopt;
|
||||
return -1;
|
||||
}
|
||||
if (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0') {
|
||||
if (d->__ordering == REQUIRE_ORDER)
|
||||
return -1;
|
||||
d->optarg = argv[d->optind++];
|
||||
return 1;
|
||||
}
|
||||
if (longopts) {
|
||||
if (argv[d->optind][1] == '-') {
|
||||
d->__nextchar = argv[d->optind] + 2;
|
||||
return process_long_option_a(argc, const_cast<char **>(argv), optstring, longopts, longind, long_only, d, print_errors, "--");
|
||||
}
|
||||
if (long_only && (argv[d->optind][2] || !strchr(optstring, argv[d->optind][1]))) {
|
||||
int code;
|
||||
d->__nextchar = argv[d->optind] + 1;
|
||||
code = process_long_option_a(argc, const_cast<char **>(argv), optstring, longopts,
|
||||
longind, long_only, d,
|
||||
print_errors, "-");
|
||||
if (code != -1)
|
||||
return code;
|
||||
}
|
||||
}
|
||||
d->__nextchar = argv[d->optind] + 1;
|
||||
}
|
||||
{
|
||||
char c = *d->__nextchar++;
|
||||
const char *temp = strchr(optstring, c);
|
||||
if (*d->__nextchar == '\0')
|
||||
++d->optind;
|
||||
if (temp == NULL || c == ':' || c == ';') {
|
||||
if (print_errors)
|
||||
fprintf(stderr, "%s: invalid option -- '%c'\n", argv[0], c);
|
||||
d->optopt = c;
|
||||
return '?';
|
||||
}
|
||||
if (temp[0] == 'W' && temp[1] == ';' && longopts != NULL) {
|
||||
if (*d->__nextchar != '\0')
|
||||
d->optarg = d->__nextchar;
|
||||
else if (d->optind == argc) {
|
||||
if (print_errors)
|
||||
fprintf(stderr, "%s: option requires an argument -- '%c'\n", argv[0], c);
|
||||
d->optopt = c;
|
||||
if (optstring[0] == ':')
|
||||
c = ':';
|
||||
else
|
||||
c = '?';
|
||||
return c;
|
||||
}
|
||||
else
|
||||
d->optarg = argv[d->optind];
|
||||
d->__nextchar = d->optarg;
|
||||
d->optarg = NULL;
|
||||
return process_long_option_a(argc, const_cast<char **>(argv), optstring, longopts, longind, 0, d, print_errors, "-W ");
|
||||
}
|
||||
if (temp[1] == ':') {
|
||||
if (temp[2] == ':') {
|
||||
if (*d->__nextchar != '\0') {
|
||||
d->optarg = d->__nextchar;
|
||||
d->optind++;
|
||||
}
|
||||
else
|
||||
d->optarg = NULL;
|
||||
d->__nextchar = NULL;
|
||||
}
|
||||
else {
|
||||
if (*d->__nextchar != '\0') {
|
||||
d->optarg = d->__nextchar;
|
||||
d->optind++;
|
||||
}
|
||||
else if (d->optind == argc) {
|
||||
if (print_errors)
|
||||
fprintf(stderr, "%s: option requires an argument -- '%c'\n", argv[0], c);
|
||||
d->optopt = c;
|
||||
if (optstring[0] == ':')
|
||||
c = ':';
|
||||
else
|
||||
c = '?';
|
||||
}
|
||||
else
|
||||
d->optarg = argv[d->optind++];
|
||||
d->__nextchar = NULL;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
int _getopt_internal_a(int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, int posixly_correct);
|
||||
int _getopt_internal_a(int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, int posixly_correct) {
|
||||
int result;
|
||||
getopt_data_a.optind = optind;
|
||||
getopt_data_a.opterr = opterr;
|
||||
result = _getopt_internal_r_a(argc, argv, optstring, longopts, longind, long_only, &getopt_data_a, posixly_correct);
|
||||
optind = getopt_data_a.optind;
|
||||
optarg_a = getopt_data_a.optarg;
|
||||
optopt = getopt_data_a.optopt;
|
||||
return result;
|
||||
}
|
||||
|
||||
int getopt_a(int argc, char *const *argv, const char *optstring) _GETOPT_THROW {
|
||||
return _getopt_internal_a(argc, argv, optstring, static_cast<const struct option_a *>(0), static_cast<int *>(0), 0, 0);
|
||||
}
|
||||
|
||||
int getopt_long_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW {
|
||||
return _getopt_internal_a(argc, argv, options, long_options, opt_index, 0, 0);
|
||||
}
|
||||
|
||||
int getopt_long_only_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW {
|
||||
return _getopt_internal_a(argc, argv, options, long_options, opt_index, 1, 0);
|
||||
}
|
||||
|
||||
int _getopt_long_r_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d);
|
||||
int _getopt_long_r_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d) {
|
||||
return _getopt_internal_r_a(argc, argv, options, long_options, opt_index, 0, d, 0);
|
||||
}
|
||||
|
||||
int _getopt_long_only_r_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d);
|
||||
int _getopt_long_only_r_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d) {
|
||||
return _getopt_internal_r_a(argc, argv, options, long_options, opt_index, 1, d, 0);
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// Unicode Structures and Functions
|
||||
//
|
||||
//
|
||||
|
||||
static struct _getopt_data_w {
|
||||
int optind;
|
||||
int opterr;
|
||||
int optopt;
|
||||
wchar_t *optarg;
|
||||
int __initialized;
|
||||
wchar_t *__nextchar;
|
||||
enum ENUM_ORDERING __ordering;
|
||||
int __first_nonopt;
|
||||
int __last_nonopt;
|
||||
} getopt_data_w;
|
||||
wchar_t *optarg_w;
|
||||
|
||||
static void exchange_w(wchar_t **argv, struct _getopt_data_w *d) {
|
||||
int bottom = d->__first_nonopt;
|
||||
int middle = d->__last_nonopt;
|
||||
int top = d->optind;
|
||||
wchar_t *tem;
|
||||
while (top > middle && middle > bottom) {
|
||||
if (top - middle > middle - bottom) {
|
||||
int len = middle - bottom;
|
||||
int i;
|
||||
for (i = 0; i < len; i++) {
|
||||
tem = argv[bottom + i];
|
||||
argv[bottom + i] = argv[top - (middle - bottom) + i];
|
||||
argv[top - (middle - bottom) + i] = tem;
|
||||
}
|
||||
top -= len;
|
||||
}
|
||||
else {
|
||||
int len = top - middle;
|
||||
int i;
|
||||
for (i = 0; i < len; i++) {
|
||||
tem = argv[bottom + i];
|
||||
argv[bottom + i] = argv[middle + i];
|
||||
argv[middle + i] = tem;
|
||||
}
|
||||
bottom += len;
|
||||
}
|
||||
}
|
||||
d->__first_nonopt += (d->optind - d->__last_nonopt);
|
||||
d->__last_nonopt = d->optind;
|
||||
}
|
||||
|
||||
static int process_long_option_w(int argc, wchar_t **argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, struct _getopt_data_w *d, int print_errors, const wchar_t *prefix) {
|
||||
assert(longopts != NULL);
|
||||
wchar_t *nameend;
|
||||
size_t namelen;
|
||||
const struct option_w *p;
|
||||
const struct option_w *pfound = NULL;
|
||||
int n_options;
|
||||
int option_index = 0;
|
||||
for (nameend = d->__nextchar; *nameend && *nameend != L'='; nameend++)
|
||||
;
|
||||
namelen = nameend - d->__nextchar;
|
||||
for (p = longopts, n_options = 0; p->name; p++, n_options++)
|
||||
if (!wcsncmp(p->name, d->__nextchar, namelen) && namelen == wcslen(p->name)) {
|
||||
pfound = p;
|
||||
option_index = n_options;
|
||||
break;
|
||||
}
|
||||
if (pfound == NULL) {
|
||||
wchar_t *ambig_set = NULL;
|
||||
int ambig_fallback = 0;
|
||||
int indfound = -1;
|
||||
for (p = longopts, option_index = 0; p->name; p++, option_index++)
|
||||
if (!wcsncmp(p->name, d->__nextchar, namelen)) {
|
||||
if (pfound == NULL) {
|
||||
pfound = p;
|
||||
indfound = option_index;
|
||||
}
|
||||
else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val) {
|
||||
if (!ambig_fallback) {
|
||||
if (!print_errors)
|
||||
ambig_fallback = 1;
|
||||
|
||||
else if (!ambig_set) {
|
||||
if ((ambig_set = reinterpret_cast<wchar_t *>(malloc(n_options * sizeof(wchar_t)))) == NULL)
|
||||
ambig_fallback = 1;
|
||||
|
||||
if (ambig_set) {
|
||||
memset(ambig_set, 0, n_options * sizeof(wchar_t));
|
||||
ambig_set[indfound] = 1;
|
||||
}
|
||||
}
|
||||
if (ambig_set)
|
||||
ambig_set[option_index] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ambig_set || ambig_fallback) {
|
||||
if (print_errors) {
|
||||
if (ambig_fallback)
|
||||
fwprintf(stderr, L"%s: option '%s%s' is ambiguous\n", argv[0], prefix, d->__nextchar);
|
||||
else {
|
||||
_lock_file(stderr);
|
||||
fwprintf(stderr, L"%s: option '%s%s' is ambiguous; possibilities:", argv[0], prefix, d->__nextchar);
|
||||
for (option_index = 0; option_index < n_options; option_index++)
|
||||
if (ambig_set[option_index])
|
||||
fwprintf(stderr, L" '%s%s'", prefix, longopts[option_index].name);
|
||||
fwprintf(stderr, L"\n");
|
||||
_unlock_file(stderr);
|
||||
}
|
||||
}
|
||||
free(ambig_set);
|
||||
d->__nextchar += wcslen(d->__nextchar);
|
||||
d->optind++;
|
||||
d->optopt = 0;
|
||||
return L'?';
|
||||
}
|
||||
option_index = indfound;
|
||||
}
|
||||
if (pfound == NULL) {
|
||||
if (!long_only || argv[d->optind][1] == L'-' || wcschr(optstring, *d->__nextchar) == NULL) {
|
||||
if (print_errors)
|
||||
fwprintf(stderr, L"%s: unrecognized option '%s%s'\n", argv[0], prefix, d->__nextchar);
|
||||
d->__nextchar = NULL;
|
||||
d->optind++;
|
||||
d->optopt = 0;
|
||||
return L'?';
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
d->optind++;
|
||||
d->__nextchar = NULL;
|
||||
if (*nameend) {
|
||||
if (pfound->has_arg)
|
||||
d->optarg = nameend + 1;
|
||||
else {
|
||||
if (print_errors)
|
||||
fwprintf(stderr, L"%s: option '%s%s' doesn't allow an argument\n", argv[0], prefix, pfound->name);
|
||||
d->optopt = pfound->val;
|
||||
return L'?';
|
||||
}
|
||||
}
|
||||
else if (pfound->has_arg == 1) {
|
||||
if (d->optind < argc)
|
||||
d->optarg = argv[d->optind++];
|
||||
else {
|
||||
if (print_errors)
|
||||
fwprintf(stderr, L"%s: option '%s%s' requires an argument\n", argv[0], prefix, pfound->name);
|
||||
d->optopt = pfound->val;
|
||||
return optstring[0] == L':' ? L':' : L'?';
|
||||
}
|
||||
}
|
||||
if (longind != NULL)
|
||||
*longind = option_index;
|
||||
if (pfound->flag) {
|
||||
*(pfound->flag) = pfound->val;
|
||||
return 0;
|
||||
}
|
||||
return pfound->val;
|
||||
}
|
||||
|
||||
static const wchar_t *_getopt_initialize_w(const wchar_t *optstring, struct _getopt_data_w *d, int posixly_correct) {
|
||||
if (d->optind == 0)
|
||||
d->optind = 1;
|
||||
|
||||
d->__first_nonopt = d->__last_nonopt = d->optind;
|
||||
d->__nextchar = NULL;
|
||||
|
||||
if (optstring[0] == L'-') {
|
||||
d->__ordering = RETURN_IN_ORDER;
|
||||
++optstring;
|
||||
}
|
||||
else if (optstring[0] == L'+') {
|
||||
d->__ordering = REQUIRE_ORDER;
|
||||
++optstring;
|
||||
}
|
||||
else if (posixly_correct | !!_wgetenv(L"POSIXLY_CORRECT"))
|
||||
d->__ordering = REQUIRE_ORDER;
|
||||
else
|
||||
d->__ordering = PERMUTE;
|
||||
|
||||
d->__initialized = 1;
|
||||
return optstring;
|
||||
}
|
||||
|
||||
int _getopt_internal_r_w(int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, struct _getopt_data_w *d, int posixly_correct);
|
||||
int _getopt_internal_r_w(int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, struct _getopt_data_w *d, int posixly_correct) {
|
||||
int print_errors = d->opterr;
|
||||
if (argc < 1)
|
||||
return -1;
|
||||
d->optarg = NULL;
|
||||
if (d->optind == 0 || !d->__initialized)
|
||||
optstring = _getopt_initialize_w(optstring, d, posixly_correct);
|
||||
else if (optstring[0] == L'-' || optstring[0] == L'+')
|
||||
optstring++;
|
||||
if (optstring[0] == L':')
|
||||
print_errors = 0;
|
||||
#define NONOPTION_P (argv[d->optind][0] != L'-' || argv[d->optind][1] == L'\0')
|
||||
|
||||
if (d->__nextchar == NULL || *d->__nextchar == L'\0') {
|
||||
if (d->__last_nonopt > d->optind)
|
||||
d->__last_nonopt = d->optind;
|
||||
if (d->__first_nonopt > d->optind)
|
||||
d->__first_nonopt = d->optind;
|
||||
if (d->__ordering == PERMUTE) {
|
||||
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
||||
exchange_w(const_cast<wchar_t **>(argv), d);
|
||||
else if (d->__last_nonopt != d->optind)
|
||||
d->__first_nonopt = d->optind;
|
||||
while (d->optind < argc && NONOPTION_P)
|
||||
d->optind++;
|
||||
d->__last_nonopt = d->optind;
|
||||
}
|
||||
if (d->optind != argc && !wcscmp(argv[d->optind], L"--")) {
|
||||
d->optind++;
|
||||
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
||||
exchange_w(const_cast<wchar_t **>(argv), d);
|
||||
else if (d->__first_nonopt == d->__last_nonopt)
|
||||
d->__first_nonopt = d->optind;
|
||||
d->__last_nonopt = argc;
|
||||
d->optind = argc;
|
||||
}
|
||||
if (d->optind == argc) {
|
||||
if (d->__first_nonopt != d->__last_nonopt)
|
||||
d->optind = d->__first_nonopt;
|
||||
return -1;
|
||||
}
|
||||
if (NONOPTION_P) {
|
||||
if (d->__ordering == REQUIRE_ORDER)
|
||||
return -1;
|
||||
d->optarg = argv[d->optind++];
|
||||
return 1;
|
||||
}
|
||||
if (longopts) {
|
||||
if (argv[d->optind][1] == L'-') {
|
||||
d->__nextchar = argv[d->optind] + 2;
|
||||
return process_long_option_w(argc, const_cast<wchar_t **>(argv), optstring, longopts, longind, long_only, d, print_errors, L"--");
|
||||
}
|
||||
if (long_only && (argv[d->optind][2] || !wcschr(optstring, argv[d->optind][1]))) {
|
||||
int code;
|
||||
d->__nextchar = argv[d->optind] + 1;
|
||||
code = process_long_option_w(argc, const_cast<wchar_t **>(argv), optstring, longopts, longind, long_only, d, print_errors, L"-");
|
||||
if (code != -1)
|
||||
return code;
|
||||
}
|
||||
}
|
||||
d->__nextchar = argv[d->optind] + 1;
|
||||
}
|
||||
{
|
||||
wchar_t c = *d->__nextchar++;
|
||||
const wchar_t *temp = wcschr(optstring, c);
|
||||
if (*d->__nextchar == L'\0')
|
||||
++d->optind;
|
||||
if (temp == NULL || c == L':' || c == L';') {
|
||||
if (print_errors)
|
||||
fwprintf(stderr, L"%s: invalid option -- '%c'\n", argv[0], c);
|
||||
d->optopt = c;
|
||||
return L'?';
|
||||
}
|
||||
if (temp[0] == L'W' && temp[1] == L';' && longopts != NULL) {
|
||||
if (*d->__nextchar != L'\0')
|
||||
d->optarg = d->__nextchar;
|
||||
else if (d->optind == argc) {
|
||||
if (print_errors)
|
||||
fwprintf(stderr, L"%s: option requires an argument -- '%c'\n", argv[0], c);
|
||||
d->optopt = c;
|
||||
if (optstring[0] == L':')
|
||||
c = L':';
|
||||
else
|
||||
c = L'?';
|
||||
return c;
|
||||
}
|
||||
else
|
||||
d->optarg = argv[d->optind];
|
||||
d->__nextchar = d->optarg;
|
||||
d->optarg = NULL;
|
||||
return process_long_option_w(argc, const_cast<wchar_t **>(argv), optstring, longopts, longind,
|
||||
0, d, print_errors, L"-W ");
|
||||
}
|
||||
if (temp[1] == L':') {
|
||||
if (temp[2] == L':') {
|
||||
if (*d->__nextchar != L'\0') {
|
||||
d->optarg = d->__nextchar;
|
||||
d->optind++;
|
||||
}
|
||||
else
|
||||
d->optarg = NULL;
|
||||
d->__nextchar = NULL;
|
||||
}
|
||||
else {
|
||||
if (*d->__nextchar != L'\0') {
|
||||
d->optarg = d->__nextchar;
|
||||
d->optind++;
|
||||
}
|
||||
else if (d->optind == argc) {
|
||||
if (print_errors)
|
||||
fwprintf(stderr, L"%s: option requires an argument -- '%c'\n", argv[0], c);
|
||||
d->optopt = c;
|
||||
if (optstring[0] == L':')
|
||||
c = L':';
|
||||
else
|
||||
c = L'?';
|
||||
}
|
||||
else
|
||||
d->optarg = argv[d->optind++];
|
||||
d->__nextchar = NULL;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
int _getopt_internal_w(int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, int posixly_correct);
|
||||
int _getopt_internal_w(int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, int posixly_correct) {
|
||||
int result;
|
||||
getopt_data_w.optind = optind;
|
||||
getopt_data_w.opterr = opterr;
|
||||
result = _getopt_internal_r_w(argc, argv, optstring, longopts, longind, long_only, &getopt_data_w, posixly_correct);
|
||||
optind = getopt_data_w.optind;
|
||||
optarg_w = getopt_data_w.optarg;
|
||||
optopt = getopt_data_w.optopt;
|
||||
return result;
|
||||
}
|
||||
|
||||
int getopt_w(int argc, wchar_t *const *argv, const wchar_t *optstring) _GETOPT_THROW {
|
||||
return _getopt_internal_w(argc, argv, optstring, static_cast<const struct option_w *>(0), static_cast<int *>(0), 0, 0);
|
||||
}
|
||||
|
||||
int getopt_long_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW {
|
||||
return _getopt_internal_w(argc, argv, options, long_options, opt_index, 0, 0);
|
||||
}
|
||||
|
||||
int getopt_long_only_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW {
|
||||
return _getopt_internal_w(argc, argv, options, long_options, opt_index, 1, 0);
|
||||
}
|
||||
|
||||
int _getopt_long_r_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d);
|
||||
int _getopt_long_r_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d) {
|
||||
return _getopt_internal_r_w(argc, argv, options, long_options, opt_index, 0, d, 0);
|
||||
}
|
||||
|
||||
int _getopt_long_only_r_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d);
|
||||
int _getopt_long_only_r_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d) {
|
||||
return _getopt_internal_r_w(argc, argv, options, long_options, opt_index, 1, d, 0);
|
||||
}
|
||||
135
3rdparty/getopt/getopt.h
vendored
135
3rdparty/getopt/getopt.h
vendored
@@ -1,135 +0,0 @@
|
||||
/* Getopt for Microsoft C
|
||||
This code is a modification of the Free Software Foundation, Inc.
|
||||
Getopt library for parsing command line argument the purpose was
|
||||
to provide a Microsoft Visual C friendly derivative. This code
|
||||
provides functionality for both Unicode and Multibyte builds.
|
||||
|
||||
Date: 02/03/2011 - Ludvik Jerabek - Initial Release
|
||||
Version: 1.1
|
||||
Comment: Supports getopt, getopt_long, and getopt_long_only
|
||||
and POSIXLY_CORRECT environment flag
|
||||
License: LGPL
|
||||
|
||||
Revisions:
|
||||
|
||||
02/03/2011 - Ludvik Jerabek - Initial Release
|
||||
02/20/2011 - Ludvik Jerabek - Fixed compiler warnings at Level 4
|
||||
07/05/2011 - Ludvik Jerabek - Added no_argument, required_argument, optional_argument defs
|
||||
08/03/2011 - Ludvik Jerabek - Fixed non-argument runtime bug which caused runtime exception
|
||||
08/09/2011 - Ludvik Jerabek - Added code to export functions for DLL and LIB
|
||||
02/15/2012 - Ludvik Jerabek - Fixed _GETOPT_THROW definition missing in implementation file
|
||||
08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
|
||||
10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
|
||||
06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
|
||||
09/24/2022 - Ludvik Jerabek - Updated to match most recent getopt release
|
||||
09/25/2022 - Ludvik Jerabek - Fixed memory allocation (malloc call) issue for wchar_t*
|
||||
|
||||
**DISCLAIMER**
|
||||
THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||
EITHER EXPRESS OR IMPLIED, INCLUDING, BUT Not LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, OR NON-INFRINGEMENT. SOME JURISDICTIONS DO NOT ALLOW THE
|
||||
EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT
|
||||
APPLY TO YOU. IN NO EVENT WILL I BE LIABLE TO ANY PARTY FOR ANY
|
||||
DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY
|
||||
USE OF THIS MATERIAL INCLUDING, WITHOUT LIMITATION, ANY LOST
|
||||
PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON
|
||||
YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE
|
||||
EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
*/
|
||||
#ifndef __GETOPT_H_
|
||||
#define __GETOPT_H_
|
||||
|
||||
#ifdef _GETOPT_API
|
||||
# undef _GETOPT_API
|
||||
#endif
|
||||
|
||||
#if defined(EXPORTS_GETOPT) && defined(STATIC_GETOPT)
|
||||
# error "The preprocessor definitions of EXPORTS_GETOPT and STATIC_GETOPT can only be used individually"
|
||||
#elif defined(STATIC_GETOPT)
|
||||
# define _GETOPT_API
|
||||
#elif defined(EXPORTS_GETOPT)
|
||||
# pragma message("Exporting getopt library")
|
||||
# define _GETOPT_API __declspec(dllexport)
|
||||
#else
|
||||
# pragma message("Importing getopt library")
|
||||
# define _GETOPT_API __declspec(dllimport)
|
||||
#endif
|
||||
|
||||
// Change behavior for C\C++
|
||||
#ifdef __cplusplus
|
||||
# define _BEGIN_EXTERN_C extern "C" {
|
||||
# define _END_EXTERN_C }
|
||||
# define _GETOPT_THROW throw()
|
||||
#else
|
||||
# define _BEGIN_EXTERN_C
|
||||
# define _END_EXTERN_C
|
||||
# define _GETOPT_THROW
|
||||
#endif
|
||||
|
||||
// Standard GNU options
|
||||
#define null_argument 0 /*Argument Null*/
|
||||
#define no_argument 0 /*Argument Switch Only*/
|
||||
#define required_argument 1 /*Argument Required*/
|
||||
#define optional_argument 2 /*Argument Optional*/
|
||||
|
||||
// Shorter Options
|
||||
#define ARG_NULL 0 /*Argument Null*/
|
||||
#define ARG_NONE 0 /*Argument Switch Only*/
|
||||
#define ARG_REQ 1 /*Argument Required*/
|
||||
#define ARG_OPT 2 /*Argument Optional*/
|
||||
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
|
||||
_BEGIN_EXTERN_C
|
||||
|
||||
extern _GETOPT_API int optind;
|
||||
extern _GETOPT_API int opterr;
|
||||
extern _GETOPT_API int optopt;
|
||||
|
||||
// Ansi
|
||||
struct option_a {
|
||||
const char *name;
|
||||
int has_arg;
|
||||
int *flag;
|
||||
int val;
|
||||
};
|
||||
extern _GETOPT_API char *optarg_a;
|
||||
extern _GETOPT_API int getopt_a(int argc, char *const *argv, const char *optstring) _GETOPT_THROW;
|
||||
extern _GETOPT_API int getopt_long_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW;
|
||||
extern _GETOPT_API int getopt_long_only_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW;
|
||||
|
||||
// Unicode
|
||||
struct option_w {
|
||||
const wchar_t *name;
|
||||
int has_arg;
|
||||
int *flag;
|
||||
int val;
|
||||
};
|
||||
extern _GETOPT_API wchar_t *optarg_w;
|
||||
extern _GETOPT_API int getopt_w(int argc, wchar_t *const *argv, const wchar_t *optstring) _GETOPT_THROW;
|
||||
extern _GETOPT_API int getopt_long_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW;
|
||||
extern _GETOPT_API int getopt_long_only_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW;
|
||||
|
||||
_END_EXTERN_C
|
||||
|
||||
#undef _BEGIN_EXTERN_C
|
||||
#undef _END_EXTERN_C
|
||||
#undef _GETOPT_THROW
|
||||
#undef _GETOPT_API
|
||||
|
||||
#ifdef _UNICODE
|
||||
# define getopt getopt_w
|
||||
# define getopt_long getopt_long_w
|
||||
# define getopt_long_only getopt_long_only_w
|
||||
# define option option_w
|
||||
# define optarg optarg_w
|
||||
#else
|
||||
# define getopt getopt_a
|
||||
# define getopt_long getopt_long_a
|
||||
# define getopt_long_only getopt_long_only_a
|
||||
# define option option_a
|
||||
# define optarg optarg_a
|
||||
#endif
|
||||
#endif // __GETOPT_H_
|
||||
@@ -1,19 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
set(SOURCES gstfastspectrum.cpp gstmoodbarplugin.cpp)
|
||||
set(SOURCES gstfastspectrum.cpp)
|
||||
|
||||
link_directories(
|
||||
${GLIB_LIBRARY_DIRS}
|
||||
${GOBJECT_LIBRARY_DIRS}
|
||||
${GSTREAMER_LIBRARY_DIRS}
|
||||
${GSTREAMER_BASE_LIBRARY_DIRS}
|
||||
${GSTREAMER_AUDIO_LIBRARY_DIRS}
|
||||
${FFTW3_LIBRARY_DIRS}
|
||||
)
|
||||
add_library(gstfastspectrum STATIC ${SOURCES})
|
||||
|
||||
add_library(gstmoodbar STATIC ${SOURCES})
|
||||
|
||||
target_include_directories(gstmoodbar SYSTEM PRIVATE
|
||||
target_include_directories(gstfastspectrum SYSTEM PRIVATE
|
||||
${GLIB_INCLUDE_DIRS}
|
||||
${GOBJECT_INCLUDE_DIRS}
|
||||
${GSTREAMER_INCLUDE_DIRS}
|
||||
@@ -22,14 +13,22 @@ target_include_directories(gstmoodbar SYSTEM PRIVATE
|
||||
${FFTW3_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
target_include_directories(gstmoodbar PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_include_directories(gstfastspectrum PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
target_link_libraries(gstmoodbar PRIVATE
|
||||
target_link_directories(gstfastspectrum PRIVATE
|
||||
${GLIB_LIBRARY_DIRS}
|
||||
${GOBJECT_LIBRARY_DIRS}
|
||||
${GSTREAMER_LIBRARY_DIRS}
|
||||
${GSTREAMER_BASE_LIBRARY_DIRS}
|
||||
${GSTREAMER_AUDIO_LIBRARY_DIRS}
|
||||
${FFTW3_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(gstfastspectrum PRIVATE
|
||||
${GLIB_LIBRARIES}
|
||||
${GOBJECT_LIBRARIES}
|
||||
${GSTREAMER_LIBRARIES}
|
||||
${GSTREAMER_BASE_LIBRARIES}
|
||||
${GSTREAMER_AUDIO_LIBRARIES}
|
||||
${FFTW3_FFTW_LIBRARY}
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
)
|
||||
520
3rdparty/gstfastspectrum/gstfastspectrum.cpp
vendored
Normal file
520
3rdparty/gstfastspectrum/gstfastspectrum.cpp
vendored
Normal file
@@ -0,0 +1,520 @@
|
||||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* <2006,2011> Stefan Kost <ensonic@users.sf.net>
|
||||
* <2007-2009> Sebastian Dröge <sebastian.droege@collabora.co.uk>
|
||||
* <2018-2024> Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/audio/gstaudiofilter.h>
|
||||
|
||||
#include <fftw3.h>
|
||||
|
||||
#include "gstfastspectrum.h"
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC(gst_strawberry_fastspectrum_debug);
|
||||
|
||||
namespace {
|
||||
|
||||
// Spectrum properties
|
||||
constexpr auto DEFAULT_INTERVAL = (GST_SECOND / 10);
|
||||
constexpr auto DEFAULT_BANDS = 128;
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_INTERVAL,
|
||||
PROP_BANDS
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
G_DEFINE_TYPE(GstStrawberryFastSpectrum, gst_strawberry_fastspectrum, GST_TYPE_AUDIO_FILTER)
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
static void gst_strawberry_fastspectrum_finalize(GObject *object);
|
||||
static void gst_strawberry_fastspectrum_set_property(GObject *object, const guint prop_id, const GValue *value, GParamSpec *pspec);
|
||||
static void gst_strawberry_fastspectrum_get_property(GObject *object, const guint prop_id, GValue *value, GParamSpec *pspec);
|
||||
static gboolean gst_strawberry_fastspectrum_start(GstBaseTransform *transform);
|
||||
static gboolean gst_strawberry_fastspectrum_stop(GstBaseTransform *transform);
|
||||
static GstFlowReturn gst_strawberry_fastspectrum_transform_ip(GstBaseTransform *transform, GstBuffer *buffer);
|
||||
static gboolean gst_strawberry_fastspectrum_setup(GstAudioFilter *audio_filter, const GstAudioInfo *audio_info);
|
||||
|
||||
static void gst_strawberry_fastspectrum_class_init(GstStrawberryFastSpectrumClass *klass) {
|
||||
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
|
||||
GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
|
||||
GstBaseTransformClass *transform_class = GST_BASE_TRANSFORM_CLASS(klass);
|
||||
GstAudioFilterClass *filter_class = GST_AUDIO_FILTER_CLASS(klass);
|
||||
|
||||
gobject_class->set_property = gst_strawberry_fastspectrum_set_property;
|
||||
gobject_class->get_property = gst_strawberry_fastspectrum_get_property;
|
||||
gobject_class->finalize = gst_strawberry_fastspectrum_finalize;
|
||||
|
||||
transform_class->start = GST_DEBUG_FUNCPTR(gst_strawberry_fastspectrum_start);
|
||||
transform_class->stop = GST_DEBUG_FUNCPTR(gst_strawberry_fastspectrum_stop);
|
||||
transform_class->transform_ip = GST_DEBUG_FUNCPTR(gst_strawberry_fastspectrum_transform_ip);
|
||||
transform_class->passthrough_on_same_caps = TRUE;
|
||||
|
||||
filter_class->setup = GST_DEBUG_FUNCPTR(gst_strawberry_fastspectrum_setup);
|
||||
|
||||
g_object_class_install_property(gobject_class, PROP_INTERVAL, g_param_spec_uint64("interval", "Interval", "Interval of time between message posts (in nanoseconds)", 1, G_MAXUINT64, DEFAULT_INTERVAL, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||
|
||||
g_object_class_install_property(gobject_class, PROP_BANDS, g_param_spec_uint("bands", "Bands", "Number of frequency bands", 0, G_MAXUINT, DEFAULT_BANDS, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT(gst_strawberry_fastspectrum_debug, "spectrum", 0, "audio spectrum analyser element");
|
||||
|
||||
gst_element_class_set_static_metadata(element_class,
|
||||
"Fast spectrum analyzer using FFTW",
|
||||
"Filter/Analyzer/Audio",
|
||||
"Run an FFT on the audio signal, output spectrum data",
|
||||
"Erik Walthinsen <omega@cse.ogi.edu>, "
|
||||
"Stefan Kost <ensonic@users.sf.net>, "
|
||||
"Sebastian Dröge <sebastian.droege@collabora.co.uk>, "
|
||||
"Jonas Kvinge <jonas@jkvinge.net>");
|
||||
|
||||
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
||||
GstCaps *caps = gst_caps_from_string(GST_AUDIO_CAPS_MAKE("{ S16LE, S24LE, S32LE, F32LE, F64LE }") ", layout = (string) interleaved, channels = 1");
|
||||
#else
|
||||
GstCaps *caps = gst_caps_from_string(GST_AUDIO_CAPS_MAKE("{ S16BE, S24BE, S32BE, F32BE, F64BE }") ", layout = (string) interleaved, channels = 1");
|
||||
#endif
|
||||
|
||||
gst_audio_filter_class_add_pad_templates(filter_class, caps);
|
||||
gst_caps_unref(caps);
|
||||
|
||||
g_mutex_init(&klass->fftw_lock);
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_init(GstStrawberryFastSpectrum *fastspectrum) {
|
||||
|
||||
fastspectrum->interval = DEFAULT_INTERVAL;
|
||||
fastspectrum->bands = DEFAULT_BANDS;
|
||||
|
||||
fastspectrum->channel_data_initialized = false;
|
||||
|
||||
g_mutex_init(&fastspectrum->lock);
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_alloc_channel_data(GstStrawberryFastSpectrum *fastspectrum) {
|
||||
|
||||
const guint bands = fastspectrum->bands;
|
||||
const guint nfft = 2 * bands - 2;
|
||||
|
||||
fastspectrum->input_ring_buffer = new double[nfft];
|
||||
fastspectrum->fft_input = reinterpret_cast<double*>(fftw_malloc(sizeof(double) * nfft));
|
||||
fastspectrum->fft_output = reinterpret_cast<fftw_complex*>(fftw_malloc(sizeof(fftw_complex) * (nfft / 2 + 1)));
|
||||
|
||||
fastspectrum->spect_magnitude = new double[bands] {};
|
||||
|
||||
GstStrawberryFastSpectrumClass *klass = reinterpret_cast<GstStrawberryFastSpectrumClass*>(G_OBJECT_GET_CLASS(fastspectrum));
|
||||
{
|
||||
g_mutex_lock(&klass->fftw_lock);
|
||||
fastspectrum->plan = fftw_plan_dft_r2c_1d(static_cast<int>(nfft), fastspectrum->fft_input, fastspectrum->fft_output, FFTW_ESTIMATE);
|
||||
g_mutex_unlock(&klass->fftw_lock);
|
||||
}
|
||||
fastspectrum->channel_data_initialized = true;
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_free_channel_data(GstStrawberryFastSpectrum *fastspectrum) {
|
||||
|
||||
GstStrawberryFastSpectrumClass *klass = reinterpret_cast<GstStrawberryFastSpectrumClass*>(G_OBJECT_GET_CLASS(fastspectrum));
|
||||
|
||||
if (fastspectrum->channel_data_initialized) {
|
||||
{
|
||||
g_mutex_lock(&klass->fftw_lock);
|
||||
fftw_destroy_plan(fastspectrum->plan);
|
||||
g_mutex_unlock(&klass->fftw_lock);
|
||||
}
|
||||
fftw_free(fastspectrum->fft_input);
|
||||
fftw_free(fastspectrum->fft_output);
|
||||
delete[] fastspectrum->input_ring_buffer;
|
||||
delete[] fastspectrum->spect_magnitude;
|
||||
|
||||
fastspectrum->channel_data_initialized = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_flush(GstStrawberryFastSpectrum *fastspectrum) {
|
||||
|
||||
fastspectrum->num_frames = 0;
|
||||
fastspectrum->num_fft = 0;
|
||||
fastspectrum->accumulated_error = 0;
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_reset_state(GstStrawberryFastSpectrum *fastspectrum) {
|
||||
|
||||
GST_DEBUG_OBJECT(fastspectrum, "resetting state");
|
||||
|
||||
gst_strawberry_fastspectrum_free_channel_data(fastspectrum);
|
||||
gst_strawberry_fastspectrum_flush(fastspectrum);
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_finalize(GObject *object) {
|
||||
|
||||
GstStrawberryFastSpectrum *fastspectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(object);
|
||||
|
||||
gst_strawberry_fastspectrum_reset_state(fastspectrum);
|
||||
g_mutex_clear(&fastspectrum->lock);
|
||||
|
||||
G_OBJECT_CLASS(gst_strawberry_fastspectrum_parent_class)->finalize(object);
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_set_property(GObject *object, const guint prop_id, const GValue *value, GParamSpec *pspec) {
|
||||
|
||||
GstStrawberryFastSpectrum *filter = reinterpret_cast<GstStrawberryFastSpectrum*>(object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_INTERVAL: {
|
||||
const guint64 interval = g_value_get_uint64(value);
|
||||
g_mutex_lock(&filter->lock);
|
||||
if (filter->interval != interval) {
|
||||
filter->interval = interval;
|
||||
gst_strawberry_fastspectrum_reset_state(filter);
|
||||
}
|
||||
g_mutex_unlock(&filter->lock);
|
||||
break;
|
||||
}
|
||||
case PROP_BANDS: {
|
||||
const guint bands = g_value_get_uint(value);
|
||||
g_mutex_lock(&filter->lock);
|
||||
if (filter->bands != bands) {
|
||||
filter->bands = bands;
|
||||
gst_strawberry_fastspectrum_reset_state(filter);
|
||||
}
|
||||
g_mutex_unlock(&filter->lock);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_get_property(GObject *object, const guint prop_id, GValue *value, GParamSpec *pspec) {
|
||||
|
||||
GstStrawberryFastSpectrum *fastspectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_INTERVAL:
|
||||
g_value_set_uint64(value, fastspectrum->interval);
|
||||
break;
|
||||
case PROP_BANDS:
|
||||
g_value_set_uint(value, fastspectrum->bands);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static gboolean gst_strawberry_fastspectrum_start(GstBaseTransform *transform) {
|
||||
|
||||
GstStrawberryFastSpectrum *fastspectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(transform);
|
||||
|
||||
gst_strawberry_fastspectrum_reset_state(fastspectrum);
|
||||
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
static gboolean gst_strawberry_fastspectrum_stop(GstBaseTransform *transform) {
|
||||
|
||||
GstStrawberryFastSpectrum *fastspectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(transform);
|
||||
|
||||
gst_strawberry_fastspectrum_reset_state(fastspectrum);
|
||||
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
// Mixing data readers
|
||||
|
||||
static void gst_strawberry_fastspectrum_input_data_mixed_float(const guint8 *_in, double *out, const guint64 len, const double max_value, guint op, const guint nfft) {
|
||||
|
||||
(void) max_value;
|
||||
|
||||
const gfloat *in = reinterpret_cast<const gfloat*>(_in);
|
||||
guint ip = 0;
|
||||
|
||||
for (guint64 j = 0; j < len; j++) {
|
||||
out[op] = in[ip++];
|
||||
op = (op + 1) % nfft;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_input_data_mixed_double(const guint8 *_in, double *out, const guint64 len, const double max_value, guint op, const guint nfft) {
|
||||
|
||||
(void) max_value;
|
||||
|
||||
const gdouble *in = reinterpret_cast<const gdouble*>(_in);
|
||||
guint ip = 0;
|
||||
|
||||
for (guint64 j = 0; j < len; j++) {
|
||||
out[op] = in[ip++];
|
||||
op = (op + 1) % nfft;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_input_data_mixed_int32_max(const guint8 *_in, double *out, const guint64 len, const double max_value, guint op, const guint nfft) {
|
||||
|
||||
const gint32 *in = reinterpret_cast<const gint32*>(_in);
|
||||
guint ip = 0;
|
||||
|
||||
for (guint64 j = 0; j < len; j++) {
|
||||
out[op] = in[ip++] / max_value;
|
||||
op = (op + 1) % nfft;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_input_data_mixed_int24_max(const guint8 *_in, double *out, const guint64 len, const double max_value, guint op, const guint nfft) {
|
||||
|
||||
for (guint64 j = 0; j < len; j++) {
|
||||
#if G_BYTE_ORDER == G_BIG_ENDIAN
|
||||
guint32 value = GST_READ_UINT24_BE(_in);
|
||||
#else
|
||||
guint32 value = GST_READ_UINT24_LE(_in);
|
||||
#endif
|
||||
if (value & 0x00800000) {
|
||||
value |= 0xff000000;
|
||||
}
|
||||
|
||||
out[op] = value / max_value;
|
||||
op = (op + 1) % nfft;
|
||||
_in += 3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_input_data_mixed_int16_max(const guint8 *_in, double *out, const guint64 len, const double max_value, guint op, const guint nfft) {
|
||||
|
||||
const gint16 *in = reinterpret_cast<const gint16*>(_in);
|
||||
guint ip = 0;
|
||||
|
||||
for (guint64 j = 0; j < len; j++) {
|
||||
out[op] = in[ip++] / max_value;
|
||||
op = (op + 1) % nfft;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static gboolean gst_strawberry_fastspectrum_setup(GstAudioFilter *audio_filter, const GstAudioInfo *audio_info) {
|
||||
|
||||
GstStrawberryFastSpectrum *fastspectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(audio_filter);
|
||||
GstStrawberryFastSpectrumInputData input_data = nullptr;
|
||||
|
||||
g_mutex_lock(&fastspectrum->lock);
|
||||
switch (GST_AUDIO_INFO_FORMAT(audio_info)) {
|
||||
case GST_AUDIO_FORMAT_S16:
|
||||
input_data = gst_strawberry_fastspectrum_input_data_mixed_int16_max;
|
||||
break;
|
||||
case GST_AUDIO_FORMAT_S24:
|
||||
input_data = gst_strawberry_fastspectrum_input_data_mixed_int24_max;
|
||||
break;
|
||||
case GST_AUDIO_FORMAT_S32:
|
||||
input_data = gst_strawberry_fastspectrum_input_data_mixed_int32_max;
|
||||
break;
|
||||
case GST_AUDIO_FORMAT_F32:
|
||||
input_data = gst_strawberry_fastspectrum_input_data_mixed_float;
|
||||
break;
|
||||
case GST_AUDIO_FORMAT_F64:
|
||||
input_data = gst_strawberry_fastspectrum_input_data_mixed_double;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
break;
|
||||
}
|
||||
fastspectrum->input_data = input_data;
|
||||
|
||||
gst_strawberry_fastspectrum_reset_state(fastspectrum);
|
||||
g_mutex_unlock(&fastspectrum->lock);
|
||||
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_run_fft(GstStrawberryFastSpectrum *fastspectrum, const guint input_pos) {
|
||||
|
||||
const guint bands = fastspectrum->bands;
|
||||
const guint nfft = 2 * bands - 2;
|
||||
|
||||
for (guint i = 0; i < nfft; i++) {
|
||||
fastspectrum->fft_input[i] = fastspectrum->input_ring_buffer[(input_pos + i) % nfft];
|
||||
}
|
||||
|
||||
// Should be safe to execute the same plan multiple times in parallel.
|
||||
fftw_execute(fastspectrum->plan);
|
||||
|
||||
// Calculate magnitude in db
|
||||
for (guint i = 0; i < bands; i++) {
|
||||
gdouble value = fastspectrum->fft_output[i][0] * fastspectrum->fft_output[i][0];
|
||||
value += fastspectrum->fft_output[i][1] * fastspectrum->fft_output[i][1];
|
||||
value /= nfft * nfft;
|
||||
fastspectrum->spect_magnitude[i] += value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static GstFlowReturn gst_strawberry_fastspectrum_transform_ip(GstBaseTransform *transform, GstBuffer *buffer) {
|
||||
|
||||
GstStrawberryFastSpectrum *fastspectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(transform);
|
||||
|
||||
const guint rate = GST_AUDIO_FILTER_RATE(fastspectrum);
|
||||
const guint bps = GST_AUDIO_FILTER_BPS(fastspectrum);
|
||||
const guint64 bpf = GST_AUDIO_FILTER_BPF(fastspectrum);
|
||||
const double max_value = static_cast<double>((1UL << ((bps << 3) - 1)) - 1);
|
||||
const guint bands = fastspectrum->bands;
|
||||
const guint nfft = 2 * bands - 2;
|
||||
|
||||
g_mutex_lock(&fastspectrum->lock);
|
||||
|
||||
GstMapInfo map;
|
||||
gst_buffer_map(buffer, &map, GST_MAP_READ);
|
||||
const guint8 *data = map.data;
|
||||
gsize size = map.size;
|
||||
|
||||
GST_LOG_OBJECT(fastspectrum, "input size: %" G_GSIZE_FORMAT " bytes", size);
|
||||
|
||||
if (GST_BUFFER_IS_DISCONT(buffer)) {
|
||||
GST_DEBUG_OBJECT(fastspectrum, "Discontinuity detected -- flushing");
|
||||
gst_strawberry_fastspectrum_flush(fastspectrum);
|
||||
}
|
||||
|
||||
// If we don't have a FFT context yet (or it was reset due to parameter changes) get one and allocate memory for everything
|
||||
if (!fastspectrum->channel_data_initialized) {
|
||||
GST_DEBUG_OBJECT(fastspectrum, "allocating for bands %u", bands);
|
||||
|
||||
gst_strawberry_fastspectrum_alloc_channel_data(fastspectrum);
|
||||
|
||||
// Number of sample frames we process before posting a message interval is in ns
|
||||
fastspectrum->frames_per_interval = gst_util_uint64_scale(fastspectrum->interval, rate, GST_SECOND);
|
||||
fastspectrum->frames_todo = fastspectrum->frames_per_interval;
|
||||
// Rounding error for frames_per_interval in ns, aggregated it in accumulated_error
|
||||
fastspectrum->error_per_interval = (fastspectrum->interval * rate) % GST_SECOND;
|
||||
if (fastspectrum->frames_per_interval == 0) {
|
||||
fastspectrum->frames_per_interval = 1;
|
||||
}
|
||||
|
||||
GST_INFO_OBJECT(fastspectrum, "interval %" GST_TIME_FORMAT ", fpi %" G_GUINT64_FORMAT ", error %" GST_TIME_FORMAT, GST_TIME_ARGS(fastspectrum->interval), fastspectrum->frames_per_interval, GST_TIME_ARGS(fastspectrum->error_per_interval));
|
||||
|
||||
fastspectrum->input_pos = 0;
|
||||
|
||||
gst_strawberry_fastspectrum_flush(fastspectrum);
|
||||
}
|
||||
|
||||
if (fastspectrum->num_frames == 0) {
|
||||
fastspectrum->message_ts = GST_BUFFER_TIMESTAMP(buffer);
|
||||
}
|
||||
|
||||
guint input_pos = fastspectrum->input_pos;
|
||||
GstStrawberryFastSpectrumInputData input_data = fastspectrum->input_data;
|
||||
|
||||
while (size >= bpf) {
|
||||
// Run input_data for a chunk of data
|
||||
guint64 fft_todo = nfft - (fastspectrum->num_frames % nfft);
|
||||
guint64 msg_todo = fastspectrum->frames_todo - fastspectrum->num_frames;
|
||||
GST_LOG_OBJECT(fastspectrum, "message frames todo: %lu, fft frames todo: %lu, input frames %" G_GSIZE_FORMAT, msg_todo, fft_todo, (size / bpf));
|
||||
guint64 block_size = msg_todo;
|
||||
if (block_size > (size / bpf)) {
|
||||
block_size = (size / bpf);
|
||||
}
|
||||
if (block_size > fft_todo) {
|
||||
block_size = fft_todo;
|
||||
}
|
||||
|
||||
// Move the current frames into our ringbuffers
|
||||
input_data(data, fastspectrum->input_ring_buffer, block_size, max_value, input_pos, nfft);
|
||||
|
||||
data += block_size * bpf;
|
||||
size -= block_size * bpf;
|
||||
input_pos = (input_pos + block_size) % nfft;
|
||||
fastspectrum->num_frames += block_size;
|
||||
|
||||
gboolean have_full_interval = (fastspectrum->num_frames == fastspectrum->frames_todo);
|
||||
|
||||
GST_LOG_OBJECT(fastspectrum, "size: %" G_GSIZE_FORMAT ", do-fft = %d, do-message = %d", size, (fastspectrum->num_frames % nfft == 0), have_full_interval);
|
||||
|
||||
// If we have enough frames for an FFT or we have all frames required for the interval and we haven't run a FFT, then run an FFT
|
||||
if ((fastspectrum->num_frames % nfft == 0) || (have_full_interval && !fastspectrum->num_fft)) {
|
||||
gst_strawberry_fastspectrum_run_fft(fastspectrum, input_pos);
|
||||
fastspectrum->num_fft++;
|
||||
}
|
||||
|
||||
// Do we have the FFTs for one interval?
|
||||
if (have_full_interval) {
|
||||
GST_DEBUG_OBJECT(fastspectrum, "nfft: %u frames: %" G_GUINT64_FORMAT " fpi: %" G_GUINT64_FORMAT " error: %" GST_TIME_FORMAT, nfft, fastspectrum->num_frames, fastspectrum->frames_per_interval, GST_TIME_ARGS(fastspectrum->accumulated_error));
|
||||
|
||||
fastspectrum->frames_todo = fastspectrum->frames_per_interval;
|
||||
if (fastspectrum->accumulated_error >= GST_SECOND) {
|
||||
fastspectrum->accumulated_error -= GST_SECOND;
|
||||
fastspectrum->frames_todo++;
|
||||
}
|
||||
fastspectrum->accumulated_error += fastspectrum->error_per_interval;
|
||||
|
||||
if (fastspectrum->output_callback) {
|
||||
// Calculate average
|
||||
for (guint i = 0; i < fastspectrum->bands; i++) {
|
||||
fastspectrum->spect_magnitude[i] /= static_cast<double>(fastspectrum->num_fft);
|
||||
}
|
||||
|
||||
fastspectrum->output_callback(fastspectrum->spect_magnitude, static_cast<int>(fastspectrum->bands));
|
||||
|
||||
// Reset spectrum accumulators
|
||||
memset(fastspectrum->spect_magnitude, 0, fastspectrum->bands * sizeof(double));
|
||||
}
|
||||
|
||||
if (GST_CLOCK_TIME_IS_VALID(fastspectrum->message_ts)) {
|
||||
fastspectrum->message_ts += gst_util_uint64_scale(fastspectrum->num_frames, GST_SECOND, rate);
|
||||
}
|
||||
|
||||
fastspectrum->num_frames = 0;
|
||||
fastspectrum->num_fft = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fastspectrum->input_pos = input_pos;
|
||||
|
||||
gst_buffer_unmap(buffer, &map);
|
||||
g_mutex_unlock(&fastspectrum->lock);
|
||||
|
||||
g_assert(size == 0);
|
||||
|
||||
return GST_FLOW_OK;
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* Copyright (C) <2009> Sebastian Dröge <sebastian.droege@collabora.co.uk>
|
||||
* Copyright (C) <2018-2024> Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
@@ -25,9 +26,8 @@
|
||||
// - Send output via a callback instead of GST messages (less overhead).
|
||||
// - Removed all properties except interval and band.
|
||||
|
||||
|
||||
#ifndef GST_MOODBAR_FASTSPECTRUM_H
|
||||
#define GST_MOODBAR_FASTSPECTRUM_H
|
||||
#ifndef GST_STRAWBERRY_FASTSPECTRUM_H
|
||||
#define GST_STRAWBERRY_FASTSPECTRUM_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
@@ -37,19 +37,17 @@
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_FASTSPECTRUM (gst_fastspectrum_get_type())
|
||||
#define GST_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_FASTSPECTRUM, GstFastSpectrum))
|
||||
#define GST_IS_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_FASTSPECTRUM))
|
||||
#define GST_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_FASTSPECTRUM, GstFastSpectrumClass))
|
||||
#define GST_IS_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_FASTSPECTRUM))
|
||||
#define GST_TYPE_STRAWBERRY_FASTSPECTRUM (gst_strawberry_fastspectrum_get_type())
|
||||
#define GST_STRAWBERRY_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_FASTSPECTRUM, GstStrawberryFastSpectrum))
|
||||
#define GST_IS_STRAWBERRY_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_FASTSPECTRUM))
|
||||
#define GST_STRAWBERRY_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_FASTSPECTRUM, GstStrawberryFastSpectrumClass))
|
||||
#define GST_IS_STRAWBERRY_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_FASTSPECTRUM))
|
||||
|
||||
class QMutex;
|
||||
typedef void (*GstStrawberryFastSpectrumInputData)(const guint8 *in, double *out, guint64 len, double max_value, guint op, guint nfft);
|
||||
|
||||
typedef void (*GstFastSpectrumInputData)(const guint8 *in, double *out, guint len, double max_value, guint op, guint nfft);
|
||||
using GstStrawberryFastSpectrumOutputCallback = std::function<void(double *magnitudes, int size)>;
|
||||
|
||||
using OutputCallback = std::function<void(double *magnitudes, int size)>;
|
||||
|
||||
struct GstFastSpectrum {
|
||||
struct GstStrawberryFastSpectrum {
|
||||
GstAudioFilter parent;
|
||||
|
||||
// Properties
|
||||
@@ -77,20 +75,17 @@ struct GstFastSpectrum {
|
||||
|
||||
GMutex lock;
|
||||
|
||||
GstFastSpectrumInputData input_data;
|
||||
|
||||
OutputCallback output_callback;
|
||||
GstStrawberryFastSpectrumInputData input_data;
|
||||
GstStrawberryFastSpectrumOutputCallback output_callback;
|
||||
};
|
||||
|
||||
struct GstFastSpectrumClass {
|
||||
struct GstStrawberryFastSpectrumClass {
|
||||
GstAudioFilterClass parent_class;
|
||||
|
||||
// Static lock for creating & destroying FFTW plans.
|
||||
QMutex *fftw_lock;
|
||||
GMutex fftw_lock;
|
||||
};
|
||||
|
||||
GType gst_fastspectrum_get_type(void);
|
||||
GType gst_strawberry_fastspectrum_get_type(void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif // GST_MOODBAR_FASTSPECTRUM_H
|
||||
#endif // GST_STRAWBERRY_FASTSPECTRUM_H
|
||||
2
3rdparty/kdsingleapplication/CMakeLists.txt
vendored
2
3rdparty/kdsingleapplication/CMakeLists.txt
vendored
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
set(SOURCES KDSingleApplication/src/kdsingleapplication.cpp KDSingleApplication/src/kdsingleapplication_localsocket.cpp)
|
||||
set(HEADERS KDSingleApplication/src/kdsingleapplication.h KDSingleApplication/src/kdsingleapplication_localsocket_p.h)
|
||||
qt_wrap_cpp(MOC ${HEADERS})
|
||||
|
||||
116
CMakeLists.txt
116
CMakeLists.txt
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
project(strawberry)
|
||||
|
||||
@@ -84,6 +84,13 @@ add_compile_options(${COMPILE_OPTIONS})
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES "Release")
|
||||
add_definitions(-DNDEBUG)
|
||||
set(ENABLE_DEBUG_OUTPUT_DEFAULT OFF)
|
||||
else()
|
||||
set(ENABLE_DEBUG_OUTPUT_DEFAULT ON)
|
||||
endif()
|
||||
|
||||
option(ENABLE_DEBUG_OUTPUT "Enable debug output" ${ENABLE_DEBUG_OUTPUT_DEFAULT})
|
||||
if(NOT ENABLE_DEBUG_OUTPUT)
|
||||
add_definitions(-DQT_NO_DEBUG_OUTPUT)
|
||||
endif()
|
||||
|
||||
@@ -92,6 +99,8 @@ if(USE_RPATH)
|
||||
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||
endif()
|
||||
|
||||
set(QT_NO_SHOW_OLD_QT_WRAP_CPP_WARNING ON)
|
||||
|
||||
find_program(CCACHE_EXECUTABLE NAMES ccache)
|
||||
if(CCACHE_EXECUTABLE)
|
||||
message(STATUS "ccache found: will be used for compilation and linkage")
|
||||
@@ -156,38 +165,14 @@ find_package(FFTW3)
|
||||
find_package(GTest)
|
||||
find_library(GMOCK_LIBRARY gmock)
|
||||
|
||||
option(BUILD_WITH_QT5 "Build with Qt 5" OFF)
|
||||
option(BUILD_WITH_QT6 "Build with Qt 6" OFF)
|
||||
|
||||
if(BUILD_WITH_QT6)
|
||||
set(QT_VERSION_MAJOR 6)
|
||||
elseif(BUILD_WITH_QT5)
|
||||
set(QT_VERSION_MAJOR 5)
|
||||
endif()
|
||||
|
||||
if(NOT QT_VERSION_MAJOR)
|
||||
message(STATUS "QT_VERSION_MAJOR, BUILD_WITH_QT5 or BUILD_WITH_QT6 not set, detecting Qt version...")
|
||||
find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED)
|
||||
endif()
|
||||
|
||||
if(QT_VERSION_MAJOR EQUAL 6)
|
||||
set(QT_MIN_VERSION 6.0)
|
||||
elseif(QT_VERSION_MAJOR EQUAL 5)
|
||||
set(QT_MIN_VERSION 5.12)
|
||||
else()
|
||||
message(FATAL_ERROR "Invalid QT_VERSION_MAJOR.")
|
||||
endif()
|
||||
|
||||
set(QT_VERSION_MAJOR 6)
|
||||
set(QT_MIN_VERSION 6.4.0)
|
||||
set(QT_DEFAULT_MAJOR_VERSION ${QT_VERSION_MAJOR})
|
||||
|
||||
set(QT_COMPONENTS Core Concurrent Gui Widgets Network Sql)
|
||||
set(QT_OPTIONAL_COMPONENTS LinguistTools Test)
|
||||
if(DBUS_FOUND AND NOT WIN32)
|
||||
list(APPEND QT_COMPONENTS DBus)
|
||||
endif()
|
||||
if(X11_FOUND AND QT_VERSION_MAJOR EQUAL 5)
|
||||
list(APPEND QT_COMPONENTS X11Extras)
|
||||
endif()
|
||||
|
||||
find_package(Qt${QT_VERSION_MAJOR} ${QT_MIN_VERSION} COMPONENTS ${QT_COMPONENTS} REQUIRED OPTIONAL_COMPONENTS ${QT_OPTIONAL_COMPONENTS})
|
||||
|
||||
@@ -197,27 +182,6 @@ endif()
|
||||
if(Qt${QT_VERSION_MAJOR}LinguistTools_FOUND)
|
||||
get_target_property(QT_LCONVERT_EXECUTABLE Qt${QT_VERSION_MAJOR}::lconvert LOCATION)
|
||||
endif()
|
||||
if(Qt${QT_VERSION_MAJOR}X11Extras_FOUND)
|
||||
set(HAVE_X11EXTRAS ON)
|
||||
endif()
|
||||
|
||||
if(QT_VERSION_MAJOR EQUAL 5 AND Qt5Core_VERSION VERSION_LESS 5.15.0)
|
||||
macro(qt_add_resources)
|
||||
qt5_add_resources(${ARGN})
|
||||
endmacro()
|
||||
macro(qt_wrap_cpp)
|
||||
qt5_wrap_cpp(${ARGN})
|
||||
endmacro()
|
||||
macro(qt_wrap_ui)
|
||||
qt5_wrap_ui(${ARGN})
|
||||
endmacro()
|
||||
macro(qt_add_dbus_adaptor)
|
||||
qt5_add_dbus_adaptor(${ARGN})
|
||||
endmacro()
|
||||
macro(qt_add_dbus_interface)
|
||||
qt5_add_dbus_interface(${ARGN})
|
||||
endmacro()
|
||||
endif()
|
||||
|
||||
if(X11_FOUND)
|
||||
find_path(KEYSYMDEF_H NAMES "keysymdef.h" PATHS "${X11_INCLUDE_DIR}" PATH_SUFFIXES "X11")
|
||||
@@ -242,21 +206,19 @@ if(X11_FOUND)
|
||||
endif()
|
||||
|
||||
# Check for QX11Application (Qt 6 compiled with XCB).
|
||||
if(QT_VERSION_MAJOR EQUAL 6 AND Qt6Gui_VERSION VERSION_GREATER_EQUAL 6.2.0)
|
||||
set(CMAKE_REQUIRED_FLAGS "-std=c++17")
|
||||
set(CMAKE_REQUIRED_LIBRARIES Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui)
|
||||
check_cxx_source_compiles("
|
||||
set(CMAKE_REQUIRED_FLAGS "-std=c++17")
|
||||
set(CMAKE_REQUIRED_LIBRARIES Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui)
|
||||
check_cxx_source_compiles("
|
||||
#include <QGuiApplication>
|
||||
int main() {
|
||||
(void)qApp->nativeInterface<QNativeInterface::QX11Application>();
|
||||
return 0;
|
||||
}
|
||||
"
|
||||
HAVE_QX11APPLICATION
|
||||
)
|
||||
unset(CMAKE_REQUIRED_FLAGS)
|
||||
unset(CMAKE_REQUIRED_LIBRARIES)
|
||||
endif()
|
||||
HAVE_QX11APPLICATION
|
||||
)
|
||||
unset(CMAKE_REQUIRED_FLAGS)
|
||||
unset(CMAKE_REQUIRED_LIBRARIES)
|
||||
|
||||
endif(X11_FOUND)
|
||||
|
||||
@@ -272,7 +234,7 @@ if(USE_TAGLIB)
|
||||
set(HAVE_TAGLIB_DSFFILE ON)
|
||||
set(HAVE_TAGLIB_DSDIFFFILE ON)
|
||||
else()
|
||||
pkg_check_modules(TAGLIB REQUIRED taglib>=1.11.1)
|
||||
pkg_check_modules(TAGLIB REQUIRED taglib>=1.12)
|
||||
endif()
|
||||
set(HAVE_TAGLIB ON)
|
||||
else()
|
||||
@@ -294,18 +256,10 @@ if(NOT HAVE_TAGLIB AND NOT HAVE_TAGPARSER)
|
||||
endif()
|
||||
|
||||
# SingleApplication
|
||||
if(QT_VERSION_MAJOR EQUAL 5)
|
||||
set(KDSINGLEAPPLICATION_NAME "KDSingleApplication")
|
||||
else()
|
||||
set(KDSINGLEAPPLICATION_NAME "KDSingleApplication-qt${QT_VERSION_MAJOR}")
|
||||
endif()
|
||||
set(KDSINGLEAPPLICATION_NAME "KDSingleApplication-qt${QT_VERSION_MAJOR}")
|
||||
find_package(${KDSINGLEAPPLICATION_NAME} 1.1.0)
|
||||
if(TARGET KDAB::kdsingleapplication)
|
||||
if(QT_VERSION_MAJOR EQUAL 5)
|
||||
set(KDSINGLEAPPLICATION_VERSION "${KDSingleApplication_VERSION}")
|
||||
elseif(QT_VERSION_MAJOR EQUAL 6)
|
||||
set(KDSINGLEAPPLICATION_VERSION "${KDSingleApplication-qt6_VERSION}")
|
||||
endif()
|
||||
set(KDSINGLEAPPLICATION_VERSION "${KDSingleApplication-qt6_VERSION}")
|
||||
message(STATUS "Using system KDSingleApplication (Version ${KDSINGLEAPPLICATION_VERSION})")
|
||||
set(SINGLEAPPLICATION_LIBRARIES KDAB::kdsingleapplication)
|
||||
else()
|
||||
@@ -321,23 +275,16 @@ if(APPLE)
|
||||
add_subdirectory(3rdparty/SPMediaKeyTap)
|
||||
set(SPMEDIAKEYTAP_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/SPMediaKeyTap)
|
||||
set(SPMEDIAKEYTAP_LIBRARIES SPMediaKeyTap)
|
||||
add_subdirectory(ext/macdeploycheck)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
find_package(getopt-win REQUIRED)
|
||||
pkg_check_modules(QTSPARKLE qtsparkle-qt${QT_VERSION_MAJOR})
|
||||
if(QTSPARKLE_FOUND)
|
||||
set(HAVE_QTSPARKLE ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
add_subdirectory(3rdparty/getopt)
|
||||
set(GETOPT_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/getopt)
|
||||
set(GETOPT_LIBRARIES getopt)
|
||||
add_definitions(-DSTATIC_GETOPT -D_UNICODE)
|
||||
endif()
|
||||
|
||||
if(WIN32 AND NOT MSVC)
|
||||
# RC compiler
|
||||
string(REPLACE "gcc" "windres" CMAKE_RC_COMPILER_INIT ${CMAKE_C_COMPILER})
|
||||
@@ -393,12 +340,9 @@ optional_component(GLOBALSHORTCUTS ON "Global shortcuts"
|
||||
DEPENDS "D-Bus, X11, Windows or macOS" HAVE_GLOBALSHORTCUTS_SUPPORT
|
||||
)
|
||||
|
||||
if(HAVE_QX11APPLICATION OR HAVE_X11EXTRAS OR HAVE_QPA_QPLATFORMNATIVEINTERFACE_H)
|
||||
set(X11_GLOBALSHORTCUTS_REQUIREMENT_FOUND ON)
|
||||
endif()
|
||||
optional_component(X11_GLOBALSHORTCUTS ON "X11 global shortcuts"
|
||||
DEPENDS "X11" X11_FOUND
|
||||
DEPENDS "QX11Application, X11Extras or qpa/qplatformnativeinterface.h header" X11_GLOBALSHORTCUTS_REQUIREMENT_FOUND
|
||||
DEPENDS "QX11Application" HAVE_QX11APPLICATION
|
||||
)
|
||||
|
||||
optional_component(AUDIOCD ON "Devices: Audio CD support"
|
||||
@@ -494,6 +438,8 @@ add_definitions(
|
||||
-DQT_NO_FOREACH
|
||||
-DQT_ASCII_CAST_WARNINGS
|
||||
-DQT_NO_CAST_FROM_ASCII
|
||||
-DQT_NO_KEYWORDS
|
||||
-DQT_NO_SIGNALS_SLOTS_KEYWORDS
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
@@ -510,7 +456,7 @@ add_subdirectory(ext/libstrawberry-common)
|
||||
add_subdirectory(ext/libstrawberry-tagreader)
|
||||
add_subdirectory(ext/strawberry-tagreader)
|
||||
if(HAVE_MOODBAR)
|
||||
add_subdirectory(ext/gstmoodbar)
|
||||
add_subdirectory(3rdparty/gstfastspectrum)
|
||||
endif()
|
||||
|
||||
if(GTest_FOUND AND GMOCK_LIBRARY AND Qt${QT_VERSION_MAJOR}Test_FOUND)
|
||||
@@ -534,16 +480,8 @@ elseif(NOT HAVE_GSTREAMER)
|
||||
message(WARNING "GStreamer is the only engine that is fully implemented. Using other engines is possible but not recommended.")
|
||||
endif()
|
||||
|
||||
if(QT_VERSION_MAJOR EQUAL 5)
|
||||
message(WARNING "It is detected that Strawberry is being built with Qt 5. There are no bugfix releases for the latest minor LTS version of Qt 5 available to open-source users, only commercial users. Therefore Strawberry should be built with Qt 6 when possible. Building with Qt 6 will also take advantage of improvements and new features not available in Qt 5. To build with Qt 6 specify -DBUILD_WITH_QT6=ON to automatically detect Qt 6, or for example -DCMAKE_PREFIX_PATH=/usr/local/lib64/cmake to manually specify the Qt 6 directory.")
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
if(NOT QT_SQLITE_TEST)
|
||||
message(WARNING "The Qt sqlite driver test failed.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(HAVE_TAGLIB AND TAGLIB_FOUND AND NOT TAGLIB_VERSION VERSION_GREATER_EQUAL 1.12)
|
||||
message(WARNING "There is a critical bug in TagLib (1.11.1) that can result in corrupt Ogg files, see: https://github.com/taglib/taglib/issues/864, please consider updating TagLib to the newest version.")
|
||||
endif()
|
||||
|
||||
31
Changelog
31
Changelog
@@ -2,17 +2,44 @@ Strawberry Music Player
|
||||
=======================
|
||||
ChangeLog
|
||||
|
||||
Unreleased:
|
||||
Version 1.1.3 (2024.09.21):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed gstreamer registry lookup leak in Spotify settings.
|
||||
* Fixed all songs in a CUE sheet starting playback at the zero position (#1549).
|
||||
* Fixed playback going to pause and back to play on song change.
|
||||
* Fixed Genius Lyrics login not working (#1554).
|
||||
* Fixed slow collection filter search.
|
||||
|
||||
Version 1.1.2 (2024.09.12):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed Tidal Open API cover provider to only login when needed instead of on startup.
|
||||
* Fixed KDE added keyboard accelerator characters (ampersands) appearing in sidebar (#1400, #1389, #1476).
|
||||
* Fixed KDE added keyboard accelerator characters (ampersands) appearing when editing playlist name (#1499).
|
||||
* Fixed collection "Search for this" adding prefix without value (#1510).
|
||||
* (macOS) Fixed missing Spotify.
|
||||
* Fixed play (-p) command line option not working on startup (#1465).
|
||||
* Fixed scan transaction being started when "Update the collection when Strawberry starts" option is unchecked (#1469)
|
||||
* Fixed Spotify bitrate being limited 128kbit/s.
|
||||
* Fixed Spotify returning too many artists and albums.
|
||||
* Fixed manually switching Spotify songs blocking UI.
|
||||
* Fixed analyzer not being set.
|
||||
* Fixed context top text being updated causing selected text to be unselected.
|
||||
* Fixed filter search to use filename for songs with empty title.
|
||||
* Fixed missing developer in Appstream appdata file.
|
||||
* Fixed MPRIS2 DesktopEntry to return desktop file entry without ".desktop" (#1516)
|
||||
* Fixed WavPack .wvc accepted as valid audio files (#1525).
|
||||
* Fixed dynamic playlist controls not following system colors (#1483).
|
||||
* Fixed freeze on playlist right click (#1478).
|
||||
* Fixed copying songs to a iPod device keeping too many files open (#1527).
|
||||
* Fixed MBIDs from MP4 being parsed incorrectly causing ListenBrainz errors (#1531).
|
||||
* Fixed playlist sorting after filename (#1538).
|
||||
|
||||
Enhancements:
|
||||
* Improved volume adjustment and track seeking using touchpad (#1498).
|
||||
* Use own thread for lyrics parsing.
|
||||
* Added url and filename columns to collection and playlist filter search.
|
||||
* (macOS) Added Spotify.
|
||||
|
||||
Version 1.1.1 (2024.07.22):
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ Resources:
|
||||
* Wiki: https://wiki.strawberrymusicplayer.org/
|
||||
* Forum: https://forum.strawberrymusicplayer.org/
|
||||
* Github: https://github.com/strawberrymusicplayer/strawberry
|
||||
* Buildbot: https://buildbot.strawberrymusicplayer.org/
|
||||
* Latest builds: https://builds.strawberrymusicplayer.org/
|
||||
* openSUSE buildservice: https://build.opensuse.org/package/show/home:jonaski:audio/strawberry
|
||||
* Ubuntu PPA: https://launchpad.net/~jonaski/+archive/ubuntu/strawberry
|
||||
@@ -76,7 +75,7 @@ To build Strawberry from source you need the following installed on your system
|
||||
* [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) or [pkgconf](https://github.com/pkgconf/pkgconf)
|
||||
* [Boost](https://www.boost.org/)
|
||||
* [GLib](https://developer.gnome.org/glib/)
|
||||
* [Qt 6 or Qt 5.12 or higher with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
|
||||
* [Qt 6.4.0 or higher with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
|
||||
* [SQLite 3.9 or newer](https://www.sqlite.org)
|
||||
* [Protobuf](https://developers.google.com/protocol-buffers/)
|
||||
* [ALSA (Required on Linux)](https://www.alsa-project.org/)
|
||||
@@ -108,14 +107,10 @@ You should also install the gstreamer plugins base and good, and optionally bad,
|
||||
cd strawberry
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DBUILD_WITH_QT6=ON
|
||||
cmake ..
|
||||
make -j $(nproc)
|
||||
sudo make install
|
||||
|
||||
Strawberry is backwards compatible with Qt 5, to compile with Qt 5 use:
|
||||
|
||||
cmake .. -DBUILD_WITH_QT5=ON
|
||||
|
||||
To compile on Windows with Visual Studio 2019 or 2022, see https://github.com/strawberrymusicplayer/strawberry-msvc
|
||||
|
||||
### :penguin: Packaging status
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
find_program(MACDEPLOYQT_EXECUTABLE NAMES macdeployqt PATHS /usr/bin /usr/local/bin /opt/local/bin /usr/local/opt/qt6/bin /usr/local/opt/qt5/bin REQUIRED)
|
||||
find_program(MACDEPLOYQT_EXECUTABLE NAMES macdeployqt PATHS /usr/bin /usr/local/bin /opt/local/bin /usr/local/opt/qt6/bin REQUIRED)
|
||||
if(MACDEPLOYQT_EXECUTABLE)
|
||||
message(STATUS "Found macdeployqt: ${MACDEPLOYQT_EXECUTABLE}")
|
||||
else()
|
||||
message(WARNING "Missing macdeployqt executable.")
|
||||
endif()
|
||||
|
||||
find_program(MACDEPLOYCHECK_EXECUTABLE NAMES macdeploycheck PATHS /usr/bin /usr/local/bin /opt/local/bin /usr/local/opt/qt6/bin REQUIRED)
|
||||
if(MACDEPLOYCHECK_EXECUTABLE)
|
||||
message(STATUS "Found macdeploycheck: ${MACDEPLOYCHECK_EXECUTABLE}")
|
||||
else()
|
||||
message(WARNING "Missing macdeploycheck executable.")
|
||||
endif()
|
||||
|
||||
find_program(CREATEDMG_EXECUTABLE NAMES create-dmg REQUIRED)
|
||||
if(CREATEDMG_EXECUTABLE)
|
||||
message(STATUS "Found create-dmg: ${CREATEDMG_EXECUTABLE}")
|
||||
@@ -31,10 +38,11 @@ if(MACDEPLOYQT_EXECUTABLE)
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
DEPENDS strawberry strawberry-tagreader
|
||||
)
|
||||
add_custom_target(deploycheck
|
||||
COMMAND ${CMAKE_BINARY_DIR}/ext/macdeploycheck/macdeploycheck strawberry.app
|
||||
DEPENDS macdeploycheck
|
||||
)
|
||||
if(MACDEPLOYCHECK_EXECUTABLE)
|
||||
add_custom_target(deploycheck
|
||||
COMMAND ${MACDEPLOYCHECK_EXECUTABLE} strawberry.app
|
||||
)
|
||||
endif()
|
||||
if(CREATEDMG_EXECUTABLE)
|
||||
add_custom_target(dmg
|
||||
COMMAND ${CREATEDMG_EXECUTABLE} --volname strawberry --background "${CMAKE_SOURCE_DIR}/dist/macos/dmg_background.png" --app-drop-link 450 218 --icon strawberry.app 150 218 --window-size 600 450 ${CREATEDMG_CODESIGN} ${CREATEDMG_SKIP_JENKINS_ARG} strawberry-${STRAWBERRY_VERSION_PACKAGE}-${CMAKE_HOST_SYSTEM_PROCESSOR}.dmg strawberry.app
|
||||
|
||||
@@ -44,7 +44,7 @@ macro(add_pot outfiles header pot)
|
||||
add_custom_command(
|
||||
OUTPUT ${pot}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMAND ${GETTEXT_XGETTEXT_EXECUTABLE} ${XGETTEXT_OPTIONS} -s -C --omit-header --output="${CMAKE_CURRENT_BINARY_DIR}/pot.temp" ${add_pot_sources}
|
||||
COMMAND ${GETTEXT_XGETTEXT_EXECUTABLE} ${XGETTEXT_OPTIONS} -C --omit-header --no-location --output="${CMAKE_CURRENT_BINARY_DIR}/pot.temp" ${add_pot_sources}
|
||||
COMMAND cat ${header} ${CMAKE_CURRENT_BINARY_DIR}/pot.temp > ${pot}
|
||||
DEPENDS ${add_pot_sources} ${header}
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
set(STRAWBERRY_VERSION_MAJOR 1)
|
||||
set(STRAWBERRY_VERSION_MINOR 1)
|
||||
set(STRAWBERRY_VERSION_PATCH 1)
|
||||
set(STRAWBERRY_VERSION_MINOR 2)
|
||||
set(STRAWBERRY_VERSION_PATCH 0)
|
||||
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
||||
|
||||
set(INCLUDE_GIT_REVISION ON)
|
||||
|
||||
@@ -45,5 +45,6 @@
|
||||
<file>mood/sample.mood</file>
|
||||
<file>text/ghosts.txt</file>
|
||||
<file>pictures/sidebar-background.png</file>
|
||||
<file>style/dynamicplaylistcontrols.css</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
13
data/style/dynamicplaylistcontrols.css
Normal file
13
data/style/dynamicplaylistcontrols.css
Normal file
@@ -0,0 +1,13 @@
|
||||
#container {
|
||||
background: %background;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(200, 200, 200, 75%);
|
||||
}
|
||||
|
||||
#label1 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#label2 {
|
||||
font-size: 7.5pt;
|
||||
}
|
||||
12
debian/CMakeLists.txt
vendored
12
debian/CMakeLists.txt
vendored
@@ -4,19 +4,7 @@ if(LSB_RELEASE_EXEC AND DPKG_BUILDPACKAGE)
|
||||
execute_process(COMMAND /bin/sh "-c" "${LSB_RELEASE_EXEC} -cs" OUTPUT_VARIABLE DEB_CODENAME OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
if(DEB_CODENAME AND DEB_DATE)
|
||||
|
||||
if(QT_VERSION_MAJOR EQUAL 5)
|
||||
set(DEBIAN_BUILD_DEPENDS_QT_PACKAGES qtbase5-dev,qtbase5-dev-tools,qttools5-dev,qttools5-dev-tools,libqt5x11extras5-dev)
|
||||
set(DEBIAN_DEPENDS_QT_PACKAGES libqt5sql5-sqlite)
|
||||
endif()
|
||||
if(QT_VERSION_MAJOR EQUAL 6)
|
||||
set(DEBIAN_BUILD_DEPENDS_QT_PACKAGES qt6-base-dev,qt6-base-dev-tools,qt6-tools-dev,qt6-tools-dev-tools,qt6-l10n-tools)
|
||||
set(DEBIAN_DEPENDS_QT_PACKAGES libqt6sql6-sqlite,qt6-qpa-plugins)
|
||||
endif()
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/control.in ${CMAKE_CURRENT_SOURCE_DIR}/control @ONLY)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/changelog.in ${CMAKE_CURRENT_SOURCE_DIR}/changelog)
|
||||
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
||||
9
debian/control.in → debian/control
vendored
9
debian/control.in → debian/control
vendored
@@ -18,7 +18,11 @@ Build-Depends: debhelper (>= 11),
|
||||
libpulse-dev,
|
||||
libtag1-dev,
|
||||
libicu-dev,
|
||||
@DEBIAN_BUILD_DEPENDS_QT_PACKAGES@,
|
||||
qt6-base-dev,
|
||||
qt6-base-dev-tools,
|
||||
qt6-tools-dev,
|
||||
qt6-tools-dev-tools,
|
||||
qt6-l10n-tools,
|
||||
libgstreamer1.0-dev,
|
||||
libgstreamer-plugins-base1.0-dev,
|
||||
libcdio-dev,
|
||||
@@ -33,7 +37,8 @@ Package: strawberry
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends},
|
||||
${misc:Depends},
|
||||
@DEBIAN_DEPENDS_QT_PACKAGES@,
|
||||
libqt6sql6-sqlite,
|
||||
qt6-qpa-plugins,
|
||||
gstreamer1.0-plugins-base,
|
||||
gstreamer1.0-plugins-good,
|
||||
gstreamer1.0-alsa,
|
||||
1
debian/copyright
vendored
1
debian/copyright
vendored
@@ -95,7 +95,6 @@ Files: src/core/main.h
|
||||
src/transcoder/transcoderoptionswavpack.h
|
||||
ext/libstrawberry-tagreader/tagreadertagparser.cpp
|
||||
ext/libstrawberry-tagreader/tagreadertagparser.h
|
||||
ext/macdeploycheck/*
|
||||
src/widgets/resizabletextedit.cpp
|
||||
src/widgets/resizabletextedit.h
|
||||
Copyright: 2012-2014, 2017-2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||
|
||||
@@ -12,11 +12,14 @@
|
||||
<summary>A music player and collection organizer</summary>
|
||||
<url type="homepage">https://www.strawberrymusicplayer.org/</url>
|
||||
<url type="bugtracker">https://github.com/strawberrymusicplayer/strawberry/</url>
|
||||
<translation type="qt">strawberry</translation>
|
||||
<developer id="net.jkvinge.jonas">
|
||||
<name>Jonas Kvinge</name>
|
||||
</developer>
|
||||
<translation type="gettext">strawberry</translation>
|
||||
<content_rating type="oars-1.1" />
|
||||
<description>
|
||||
<p>
|
||||
Strawberry is a music player and music collection organizer. It is aimed at music collectors and audiophiles. With Strawberry you can play and manage your digital music collection, or stream your favorite radios. It also has unofficial streaming support for Tidal and Qobuz. Strawberry is free software released under GPL. The source code is available on GitHub. It's written in C++ using the Qt toolkit and GStreamer. Strawberry is compatible with both Qt version 5 and 6.
|
||||
Strawberry is a music player and music collection organizer. It is aimed at music collectors and audiophiles. Strawberry is free software released under GPL. It's written in C++ using the Qt framework and GStreamer.
|
||||
</p>
|
||||
<p>Features:</p>
|
||||
<ul>
|
||||
@@ -30,12 +33,11 @@
|
||||
<li>Automatically retrieve tags from MusicBrainz</li>
|
||||
<li>Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify</li>
|
||||
<li>Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com and elyrics.net</li>
|
||||
<li>Support for multiple backends</li>
|
||||
<li>Audio analyzer and equalizer</li>
|
||||
<li>Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic</li>
|
||||
<li>Scrobbler with support for Last.fm, Libre.fm and ListenBrainz</li>
|
||||
<li>Streaming support for Subsonic-compatible servers</li>
|
||||
<li>Unofficial streaming support for Tidal and Qobuz</li>
|
||||
<li>Unofficial streaming support for Tidal, Spotify and Qobuz</li>
|
||||
</ul>
|
||||
</description>
|
||||
<screenshots>
|
||||
@@ -50,6 +52,8 @@
|
||||
</screenshots>
|
||||
<update_contact>eclipseo@fedoraproject.org</update_contact>
|
||||
<releases>
|
||||
<release version="1.1.3" date="2024-09-21"/>
|
||||
<release version="1.1.2" date="2024-09-12"/>
|
||||
<release version="1.1.1" date="2024-07-22"/>
|
||||
<release version="1.1.0" date="2024-07-14"/>
|
||||
<release version="1.0.23" date="2024-01-11"/>
|
||||
|
||||
8
dist/unix/strawberry.spec.in
vendored
8
dist/unix/strawberry.spec.in
vendored
@@ -55,9 +55,6 @@ BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Gui)
|
||||
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Widgets)
|
||||
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Test)
|
||||
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@LinguistTools)
|
||||
%if "@QT_VERSION_MAJOR@" == "5"
|
||||
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@X11Extras)
|
||||
%endif
|
||||
BuildRequires: pkgconfig(gstreamer-1.0)
|
||||
BuildRequires: pkgconfig(gstreamer-app-1.0)
|
||||
BuildRequires: pkgconfig(gstreamer-audio-1.0)
|
||||
@@ -74,13 +71,8 @@ BuildRequires: pkgconfig(libvlc)
|
||||
%endif
|
||||
|
||||
%if 0%{?suse_version}
|
||||
%if "@QT_VERSION_MAJOR@" == "6"
|
||||
Requires: qt6-sql-sqlite
|
||||
Requires: qt6-network-tls
|
||||
%endif
|
||||
%if "@QT_VERSION_MAJOR@" == "5"
|
||||
Requires: libQt5Sql5-sqlite
|
||||
%endif
|
||||
%endif
|
||||
|
||||
%description
|
||||
|
||||
42
dist/windows/strawberry.nsi.in
vendored
42
dist/windows/strawberry.nsi.in
vendored
@@ -261,6 +261,7 @@ Section "Strawberry" Strawberry
|
||||
File "libFLAC-12.dll"
|
||||
File "libbrotlicommon.dll"
|
||||
File "libbrotlidec.dll"
|
||||
File "libbrotlienc.dll"
|
||||
File "libbs2b-0.dll"
|
||||
File "libbz2.dll"
|
||||
File "libchromaprint.dll"
|
||||
@@ -272,6 +273,7 @@ Section "Strawberry" Strawberry
|
||||
File "libffi-8.dll"
|
||||
File "libfreetype-6.dll"
|
||||
File "libgcrypt-20.dll"
|
||||
File "libgetopt.dll"
|
||||
File "libgio-2.0-0.dll"
|
||||
File "libglib-2.0-0.dll"
|
||||
File "libgme.dll"
|
||||
@@ -328,6 +330,7 @@ Section "Strawberry" Strawberry
|
||||
File "libtasn1-6.dll"
|
||||
File "libtwolame-0.dll"
|
||||
File "libunistring-5.dll"
|
||||
File "libutf8_validity.dll"
|
||||
File "libvorbis-0.dll"
|
||||
File "libvorbisenc-2.dll"
|
||||
File "libvorbisfile-3.dll"
|
||||
@@ -409,6 +412,7 @@ Section "Strawberry" Strawberry
|
||||
!endif
|
||||
|
||||
File "FLAC.dll"
|
||||
File "abseil_dll.dll"
|
||||
File "brotlicommon.dll"
|
||||
File "brotlidec.dll"
|
||||
File "chromaprint.dll"
|
||||
@@ -416,6 +420,7 @@ Section "Strawberry" Strawberry
|
||||
File "faad-2.dll"
|
||||
File "fdk-aac.dll"
|
||||
File "ffi-7.dll"
|
||||
File "getopt.dll"
|
||||
File "gio-2.0-0.dll"
|
||||
File "glib-2.0-0.dll"
|
||||
File "gme.dll"
|
||||
@@ -461,10 +466,10 @@ Section "Strawberry" Strawberry
|
||||
File "soup-3.0-0.dll"
|
||||
File "sqlite3.dll"
|
||||
File "tag.dll"
|
||||
File "utf8_validity.dll"
|
||||
File "vorbis.dll"
|
||||
File "vorbisfile.dll"
|
||||
File "wavpackdll.dll"
|
||||
File "abseil_dll.dll"
|
||||
|
||||
!ifdef release
|
||||
File "freetype.dll"
|
||||
@@ -526,13 +531,13 @@ Section "Strawberry" Strawberry
|
||||
File "Qt6Widgets.dll"
|
||||
!endif
|
||||
|
||||
File "avcodec-60.dll"
|
||||
File "avfilter-9.dll"
|
||||
File "avformat-60.dll"
|
||||
File "avutil-58.dll"
|
||||
File "postproc-57.dll"
|
||||
File "swresample-4.dll"
|
||||
File "swscale-7.dll"
|
||||
File "avcodec-61.dll"
|
||||
File "avfilter-10.dll"
|
||||
File "avformat-61.dll"
|
||||
File "avutil-59.dll"
|
||||
File "postproc-58.dll"
|
||||
File "swresample-5.dll"
|
||||
File "swscale-8.dll"
|
||||
|
||||
; Register Strawberry with Default Programs
|
||||
Var /GLOBAL AppIcon
|
||||
@@ -831,6 +836,7 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libFLAC-12.dll"
|
||||
Delete "$INSTDIR\libbrotlicommon.dll"
|
||||
Delete "$INSTDIR\libbrotlidec.dll"
|
||||
Delete "$INSTDIR\libbrotlienc.dll"
|
||||
Delete "$INSTDIR\libbs2b-0.dll"
|
||||
Delete "$INSTDIR\libbz2.dll"
|
||||
Delete "$INSTDIR\libchromaprint.dll"
|
||||
@@ -842,6 +848,7 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libffi-8.dll"
|
||||
Delete "$INSTDIR\libfreetype-6.dll"
|
||||
Delete "$INSTDIR\libgcrypt-20.dll"
|
||||
Delete "$INSTDIR\libgetopt.dll"
|
||||
Delete "$INSTDIR\libgio-2.0-0.dll"
|
||||
Delete "$INSTDIR\libglib-2.0-0.dll"
|
||||
Delete "$INSTDIR\libgme.dll"
|
||||
@@ -898,6 +905,7 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libtasn1-6.dll"
|
||||
Delete "$INSTDIR\libtwolame-0.dll"
|
||||
Delete "$INSTDIR\libunistring-5.dll"
|
||||
Delete "$INSTDIR\libutf8_validity.dll"
|
||||
Delete "$INSTDIR\libvorbis-0.dll"
|
||||
Delete "$INSTDIR\libvorbisenc-2.dll"
|
||||
Delete "$INSTDIR\libvorbisfile-3.dll"
|
||||
@@ -979,6 +987,7 @@ Section "Uninstall"
|
||||
!endif
|
||||
|
||||
Delete "$INSTDIR\FLAC.dll"
|
||||
Delete "$INSTDIR\abseil_dll.dll"
|
||||
Delete "$INSTDIR\brotlicommon.dll"
|
||||
Delete "$INSTDIR\brotlidec.dll"
|
||||
Delete "$INSTDIR\chromaprint.dll"
|
||||
@@ -986,6 +995,7 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\faad-2.dll"
|
||||
Delete "$INSTDIR\fdk-aac.dll"
|
||||
Delete "$INSTDIR\ffi-7.dll"
|
||||
Delete "$INSTDIR\getopt.dll"
|
||||
Delete "$INSTDIR\gio-2.0-0.dll"
|
||||
Delete "$INSTDIR\glib-2.0-0.dll"
|
||||
Delete "$INSTDIR\gme.dll"
|
||||
@@ -1031,10 +1041,10 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\soup-3.0-0.dll"
|
||||
Delete "$INSTDIR\sqlite3.dll"
|
||||
Delete "$INSTDIR\tag.dll"
|
||||
Delete "$INSTDIR\utf8_validity.dll"
|
||||
Delete "$INSTDIR\vorbis.dll"
|
||||
Delete "$INSTDIR\vorbisfile.dll"
|
||||
Delete "$INSTDIR\wavpackdll.dll"
|
||||
Delete "$INSTDIR\abseil_dll.dll"
|
||||
|
||||
!ifdef release
|
||||
Delete "$INSTDIR\freetype.dll"
|
||||
@@ -1095,13 +1105,13 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\Qt6Widgets.dll"
|
||||
!endif
|
||||
|
||||
Delete "$INSTDIR\avcodec-60.dll"
|
||||
Delete "$INSTDIR\avfilter-9.dll"
|
||||
Delete "$INSTDIR\avformat-60.dll"
|
||||
Delete "$INSTDIR\avutil-58.dll"
|
||||
Delete "$INSTDIR\postproc-57.dll"
|
||||
Delete "$INSTDIR\swresample-4.dll"
|
||||
Delete "$INSTDIR\swscale-7.dll"
|
||||
Delete "$INSTDIR\avcodec-61.dll"
|
||||
Delete "$INSTDIR\avfilter-10.dll"
|
||||
Delete "$INSTDIR\avformat-61.dll"
|
||||
Delete "$INSTDIR\avutil-59.dll"
|
||||
Delete "$INSTDIR\postproc-58.dll"
|
||||
Delete "$INSTDIR\swresample-5.dll"
|
||||
Delete "$INSTDIR\swscale-8.dll"
|
||||
|
||||
!ifdef mingw
|
||||
Delete "$INSTDIR\gio-modules\libgiognutls.dll"
|
||||
|
||||
@@ -1,520 +0,0 @@
|
||||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* <2006,2011> Stefan Kost <ensonic@users.sf.net>
|
||||
* <2007-2009> Sebastian Dröge <sebastian.droege@collabora.co.uk>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/audio/gstaudiofilter.h>
|
||||
|
||||
#include <QMutex>
|
||||
|
||||
#include "gstfastspectrum.h"
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC(gst_fastspectrum_debug);
|
||||
|
||||
namespace {
|
||||
|
||||
// Spectrum properties
|
||||
constexpr auto DEFAULT_INTERVAL = (GST_SECOND / 10);
|
||||
constexpr auto DEFAULT_BANDS = 128;
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_INTERVAL,
|
||||
PROP_BANDS
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#define gst_fastspectrum_parent_class parent_class
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
G_DEFINE_TYPE(GstFastSpectrum, gst_fastspectrum, GST_TYPE_AUDIO_FILTER)
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
static void gst_fastspectrum_finalize(GObject *object);
|
||||
static void gst_fastspectrum_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
|
||||
static void gst_fastspectrum_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
|
||||
static gboolean gst_fastspectrum_start(GstBaseTransform *trans);
|
||||
static gboolean gst_fastspectrum_stop(GstBaseTransform *trans);
|
||||
static GstFlowReturn gst_fastspectrum_transform_ip(GstBaseTransform *trans, GstBuffer *buffer);
|
||||
static gboolean gst_fastspectrum_setup(GstAudioFilter *base, const GstAudioInfo *info);
|
||||
|
||||
static void gst_fastspectrum_class_init(GstFastSpectrumClass *klass) {
|
||||
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
|
||||
GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
|
||||
GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS(klass);
|
||||
GstAudioFilterClass *filter_class = GST_AUDIO_FILTER_CLASS(klass);
|
||||
GstCaps *caps = nullptr;
|
||||
|
||||
gobject_class->set_property = gst_fastspectrum_set_property;
|
||||
gobject_class->get_property = gst_fastspectrum_get_property;
|
||||
gobject_class->finalize = gst_fastspectrum_finalize;
|
||||
|
||||
trans_class->start = GST_DEBUG_FUNCPTR(gst_fastspectrum_start);
|
||||
trans_class->stop = GST_DEBUG_FUNCPTR(gst_fastspectrum_stop);
|
||||
trans_class->transform_ip = GST_DEBUG_FUNCPTR(gst_fastspectrum_transform_ip);
|
||||
trans_class->passthrough_on_same_caps = TRUE;
|
||||
|
||||
filter_class->setup = GST_DEBUG_FUNCPTR(gst_fastspectrum_setup);
|
||||
|
||||
g_object_class_install_property(gobject_class, PROP_INTERVAL, g_param_spec_uint64("interval", "Interval", "Interval of time between message posts (in nanoseconds)", 1, G_MAXUINT64, DEFAULT_INTERVAL, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||
|
||||
g_object_class_install_property(gobject_class, PROP_BANDS, g_param_spec_uint("bands", "Bands", "Number of frequency bands", 0, G_MAXUINT, DEFAULT_BANDS, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT(gst_fastspectrum_debug, "spectrum", 0, "audio spectrum analyser element");
|
||||
|
||||
gst_element_class_set_static_metadata(element_class, "Spectrum analyzer",
|
||||
"Filter/Analyzer/Audio",
|
||||
"Run an FFT on the audio signal, output spectrum data",
|
||||
"Erik Walthinsen <omega@cse.ogi.edu>, "
|
||||
"Stefan Kost <ensonic@users.sf.net>, "
|
||||
"Sebastian Dröge <sebastian.droege@collabora.co.uk>");
|
||||
|
||||
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
||||
caps = gst_caps_from_string(GST_AUDIO_CAPS_MAKE("{ S16LE, S24LE, S32LE, F32LE, F64LE }") ", layout = (string) interleaved, channels = 1");
|
||||
#else
|
||||
caps = gst_caps_from_string(GST_AUDIO_CAPS_MAKE("{ S16BE, S24BE, S32BE, F32BE, F64BE }") ", layout = (string) interleaved, channels = 1");
|
||||
#endif
|
||||
|
||||
gst_audio_filter_class_add_pad_templates(filter_class, caps);
|
||||
gst_caps_unref(caps);
|
||||
|
||||
klass->fftw_lock = new QMutex;
|
||||
}
|
||||
|
||||
static void gst_fastspectrum_init(GstFastSpectrum *spectrum) {
|
||||
|
||||
spectrum->interval = DEFAULT_INTERVAL;
|
||||
spectrum->bands = DEFAULT_BANDS;
|
||||
|
||||
spectrum->channel_data_initialized = false;
|
||||
|
||||
g_mutex_init(&spectrum->lock);
|
||||
|
||||
}
|
||||
|
||||
static void gst_fastspectrum_alloc_channel_data(GstFastSpectrum *spectrum) {
|
||||
|
||||
guint bands = spectrum->bands;
|
||||
guint nfft = 2 * bands - 2;
|
||||
|
||||
spectrum->input_ring_buffer = new double[nfft];
|
||||
spectrum->fft_input = reinterpret_cast<double*>(fftw_malloc(sizeof(double) * nfft));
|
||||
spectrum->fft_output = reinterpret_cast<fftw_complex*>(fftw_malloc(sizeof(fftw_complex) * (nfft / 2 + 1)));
|
||||
|
||||
spectrum->spect_magnitude = new double[bands] {};
|
||||
|
||||
GstFastSpectrumClass *klass = reinterpret_cast<GstFastSpectrumClass*>(G_OBJECT_GET_CLASS(spectrum));
|
||||
{
|
||||
QMutexLocker l(klass->fftw_lock);
|
||||
spectrum->plan = fftw_plan_dft_r2c_1d(static_cast<int>(nfft), spectrum->fft_input, spectrum->fft_output, FFTW_ESTIMATE);
|
||||
}
|
||||
spectrum->channel_data_initialized = true;
|
||||
|
||||
}
|
||||
|
||||
static void gst_fastspectrum_free_channel_data(GstFastSpectrum *spectrum) {
|
||||
|
||||
GstFastSpectrumClass *klass = reinterpret_cast<GstFastSpectrumClass*>(G_OBJECT_GET_CLASS(spectrum));
|
||||
if (spectrum->channel_data_initialized) {
|
||||
{
|
||||
QMutexLocker l(klass->fftw_lock);
|
||||
fftw_destroy_plan(spectrum->plan);
|
||||
}
|
||||
fftw_free(spectrum->fft_input);
|
||||
fftw_free(spectrum->fft_output);
|
||||
delete[] spectrum->input_ring_buffer;
|
||||
delete[] spectrum->spect_magnitude;
|
||||
|
||||
spectrum->channel_data_initialized = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void gst_fastspectrum_flush(GstFastSpectrum *spectrum) {
|
||||
|
||||
spectrum->num_frames = 0;
|
||||
spectrum->num_fft = 0;
|
||||
|
||||
spectrum->accumulated_error = 0;
|
||||
|
||||
}
|
||||
|
||||
static void gst_fastspectrum_reset_state(GstFastSpectrum *spectrum) {
|
||||
|
||||
GST_DEBUG_OBJECT(spectrum, "resetting state");
|
||||
|
||||
gst_fastspectrum_free_channel_data(spectrum);
|
||||
gst_fastspectrum_flush(spectrum);
|
||||
|
||||
}
|
||||
|
||||
static void gst_fastspectrum_finalize(GObject *object) {
|
||||
|
||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(object);
|
||||
|
||||
gst_fastspectrum_reset_state(spectrum);
|
||||
g_mutex_clear(&spectrum->lock);
|
||||
|
||||
G_OBJECT_CLASS(parent_class)->finalize(object);
|
||||
|
||||
}
|
||||
|
||||
static void gst_fastspectrum_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) {
|
||||
|
||||
GstFastSpectrum *filter = reinterpret_cast<GstFastSpectrum*>(object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_INTERVAL: {
|
||||
guint64 interval = g_value_get_uint64(value);
|
||||
g_mutex_lock(&filter->lock);
|
||||
if (filter->interval != interval) {
|
||||
filter->interval = interval;
|
||||
gst_fastspectrum_reset_state(filter);
|
||||
}
|
||||
g_mutex_unlock(&filter->lock);
|
||||
break;
|
||||
}
|
||||
case PROP_BANDS: {
|
||||
guint bands = g_value_get_uint(value);
|
||||
g_mutex_lock(&filter->lock);
|
||||
if (filter->bands != bands) {
|
||||
filter->bands = bands;
|
||||
gst_fastspectrum_reset_state(filter);
|
||||
}
|
||||
g_mutex_unlock(&filter->lock);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void gst_fastspectrum_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) {
|
||||
|
||||
GstFastSpectrum *filter = reinterpret_cast<GstFastSpectrum*>(object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_INTERVAL:
|
||||
g_value_set_uint64(value, filter->interval);
|
||||
break;
|
||||
case PROP_BANDS:
|
||||
g_value_set_uint(value, filter->bands);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static gboolean gst_fastspectrum_start(GstBaseTransform *trans) {
|
||||
|
||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
|
||||
|
||||
gst_fastspectrum_reset_state(spectrum);
|
||||
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
static gboolean gst_fastspectrum_stop(GstBaseTransform *trans) {
|
||||
|
||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
|
||||
|
||||
gst_fastspectrum_reset_state(spectrum);
|
||||
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
// Mixing data readers
|
||||
|
||||
static void input_data_mixed_float(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
|
||||
|
||||
Q_UNUSED(max_value);
|
||||
|
||||
const gfloat *in = reinterpret_cast<const gfloat*>(_in);
|
||||
guint ip = 0;
|
||||
|
||||
for (guint j = 0; j < len; j++) {
|
||||
out[op] = in[ip++];
|
||||
op = (op + 1) % nfft;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void input_data_mixed_double(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
|
||||
|
||||
Q_UNUSED(max_value);
|
||||
|
||||
const gdouble *in = reinterpret_cast<const gdouble*>(_in);
|
||||
guint ip = 0;
|
||||
|
||||
for (guint j = 0; j < len; j++) {
|
||||
out[op] = in[ip++];
|
||||
op = (op + 1) % nfft;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void input_data_mixed_int32_max(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
|
||||
|
||||
const gint32 *in = reinterpret_cast<const gint32*>(_in);
|
||||
guint ip = 0;
|
||||
|
||||
for (guint j = 0; j < len; j++) {
|
||||
out[op] = in[ip++] / max_value;
|
||||
op = (op + 1) % nfft;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void input_data_mixed_int24_max(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
|
||||
|
||||
for (guint j = 0; j < len; j++) {
|
||||
#if G_BYTE_ORDER == G_BIG_ENDIAN
|
||||
guint32 value = GST_READ_UINT24_BE(_in);
|
||||
#else
|
||||
guint32 value = GST_READ_UINT24_LE(_in);
|
||||
#endif
|
||||
if (value & 0x00800000) {
|
||||
value |= 0xff000000;
|
||||
}
|
||||
|
||||
out[op] = value / max_value;
|
||||
op = (op + 1) % nfft;
|
||||
_in += 3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void input_data_mixed_int16_max(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
|
||||
|
||||
const gint16 *in = reinterpret_cast<const gint16*>(_in);
|
||||
guint ip = 0;
|
||||
|
||||
for (guint j = 0; j < len; j++) {
|
||||
out[op] = in[ip++] / max_value;
|
||||
op = (op + 1) % nfft;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static gboolean gst_fastspectrum_setup(GstAudioFilter *base, const GstAudioInfo *info) {
|
||||
|
||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(base);
|
||||
GstFastSpectrumInputData input_data = nullptr;
|
||||
|
||||
g_mutex_lock(&spectrum->lock);
|
||||
switch (GST_AUDIO_INFO_FORMAT(info)) {
|
||||
case GST_AUDIO_FORMAT_S16:
|
||||
input_data = input_data_mixed_int16_max;
|
||||
break;
|
||||
case GST_AUDIO_FORMAT_S24:
|
||||
input_data = input_data_mixed_int24_max;
|
||||
break;
|
||||
case GST_AUDIO_FORMAT_S32:
|
||||
input_data = input_data_mixed_int32_max;
|
||||
break;
|
||||
case GST_AUDIO_FORMAT_F32:
|
||||
input_data = input_data_mixed_float;
|
||||
break;
|
||||
case GST_AUDIO_FORMAT_F64:
|
||||
input_data = input_data_mixed_double;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
break;
|
||||
}
|
||||
spectrum->input_data = input_data;
|
||||
|
||||
gst_fastspectrum_reset_state(spectrum);
|
||||
g_mutex_unlock(&spectrum->lock);
|
||||
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
static void gst_fastspectrum_run_fft(GstFastSpectrum *spectrum, guint input_pos) {
|
||||
|
||||
guint bands = spectrum->bands;
|
||||
guint nfft = 2 * bands - 2;
|
||||
|
||||
for (guint i = 0; i < nfft; i++) {
|
||||
spectrum->fft_input[i] = spectrum->input_ring_buffer[(input_pos + i) % nfft];
|
||||
}
|
||||
|
||||
// Should be safe to execute the same plan multiple times in parallel.
|
||||
fftw_execute(spectrum->plan);
|
||||
|
||||
// Calculate magnitude in db
|
||||
for (guint i = 0; i < bands; i++) {
|
||||
gdouble val = spectrum->fft_output[i][0] * spectrum->fft_output[i][0];
|
||||
val += spectrum->fft_output[i][1] * spectrum->fft_output[i][1];
|
||||
val /= nfft * nfft;
|
||||
spectrum->spect_magnitude[i] += val;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static GstFlowReturn gst_fastspectrum_transform_ip(GstBaseTransform *trans, GstBuffer *buffer) {
|
||||
|
||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
|
||||
guint rate = GST_AUDIO_FILTER_RATE(spectrum);
|
||||
guint bps = GST_AUDIO_FILTER_BPS(spectrum);
|
||||
guint bpf = GST_AUDIO_FILTER_BPF(spectrum);
|
||||
double max_value = static_cast<double>((1UL << ((bps << 3) - 1)) - 1);
|
||||
guint bands = spectrum->bands;
|
||||
guint nfft = 2 * bands - 2;
|
||||
guint input_pos = 0;
|
||||
GstMapInfo map;
|
||||
const guint8 *data = nullptr;
|
||||
gsize size = 0;
|
||||
GstFastSpectrumInputData input_data = nullptr;
|
||||
|
||||
g_mutex_lock(&spectrum->lock);
|
||||
gst_buffer_map(buffer, &map, GST_MAP_READ);
|
||||
data = map.data;
|
||||
size = map.size;
|
||||
|
||||
GST_LOG_OBJECT(spectrum, "input size: %" G_GSIZE_FORMAT " bytes", size);
|
||||
|
||||
if (GST_BUFFER_IS_DISCONT(buffer)) {
|
||||
GST_DEBUG_OBJECT(spectrum, "Discontinuity detected -- flushing");
|
||||
gst_fastspectrum_flush(spectrum);
|
||||
}
|
||||
|
||||
// If we don't have a FFT context yet (or it was reset due to parameter changes) get one and allocate memory for everything
|
||||
if (!spectrum->channel_data_initialized) {
|
||||
GST_DEBUG_OBJECT(spectrum, "allocating for bands %u", bands);
|
||||
|
||||
gst_fastspectrum_alloc_channel_data(spectrum);
|
||||
|
||||
// Number of sample frames we process before posting a message interval is in ns
|
||||
spectrum->frames_per_interval = gst_util_uint64_scale(spectrum->interval, rate, GST_SECOND);
|
||||
spectrum->frames_todo = spectrum->frames_per_interval;
|
||||
// Rounding error for frames_per_interval in ns, aggregated it in accumulated_error
|
||||
spectrum->error_per_interval = (spectrum->interval * rate) % GST_SECOND;
|
||||
if (spectrum->frames_per_interval == 0) {
|
||||
spectrum->frames_per_interval = 1;
|
||||
}
|
||||
|
||||
GST_INFO_OBJECT(spectrum, "interval %" GST_TIME_FORMAT ", fpi %" G_GUINT64_FORMAT ", error %" GST_TIME_FORMAT, GST_TIME_ARGS(spectrum->interval), spectrum->frames_per_interval, GST_TIME_ARGS(spectrum->error_per_interval));
|
||||
|
||||
spectrum->input_pos = 0;
|
||||
|
||||
gst_fastspectrum_flush(spectrum);
|
||||
}
|
||||
|
||||
if (spectrum->num_frames == 0) {
|
||||
spectrum->message_ts = GST_BUFFER_TIMESTAMP(buffer);
|
||||
}
|
||||
|
||||
input_pos = spectrum->input_pos;
|
||||
input_data = spectrum->input_data;
|
||||
|
||||
while (size >= bpf) {
|
||||
// Run input_data for a chunk of data
|
||||
guint fft_todo = nfft - (spectrum->num_frames % nfft);
|
||||
guint msg_todo = spectrum->frames_todo - spectrum->num_frames;
|
||||
GST_LOG_OBJECT(spectrum, "message frames todo: %u, fft frames todo: %u, input frames %" G_GSIZE_FORMAT, msg_todo, fft_todo, (size / bpf));
|
||||
guint block_size = msg_todo;
|
||||
if (block_size > (size / bpf)) {
|
||||
block_size = (size / bpf);
|
||||
}
|
||||
if (block_size > fft_todo) {
|
||||
block_size = fft_todo;
|
||||
}
|
||||
|
||||
// Move the current frames into our ringbuffers
|
||||
input_data(data, spectrum->input_ring_buffer, block_size, max_value, input_pos, nfft);
|
||||
|
||||
data += block_size * bpf;
|
||||
size -= block_size * bpf;
|
||||
input_pos = (input_pos + block_size) % nfft;
|
||||
spectrum->num_frames += block_size;
|
||||
|
||||
gboolean have_full_interval = (spectrum->num_frames == spectrum->frames_todo);
|
||||
|
||||
GST_LOG_OBJECT(spectrum, "size: %" G_GSIZE_FORMAT ", do-fft = %d, do-message = %d", size, (spectrum->num_frames % nfft == 0), have_full_interval);
|
||||
|
||||
// If we have enough frames for an FFT or we have all frames required for the interval and we haven't run a FFT, then run an FFT
|
||||
if ((spectrum->num_frames % nfft == 0) || (have_full_interval && !spectrum->num_fft)) {
|
||||
gst_fastspectrum_run_fft(spectrum, input_pos);
|
||||
spectrum->num_fft++;
|
||||
}
|
||||
|
||||
// Do we have the FFTs for one interval?
|
||||
if (have_full_interval) {
|
||||
GST_DEBUG_OBJECT(spectrum, "nfft: %u frames: %" G_GUINT64_FORMAT " fpi: %" G_GUINT64_FORMAT " error: %" GST_TIME_FORMAT, nfft, spectrum->num_frames, spectrum->frames_per_interval, GST_TIME_ARGS(spectrum->accumulated_error));
|
||||
|
||||
spectrum->frames_todo = spectrum->frames_per_interval;
|
||||
if (spectrum->accumulated_error >= GST_SECOND) {
|
||||
spectrum->accumulated_error -= GST_SECOND;
|
||||
spectrum->frames_todo++;
|
||||
}
|
||||
spectrum->accumulated_error += spectrum->error_per_interval;
|
||||
|
||||
if (spectrum->output_callback) {
|
||||
// Calculate average
|
||||
for (guint i = 0; i < spectrum->bands; i++) {
|
||||
spectrum->spect_magnitude[i] /= static_cast<double>(spectrum->num_fft);
|
||||
}
|
||||
|
||||
spectrum->output_callback(spectrum->spect_magnitude, static_cast<int>(spectrum->bands));
|
||||
|
||||
// Reset spectrum accumulators
|
||||
memset(spectrum->spect_magnitude, 0, spectrum->bands * sizeof(double));
|
||||
}
|
||||
|
||||
if (GST_CLOCK_TIME_IS_VALID(spectrum->message_ts)) {
|
||||
spectrum->message_ts += gst_util_uint64_scale(spectrum->num_frames, GST_SECOND, rate);
|
||||
}
|
||||
|
||||
spectrum->num_frames = 0;
|
||||
spectrum->num_fft = 0;
|
||||
}
|
||||
}
|
||||
|
||||
spectrum->input_pos = input_pos;
|
||||
|
||||
gst_buffer_unmap(buffer, &map);
|
||||
g_mutex_unlock(&spectrum->lock);
|
||||
|
||||
g_assert(size == 0);
|
||||
|
||||
return GST_FLOW_OK;
|
||||
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/* This file was part of Clementine.
|
||||
Copyright 2014, David Sansome <me@davidsansome.com>
|
||||
|
||||
Strawberry is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Strawberry is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "gstfastspectrum.h"
|
||||
#include "gstmoodbarplugin.h"
|
||||
|
||||
static gboolean gst_moodbar_plugin_init(GstPlugin *plugin) {
|
||||
|
||||
if (!gst_element_register(plugin, "fastspectrum", GST_RANK_NONE, GST_TYPE_FASTSPECTRUM)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int gstfastspectrum_register_static() {
|
||||
|
||||
return gst_plugin_register_static(
|
||||
GST_VERSION_MAJOR,
|
||||
GST_VERSION_MINOR,
|
||||
"fastspectrum",
|
||||
"Fast spectrum analyzer for generating Moodbars",
|
||||
gst_moodbar_plugin_init,
|
||||
"0.1",
|
||||
"GPL",
|
||||
"FastSpectrum",
|
||||
"FastSpectrum",
|
||||
"https://www.strawberrymusicplayer.org");
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/* This file was part of Clementine.
|
||||
Copyright 2014, David Sansome <me@davidsansome.com>
|
||||
|
||||
Strawberry is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Strawberry is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef GST_MOODBAR_PLUGIN_H
|
||||
#define GST_MOODBAR_PLUGIN_H
|
||||
|
||||
extern "C" {
|
||||
int gstfastspectrum_register_static();
|
||||
}
|
||||
|
||||
#endif // GST_MOODBAR_PLUGIN_H
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
set(SOURCES
|
||||
core/logging.cpp
|
||||
@@ -16,8 +16,6 @@ set(HEADERS
|
||||
|
||||
qt_wrap_cpp(MOC ${HEADERS})
|
||||
|
||||
link_directories(${GLIB_LIBRARY_DIRS})
|
||||
|
||||
add_library(libstrawberry-common STATIC ${SOURCES} ${MOC})
|
||||
|
||||
target_include_directories(libstrawberry-common SYSTEM PRIVATE ${GLIB_INCLUDE_DIRS})
|
||||
@@ -31,6 +29,8 @@ if(Backtrace_FOUND)
|
||||
target_include_directories(libstrawberry-common SYSTEM PRIVATE ${Backtrace_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
target_link_directories(libstrawberry-common PRIVATE ${GLIB_LIBRARY_DIRS})
|
||||
|
||||
target_link_libraries(libstrawberry-common PRIVATE
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${GLIB_LIBRARIES}
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
#include <cstring>
|
||||
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
|
||||
#ifndef _MSC_VER
|
||||
# include <cxxabi.h>
|
||||
@@ -59,6 +61,8 @@
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
namespace logging {
|
||||
|
||||
static Level sDefaultLevel = Level_Debug;
|
||||
@@ -157,12 +161,13 @@ static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QStr
|
||||
break;
|
||||
}
|
||||
|
||||
for (const QString &line : message.split(QLatin1Char('\n'))) {
|
||||
const QStringList lines = message.split(u'\n');
|
||||
for (const QString &line : lines) {
|
||||
BufferedDebug d = CreateLogger<BufferedDebug>(level, QStringLiteral("unknown"), -1, nullptr);
|
||||
d << line.toLocal8Bit().constData();
|
||||
if (d.buf_) {
|
||||
d.buf_->close();
|
||||
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", d.buf_->buffer().data());
|
||||
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", d.buf_->buffer().constData());
|
||||
fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout);
|
||||
}
|
||||
}
|
||||
@@ -193,8 +198,9 @@ void SetLevels(const QString &levels) {
|
||||
|
||||
if (!sClassLevels) return;
|
||||
|
||||
for (const QString &item : levels.split(QLatin1Char(','))) {
|
||||
const QStringList class_level = item.split(QLatin1Char(':'));
|
||||
const QStringList items = levels.split(u',');
|
||||
for (const QString &item : items) {
|
||||
const QStringList class_level = item.split(u':');
|
||||
|
||||
QString class_name;
|
||||
bool ok = false;
|
||||
@@ -212,7 +218,7 @@ void SetLevels(const QString &levels) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (class_name.isEmpty() || class_name == QLatin1Char('*')) {
|
||||
if (class_name.isEmpty() || class_name == u'*') {
|
||||
sDefaultLevel = static_cast<Level>(level);
|
||||
}
|
||||
else {
|
||||
@@ -226,9 +232,9 @@ static QString ParsePrettyFunction(const char *pretty_function) {
|
||||
|
||||
// Get the class name out of the function name.
|
||||
QString class_name = QLatin1String(pretty_function);
|
||||
const qint64 paren = class_name.indexOf(QLatin1Char('('));
|
||||
const qint64 paren = class_name.indexOf(u'(');
|
||||
if (paren != -1) {
|
||||
const qint64 colons = class_name.lastIndexOf(QLatin1String("::"), paren);
|
||||
const qint64 colons = class_name.lastIndexOf("::"_L1, paren);
|
||||
if (colons != -1) {
|
||||
class_name = class_name.left(colons);
|
||||
}
|
||||
@@ -237,7 +243,7 @@ static QString ParsePrettyFunction(const char *pretty_function) {
|
||||
}
|
||||
}
|
||||
|
||||
const qint64 space = class_name.lastIndexOf(QLatin1Char(' '));
|
||||
const qint64 space = class_name.lastIndexOf(u' ');
|
||||
if (space != -1) {
|
||||
class_name = class_name.mid(space + 1);
|
||||
}
|
||||
@@ -310,8 +316,8 @@ QString CXXDemangle(const QString &mangled_function) {
|
||||
QString LinuxDemangle(const QString &symbol);
|
||||
QString LinuxDemangle(const QString &symbol) {
|
||||
|
||||
QRegularExpression regex(QStringLiteral("\\(([^+]+)"));
|
||||
QRegularExpressionMatch match = regex.match(symbol);
|
||||
static const QRegularExpression regex_symbol(QStringLiteral("\\(([^+]+)"));
|
||||
QRegularExpressionMatch match = regex_symbol.match(symbol);
|
||||
if (!match.hasMatch()) {
|
||||
return symbol;
|
||||
}
|
||||
@@ -325,11 +331,7 @@ QString LinuxDemangle(const QString &symbol) {
|
||||
QString DarwinDemangle(const QString &symbol);
|
||||
QString DarwinDemangle(const QString &symbol) {
|
||||
|
||||
# if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
QStringList split = symbol.split(QLatin1Char(' '), Qt::SkipEmptyParts);
|
||||
# else
|
||||
QStringList split = symbol.split(QLatin1Char(' '), QString::SkipEmptyParts);
|
||||
# endif
|
||||
const QStringList split = symbol.split(QLatin1Char(' '), Qt::SkipEmptyParts);
|
||||
QString mangled_function = split[3];
|
||||
return CXXDemangle(mangled_function);
|
||||
|
||||
@@ -370,20 +372,49 @@ void DumpStackTrace() {
|
||||
// It's okay that the LoggedDebug instance is copied to a QDebug in these. It doesn't override any behavior that should be needed after return.
|
||||
#define qCreateLogger(line, pretty_function, category, level) logging::CreateLogger<LoggedDebug>(logging::Level_##level, logging::ParsePrettyFunction(pretty_function), line, category)
|
||||
|
||||
QDebug CreateLoggerInfo(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Info); }
|
||||
QDebug CreateLoggerFatal(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Fatal); }
|
||||
QDebug CreateLoggerError(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Error); }
|
||||
QDebug CreateLoggerFatal(const int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Fatal); }
|
||||
QDebug CreateLoggerError(const int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Error); }
|
||||
|
||||
#ifdef QT_NO_INFO_OUTPUT
|
||||
QNoDebug CreateLoggerInfo(const int line, const char *pretty_function, const char *category) {
|
||||
|
||||
Q_UNUSED(line)
|
||||
Q_UNUSED(pretty_function)
|
||||
Q_UNUSED(category)
|
||||
|
||||
return QNoDebug();
|
||||
|
||||
}
|
||||
#else
|
||||
QDebug CreateLoggerInfo(const int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Info); }
|
||||
#endif // QT_NO_INFO_OUTPUT
|
||||
|
||||
#ifdef QT_NO_WARNING_OUTPUT
|
||||
QNoDebug CreateLoggerWarning(int, const char*, const char*) { return QNoDebug(); }
|
||||
QNoDebug CreateLoggerWarning(const int line, const char *pretty_function, const char *category) {
|
||||
|
||||
Q_UNUSED(line)
|
||||
Q_UNUSED(pretty_function)
|
||||
Q_UNUSED(category)
|
||||
|
||||
return QNoDebug();
|
||||
|
||||
}
|
||||
#else
|
||||
QDebug CreateLoggerWarning(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Warning); }
|
||||
QDebug CreateLoggerWarning(const int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Warning); }
|
||||
#endif // QT_NO_WARNING_OUTPUT
|
||||
|
||||
#ifdef QT_NO_DEBUG_OUTPUT
|
||||
QNoDebug CreateLoggerDebug(int, const char*, const char*) { return QNoDebug(); }
|
||||
QNoDebug CreateLoggerDebug(const int line, const char *pretty_function, const char *category) {
|
||||
|
||||
Q_UNUSED(line)
|
||||
Q_UNUSED(pretty_function)
|
||||
Q_UNUSED(category)
|
||||
|
||||
return QNoDebug();
|
||||
|
||||
}
|
||||
#else
|
||||
QDebug CreateLoggerDebug(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Debug); }
|
||||
QDebug CreateLoggerDebug(const int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Debug); }
|
||||
#endif // QT_NO_DEBUG_OUTPUT
|
||||
|
||||
} // namespace logging
|
||||
|
||||
@@ -72,20 +72,25 @@ enum Level {
|
||||
|
||||
void DumpStackTrace();
|
||||
|
||||
QDebug CreateLoggerInfo(int line, const char *pretty_function, const char *category);
|
||||
QDebug CreateLoggerFatal(int line, const char *pretty_function, const char *category);
|
||||
QDebug CreateLoggerError(int line, const char *pretty_function, const char *category);
|
||||
QDebug CreateLoggerFatal(const int line, const char *pretty_function, const char *category);
|
||||
QDebug CreateLoggerError(const int line, const char *pretty_function, const char *category);
|
||||
|
||||
#ifdef QT_NO_INFO_OUTPUT
|
||||
QNoDebug CreateLoggerInfo(const int line, const char *pretty_function, const char *category);
|
||||
#else
|
||||
QDebug CreateLoggerInfo(const int line, const char *pretty_function, const char *category);
|
||||
#endif // QT_NO_INFO_OUTPUT
|
||||
|
||||
#ifdef QT_NO_WARNING_OUTPUT
|
||||
QNoDebug CreateLoggerWarning(int, const char*, const char*);
|
||||
QNoDebug CreateLoggerWarning(const int line, const char *pretty_function, const char *category);
|
||||
#else
|
||||
QDebug CreateLoggerWarning(int line, const char *pretty_function, const char *category);
|
||||
QDebug CreateLoggerWarning(const int line, const char *pretty_function, const char *category);
|
||||
#endif // QT_NO_WARNING_OUTPUT
|
||||
|
||||
#ifdef QT_NO_DEBUG_OUTPUT
|
||||
QNoDebug CreateLoggerDebug(int, const char*, const char*);
|
||||
QNoDebug CreateLoggerDebug(const int line, const char *pretty_function, const char *category);
|
||||
#else
|
||||
QDebug CreateLoggerDebug(int line, const char *pretty_function, const char *category);
|
||||
QDebug CreateLoggerDebug(const int line, const char *pretty_function, const char *category);
|
||||
#endif // QT_NO_DEBUG_OUTPUT
|
||||
|
||||
void GLog(const char *domain, int level, const char *message, void *user_data);
|
||||
|
||||
@@ -50,7 +50,7 @@ class _MessageHandlerBase : public QObject {
|
||||
// After this is true, messages cannot be sent to the handler any more.
|
||||
bool is_device_closed() const { return is_device_closed_; }
|
||||
|
||||
protected slots:
|
||||
protected Q_SLOTS:
|
||||
void WriteMessage(const QByteArray &data);
|
||||
void DeviceReadyRead();
|
||||
virtual void DeviceClosed();
|
||||
|
||||
@@ -41,7 +41,7 @@ void _MessageReplyBase::Abort() {
|
||||
finished_ = true;
|
||||
success_ = false;
|
||||
|
||||
emit Finished();
|
||||
Q_EMIT Finished();
|
||||
qLog(Debug) << "Releasing ID" << id() << "(aborted)";
|
||||
semaphore_.release();
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class _MessageReplyBase : public QObject {
|
||||
|
||||
void Abort();
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void Finished();
|
||||
|
||||
protected:
|
||||
|
||||
@@ -52,11 +52,11 @@ class _WorkerPoolBase : public QObject {
|
||||
public:
|
||||
explicit _WorkerPoolBase(QObject *parent = nullptr);
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
// Emitted when a worker failed to start. This usually happens when the worker wasn't found, or couldn't be executed.
|
||||
void WorkerFailedToStart();
|
||||
|
||||
protected slots:
|
||||
protected Q_SLOTS:
|
||||
virtual void DoStart() {}
|
||||
virtual void NewConnection() {}
|
||||
virtual void ProcessReadyReadStandardOutput() {}
|
||||
@@ -293,7 +293,7 @@ void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
|
||||
QObject::connect(worker->process_, &QProcess::readyReadStandardError, this, &WorkerPool::ProcessReadyReadStandardError);
|
||||
|
||||
// Create a server, find an unused name and start listening
|
||||
forever {
|
||||
Q_FOREVER {
|
||||
const quint32 unique_number = QRandomGenerator::global()->bounded(static_cast<quint32>(quint64(this) & 0xFFFFFFFF));
|
||||
const QString name = QStringLiteral("%1_%2").arg(local_server_name_).arg(unique_number);
|
||||
|
||||
@@ -357,7 +357,7 @@ void WorkerPool<HandlerType>::ProcessError(QProcess::ProcessError error) {
|
||||
// Failed to start errors are bad - it usually means the worker isn't installed.
|
||||
// Don't restart the process, but tell our owner, who will probably want to do something fatal.
|
||||
qLog(Error) << "Worker failed to start";
|
||||
emit WorkerFailedToStart();
|
||||
Q_EMIT WorkerFailedToStart();
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
# Workaround a bug in protobuf-generate.cmake (https://github.com/protocolbuffers/protobuf/issues/12450)
|
||||
if(NOT protobuf_PROTOC_EXE)
|
||||
@@ -19,19 +19,6 @@ if(HAVE_TAGPARSER)
|
||||
list(APPEND SOURCES tagreadertagparser.cpp)
|
||||
endif()
|
||||
|
||||
link_directories(
|
||||
${GLIB_LIBRARY_DIRS}
|
||||
${PROTOBUF_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
if(HAVE_TAGLIB)
|
||||
link_directories(${TAGLIB_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_TAGPARSER)
|
||||
link_directories(${TAGPARSER_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
add_library(libstrawberry-tagreader STATIC ${PROTO_SOURCES} ${SOURCES})
|
||||
|
||||
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE
|
||||
@@ -47,6 +34,11 @@ target_include_directories(libstrawberry-tagreader PRIVATE
|
||||
${CMAKE_BINARY_DIR}/src
|
||||
)
|
||||
|
||||
target_link_directories(libstrawberry-tagreader PRIVATE
|
||||
${GLIB_LIBRARY_DIRS}
|
||||
${PROTOBUF_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(libstrawberry-tagreader PRIVATE
|
||||
${GLIB_LIBRARIES}
|
||||
${Protobuf_LIBRARIES}
|
||||
@@ -58,11 +50,13 @@ target_link_libraries(libstrawberry-tagreader PRIVATE
|
||||
|
||||
if(HAVE_TAGLIB)
|
||||
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGLIB_INCLUDE_DIRS})
|
||||
target_link_directories(libstrawberry-tagreader PRIVATE ${TAGLIB_LIBRARY_DIRS})
|
||||
target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGLIB_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_TAGPARSER)
|
||||
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGPARSER_INCLUDE_DIRS})
|
||||
target_link_directories(libstrawberry-tagreader PRIVATE ${TAGPARSER_LIBRARY_DIRS})
|
||||
target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGPARSER_LIBRARIES})
|
||||
endif()
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
#include "core/logging.h"
|
||||
#include "tagreaderbase.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
TagReaderBase::TagReaderBase() = default;
|
||||
TagReaderBase::~TagReaderBase() = default;
|
||||
|
||||
@@ -142,11 +144,11 @@ TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const QString &song_fil
|
||||
if (cover_mime_type.isEmpty()) {
|
||||
cover_mime_type = QMimeDatabase().mimeTypeForData(cover_data).name();
|
||||
}
|
||||
if (cover_mime_type == QLatin1String("image/jpeg")) {
|
||||
if (cover_mime_type == "image/jpeg"_L1) {
|
||||
qLog(Debug) << "Using cover from JPEG data for" << song_filename;
|
||||
return Cover(cover_data, cover_mime_type);
|
||||
}
|
||||
if (cover_mime_type == QLatin1String("image/png")) {
|
||||
if (cover_mime_type == "image/png"_L1) {
|
||||
qLog(Debug) << "Using cover from PNG data for" << song_filename;
|
||||
return Cover(cover_data, cover_mime_type);
|
||||
}
|
||||
|
||||
@@ -34,19 +34,21 @@
|
||||
#include "tagreaderbase.h"
|
||||
#include "tagreadertaglib.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
#undef TStringToQString
|
||||
#undef QStringToTString
|
||||
|
||||
bool GME::IsSupportedFormat(const QFileInfo &fileinfo) {
|
||||
return fileinfo.exists() && (fileinfo.completeSuffix().endsWith(QLatin1String("spc"), Qt::CaseInsensitive) || fileinfo.completeSuffix().endsWith(QLatin1String("vgm")), Qt::CaseInsensitive);
|
||||
return fileinfo.exists() && (fileinfo.completeSuffix().endsWith("spc"_L1, Qt::CaseInsensitive) || fileinfo.completeSuffix().endsWith("vgm"_L1), Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
TagReaderBase::Result GME::ReadFile(const QFileInfo &fileinfo, spb::tagreader::SongMetadata *song) {
|
||||
|
||||
if (fileinfo.completeSuffix().endsWith(QLatin1String("spc")), Qt::CaseInsensitive) {
|
||||
if (fileinfo.completeSuffix().endsWith("spc"_L1), Qt::CaseInsensitive) {
|
||||
return SPC::Read(fileinfo, song);
|
||||
}
|
||||
if (fileinfo.completeSuffix().endsWith(QLatin1String("vgm"), Qt::CaseInsensitive)) {
|
||||
if (fileinfo.completeSuffix().endsWith("vgm"_L1, Qt::CaseInsensitive)) {
|
||||
return VGM::Read(fileinfo, song);
|
||||
}
|
||||
|
||||
@@ -229,6 +231,7 @@ TagReaderBase::Result GME::VGM::Read(const QFileInfo &fileinfo, spb::tagreader::
|
||||
|
||||
file.seek(static_cast<qint64>(GD3_TAG_PTR + pt));
|
||||
QByteArray gd3_version = file.read(4);
|
||||
Q_UNUSED(gd3_version)
|
||||
|
||||
file.seek(file.pos() + 4);
|
||||
QByteArray gd3_length_bytes = file.read(4);
|
||||
@@ -237,12 +240,8 @@ TagReaderBase::Result GME::VGM::Read(const QFileInfo &fileinfo, spb::tagreader::
|
||||
QByteArray gd3Data = file.read(gd3_length);
|
||||
QTextStream fileTagStream(gd3Data, QIODevice::ReadOnly);
|
||||
// Stored as 16 bit UTF string, two bytes per letter.
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
fileTagStream.setEncoding(QStringConverter::Utf16);
|
||||
#else
|
||||
fileTagStream.setCodec("UTF-16");
|
||||
#endif
|
||||
QStringList strings = fileTagStream.readLine(0).split(QLatin1Char('\0'));
|
||||
QStringList strings = fileTagStream.readLine(0).split(u'\0');
|
||||
if (strings.count() < 10) {
|
||||
return TagReaderBase::Result::ErrorCode::FileParseError;
|
||||
}
|
||||
|
||||
@@ -97,6 +97,8 @@
|
||||
#include "core/messagehandler.h"
|
||||
#include "utilities/timeconstants.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
#undef TStringToQString
|
||||
#undef QStringToTString
|
||||
|
||||
@@ -443,7 +445,7 @@ TagReaderBase::Result TagReaderTagLib::ReadFile(const QString &filename, spb::ta
|
||||
}
|
||||
|
||||
if (!disc.isEmpty()) {
|
||||
const qint64 i = disc.indexOf(QLatin1Char('/'));
|
||||
const qint64 i = disc.indexOf(u'/');
|
||||
if (i != -1) {
|
||||
// disc.right( i ).toInt() is total number of discs, we don't use this at the moment
|
||||
song->set_disc(disc.left(i).toInt());
|
||||
@@ -457,7 +459,7 @@ TagReaderBase::Result TagReaderTagLib::ReadFile(const QString &filename, spb::ta
|
||||
// well, it wasn't set, but if the artist is VA assume it's a compilation
|
||||
const QString albumartist = QString::fromStdString(song->albumartist());
|
||||
const QString artist = QString::fromStdString(song->artist());
|
||||
if (artist.compare(QLatin1String("various artists")) == 0 || albumartist.compare(QLatin1String("various artists")) == 0) {
|
||||
if (artist.compare("various artists"_L1) == 0 || albumartist.compare("various artists"_L1) == 0) {
|
||||
song->set_compilation(true);
|
||||
}
|
||||
}
|
||||
@@ -529,7 +531,7 @@ void TagReaderTagLib::ParseID3v2Tags(TagLib::ID3v2::Tag *tag, QString *disc, QSt
|
||||
for (uint i = 0; i < map[kID3v2_CommercialFrame].size(); ++i) {
|
||||
const TagLib::ID3v2::CommentsFrame *frame = dynamic_cast<const TagLib::ID3v2::CommentsFrame*>(map[kID3v2_CommercialFrame][i]);
|
||||
|
||||
if (frame && TagLibStringToQString(frame->description()) != QLatin1String("iTunNORM")) {
|
||||
if (frame && TagLibStringToQString(frame->description()) != "iTunNORM"_L1) {
|
||||
AssignTagLibStringToStdString(frame->text(), song->mutable_comment());
|
||||
break;
|
||||
}
|
||||
@@ -798,13 +800,13 @@ void TagReaderTagLib::ParseMP4Tags(TagLib::MP4::Tag *tag, QString *disc, QString
|
||||
}
|
||||
|
||||
if (tag->contains(kMP4_MusicBrainz_AlbumArtistId)) {
|
||||
AssignTagLibStringToStdString(tag->item(kMP4_MusicBrainz_AlbumArtistId).toStringList().toString(), song->mutable_musicbrainz_album_artist_id());
|
||||
AssignTagLibStringToStdString(TagLibStringListToSlashSeparatedString(tag->item(kMP4_MusicBrainz_AlbumArtistId).toStringList()), song->mutable_musicbrainz_album_artist_id());
|
||||
}
|
||||
if (tag->contains(kMP4_MusicBrainz_ArtistId)) {
|
||||
AssignTagLibStringToStdString(tag->item(kMP4_MusicBrainz_ArtistId).toStringList().toString(), song->mutable_musicbrainz_artist_id());
|
||||
AssignTagLibStringToStdString(TagLibStringListToSlashSeparatedString(tag->item(kMP4_MusicBrainz_ArtistId).toStringList()), song->mutable_musicbrainz_artist_id());
|
||||
}
|
||||
if (tag->contains(kMP4_MusicBrainz_OriginalArtistId)) {
|
||||
AssignTagLibStringToStdString(tag->item(kMP4_MusicBrainz_OriginalArtistId).toStringList().toString(), song->mutable_musicbrainz_original_artist_id());
|
||||
AssignTagLibStringToStdString(TagLibStringListToSlashSeparatedString(tag->item(kMP4_MusicBrainz_OriginalArtistId).toStringList()), song->mutable_musicbrainz_original_artist_id());
|
||||
}
|
||||
if (tag->contains(kMP4_MusicBrainz_AlbumId)) {
|
||||
AssignTagLibStringToStdString(tag->item(kMP4_MusicBrainz_AlbumId).toStringList().toString(), song->mutable_musicbrainz_album_id());
|
||||
@@ -825,7 +827,7 @@ void TagReaderTagLib::ParseMP4Tags(TagLib::MP4::Tag *tag, QString *disc, QString
|
||||
AssignTagLibStringToStdString(tag->item(kMP4_MusicBrainz_ReleaseGroupId).toStringList().toString(), song->mutable_musicbrainz_release_group_id());
|
||||
}
|
||||
if (tag->contains(kMP4_MusicBrainz_WorkId)) {
|
||||
AssignTagLibStringToStdString(tag->item(kMP4_MusicBrainz_WorkId).toStringList().toString(), song->mutable_musicbrainz_work_id());
|
||||
AssignTagLibStringToStdString(TagLibStringListToSlashSeparatedString(tag->item(kMP4_MusicBrainz_WorkId).toStringList()), song->mutable_musicbrainz_work_id());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -937,7 +939,7 @@ TagReaderBase::Result TagReaderTagLib::WriteFile(const QString &filename, const
|
||||
save_tags_options << QStringLiteral("embedded cover");
|
||||
}
|
||||
|
||||
qLog(Debug) << "Saving" << save_tags_options.join(QLatin1String(", ")) << "to" << filename;
|
||||
qLog(Debug) << "Saving" << save_tags_options.join(", "_L1) << "to" << filename;
|
||||
|
||||
const Cover cover = LoadCoverFromRequest(filename, request);
|
||||
|
||||
@@ -1211,7 +1213,7 @@ void TagReaderTagLib::SetUnsyncLyricsFrame(const std::string &value, TagLib::ID3
|
||||
// If no frames stored create empty frame
|
||||
if (frames_buffer.isEmpty()) {
|
||||
TagLib::ID3v2::UnsynchronizedLyricsFrame frame(TagLib::String::UTF8);
|
||||
frame.setDescription("Clementine editor");
|
||||
frame.setDescription("Strawberry editor");
|
||||
frames_buffer.push_back(frame.render());
|
||||
}
|
||||
|
||||
@@ -1501,10 +1503,10 @@ void TagReaderTagLib::SetEmbeddedArt(TagLib::MP4::File *aac_file, TagLib::MP4::T
|
||||
}
|
||||
else {
|
||||
TagLib::MP4::CoverArt::Format cover_format = TagLib::MP4::CoverArt::Format::JPEG;
|
||||
if (mime_type == QLatin1String("image/jpeg")) {
|
||||
if (mime_type == "image/jpeg"_L1) {
|
||||
cover_format = TagLib::MP4::CoverArt::Format::JPEG;
|
||||
}
|
||||
else if (mime_type == QLatin1String("image/png")) {
|
||||
else if (mime_type == "image/png"_L1) {
|
||||
cover_format = TagLib::MP4::CoverArt::Format::PNG;
|
||||
}
|
||||
else {
|
||||
@@ -1870,3 +1872,17 @@ TagReaderBase::Result TagReaderTagLib::SaveSongRatingToFile(const QString &filen
|
||||
return success ? Result::ErrorCode::Success : Result::ErrorCode::FileSaveError;
|
||||
|
||||
}
|
||||
|
||||
TagLib::String TagReaderTagLib::TagLibStringListToSlashSeparatedString(const TagLib::StringList &taglib_string_list) {
|
||||
|
||||
TagLib::String result_string;
|
||||
for (const TagLib::String &taglib_string : taglib_string_list) {
|
||||
if (!result_string.isEmpty()) {
|
||||
result_string += '/';
|
||||
}
|
||||
result_string += taglib_string;
|
||||
}
|
||||
|
||||
return result_string;
|
||||
|
||||
}
|
||||
|
||||
@@ -136,6 +136,8 @@ class TagReaderTagLib : public TagReaderBase {
|
||||
void SetEmbeddedArt(TagLib::ID3v2::Tag *tag, const QByteArray &data, const QString &mime_type) const;
|
||||
void SetEmbeddedArt(TagLib::MP4::File *aac_file, TagLib::MP4::Tag *tag, const QByteArray &data, const QString &mime_type) const;
|
||||
|
||||
static TagLib::String TagLibStringListToSlashSeparatedString(const TagLib::StringList &taglib_string_list);
|
||||
|
||||
private:
|
||||
FileRefFactory *factory_;
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
qt_wrap_cpp(MACDEPLOYCHECK_MOC ${CMAKE_SOURCE_DIR}/ext/libstrawberry-common/core/logging.h)
|
||||
link_directories(${GLIB_LIBRARY_DIRS})
|
||||
add_executable(macdeploycheck macdeploycheck.cpp ${CMAKE_SOURCE_DIR}/ext/libstrawberry-common/core/logging.cpp ${MACDEPLOYCHECK_MOC})
|
||||
target_include_directories(macdeploycheck PUBLIC SYSTEM
|
||||
${GLIB_INCLUDE_DIRS}
|
||||
)
|
||||
target_include_directories(macdeploycheck PUBLIC
|
||||
${CMAKE_SOURCE_DIR}/ext/libstrawberry-common
|
||||
${CMAKE_BINARY_DIR}/src
|
||||
)
|
||||
target_link_libraries(macdeploycheck PUBLIC
|
||||
"-framework AppKit"
|
||||
${GLIB_LIBRARIES}
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
)
|
||||
@@ -1,147 +0,0 @@
|
||||
/* This file is part of Strawberry.
|
||||
Copyright 2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
|
||||
Strawberry is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Strawberry is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QProcess>
|
||||
#include <QRegularExpression>
|
||||
#include <QRegularExpressionMatch>
|
||||
|
||||
#include "core/logging.h"
|
||||
|
||||
int main(int argc, char **argv);
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
logging::Init();
|
||||
|
||||
qLog(Info) << "Running macdeploycheck";
|
||||
|
||||
if (argc < 1) {
|
||||
qLog(Error) << "Usage: macdeploycheck <bundledir>";
|
||||
return 1;
|
||||
}
|
||||
QString bundle_path = QString::fromLocal8Bit(argv[1]);
|
||||
|
||||
bool success = true;
|
||||
|
||||
QDirIterator iter(bundle_path, QDir::Files | QDir::NoSymLinks, QDirIterator::Subdirectories);
|
||||
while (iter.hasNext()) {
|
||||
|
||||
iter.next();
|
||||
|
||||
QString filepath = iter.fileInfo().filePath();
|
||||
|
||||
// Ignore these files.
|
||||
if (filepath.endsWith(".plist") ||
|
||||
filepath.endsWith(".icns") ||
|
||||
filepath.endsWith(".prl") ||
|
||||
filepath.endsWith(".conf") ||
|
||||
filepath.endsWith(".h") ||
|
||||
filepath.endsWith(".nib") ||
|
||||
filepath.endsWith(".strings") ||
|
||||
filepath.endsWith(".css") ||
|
||||
filepath.endsWith("CodeResources") ||
|
||||
filepath.endsWith("PkgInfo") ||
|
||||
filepath.endsWith(".modulemap")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QProcess otool;
|
||||
otool.start("otool", QStringList() << "-L" << filepath);
|
||||
otool.waitForFinished();
|
||||
if (otool.exitStatus() != QProcess::NormalExit || otool.exitCode() != 0) {
|
||||
qLog(Error) << "otool failed for" << filepath << ":" << otool.readAllStandardError();
|
||||
success = false;
|
||||
continue;
|
||||
}
|
||||
QString output = otool.readAllStandardOutput();
|
||||
QStringList output_lines = output.split("\n", Qt::SkipEmptyParts);
|
||||
if (output_lines.size() < 2) {
|
||||
qLog(Error) << "Could not parse otool output:" << output;
|
||||
success = false;
|
||||
continue;
|
||||
}
|
||||
QString first_line = output_lines.first();
|
||||
if (first_line.endsWith(':')) first_line.chop(1);
|
||||
if (first_line == filepath) {
|
||||
output_lines.removeFirst();
|
||||
}
|
||||
else {
|
||||
qLog(Error) << "First line" << first_line << "does not match" << filepath;
|
||||
success = false;
|
||||
}
|
||||
QRegularExpression regexp(QStringLiteral("^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), current version (\\d+\\.\\d+\\.\\d+)(, weak|, reexport)?\\)$"));
|
||||
for (const QString &output_line : output_lines) {
|
||||
|
||||
//qDebug() << "Final check on" << filepath << output_line;
|
||||
|
||||
QRegularExpressionMatch match = regexp.match(output_line);
|
||||
if (match.hasMatch()) {
|
||||
QString library = match.captured(1);
|
||||
if (QFileInfo(library).fileName() == QFileInfo(filepath).fileName()) { // It's this.
|
||||
continue;
|
||||
}
|
||||
else if (library.startsWith("@executable_path")) {
|
||||
QString real_path = library;
|
||||
real_path = real_path.replace("@executable_path", bundle_path + "/Contents/MacOS");
|
||||
if (!QFile::exists(real_path)) {
|
||||
qLog(Error) << real_path << "does not exist for" << filepath;
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
else if (library.startsWith("@rpath")) {
|
||||
QString real_path = library;
|
||||
real_path = real_path.replace("@rpath", bundle_path + "/Contents/Frameworks");
|
||||
if (!QFile::exists(real_path) && !real_path.endsWith("QtSvg")) { // FIXME: Ignore broken svg image plugin.
|
||||
qLog(Error) << real_path << "does not exist for" << filepath;
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
else if (library.startsWith("@loader_path")) {
|
||||
QString loader_path = QFileInfo(filepath).path();
|
||||
QString real_path = library;
|
||||
real_path = real_path.replace("@loader_path", loader_path);
|
||||
if (!QFile::exists(real_path)) {
|
||||
qLog(Error) << real_path << "does not exist for" << filepath;
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
else if (library.startsWith("/System/Library/") || library.startsWith("/usr/lib/")) { // System library
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
qLog(Error) << "File" << filepath << "points to" << library;
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
qLog(Error) << "Could not parse otool output line:" << output_line;
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return success ? 0 : 1;
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})
|
||||
|
||||
@@ -7,16 +7,6 @@ set(HEADERS tagreaderworker.h)
|
||||
|
||||
qt_wrap_cpp(MOC ${HEADERS})
|
||||
|
||||
link_directories(${GLIB_LIBRARY_DIRS})
|
||||
|
||||
if(HAVE_TAGLIB)
|
||||
link_directories(${TAGLIB_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_TAGPARSER)
|
||||
link_directories(${TAGPARSER_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
add_executable(strawberry-tagreader ${SOURCES} ${MOC} ${QRC})
|
||||
|
||||
target_include_directories(strawberry-tagreader SYSTEM PRIVATE
|
||||
@@ -31,6 +21,8 @@ target_include_directories(strawberry-tagreader PRIVATE
|
||||
${CMAKE_BINARY_DIR}/src
|
||||
)
|
||||
|
||||
target_link_directories(strawberry-tagreader PRIVATE ${GLIB_LIBRARY_DIRS})
|
||||
|
||||
target_link_libraries(strawberry-tagreader PRIVATE
|
||||
${GLIB_LIBRARIES}
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
@@ -41,11 +33,13 @@ target_link_libraries(strawberry-tagreader PRIVATE
|
||||
|
||||
if(HAVE_TAGLIB)
|
||||
target_include_directories(strawberry-tagreader SYSTEM PRIVATE ${TAGLIB_INCLUDE_DIRS})
|
||||
target_link_directories(strawberry-tagreader PRIVATE ${TAGLIB_LIBRARY_DIRS})
|
||||
target_link_libraries(strawberry-tagreader PRIVATE ${TAGLIB_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_TAGPARSER)
|
||||
target_include_directories(strawberry-tagreader SYSTEM PRIVATE ${TAGPARSER_INCLUDE_DIRS})
|
||||
target_link_directories(strawberry-tagreader PRIVATE ${TAGPARSER_LIBRARY_DIRS})
|
||||
target_link_libraries(strawberry-tagreader PRIVATE ${TAGPARSER_LIBRARIES})
|
||||
endif()
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
@@ -73,7 +74,7 @@ void TagReaderWorker::DeviceClosed() {
|
||||
|
||||
void TagReaderWorker::HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply) {
|
||||
|
||||
for (shared_ptr<TagReaderBase> reader : tagreaders_) {
|
||||
for (shared_ptr<TagReaderBase> reader : std::as_const(tagreaders_)) {
|
||||
|
||||
if (message.has_is_media_file_request()) {
|
||||
const QString filename = QString::fromStdString(message.is_media_file_request().filename());
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
if(HAVE_TRANSLATIONS)
|
||||
include(../cmake/Translations.cmake)
|
||||
@@ -41,6 +41,9 @@ set(SOURCES
|
||||
core/translations.cpp
|
||||
core/systemtrayicon.cpp
|
||||
core/localredirectserver.cpp
|
||||
core/mimedata.cpp
|
||||
core/potranslator.cpp
|
||||
core/temporaryfile.cpp
|
||||
utilities/strutils.cpp
|
||||
utilities/envutils.cpp
|
||||
utilities/colorutils.cpp
|
||||
@@ -110,8 +113,10 @@ set(SOURCES
|
||||
playlist/playlistfilter.cpp
|
||||
playlist/playlistheader.cpp
|
||||
playlist/playlistitem.cpp
|
||||
playlist/playlistitemmimedata.cpp
|
||||
playlist/playlistlistcontainer.cpp
|
||||
playlist/playlistlistmodel.cpp
|
||||
playlist/playlistlistsortfiltermodel.cpp
|
||||
playlist/playlistlistview.cpp
|
||||
playlist/playlistmanager.cpp
|
||||
playlist/playlistsaveoptionsdialog.cpp
|
||||
@@ -120,6 +125,7 @@ set(SOURCES
|
||||
playlist/playlistundocommands.cpp
|
||||
playlist/playlistview.cpp
|
||||
playlist/playlistproxystyle.cpp
|
||||
playlist/songmimedata.cpp
|
||||
playlist/songloaderinserter.cpp
|
||||
playlist/songplaylistitem.cpp
|
||||
playlist/dynamicplaylistcontrols.cpp
|
||||
@@ -140,17 +146,23 @@ set(SOURCES
|
||||
|
||||
smartplaylists/playlistgenerator.cpp
|
||||
smartplaylists/playlistgeneratorinserter.cpp
|
||||
smartplaylists/playlistgeneratormimedata.cpp
|
||||
smartplaylists/playlistquerygenerator.cpp
|
||||
smartplaylists/smartplaylistquerywizardplugin.cpp
|
||||
smartplaylists/smartplaylistquerywizardpluginsortpage.cpp
|
||||
smartplaylists/smartplaylistquerywizardpluginsearchpage.cpp
|
||||
smartplaylists/smartplaylistsearch.cpp
|
||||
smartplaylists/smartplaylistsearchpreview.cpp
|
||||
smartplaylists/smartplaylistsearchterm.cpp
|
||||
smartplaylists/smartplaylistsearchtermwidget.cpp
|
||||
smartplaylists/smartplaylistsearchtermwidgetoverlay.cpp
|
||||
smartplaylists/smartplaylistsmodel.cpp
|
||||
smartplaylists/smartplaylistsviewcontainer.cpp
|
||||
smartplaylists/smartplaylistsview.cpp
|
||||
smartplaylists/smartplaylistwizard.cpp
|
||||
smartplaylists/smartplaylistwizardplugin.cpp
|
||||
smartplaylists/smartplaylistwizardtypepage.cpp
|
||||
smartplaylists/smartplaylistwizardfinishpage.cpp
|
||||
|
||||
covermanager/albumcovermanager.cpp
|
||||
covermanager/albumcovermanagerlist.cpp
|
||||
@@ -195,6 +207,7 @@ set(SOURCES
|
||||
lyrics/azlyricscomlyricsprovider.cpp
|
||||
lyrics/elyricsnetlyricsprovider.cpp
|
||||
lyrics/letraslyricsprovider.cpp
|
||||
lyrics/lyricfindlyricsprovider.cpp
|
||||
|
||||
providers/musixmatchprovider.cpp
|
||||
|
||||
@@ -270,6 +283,7 @@ set(SOURCES
|
||||
streaming/streamingcollectionview.cpp
|
||||
streaming/streamingcollectionviewcontainer.cpp
|
||||
streaming/streamingsearchview.cpp
|
||||
streaming/streamsongmimedata.cpp
|
||||
|
||||
radios/radioservices.cpp
|
||||
radios/radiobackend.cpp
|
||||
@@ -281,6 +295,7 @@ set(SOURCES
|
||||
radios/radiochannel.cpp
|
||||
radios/somafmservice.cpp
|
||||
radios/radioparadiseservice.cpp
|
||||
radios/radiomimedata.cpp
|
||||
|
||||
scrobbler/audioscrobbler.cpp
|
||||
scrobbler/scrobblersettings.cpp
|
||||
@@ -296,6 +311,8 @@ set(SOURCES
|
||||
|
||||
organize/organize.cpp
|
||||
organize/organizeformat.cpp
|
||||
organize/organizeformatvalidator.cpp
|
||||
organize/organizesyntaxhighlighter.cpp
|
||||
organize/organizedialog.cpp
|
||||
organize/organizeerrordialog.cpp
|
||||
|
||||
@@ -398,13 +415,18 @@ set(HEADERS
|
||||
smartplaylists/playlistquerygenerator.h
|
||||
smartplaylists/playlistgeneratormimedata.h
|
||||
smartplaylists/smartplaylistquerywizardplugin.h
|
||||
smartplaylists/smartplaylistquerywizardpluginsortpage.h
|
||||
smartplaylists/smartplaylistquerywizardpluginsearchpage.h
|
||||
smartplaylists/smartplaylistsearchpreview.h
|
||||
smartplaylists/smartplaylistsearchtermwidget.h
|
||||
smartplaylists/smartplaylistsearchtermwidgetoverlay.h
|
||||
smartplaylists/smartplaylistsmodel.h
|
||||
smartplaylists/smartplaylistsviewcontainer.h
|
||||
smartplaylists/smartplaylistsview.h
|
||||
smartplaylists/smartplaylistwizard.h
|
||||
smartplaylists/smartplaylistwizardplugin.h
|
||||
smartplaylists/smartplaylistwizardtypepage.h
|
||||
smartplaylists/smartplaylistwizardfinishpage.h
|
||||
|
||||
covermanager/albumcovermanager.h
|
||||
covermanager/albumcovermanagerlist.h
|
||||
@@ -445,6 +467,7 @@ set(HEADERS
|
||||
lyrics/azlyricscomlyricsprovider.h
|
||||
lyrics/elyricsnetlyricsprovider.h
|
||||
lyrics/letraslyricsprovider.h
|
||||
lyrics/lyricfindlyricsprovider.h
|
||||
|
||||
settings/settingsdialog.h
|
||||
settings/settingspage.h
|
||||
@@ -499,7 +522,7 @@ set(HEADERS
|
||||
widgets/tracksliderpopup.h
|
||||
widgets/tracksliderslider.h
|
||||
widgets/loginstatewidget.h
|
||||
widgets/qsearchfield.h
|
||||
widgets/searchfield.h
|
||||
widgets/ratingwidget.h
|
||||
widgets/forcescrollperpixel.h
|
||||
widgets/resizabletextedit.h
|
||||
@@ -540,6 +563,8 @@ set(HEADERS
|
||||
scrobbler/lastfmimport.h
|
||||
|
||||
organize/organize.h
|
||||
organize/organizeformatvalidator.h
|
||||
organize/organizesyntaxhighlighter.h
|
||||
organize/organizedialog.h
|
||||
organize/organizeerrordialog.h
|
||||
|
||||
@@ -625,7 +650,7 @@ option(USE_INSTALL_PREFIX "Look for data in CMAKE_INSTALL_PREFIX" ON)
|
||||
|
||||
if(NOT APPLE)
|
||||
set(NOT_APPLE ON)
|
||||
optional_source(NOT_APPLE SOURCES widgets/qsearchfield_qt.cpp core/qtsystemtrayicon.cpp HEADERS core/qtsystemtrayicon.h)
|
||||
optional_source(NOT_APPLE SOURCES widgets/searchfield_qt.cpp widgets/searchfield_qt_private.cpp core/qtsystemtrayicon.cpp HEADERS core/qtsystemtrayicon.h widgets/searchfield_qt_private.h)
|
||||
endif()
|
||||
|
||||
if(HAVE_GLOBALSHORTCUTS)
|
||||
@@ -764,6 +789,7 @@ optional_source(HAVE_LIBPULSE SOURCES engine/pulsedevicefinder.cpp)
|
||||
optional_source(HAVE_GSTREAMER
|
||||
SOURCES
|
||||
transcoder/transcoder.cpp
|
||||
transcoder/transcoderoptionsinterface.cpp
|
||||
transcoder/transcodedialog.cpp
|
||||
transcoder/transcoderoptionsdialog.cpp
|
||||
transcoder/transcoderoptionsflac.cpp
|
||||
@@ -842,7 +868,7 @@ optional_source(APPLE
|
||||
core/macsystemtrayicon.mm
|
||||
core/macfslistener.mm
|
||||
osd/osdmac.mm
|
||||
widgets/qsearchfield_mac.mm
|
||||
widgets/searchfield_mac.mm
|
||||
engine/macosdevicefinder.cpp
|
||||
globalshortcuts/globalshortcutsbackend-macos.mm
|
||||
globalshortcuts/globalshortcutgrabber.mm
|
||||
@@ -962,6 +988,7 @@ optional_source(HAVE_MOODBAR
|
||||
moodbar/moodbarpipeline.cpp
|
||||
moodbar/moodbarproxystyle.cpp
|
||||
moodbar/moodbarrenderer.cpp
|
||||
moodbar/gstfastspectrumplugin.cpp
|
||||
settings/moodbarsettingspage.cpp
|
||||
HEADERS
|
||||
moodbar/moodbarcontroller.h
|
||||
@@ -1004,95 +1031,20 @@ if(HAVE_TRANSLATIONS)
|
||||
endif(NOT LINGUAS OR LINGUAS STREQUAL "None")
|
||||
endif(LINGUAS STREQUAL "All")
|
||||
|
||||
add_pot(POT
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/translations/header
|
||||
${CMAKE_CURRENT_BINARY_DIR}/translations/translations.pot
|
||||
${SOURCES}
|
||||
${MOC}
|
||||
${UIC}
|
||||
${CMAKE_SOURCE_DIR}/data/html/oauthsuccess.html
|
||||
)
|
||||
if(NOT MSVC)
|
||||
add_pot(POT
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/translations/header
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/translations/translations.pot
|
||||
${SOURCES}
|
||||
${MOC}
|
||||
${UIC}
|
||||
${CMAKE_SOURCE_DIR}/data/html/oauthsuccess.html
|
||||
)
|
||||
endif()
|
||||
add_po(PO strawberry_ LANGUAGES ${LANGUAGES} DIRECTORY translations)
|
||||
|
||||
endif(HAVE_TRANSLATIONS)
|
||||
|
||||
link_directories(
|
||||
${Boost_LIBRARY_DIRS}
|
||||
${GLIB_LIBRARY_DIRS}
|
||||
${GOBJECT_LIBRARY_DIRS}
|
||||
${SQLITE_LIBRARY_DIRS}
|
||||
${PROTOBUF_LIBRARY_DIRS}
|
||||
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
||||
${ICU_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
if(HAVE_ALSA)
|
||||
link_directories(${ALSA_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_LIBPULSE)
|
||||
link_directories(${LIBPULSE_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_GSTREAMER)
|
||||
link_directories(
|
||||
${GSTREAMER_LIBRARY_DIRS}
|
||||
${GSTREAMER_BASE_LIBRARY_DIRS}
|
||||
${GSTREAMER_APP_LIBRARY_DIRS}
|
||||
${GSTREAMER_AUDIO_LIBRARY_DIRS}
|
||||
${GSTREAMER_TAG_LIBRARY_DIRS}
|
||||
${GSTREAMER_PBUTILS_LIBRARY_DIRS}
|
||||
)
|
||||
endif()
|
||||
|
||||
if(HAVE_VLC)
|
||||
link_directories(${LIBVLC_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_SONGFINGERPRINTING OR HAVE_MUSICBRAINZ)
|
||||
link_directories(${CHROMAPRINT_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
if(X11_FOUND)
|
||||
link_directories(${X11_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
if(XCB_FOUND)
|
||||
link_directories(${XCB_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_GIO)
|
||||
link_directories(${GIO_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_GIO_UNIX)
|
||||
link_directories(${GIO_UNIX_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_AUDIOCD)
|
||||
link_directories(${LIBCDIO_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_LIBGPOD)
|
||||
link_directories(${LIBGPOD_LIBRARY_DIRS} ${GDK_PIXBUF_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_LIBMTP)
|
||||
link_directories(${LIBMTP_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_TAGLIB)
|
||||
link_directories(${TAGLIB_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_TAGPARSER)
|
||||
link_directories(${TAGPARSER_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_QTSPARKLE)
|
||||
link_directories(${QTSPARKLE_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
add_library(strawberry_lib STATIC
|
||||
${SOURCES}
|
||||
${MOC}
|
||||
@@ -1127,6 +1079,16 @@ target_include_directories(strawberry_lib PUBLIC
|
||||
${SINGLEAPPLICATION_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_directories(strawberry_lib PUBLIC
|
||||
${Boost_LIBRARY_DIRS}
|
||||
${GLIB_LIBRARY_DIRS}
|
||||
${GOBJECT_LIBRARY_DIRS}
|
||||
${SQLITE_LIBRARY_DIRS}
|
||||
${PROTOBUF_LIBRARY_DIRS}
|
||||
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
||||
${ICU_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(strawberry_lib PUBLIC
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${GLIB_LIBRARIES}
|
||||
@@ -1149,17 +1111,15 @@ if(HAVE_DBUS)
|
||||
target_link_libraries(strawberry_lib PUBLIC Qt${QT_VERSION_MAJOR}::DBus)
|
||||
endif()
|
||||
|
||||
if(HAVE_X11_GLOBALSHORTCUTS AND HAVE_X11EXTRAS)
|
||||
target_link_libraries(strawberry_lib PUBLIC Qt${QT_VERSION_MAJOR}::X11Extras)
|
||||
endif()
|
||||
|
||||
if(HAVE_ALSA)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${ALSA_INCLUDE_DIRS})
|
||||
target_link_directories(strawberry_lib PRIVATE ${ALSA_LIBRARY_DIRS})
|
||||
target_link_libraries(strawberry_lib PRIVATE ${ALSA_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_LIBPULSE)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBPULSE_INCLUDE_DIRS})
|
||||
target_link_directories(strawberry_lib PRIVATE ${LIBPULSE_LIBRARY_DIRS})
|
||||
target_link_libraries(strawberry_lib PRIVATE ${LIBPULSE_LIBRARIES})
|
||||
endif()
|
||||
|
||||
@@ -1172,6 +1132,14 @@ if(HAVE_GSTREAMER)
|
||||
${GSTREAMER_TAG_INCLUDE_DIRS}
|
||||
${GSTREAMER_PBUTILS_INCLUDE_DIRS}
|
||||
)
|
||||
target_link_directories(strawberry_lib PRIVATE
|
||||
${GSTREAMER_LIBRARY_DIRS}
|
||||
${GSTREAMER_BASE_LIBRARY_DIRS}
|
||||
${GSTREAMER_APP_LIBRARY_DIRS}
|
||||
${GSTREAMER_AUDIO_LIBRARY_DIRS}
|
||||
${GSTREAMER_TAG_LIBRARY_DIRS}
|
||||
${GSTREAMER_PBUTILS_LIBRARY_DIRS}
|
||||
)
|
||||
target_link_libraries(strawberry_lib PRIVATE
|
||||
${GSTREAMER_LIBRARIES}
|
||||
${GSTREAMER_BASE_LIBRARIES}
|
||||
@@ -1183,16 +1151,19 @@ if(HAVE_GSTREAMER)
|
||||
endif()
|
||||
|
||||
if(HAVE_MOODBAR)
|
||||
target_link_libraries(strawberry_lib PRIVATE gstmoodbar)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${CMAKE_SOURCE_DIR}/3rdparty/gstfastspectrum)
|
||||
target_link_libraries(strawberry_lib PRIVATE gstfastspectrum)
|
||||
endif()
|
||||
|
||||
if(HAVE_VLC)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBVLC_INCLUDE_DIRS})
|
||||
target_link_directories(strawberry_lib PRIVATE ${LIBVLC_LIBRARY_DIRS})
|
||||
target_link_libraries(strawberry_lib PRIVATE ${LIBVLC_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_SONGFINGERPRINTING OR HAVE_MUSICBRAINZ)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${CHROMAPRINT_INCLUDE_DIRS})
|
||||
target_link_directories(strawberry_lib PRIVATE ${CHROMAPRINT_LIBRARY_DIRS})
|
||||
target_link_libraries(strawberry_lib PRIVATE ${CHROMAPRINT_LIBRARIES})
|
||||
endif()
|
||||
|
||||
@@ -1202,36 +1173,43 @@ endif()
|
||||
|
||||
if(X11_FOUND)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${X11_INCLUDE_DIR})
|
||||
target_link_directories(strawberry_lib PRIVATE ${X11_LIBRARY_DIRS})
|
||||
target_link_libraries(strawberry_lib PRIVATE ${X11_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(XCB_FOUND)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${XCB_INCLUDE_DIR})
|
||||
target_link_directories(strawberry_lib PRIVATE ${XCB_LIBRARY_DIRS})
|
||||
target_link_libraries(strawberry_lib PRIVATE ${XCB_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_GIO)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${GIO_INCLUDE_DIRS})
|
||||
target_link_directories(strawberry_lib PRIVATE ${GIO_LIBRARY_DIRS})
|
||||
target_link_libraries(strawberry_lib PRIVATE ${GIO_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_GIO_UNIX)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${GIO_UNIX_INCLUDE_DIRS})
|
||||
target_link_directories(strawberry_lib PRIVATE ${GIO_UNIX_LIBRARY_DIRS})
|
||||
target_link_libraries(strawberry_lib PRIVATE ${GIO_UNIX_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_AUDIOCD)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBCDIO_INCLUDE_DIRS})
|
||||
target_link_directories(strawberry_lib PRIVATE ${LIBCDIO_LIBRARY_DIRS})
|
||||
target_link_libraries(strawberry_lib PRIVATE ${LIBCDIO_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_LIBGPOD)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBGPOD_INCLUDE_DIRS} ${GDK_PIXBUF_INCLUDE_DIRS})
|
||||
target_link_directories(strawberry_lib PRIVATE ${LIBGPOD_LIBRARY_DIRS} ${GDK_PIXBUF_LIBRARY_DIRS})
|
||||
target_link_libraries(strawberry_lib PRIVATE ${LIBGPOD_LIBRARIES} ${GDK_PIXBUF_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_LIBMTP)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBMTP_INCLUDE_DIRS})
|
||||
target_link_directories(strawberry_lib PRIVATE ${LIBMTP_LIBRARY_DIRS})
|
||||
target_link_libraries(strawberry_lib PRIVATE ${LIBMTP_LIBRARIES})
|
||||
endif()
|
||||
|
||||
@@ -1253,16 +1231,12 @@ if(WIN32)
|
||||
if(MSVC)
|
||||
target_link_libraries(strawberry_lib PRIVATE WindowsApp)
|
||||
endif()
|
||||
if(GETOPT_INCLUDE_DIRS)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${GETOPT_INCLUDE_DIRS})
|
||||
endif()
|
||||
if(GETOPT_LIBRARIES)
|
||||
target_link_libraries(strawberry_lib PRIVATE ${GETOPT_LIBRARIES})
|
||||
endif()
|
||||
target_link_libraries(strawberry_lib PRIVATE getopt-win::getopt)
|
||||
endif()
|
||||
|
||||
if(HAVE_QTSPARKLE)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${QTSPARKLE_INCLUDE_DIRS})
|
||||
target_link_directories(strawberry_lib PRIVATE ${QTSPARKLE_LIBRARY_DIRS})
|
||||
target_link_libraries(strawberry_lib PRIVATE ${QTSPARKLE_LIBRARIES})
|
||||
endif()
|
||||
|
||||
|
||||
@@ -116,11 +116,7 @@ void AnalyzerContainer::mouseReleaseEvent(QMouseEvent *e) {
|
||||
}
|
||||
|
||||
if (e->button() == Qt::RightButton) {
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
context_menu_->popup(e->globalPosition().toPoint());
|
||||
#else
|
||||
context_menu_->popup(e->globalPos());
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
@@ -130,7 +126,7 @@ void AnalyzerContainer::ShowPopupMenu() {
|
||||
}
|
||||
|
||||
void AnalyzerContainer::wheelEvent(QWheelEvent *e) {
|
||||
emit WheelEvent(e->angleDelta().y());
|
||||
Q_EMIT WheelEvent(e->angleDelta().y());
|
||||
}
|
||||
|
||||
void AnalyzerContainer::SetEngine(SharedPtr<EngineBase> engine) {
|
||||
@@ -149,7 +145,7 @@ void AnalyzerContainer::DisableAnalyzer() {
|
||||
|
||||
void AnalyzerContainer::ChangeAnalyzer(const int id) {
|
||||
|
||||
QObject *instance = analyzer_types_[id]->newInstance(Q_ARG(QWidget*, this));
|
||||
QObject *instance = analyzer_types_.at(id)->newInstance(Q_ARG(QWidget*, this));
|
||||
|
||||
if (!instance) {
|
||||
qLog(Warning) << "Couldn't initialize a new" << analyzer_types_[id]->className();
|
||||
@@ -200,22 +196,25 @@ void AnalyzerContainer::Load() {
|
||||
for (int i = 0; i < analyzer_types_.count(); ++i) {
|
||||
if (type == QString::fromLatin1(analyzer_types_[i]->className())) {
|
||||
ChangeAnalyzer(i);
|
||||
actions_[i]->setChecked(true);
|
||||
QAction *action = actions_.value(i);
|
||||
action->setChecked(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!current_analyzer_) {
|
||||
ChangeAnalyzer(0);
|
||||
actions_[0]->setChecked(true);
|
||||
QAction *action = actions_.value(0);
|
||||
action->setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Framerate
|
||||
QList<QAction*> actions = group_framerate_->actions();
|
||||
const QList<QAction*> actions = group_framerate_->actions();
|
||||
for (int i = 0; i < framerate_list_.count(); ++i) {
|
||||
if (current_framerate_ == framerate_list_[i]) {
|
||||
if (current_framerate_ == framerate_list_.value(i)) {
|
||||
ChangeFramerate(current_framerate_);
|
||||
actions[i]->setChecked(true);
|
||||
QAction *action = actions[i];
|
||||
action->setChecked(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,14 +50,14 @@ class AnalyzerContainer : public QWidget {
|
||||
static const char *kSettingsGroup;
|
||||
static const char *kSettingsFramerate;
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void WheelEvent(const int delta);
|
||||
|
||||
protected:
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void wheelEvent(QWheelEvent *e) override;
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void ChangeAnalyzer(const int id);
|
||||
void ChangeFramerate(int new_framerate);
|
||||
void DisableAnalyzer();
|
||||
|
||||
@@ -167,11 +167,12 @@ void BlockAnalyzer::analyze(QPainter &p, const Scope &s, const bool new_frame) {
|
||||
|
||||
for (int x = 0, y = 0; x < static_cast<int>(scope_.size()); ++x) {
|
||||
// determine y
|
||||
for (y = 0; scope_[x] < yscale_[y]; ++y);
|
||||
for (y = 0; scope_[x] < yscale_.at(y); ++y);
|
||||
|
||||
// This is opposite to what you'd think, higher than y means the bar is lower than y (physically)
|
||||
if (static_cast<double>(y) > store_[x]) {
|
||||
y = static_cast<int>(store_[x] += step_);
|
||||
if (static_cast<double>(y) > store_.at(x)) {
|
||||
store_[x] += step_;
|
||||
y = static_cast<int>(store_.value(x));
|
||||
}
|
||||
else {
|
||||
store_[x] = y;
|
||||
@@ -179,18 +180,19 @@ void BlockAnalyzer::analyze(QPainter &p, const Scope &s, const bool new_frame) {
|
||||
|
||||
// If y is lower than fade_pos_, then the bar has exceeded the height of the fadeout
|
||||
// if the fadeout is quite faded now, then display the new one
|
||||
if (y <= fade_pos_[x] /*|| fade_intensity_[x] < kFadeSize / 3*/) {
|
||||
if (y <= fade_pos_.at(x) /*|| fade_intensity_[x] < kFadeSize / 3*/) {
|
||||
fade_pos_[x] = y;
|
||||
fade_intensity_[x] = kFadeSize;
|
||||
}
|
||||
|
||||
if (fade_intensity_[x] > 0) {
|
||||
const int offset = --fade_intensity_[x];
|
||||
const int y2 = y_ + (fade_pos_[x] * (kHeight + 1));
|
||||
if (fade_intensity_.at(x) > 0) {
|
||||
--fade_intensity_[x];
|
||||
const int offset = fade_intensity_.value(x);
|
||||
const int y2 = y_ + (fade_pos_.value(x) * (kHeight + 1));
|
||||
canvas_painter.drawPixmap(x * (kWidth + 1), y2, fade_bars_[offset], 0, 0, kWidth, height() - y2);
|
||||
}
|
||||
|
||||
if (fade_intensity_[x] == 0) fade_pos_[x] = rows_;
|
||||
if (fade_intensity_.at(x) == 0) fade_pos_[x] = rows_;
|
||||
|
||||
// REMEMBER: y is a number from 0 to rows_, 0 means all blocks are glowing, rows_ means none are
|
||||
canvas_painter.drawPixmap(x * (kWidth + 1), y * (kHeight + 1) + y_, *bar(), 0, y * (kHeight + 1), bar()->width(), bar()->height());
|
||||
|
||||
@@ -47,7 +47,7 @@ class BoomAnalyzer : public AnalyzerBase {
|
||||
void transform(Scope &s) override;
|
||||
void analyze(QPainter &p, const Scope &scope, const bool new_frame) override;
|
||||
|
||||
public slots:
|
||||
public Q_SLOTS:
|
||||
void changeK_barHeight(int);
|
||||
void changeF_peakSpeed(int);
|
||||
|
||||
|
||||
@@ -63,6 +63,8 @@ SCollection::SCollection(Application *app, QObject *parent)
|
||||
save_playcounts_to_files_(false),
|
||||
save_ratings_to_files_(false) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
|
||||
original_thread_ = thread();
|
||||
|
||||
backend_ = make_shared<CollectionBackend>();
|
||||
@@ -80,7 +82,7 @@ SCollection::SCollection(Application *app, QObject *parent)
|
||||
SCollection::~SCollection() {
|
||||
|
||||
if (watcher_) {
|
||||
watcher_->Stop();
|
||||
watcher_->Abort();
|
||||
watcher_->deleteLater();
|
||||
}
|
||||
if (watcher_thread_) {
|
||||
@@ -94,6 +96,7 @@ void SCollection::Init() {
|
||||
|
||||
watcher_ = new CollectionWatcher(Song::Source::Collection);
|
||||
watcher_thread_ = new Thread(this);
|
||||
watcher_thread_->setObjectName(watcher_->objectName());
|
||||
|
||||
watcher_thread_->SetIoPriority(Utilities::IoPriority::IOPRIO_CLASS_IDLE);
|
||||
|
||||
@@ -151,7 +154,7 @@ void SCollection::ExitReceived() {
|
||||
QObject::disconnect(obj, nullptr, this, nullptr);
|
||||
qLog(Debug) << obj << "successfully exited.";
|
||||
wait_for_exit_.removeAll(obj);
|
||||
if (wait_for_exit_.isEmpty()) emit ExitFinished();
|
||||
if (wait_for_exit_.isEmpty()) Q_EMIT ExitFinished();
|
||||
|
||||
}
|
||||
|
||||
@@ -159,7 +162,7 @@ void SCollection::IncrementalScan() { watcher_->IncrementalScanAsync(); }
|
||||
|
||||
void SCollection::FullScan() { watcher_->FullScanAsync(); }
|
||||
|
||||
void SCollection::AbortScan() { watcher_->Stop(); }
|
||||
void SCollection::StopScan() { watcher_->Stop(); }
|
||||
|
||||
void SCollection::Rescan(const SongList &songs) {
|
||||
|
||||
@@ -189,11 +192,7 @@ void SCollection::ReloadSettings() {
|
||||
|
||||
void SCollection::SyncPlaycountAndRatingToFilesAsync() {
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
(void)QtConcurrent::run(&SCollection::SyncPlaycountAndRatingToFiles, this);
|
||||
#else
|
||||
(void)QtConcurrent::run(this, &SCollection::SyncPlaycountAndRatingToFiles);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -64,24 +64,24 @@ class SCollection : public QObject {
|
||||
private:
|
||||
void SyncPlaycountAndRatingToFiles();
|
||||
|
||||
public slots:
|
||||
public Q_SLOTS:
|
||||
void ReloadSettings();
|
||||
|
||||
void PauseWatcher();
|
||||
void ResumeWatcher();
|
||||
|
||||
void FullScan();
|
||||
void AbortScan();
|
||||
void StopScan();
|
||||
void Rescan(const SongList &songs);
|
||||
|
||||
void IncrementalScan();
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void ExitReceived();
|
||||
void SongsPlaycountChanged(const SongList &songs, const bool save_tags = false);
|
||||
void SongsRatingChanged(const SongList &songs, const bool save_tags = false);
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void Error(const QString &error);
|
||||
void ExitFinished();
|
||||
|
||||
|
||||
@@ -59,6 +59,8 @@
|
||||
#include "collectionquery.h"
|
||||
#include "collectiontask.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
CollectionBackend::CollectionBackend(QObject *parent)
|
||||
: CollectionBackendInterface(parent),
|
||||
db_(nullptr),
|
||||
@@ -72,17 +74,21 @@ CollectionBackend::CollectionBackend(QObject *parent)
|
||||
|
||||
CollectionBackend::~CollectionBackend() {
|
||||
|
||||
qLog(Debug) << "Collection backend" << this << "for" << Song::TextForSource(source_) << "deleted";
|
||||
qLog(Debug) << "Collection backend" << this << "deleted";
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::Init(SharedPtr<Database> db, SharedPtr<TaskManager> task_manager, const Song::Source source, const QString &songs_table, const QString &dirs_table, const QString &subdirs_table) {
|
||||
|
||||
setObjectName(source == Song::Source::Collection ? QLatin1String(metaObject()->className()) : QStringLiteral("%1%2").arg(Song::DescriptionForSource(source), QLatin1String(metaObject()->className())));
|
||||
|
||||
db_ = db;
|
||||
task_manager_ = task_manager;
|
||||
source_ = source;
|
||||
songs_table_ = songs_table;
|
||||
dirs_table_ = dirs_table;
|
||||
subdirs_table_ = subdirs_table;
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::Close() {
|
||||
@@ -103,7 +109,7 @@ void CollectionBackend::Exit() {
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
moveToThread(original_thread_);
|
||||
emit ExitFinished();
|
||||
Q_EMIT ExitFinished();
|
||||
|
||||
}
|
||||
|
||||
@@ -114,8 +120,8 @@ void CollectionBackend::ReportErrors(const CollectionQuery &query) {
|
||||
qLog(Error) << "Unable to execute collection SQL query:" << sql_error;
|
||||
qLog(Error) << "Failed SQL query:" << query.lastQuery();
|
||||
qLog(Error) << "Bound SQL values:" << query.boundValues();
|
||||
emit Error(tr("Unable to execute collection SQL query: %1").arg(sql_error.text()));
|
||||
emit Error(tr("Failed SQL query: %1").arg(query.lastQuery()));
|
||||
Q_EMIT Error(tr("Unable to execute collection SQL query: %1").arg(sql_error.text()));
|
||||
Q_EMIT Error(tr("Failed SQL query: %1").arg(query.lastQuery()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -134,7 +140,7 @@ void CollectionBackend::GetAllSongs(const int id) {
|
||||
q.prepare(QStringLiteral("SELECT %1 FROM %2").arg(Song::kRowIdColumnSpec, songs_table_));
|
||||
if (!q.exec()) {
|
||||
db_->ReportErrors(q);
|
||||
emit GotSongs(SongList(), id);
|
||||
Q_EMIT GotSongs(SongList(), id);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -145,7 +151,7 @@ void CollectionBackend::GetAllSongs(const int id) {
|
||||
songs << song;
|
||||
}
|
||||
|
||||
emit GotSongs(songs, id);
|
||||
Q_EMIT GotSongs(songs, id);
|
||||
|
||||
}
|
||||
|
||||
@@ -189,7 +195,7 @@ void CollectionBackend::LoadDirectories() {
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
for (const CollectionDirectory &dir : dirs) {
|
||||
emit DirectoryAdded(dir, SubdirsInDirectory(dir.id, db));
|
||||
Q_EMIT DirectoryAdded(dir, SubdirsInDirectory(dir.id, db));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -317,7 +323,7 @@ void CollectionBackend::UpdateTotalSongCount() {
|
||||
return;
|
||||
}
|
||||
|
||||
emit TotalSongCountUpdated(q.value(0).toInt());
|
||||
Q_EMIT TotalSongCountUpdated(q.value(0).toInt());
|
||||
|
||||
}
|
||||
|
||||
@@ -337,7 +343,7 @@ void CollectionBackend::UpdateTotalArtistCount() {
|
||||
return;
|
||||
}
|
||||
|
||||
emit TotalArtistCountUpdated(q.value(0).toInt());
|
||||
Q_EMIT TotalArtistCountUpdated(q.value(0).toInt());
|
||||
|
||||
}
|
||||
|
||||
@@ -357,7 +363,7 @@ void CollectionBackend::UpdateTotalAlbumCount() {
|
||||
return;
|
||||
}
|
||||
|
||||
emit TotalAlbumCountUpdated(q.value(0).toInt());
|
||||
Q_EMIT TotalAlbumCountUpdated(q.value(0).toInt());
|
||||
|
||||
}
|
||||
|
||||
@@ -395,7 +401,7 @@ void CollectionBackend::AddDirectory(const QString &path) {
|
||||
dir.path = path;
|
||||
dir.id = q.lastInsertId().toInt();
|
||||
|
||||
emit DirectoryAdded(dir, CollectionSubdirectoryList());
|
||||
Q_EMIT DirectoryAdded(dir, CollectionSubdirectoryList());
|
||||
|
||||
}
|
||||
|
||||
@@ -437,7 +443,7 @@ void CollectionBackend::RemoveDirectory(const CollectionDirectory &dir) {
|
||||
|
||||
transaction.Commit();
|
||||
|
||||
emit DirectoryDeleted(dir);
|
||||
Q_EMIT DirectoryDeleted(dir);
|
||||
|
||||
}
|
||||
|
||||
@@ -717,8 +723,8 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
|
||||
|
||||
transaction.Commit();
|
||||
|
||||
if (!added_songs.isEmpty()) emit SongsAdded(added_songs);
|
||||
if (!changed_songs.isEmpty()) emit SongsChanged(changed_songs);
|
||||
if (!added_songs.isEmpty()) Q_EMIT SongsAdded(added_songs);
|
||||
if (!changed_songs.isEmpty()) Q_EMIT SongsChanged(changed_songs);
|
||||
|
||||
UpdateTotalSongCountAsync();
|
||||
UpdateTotalArtistCountAsync();
|
||||
@@ -819,9 +825,9 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
||||
|
||||
transaction.Commit();
|
||||
|
||||
if (!deleted_songs.isEmpty()) emit SongsDeleted(deleted_songs);
|
||||
if (!added_songs.isEmpty()) emit SongsAdded(added_songs);
|
||||
if (!changed_songs.isEmpty()) emit SongsChanged(changed_songs);
|
||||
if (!deleted_songs.isEmpty()) Q_EMIT SongsDeleted(deleted_songs);
|
||||
if (!added_songs.isEmpty()) Q_EMIT SongsAdded(added_songs);
|
||||
if (!changed_songs.isEmpty()) Q_EMIT SongsChanged(changed_songs);
|
||||
|
||||
UpdateTotalSongCountAsync();
|
||||
UpdateTotalArtistCountAsync();
|
||||
@@ -867,7 +873,7 @@ void CollectionBackend::DeleteSongs(const SongList &songs) {
|
||||
|
||||
transaction.Commit();
|
||||
|
||||
emit SongsDeleted(songs);
|
||||
Q_EMIT SongsDeleted(songs);
|
||||
|
||||
UpdateTotalSongCountAsync();
|
||||
UpdateTotalArtistCountAsync();
|
||||
@@ -894,10 +900,10 @@ void CollectionBackend::MarkSongsUnavailable(const SongList &songs, const bool u
|
||||
transaction.Commit();
|
||||
|
||||
if (unavailable) {
|
||||
emit SongsDeleted(songs);
|
||||
Q_EMIT SongsDeleted(songs);
|
||||
}
|
||||
else {
|
||||
emit SongsAdded(songs);
|
||||
Q_EMIT SongsAdded(songs);
|
||||
}
|
||||
|
||||
UpdateTotalSongCountAsync();
|
||||
@@ -942,14 +948,14 @@ QStringList CollectionBackend::GetAllArtistsWithAlbums(const CollectionFilterOpt
|
||||
CollectionQuery query(db, songs_table_, opt);
|
||||
query.SetColumnSpec(QStringLiteral("DISTINCT albumartist"));
|
||||
query.AddCompilationRequirement(false);
|
||||
query.AddWhere(QStringLiteral("album"), QLatin1String(""), QStringLiteral("!="));
|
||||
query.AddWhere(QStringLiteral("album"), ""_L1, QStringLiteral("!="));
|
||||
|
||||
// Albums with no 'albumartist' (extract 'artist'):
|
||||
CollectionQuery query2(db, songs_table_, opt);
|
||||
query2.SetColumnSpec(QStringLiteral("DISTINCT artist"));
|
||||
query2.AddCompilationRequirement(false);
|
||||
query2.AddWhere(QStringLiteral("album"), QLatin1String(""), QStringLiteral("!="));
|
||||
query2.AddWhere(QStringLiteral("albumartist"), QLatin1String(""), QStringLiteral("="));
|
||||
query2.AddWhere(QStringLiteral("album"), ""_L1, QStringLiteral("!="));
|
||||
query2.AddWhere(QStringLiteral("albumartist"), ""_L1, QStringLiteral("="));
|
||||
|
||||
if (!query.Exec()) {
|
||||
ReportErrors(query);
|
||||
@@ -1103,7 +1109,7 @@ SongList CollectionBackend::GetSongsByForeignId(const QStringList &ids, const QS
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
QString in = ids.join(QLatin1Char(','));
|
||||
QString in = ids.join(u',');
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("SELECT %3.ROWID, %2, %3.%4 FROM %3, %1 WHERE %3.%4 IN (in) AND %1.ROWID = %3.ROWID AND unavailable = 0").arg(songs_table_, Song::kColumnSpec, table, column, in));
|
||||
@@ -1134,7 +1140,7 @@ Song CollectionBackend::GetSongById(const int id, QSqlDatabase &db) {
|
||||
|
||||
SongList CollectionBackend::GetSongsById(const QStringList &ids, QSqlDatabase &db) {
|
||||
|
||||
QString in = ids.join(QLatin1Char(','));
|
||||
QString in = ids.join(u',');
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("SELECT %1 FROM %2 WHERE ROWID IN (%3)").arg(Song::kRowIdColumnSpec, songs_table_, in));
|
||||
@@ -1272,7 +1278,7 @@ SongList CollectionBackend::GetSongsBySongId(const QStringList &song_ids, QSqlDa
|
||||
for (const QString &song_id : song_ids) {
|
||||
song_ids2 << QLatin1Char('\'') + song_id + QLatin1Char('\'');
|
||||
}
|
||||
QString in = song_ids2.join(QLatin1Char(','));
|
||||
QString in = song_ids2.join(u',');
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("SELECT %1 FROM %2 WHERE SONG_ID IN (%3)").arg(Song::kRowIdColumnSpec, songs_table_, in));
|
||||
@@ -1410,7 +1416,7 @@ void CollectionBackend::CompilationsNeedUpdating() {
|
||||
transaction.Commit();
|
||||
|
||||
if (!changed_songs.isEmpty()) {
|
||||
emit SongsChanged(changed_songs);
|
||||
Q_EMIT SongsChanged(changed_songs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1497,7 +1503,8 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
||||
album_info.art_embedded = query.Value(6).toBool();
|
||||
|
||||
const QString art_automatic = query.Value(7).toString();
|
||||
if (art_automatic.contains(QRegularExpression(QStringLiteral("..+:.*")))) {
|
||||
static const QRegularExpression regex_url_schema(QStringLiteral("..+:.*"));
|
||||
if (art_automatic.contains(regex_url_schema)) {
|
||||
album_info.art_automatic = QUrl::fromEncoded(art_automatic.toUtf8());
|
||||
}
|
||||
else {
|
||||
@@ -1505,7 +1512,7 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
||||
}
|
||||
|
||||
const QString art_manual = query.Value(8).toString();
|
||||
if (art_manual.contains(QRegularExpression(QStringLiteral("..+:.*")))) {
|
||||
if (art_manual.contains(regex_url_schema)) {
|
||||
album_info.art_manual = QUrl::fromEncoded(art_manual.toUtf8());
|
||||
}
|
||||
else {
|
||||
@@ -1519,7 +1526,7 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
||||
key.append(album_info.album_artist);
|
||||
}
|
||||
if (!album_info.album.isEmpty()) {
|
||||
if (!key.isEmpty()) key.append(QLatin1Char('-'));
|
||||
if (!key.isEmpty()) key.append(u'-');
|
||||
key.append(album_info.album);
|
||||
}
|
||||
if (!filetype.isEmpty()) {
|
||||
@@ -1616,7 +1623,7 @@ void CollectionBackend::UpdateEmbeddedAlbumArt(const QString &effective_albumart
|
||||
}
|
||||
|
||||
if (!songs.isEmpty()) {
|
||||
emit SongsChanged(songs);
|
||||
Q_EMIT SongsChanged(songs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1635,7 +1642,7 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("UPDATE %1 SET art_manual = :art_manual, art_unset = 0 WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_));
|
||||
q.BindValue(QStringLiteral(":art_manual"), art_manual.isValid() ? art_manual.toString(QUrl::FullyEncoded) : QLatin1String(""));
|
||||
q.BindValue(QStringLiteral(":art_manual"), art_manual.isValid() ? art_manual.toString(QUrl::FullyEncoded) : ""_L1);
|
||||
q.BindValue(QStringLiteral(":effective_albumartist"), effective_albumartist);
|
||||
q.BindValue(QStringLiteral(":album"), album);
|
||||
if (!q.Exec()) {
|
||||
@@ -1662,7 +1669,7 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis
|
||||
}
|
||||
|
||||
if (!songs.isEmpty()) {
|
||||
emit SongsChanged(songs);
|
||||
Q_EMIT SongsChanged(songs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1707,7 +1714,7 @@ void CollectionBackend::UnsetAlbumArt(const QString &effective_albumartist, cons
|
||||
}
|
||||
|
||||
if (!songs.isEmpty()) {
|
||||
emit SongsChanged(songs);
|
||||
Q_EMIT SongsChanged(songs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1753,7 +1760,7 @@ void CollectionBackend::ClearAlbumArt(const QString &effective_albumartist, cons
|
||||
}
|
||||
|
||||
if (!songs.isEmpty()) {
|
||||
emit SongsChanged(songs);
|
||||
Q_EMIT SongsChanged(songs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1768,7 +1775,7 @@ void CollectionBackend::ForceCompilation(const QString &album, const QStringList
|
||||
|
||||
// Update the songs
|
||||
QString sql(QStringLiteral("UPDATE %1 SET compilation_on = :compilation_on, compilation_off = :compilation_off, compilation_effective = ((compilation OR compilation_detected OR :compilation_on) AND NOT :compilation_off) + 0 WHERE album = :album AND unavailable = 0").arg(songs_table_));
|
||||
if (!artist.isEmpty()) sql += QLatin1String(" AND artist = :artist");
|
||||
if (!artist.isEmpty()) sql += " AND artist = :artist"_L1;
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(sql);
|
||||
@@ -1802,7 +1809,7 @@ void CollectionBackend::ForceCompilation(const QString &album, const QStringList
|
||||
}
|
||||
|
||||
if (!songs.isEmpty()) {
|
||||
emit SongsChanged(songs);
|
||||
Q_EMIT SongsChanged(songs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1824,7 +1831,7 @@ void CollectionBackend::IncrementPlayCount(const int id) {
|
||||
}
|
||||
|
||||
Song new_song = GetSongById(id, db);
|
||||
emit SongsStatisticsChanged(SongList() << new_song);
|
||||
Q_EMIT SongsStatisticsChanged(SongList() << new_song);
|
||||
|
||||
}
|
||||
|
||||
@@ -1846,7 +1853,7 @@ void CollectionBackend::IncrementSkipCount(const int id, const float progress) {
|
||||
}
|
||||
|
||||
Song new_song = GetSongById(id, db);
|
||||
emit SongsStatisticsChanged(SongList() << new_song);
|
||||
Q_EMIT SongsStatisticsChanged(SongList() << new_song);
|
||||
|
||||
}
|
||||
|
||||
@@ -1871,7 +1878,7 @@ void CollectionBackend::ResetPlayStatistics(const QList<int> &id_list, const boo
|
||||
const bool success = ResetPlayStatistics(id_str_list);
|
||||
if (success) {
|
||||
const SongList songs = GetSongsById(id_list);
|
||||
emit SongsStatisticsChanged(songs, save_tags);
|
||||
Q_EMIT SongsStatisticsChanged(songs, save_tags);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1885,7 +1892,7 @@ bool CollectionBackend::ResetPlayStatistics(const QStringList &id_str_list) {
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("UPDATE %1 SET playcount = 0, skipcount = 0, lastplayed = -1 WHERE ROWID IN (:ids)").arg(songs_table_));
|
||||
q.BindValue(QStringLiteral(":ids"), id_str_list.join(QLatin1Char(',')));
|
||||
q.BindValue(QStringLiteral(":ids"), id_str_list.join(u','));
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return false;
|
||||
@@ -1920,7 +1927,7 @@ void CollectionBackend::DeleteAll() {
|
||||
t.Commit();
|
||||
}
|
||||
|
||||
emit DatabaseReset();
|
||||
Q_EMIT DatabaseReset();
|
||||
|
||||
}
|
||||
|
||||
@@ -2013,7 +2020,7 @@ void CollectionBackend::UpdateLastPlayed(const QString &artist, const QString &a
|
||||
}
|
||||
}
|
||||
|
||||
emit SongsStatisticsChanged(SongList() << songs);
|
||||
Q_EMIT SongsStatisticsChanged(SongList() << songs);
|
||||
|
||||
}
|
||||
|
||||
@@ -2039,7 +2046,7 @@ void CollectionBackend::UpdatePlayCount(const QString &artist, const QString &ti
|
||||
}
|
||||
}
|
||||
|
||||
emit SongsStatisticsChanged(SongList() << songs, save_tags);
|
||||
Q_EMIT SongsStatisticsChanged(SongList() << songs, save_tags);
|
||||
|
||||
}
|
||||
|
||||
@@ -2063,7 +2070,7 @@ void CollectionBackend::UpdateSongsRating(const QList<int> &id_list, const float
|
||||
for (int i : id_list) {
|
||||
id_str_list << QString::number(i);
|
||||
}
|
||||
QString ids = id_str_list.join(QLatin1Char(','));
|
||||
QString ids = id_str_list.join(u',');
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("UPDATE %1 SET rating = :rating WHERE ROWID IN (%2)").arg(songs_table_, ids));
|
||||
q.BindValue(QStringLiteral(":rating"), rating);
|
||||
@@ -2074,7 +2081,7 @@ void CollectionBackend::UpdateSongsRating(const QList<int> &id_list, const float
|
||||
|
||||
SongList new_song_list = GetSongsById(id_str_list, db);
|
||||
|
||||
emit SongsRatingChanged(new_song_list, save_tags);
|
||||
Q_EMIT SongsRatingChanged(new_song_list, save_tags);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -236,7 +236,7 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
void UpdateSongRatingAsync(const int id, const float rating, const bool save_tags = false);
|
||||
void UpdateSongsRatingAsync(const QList<int> &ids, const float rating, const bool save_tags = false);
|
||||
|
||||
public slots:
|
||||
public Q_SLOTS:
|
||||
void Exit();
|
||||
void GetAllSongs(const int id);
|
||||
void LoadDirectories();
|
||||
@@ -275,7 +275,7 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
void UpdateLastSeen(const int directory_id, const int expire_unavailable_songs_days);
|
||||
void ExpireSongs(const int directory_id, const int expire_unavailable_songs_days);
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void DirectoryAdded(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdir);
|
||||
void DirectoryDeleted(const CollectionDirectory &dir);
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ class CollectionDirectoryModel : public QStandardItemModel {
|
||||
QMap<int, CollectionDirectory> directories() const { return directories_; }
|
||||
QStringList paths() const { return paths_; }
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void AddDirectory(const CollectionDirectory &directory);
|
||||
void RemoveDirectory(const CollectionDirectory &directory);
|
||||
|
||||
|
||||
@@ -60,11 +60,7 @@ bool CollectionFilter::filterAcceptsRow(const int source_row, const QModelIndex
|
||||
return item->type == CollectionItem::Type::LoadingIndicator;
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
const size_t hash = qHash(filter_string_);
|
||||
#else
|
||||
const uint hash = qHash(filter_string_);
|
||||
#endif
|
||||
size_t hash = qHash(filter_string_);
|
||||
if (hash != query_hash_) {
|
||||
FilterParser p(filter_string_);
|
||||
filter_tree_.reset(p.parse());
|
||||
|
||||
@@ -51,11 +51,7 @@ class CollectionFilter : public QSortFilterProxyModel {
|
||||
|
||||
private:
|
||||
mutable QScopedPointer<FilterTree> filter_tree_;
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
mutable size_t query_hash_;
|
||||
#else
|
||||
mutable uint query_hash_;
|
||||
#endif
|
||||
QString filter_string_;
|
||||
};
|
||||
|
||||
|
||||
@@ -57,10 +57,12 @@
|
||||
#include "collectionfilterwidget.h"
|
||||
#include "groupbydialog.h"
|
||||
#include "ui_collectionfilterwidget.h"
|
||||
#include "widgets/qsearchfield.h"
|
||||
#include "widgets/searchfield.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
#include "settings/appearancesettingspage.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
namespace {
|
||||
constexpr int kFilterDelay = 500; // msec
|
||||
}
|
||||
@@ -84,7 +86,7 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
||||
|
||||
ui_->search_field->setToolTip(FilterParser::ToolTip());
|
||||
|
||||
QObject::connect(ui_->search_field, &QSearchField::returnPressed, this, &CollectionFilterWidget::ReturnPressed);
|
||||
QObject::connect(ui_->search_field, &SearchField::returnPressed, this, &CollectionFilterWidget::ReturnPressed);
|
||||
QObject::connect(timer_filter_delay_, &QTimer::timeout, this, &CollectionFilterWidget::FilterDelayTimeout);
|
||||
|
||||
timer_filter_delay_->setInterval(kFilterDelay);
|
||||
@@ -127,7 +129,7 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
||||
collection_menu_->addSeparator();
|
||||
ui_->options->setMenu(collection_menu_);
|
||||
|
||||
QObject::connect(ui_->search_field, &QSearchField::textChanged, this, &CollectionFilterWidget::FilterTextChanged);
|
||||
QObject::connect(ui_->search_field, &SearchField::textChanged, this, &CollectionFilterWidget::FilterTextChanged);
|
||||
QObject::connect(ui_->options, &QToolButton::clicked, ui_->options, &QToolButton::showMenu);
|
||||
|
||||
ReloadSettings();
|
||||
@@ -158,7 +160,7 @@ void CollectionFilterWidget::Init(CollectionModel *model, CollectionFilter *filt
|
||||
|
||||
const QList<QAction*> actions = filter_max_ages_.keys();
|
||||
for (QAction *action : actions) {
|
||||
int filter_max_age = filter_max_ages_[action];
|
||||
const int filter_max_age = filter_max_ages_.value(action);
|
||||
QObject::connect(action, &QAction::triggered, this, [this, filter_max_age]() { model_->SetFilterMaxAge(filter_max_age); } );
|
||||
}
|
||||
|
||||
@@ -296,7 +298,7 @@ QActionGroup *CollectionFilterWidget::CreateGroupByActions(const QString &saved_
|
||||
if (version == 1) {
|
||||
QStringList saved = s.childKeys();
|
||||
for (int i = 0; i < saved.size(); ++i) {
|
||||
if (saved.at(i) == QLatin1String("version")) continue;
|
||||
if (saved.at(i) == "version"_L1) continue;
|
||||
QByteArray bytes = s.value(saved.at(i)).toByteArray();
|
||||
QDataStream ds(&bytes, QIODevice::ReadOnly);
|
||||
CollectionModel::Grouping g;
|
||||
@@ -307,7 +309,7 @@ QActionGroup *CollectionFilterWidget::CreateGroupByActions(const QString &saved_
|
||||
else {
|
||||
QStringList saved = s.childKeys();
|
||||
for (int i = 0; i < saved.size(); ++i) {
|
||||
if (saved.at(i) == QLatin1String("version")) continue;
|
||||
if (saved.at(i) == "version"_L1) continue;
|
||||
s.remove(saved.at(i));
|
||||
}
|
||||
}
|
||||
@@ -479,12 +481,12 @@ void CollectionFilterWidget::keyReleaseEvent(QKeyEvent *e) {
|
||||
|
||||
switch (e->key()) {
|
||||
case Qt::Key_Up:
|
||||
emit UpPressed();
|
||||
Q_EMIT UpPressed();
|
||||
e->accept();
|
||||
break;
|
||||
|
||||
case Qt::Key_Down:
|
||||
emit DownPressed();
|
||||
Q_EMIT DownPressed();
|
||||
e->accept();
|
||||
break;
|
||||
|
||||
|
||||
@@ -86,12 +86,12 @@ class CollectionFilterWidget : public QWidget {
|
||||
bool SearchFieldHasFocus() const;
|
||||
void FocusSearchField();
|
||||
|
||||
public slots:
|
||||
public Q_SLOTS:
|
||||
void UpdateGroupByActions();
|
||||
void SetFilterMode(CollectionFilterOptions::FilterMode filter_mode);
|
||||
void FocusOnFilter(QKeyEvent *e);
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void UpPressed();
|
||||
void DownPressed();
|
||||
void ReturnPressed();
|
||||
@@ -99,7 +99,7 @@ class CollectionFilterWidget : public QWidget {
|
||||
protected:
|
||||
void keyReleaseEvent(QKeyEvent *e) override;
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void GroupingChanged(const CollectionModel::Grouping g, const bool separate_albums_by_grouping);
|
||||
void GroupByClicked(QAction *action);
|
||||
void SaveGroupBy();
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QSearchField" name="search_field" native="true">
|
||||
<widget class="SearchField" name="search_field" native="true">
|
||||
<property name="placeholderText" stdset="0">
|
||||
<string>Enter search terms here</string>
|
||||
</property>
|
||||
@@ -123,9 +123,9 @@
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QSearchField</class>
|
||||
<class>SearchField</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/qsearchfield.h</header>
|
||||
<header>widgets/searchfield.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
|
||||
@@ -40,7 +40,7 @@ class CollectionItemDelegate : public QStyledItemDelegate {
|
||||
explicit CollectionItemDelegate(QObject *parent);
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const override;
|
||||
|
||||
public slots:
|
||||
public Q_SLOTS:
|
||||
bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &idx) override;
|
||||
};
|
||||
|
||||
|
||||
@@ -24,10 +24,11 @@
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <optional>
|
||||
#include <chrono>
|
||||
|
||||
#include <QObject>
|
||||
#include <QtGlobal>
|
||||
#include <QtConcurrent>
|
||||
#include <QtConcurrentRun>
|
||||
#include <QThread>
|
||||
#include <QMutex>
|
||||
#include <QFuture>
|
||||
@@ -75,6 +76,9 @@
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
const int CollectionModel::kPrettyCoverSize = 32;
|
||||
namespace {
|
||||
constexpr char kPixmapDiskCacheDir[] = "pixmapcache";
|
||||
@@ -98,6 +102,8 @@ CollectionModel::CollectionModel(SharedPtr<CollectionBackend> backend, Applicati
|
||||
total_album_count_(0),
|
||||
loading_(false) {
|
||||
|
||||
setObjectName(backend_->source() == Song::Source::Collection ? QLatin1String(metaObject()->className()) : QStringLiteral("%1%2").arg(Song::DescriptionForSource(backend_->source()), QLatin1String(metaObject()->className())));
|
||||
|
||||
filter_->setSourceModel(this);
|
||||
filter_->setSortRole(Role_SortText);
|
||||
filter_->sort(0);
|
||||
@@ -114,7 +120,7 @@ CollectionModel::CollectionModel(SharedPtr<CollectionBackend> backend, Applicati
|
||||
|
||||
if (app_ && !sIconCache) {
|
||||
sIconCache = new QNetworkDiskCache(this);
|
||||
sIconCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1Char('/') + QLatin1String(kPixmapDiskCacheDir));
|
||||
sIconCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u'/' + QLatin1String(kPixmapDiskCacheDir));
|
||||
QObject::connect(app_, &Application::ClearPixmapDiskCache, this, &CollectionModel::ClearDiskCache);
|
||||
}
|
||||
|
||||
@@ -133,11 +139,11 @@ CollectionModel::CollectionModel(SharedPtr<CollectionBackend> backend, Applicati
|
||||
backend_->UpdateTotalAlbumCountAsync();
|
||||
|
||||
timer_reload_->setSingleShot(true);
|
||||
timer_reload_->setInterval(300);
|
||||
timer_reload_->setInterval(300ms);
|
||||
QObject::connect(timer_reload_, &QTimer::timeout, this, &CollectionModel::Reload);
|
||||
|
||||
timer_update_->setSingleShot(false);
|
||||
timer_update_->setInterval(20);
|
||||
timer_update_->setInterval(20ms);
|
||||
QObject::connect(timer_update_, &QTimer::timeout, this, &CollectionModel::ProcessUpdate);
|
||||
|
||||
ReloadSettings();
|
||||
@@ -146,7 +152,7 @@ CollectionModel::CollectionModel(SharedPtr<CollectionBackend> backend, Applicati
|
||||
|
||||
CollectionModel::~CollectionModel() {
|
||||
|
||||
qLog(Debug) << "Collection model" << this << "for" << Song::TextForSource(backend_->source()) << "deleted";
|
||||
qLog(Debug) << "Collection model" << this << "deleted";
|
||||
|
||||
beginResetModel();
|
||||
Clear();
|
||||
@@ -266,7 +272,7 @@ void CollectionModel::SetGroupBy(const Grouping g, const std::optional<bool> sep
|
||||
|
||||
ScheduleReset();
|
||||
|
||||
emit GroupingChanged(g, options_current_.separate_albums_by_grouping);
|
||||
Q_EMIT GroupingChanged(g, options_current_.separate_albums_by_grouping);
|
||||
|
||||
}
|
||||
|
||||
@@ -514,7 +520,7 @@ void CollectionModel::AddReAddOrUpdateSongsInternal(const SongList &songs) {
|
||||
songs_added << new_song;
|
||||
continue;
|
||||
}
|
||||
const Song &old_song = song_nodes_[new_song.id()]->metadata;
|
||||
const Song old_song = song_nodes_.value(new_song.id())->metadata;
|
||||
bool container_key_changed = false;
|
||||
bool has_unique_album_identifier_1 = false;
|
||||
bool has_unique_album_identifier_2 = false;
|
||||
@@ -580,7 +586,7 @@ void CollectionModel::AddSongsInternal(const SongList &songs) {
|
||||
container_key = container->container_key;
|
||||
}
|
||||
else {
|
||||
if (!container_key.isEmpty()) container_key.append(QLatin1Char('-'));
|
||||
if (!container_key.isEmpty()) container_key.append(u'-');
|
||||
container_key.append(ContainerKey(group_by, song, has_unique_album_identifier));
|
||||
if (container_nodes_[i].contains(container_key)) {
|
||||
container = container_nodes_[i][container_key];
|
||||
@@ -606,7 +612,7 @@ void CollectionModel::UpdateSongsInternal(const SongList &songs) {
|
||||
qLog(Error) << "Song does not exist in model" << new_song.id() << new_song.PrettyTitleWithArtist();
|
||||
continue;
|
||||
}
|
||||
CollectionItem *item = song_nodes_[new_song.id()];
|
||||
CollectionItem *item = song_nodes_.value(new_song.id());
|
||||
const Song &old_song = item->metadata;
|
||||
const bool song_title_data_changed = IsSongTitleDataChanged(old_song, new_song);
|
||||
const bool art_changed = !old_song.IsArtEqual(new_song);
|
||||
@@ -622,18 +628,18 @@ void CollectionModel::UpdateSongsInternal(const SongList &songs) {
|
||||
qLog(Debug) << "Song metadata and title for" << new_song.id() << new_song.PrettyTitleWithArtist() << "changed, informing model";
|
||||
const QModelIndex idx = ItemToIndex(item);
|
||||
if (!idx.isValid()) continue;
|
||||
emit dataChanged(idx, idx);
|
||||
Q_EMIT dataChanged(idx, idx);
|
||||
}
|
||||
else {
|
||||
qLog(Debug) << "Song metadata for" << new_song.id() << new_song.PrettyTitleWithArtist() << "changed";
|
||||
}
|
||||
}
|
||||
|
||||
for (CollectionItem *item : album_parents) {
|
||||
for (CollectionItem *item : std::as_const(album_parents)) {
|
||||
ClearItemPixmapCache(item);
|
||||
const QModelIndex idx = ItemToIndex(item);
|
||||
if (idx.isValid()) {
|
||||
emit dataChanged(idx, idx);
|
||||
Q_EMIT dataChanged(idx, idx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -648,7 +654,7 @@ void CollectionModel::RemoveSongsInternal(const SongList &songs) {
|
||||
for (const Song &song : songs) {
|
||||
|
||||
if (song_nodes_.contains(song.id())) {
|
||||
CollectionItem *node = song_nodes_[song.id()];
|
||||
CollectionItem *node = song_nodes_.value(song.id());
|
||||
|
||||
if (node->parent != root_) parents << node->parent;
|
||||
|
||||
@@ -706,7 +712,7 @@ void CollectionModel::RemoveSongsInternal(const SongList &songs) {
|
||||
}
|
||||
|
||||
// Remove the divider
|
||||
int row = divider_nodes_[divider_key]->row;
|
||||
const int row = divider_nodes_.value(divider_key)->row;
|
||||
beginRemoveRows(ItemToIndex(root_), row, row);
|
||||
root_->Delete(row);
|
||||
endRemoveRows();
|
||||
@@ -753,7 +759,7 @@ void CollectionModel::CreateDividerItem(const QString ÷r_key, const QStrin
|
||||
CollectionItem *divider = new CollectionItem(CollectionItem::Type::Divider, root_);
|
||||
divider->container_key = divider_key;
|
||||
divider->display_text = display_text;
|
||||
divider->sort_text = divider_key + QLatin1String(" ");
|
||||
divider->sort_text = divider_key + " "_L1;
|
||||
divider_nodes_[divider_key] = divider;
|
||||
|
||||
endInsertRows();
|
||||
@@ -797,7 +803,7 @@ CollectionItem *CollectionModel::CreateCompilationArtistNode(CollectionItem *par
|
||||
if (parent != root_ && !parent->container_key.isEmpty()) parent->compilation_artist_node_->container_key.append(parent->container_key);
|
||||
parent->compilation_artist_node_->container_key.append(QLatin1String(kVariousArtists));
|
||||
parent->compilation_artist_node_->display_text = QLatin1String(kVariousArtists);
|
||||
parent->compilation_artist_node_->sort_text = QLatin1String(" various");
|
||||
parent->compilation_artist_node_->sort_text = " various"_L1;
|
||||
parent->compilation_artist_node_->container_level = parent->container_level + 1;
|
||||
|
||||
endInsertRows();
|
||||
@@ -808,11 +814,7 @@ CollectionItem *CollectionModel::CreateCompilationArtistNode(CollectionItem *par
|
||||
|
||||
void CollectionModel::LoadSongsFromSqlAsync() {
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QFuture<SongList> future = QtConcurrent::run(&CollectionModel::LoadSongsFromSql, this, options_active_.filter_options);
|
||||
#else
|
||||
QFuture<SongList> future = QtConcurrent::run(this, &CollectionModel::LoadSongsFromSql, options_active_.filter_options);
|
||||
#endif
|
||||
QFutureWatcher<SongList> *watcher = new QFutureWatcher<SongList>();
|
||||
QObject::connect(watcher, &QFutureWatcher<void>::finished, this, &CollectionModel::LoadSongsFromSqlAsyncFinished);
|
||||
watcher->setFuture(future);
|
||||
@@ -989,7 +991,7 @@ void CollectionModel::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderR
|
||||
const QModelIndex idx = ItemToIndex(item);
|
||||
if (!idx.isValid()) return;
|
||||
|
||||
emit dataChanged(idx, idx);
|
||||
Q_EMIT dataChanged(idx, idx);
|
||||
|
||||
}
|
||||
|
||||
@@ -1055,14 +1057,14 @@ QString CollectionModel::TextOrUnknown(const QString &text) {
|
||||
QString CollectionModel::PrettyYearAlbum(const int year, const QString &album) {
|
||||
|
||||
if (year <= 0) return TextOrUnknown(album);
|
||||
return QString::number(year) + QLatin1String(" - ") + TextOrUnknown(album);
|
||||
return QString::number(year) + " - "_L1 + TextOrUnknown(album);
|
||||
|
||||
}
|
||||
|
||||
QString CollectionModel::PrettyAlbumDisc(const QString &album, const int disc) {
|
||||
|
||||
if (disc <= 0 || Song::AlbumContainsDisc(album)) return TextOrUnknown(album);
|
||||
return TextOrUnknown(album) + QLatin1String(" - (Disc ") + QString::number(disc) + QLatin1String(")");
|
||||
return TextOrUnknown(album) + " - (Disc "_L1 + QString::number(disc) + ")"_L1;
|
||||
|
||||
}
|
||||
|
||||
@@ -1071,9 +1073,9 @@ QString CollectionModel::PrettyYearAlbumDisc(const int year, const QString &albu
|
||||
QString str;
|
||||
|
||||
if (year <= 0) str = TextOrUnknown(album);
|
||||
else str = QString::number(year) + QLatin1String(" - ") + TextOrUnknown(album);
|
||||
else str = QString::number(year) + " - "_L1 + TextOrUnknown(album);
|
||||
|
||||
if (!Song::AlbumContainsDisc(album) && disc > 0) str += QLatin1String(" - (Disc ") + QString::number(disc) + QLatin1String(")");
|
||||
if (!Song::AlbumContainsDisc(album) && disc > 0) str += " - (Disc "_L1 + QString::number(disc) + ")"_L1;
|
||||
|
||||
return str;
|
||||
|
||||
@@ -1081,7 +1083,7 @@ QString CollectionModel::PrettyYearAlbumDisc(const int year, const QString &albu
|
||||
|
||||
QString CollectionModel::PrettyDisc(const int disc) {
|
||||
|
||||
return QLatin1String("Disc ") + QString::number(std::max(1, disc));
|
||||
return "Disc "_L1 + QString::number(std::max(1, disc));
|
||||
|
||||
}
|
||||
|
||||
@@ -1158,12 +1160,13 @@ QString CollectionModel::SortText(const GroupBy group_by, const int container_le
|
||||
QString CollectionModel::SortText(QString text) {
|
||||
|
||||
if (text.isEmpty()) {
|
||||
text = QLatin1String(" unknown");
|
||||
text = " unknown"_L1;
|
||||
}
|
||||
else {
|
||||
text = text.toLower();
|
||||
}
|
||||
text = text.remove(QRegularExpression(QStringLiteral("[^\\w ]"), QRegularExpression::UseUnicodePropertiesOption));
|
||||
static const QRegularExpression regex_not_words(QStringLiteral("[^\\w ]"), QRegularExpression::UseUnicodePropertiesOption);
|
||||
text = text.remove(regex_not_words);
|
||||
|
||||
return text;
|
||||
|
||||
@@ -1177,7 +1180,7 @@ QString CollectionModel::SortTextForArtist(QString artist, const bool skip_artic
|
||||
for (const auto &i : Song::kArticles) {
|
||||
if (artist.startsWith(i)) {
|
||||
qint64 ilen = i.length();
|
||||
artist = artist.right(artist.length() - ilen) + QLatin1String(", ") + i.left(ilen - 1);
|
||||
artist = artist.right(artist.length() - ilen) + ", "_L1 + i.left(ilen - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1188,7 +1191,6 @@ QString CollectionModel::SortTextForArtist(QString artist, const bool skip_artic
|
||||
}
|
||||
|
||||
QString CollectionModel::SortTextForNumber(const int number) {
|
||||
|
||||
return QStringLiteral("%1").arg(number, 4, 10, QLatin1Char('0'));
|
||||
}
|
||||
|
||||
@@ -1316,7 +1318,7 @@ QString CollectionModel::ContainerKey(const GroupBy group_by, const Song &song,
|
||||
|
||||
// Make sure we distinguish albums by different artists if the parent group by is not including artist.
|
||||
if (IsAlbumGroupBy(group_by) && !has_unique_album_identifier && !song.is_compilation() && !song.effective_albumartist().isEmpty()) {
|
||||
key.prepend(QLatin1Char('-'));
|
||||
key.prepend(u'-');
|
||||
key.prepend(TextOrUnknown(song.effective_albumartist()));
|
||||
has_unique_album_identifier = true;
|
||||
}
|
||||
@@ -1346,7 +1348,7 @@ QString CollectionModel::DividerKey(const GroupBy group_by, const Song &song, co
|
||||
case GroupBy::FileType: {
|
||||
QChar c = sort_text[0];
|
||||
if (c.isDigit()) return QStringLiteral("0");
|
||||
if (c == QLatin1Char(' ')) return QString();
|
||||
if (c == u' ') return QString();
|
||||
if (c.decompositionTag() != QChar::NoDecomposition) {
|
||||
QString decomposition = c.decomposition();
|
||||
return QChar(decomposition[0]);
|
||||
@@ -1395,25 +1397,25 @@ QString CollectionModel::DividerDisplayText(const GroupBy group_by, const QStrin
|
||||
case GroupBy::Genre:
|
||||
case GroupBy::FileType:
|
||||
case GroupBy::Format:
|
||||
if (key == QLatin1String("0")) return QStringLiteral("0-9");
|
||||
if (key == "0"_L1) return QStringLiteral("0-9");
|
||||
return key.toUpper();
|
||||
|
||||
case GroupBy::YearAlbum:
|
||||
case GroupBy::YearAlbumDisc:
|
||||
case GroupBy::OriginalYearAlbum:
|
||||
case GroupBy::OriginalYearAlbumDisc:
|
||||
if (key == QLatin1String("0000")) return tr("Unknown");
|
||||
if (key == "0000"_L1) return tr("Unknown");
|
||||
return key.toUpper();
|
||||
|
||||
case GroupBy::Year:
|
||||
case GroupBy::OriginalYear:
|
||||
if (key == QLatin1String("0000")) return tr("Unknown");
|
||||
if (key == "0000"_L1) return tr("Unknown");
|
||||
return QString::number(key.toInt()); // To remove leading 0s
|
||||
|
||||
case GroupBy::Samplerate:
|
||||
case GroupBy::Bitdepth:
|
||||
case GroupBy::Bitrate:
|
||||
if (key == QLatin1String("000")) return tr("Unknown");
|
||||
if (key == "000"_L1) return tr("Unknown");
|
||||
return QString::number(key.toInt()); // To remove leading 0s
|
||||
|
||||
case GroupBy::None:
|
||||
@@ -1429,17 +1431,15 @@ QString CollectionModel::DividerDisplayText(const GroupBy group_by, const QStrin
|
||||
|
||||
bool CollectionModel::CompareItems(const CollectionItem *a, const CollectionItem *b) const {
|
||||
|
||||
QVariant left(data(a, CollectionModel::Role_SortText));
|
||||
QVariant right(data(b, CollectionModel::Role_SortText));
|
||||
QVariant left = data(a, CollectionModel::Role_SortText);
|
||||
QVariant right = data(b, CollectionModel::Role_SortText);
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
if (left.metaType().id() == QMetaType::Int)
|
||||
#else
|
||||
if (left.type() == QVariant::Int)
|
||||
#endif
|
||||
if (left.metaType().id() == QMetaType::Int) {
|
||||
return left.toInt() < right.toInt();
|
||||
else
|
||||
}
|
||||
else {
|
||||
return left.toString() < right.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1531,21 +1531,21 @@ CollectionModel::GroupBy &CollectionModel::Grouping::operator[](const int i) {
|
||||
void CollectionModel::TotalSongCountUpdatedSlot(const int count) {
|
||||
|
||||
total_song_count_ = count;
|
||||
emit TotalSongCountUpdated(count);
|
||||
Q_EMIT TotalSongCountUpdated(count);
|
||||
|
||||
}
|
||||
|
||||
void CollectionModel::TotalArtistCountUpdatedSlot(const int count) {
|
||||
|
||||
total_artist_count_ = count;
|
||||
emit TotalArtistCountUpdated(count);
|
||||
Q_EMIT TotalArtistCountUpdated(count);
|
||||
|
||||
}
|
||||
|
||||
void CollectionModel::TotalAlbumCountUpdatedSlot(const int count) {
|
||||
|
||||
total_album_count_ = count;
|
||||
emit TotalAlbumCountUpdated(count);
|
||||
Q_EMIT TotalAlbumCountUpdated(count);
|
||||
|
||||
}
|
||||
|
||||
@@ -1565,7 +1565,7 @@ void CollectionModel::RowsInserted(const QModelIndex &parent, const int first, c
|
||||
}
|
||||
|
||||
if (!songs.isEmpty()) {
|
||||
emit SongsAdded(songs);
|
||||
Q_EMIT SongsAdded(songs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1581,7 +1581,7 @@ void CollectionModel::RowsRemoved(const QModelIndex &parent, const int first, co
|
||||
songs << item->metadata;
|
||||
}
|
||||
|
||||
emit SongsRemoved(songs);
|
||||
Q_EMIT SongsRemoved(songs);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
|
||||
bool CompareItems(const CollectionItem *a, const CollectionItem *b) const;
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void TotalSongCountUpdated(const int count);
|
||||
void TotalArtistCountUpdated(const int count);
|
||||
void TotalAlbumCountUpdated(const int count);
|
||||
@@ -209,7 +209,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
void SongsAdded(const SongList &songs);
|
||||
void SongsRemoved(const SongList &songs);
|
||||
|
||||
public slots:
|
||||
public Q_SLOTS:
|
||||
void SetFilterMode(const CollectionFilterOptions::FilterMode filter_mode);
|
||||
void SetFilterMaxAge(const int filter_max_age);
|
||||
|
||||
@@ -253,7 +253,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
void ClearItemPixmapCache(CollectionItem *item);
|
||||
static qint64 MaximumCacheSize(Settings *s, const char *size_id, const char *size_unit_id, const qint64 cache_size_default);
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void Reload();
|
||||
void ScheduleReset();
|
||||
void ProcessUpdate();
|
||||
|
||||
@@ -19,5 +19,5 @@
|
||||
|
||||
#include "collectionmodelupdate.h"
|
||||
|
||||
CollectionModelUpdate::CollectionModelUpdate(const Type &_type, const SongList &_songs)
|
||||
CollectionModelUpdate::CollectionModelUpdate(const Type _type, const SongList &_songs)
|
||||
: type(_type), songs(_songs) {}
|
||||
|
||||
@@ -30,7 +30,7 @@ class CollectionModelUpdate {
|
||||
Update,
|
||||
Remove,
|
||||
};
|
||||
explicit CollectionModelUpdate(const Type &_type, const SongList &_songs);
|
||||
explicit CollectionModelUpdate(const Type _type, const SongList &_songs);
|
||||
Type type;
|
||||
SongList songs;
|
||||
};
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QMetaType>
|
||||
#include <QDateTime>
|
||||
@@ -36,6 +38,8 @@
|
||||
#include "collectionquery.h"
|
||||
#include "collectionfilteroptions.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const CollectionFilterOptions &filter_options)
|
||||
: SqlQuery(db),
|
||||
songs_table_(songs_table),
|
||||
@@ -44,7 +48,7 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta
|
||||
limit_(-1) {
|
||||
|
||||
if (filter_options.max_age() != -1) {
|
||||
qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - filter_options.max_age();
|
||||
qint64 cutoff = QDateTime::currentSecsSinceEpoch() - filter_options.max_age();
|
||||
|
||||
where_clauses_ << QStringLiteral("ctime > ?");
|
||||
bound_values_ << cutoff;
|
||||
@@ -61,8 +65,8 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta
|
||||
void CollectionQuery::AddWhere(const QString &column, const QVariant &value, const QString &op) {
|
||||
|
||||
// Ignore 'literal' for IN
|
||||
if (op.compare(QLatin1String("IN"), Qt::CaseInsensitive) == 0) {
|
||||
QStringList values = value.toStringList();
|
||||
if (op.compare("IN"_L1, Qt::CaseInsensitive) == 0) {
|
||||
const QStringList values = value.toStringList();
|
||||
QStringList final_values;
|
||||
final_values.reserve(values.count());
|
||||
for (const QString &single_value : values) {
|
||||
@@ -70,26 +74,16 @@ void CollectionQuery::AddWhere(const QString &column, const QVariant &value, con
|
||||
bound_values_ << single_value;
|
||||
}
|
||||
|
||||
where_clauses_ << QStringLiteral("%1 IN (%2)").arg(column, final_values.join(QLatin1Char(',')));
|
||||
where_clauses_ << QStringLiteral("%1 IN (%2)").arg(column, final_values.join(u','));
|
||||
}
|
||||
else {
|
||||
// Do integers inline - sqlite seems to get confused when you pass integers to bound parameters
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
if (value.metaType().id() == QMetaType::Int) {
|
||||
#else
|
||||
if (value.type() == QVariant::Int) {
|
||||
#endif
|
||||
where_clauses_ << QStringLiteral("%1 %2 %3").arg(column, op, value.toString());
|
||||
}
|
||||
else if (
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
value.metaType().id() == QMetaType::QString
|
||||
#else
|
||||
value.type() == QVariant::String
|
||||
#endif
|
||||
&& value.toString().isNull()) {
|
||||
else if (value.metaType().id() == QMetaType::QString && value.toString().isNull()) {
|
||||
where_clauses_ << QStringLiteral("%1 %2 ?").arg(column, op);
|
||||
bound_values_ << QLatin1String("");
|
||||
bound_values_ << ""_L1;
|
||||
}
|
||||
else {
|
||||
where_clauses_ << QStringLiteral("%1 %2 ?").arg(column, op);
|
||||
@@ -123,18 +117,18 @@ bool CollectionQuery::Exec() {
|
||||
where_clauses << QStringLiteral("unavailable = 0");
|
||||
}
|
||||
|
||||
if (!where_clauses.isEmpty()) sql += QLatin1String(" WHERE ") + where_clauses.join(QLatin1String(" AND "));
|
||||
if (!where_clauses.isEmpty()) sql += " WHERE "_L1 + where_clauses.join(" AND "_L1);
|
||||
|
||||
if (!order_by_.isEmpty()) sql += QLatin1String(" ORDER BY ") + order_by_;
|
||||
if (!order_by_.isEmpty()) sql += " ORDER BY "_L1 + order_by_;
|
||||
|
||||
if (limit_ != -1) sql += QLatin1String(" LIMIT ") + QString::number(limit_);
|
||||
if (limit_ != -1) sql += " LIMIT "_L1 + QString::number(limit_);
|
||||
|
||||
sql.replace(QLatin1String("%songs_table"), songs_table_);
|
||||
sql.replace("%songs_table"_L1, songs_table_);
|
||||
|
||||
if (!QSqlQuery::prepare(sql)) return false;
|
||||
|
||||
// Bind values
|
||||
for (const QVariant &value : bound_values_) {
|
||||
for (const QVariant &value : std::as_const(bound_values_)) {
|
||||
QSqlQuery::addBindValue(value);
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,8 @@ CollectionView::CollectionView(QWidget *parent)
|
||||
is_in_keyboard_search_(false),
|
||||
delete_files_(false) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
|
||||
setItemDelegate(new CollectionItemDelegate(this));
|
||||
setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
setHeaderHidden(true);
|
||||
@@ -273,7 +275,7 @@ void CollectionView::TotalSongCountUpdated(const int count) {
|
||||
unsetCursor();
|
||||
}
|
||||
|
||||
emit TotalSongCountUpdated_();
|
||||
Q_EMIT TotalSongCountUpdated_();
|
||||
|
||||
}
|
||||
|
||||
@@ -290,7 +292,7 @@ void CollectionView::TotalArtistCountUpdated(const int count) {
|
||||
unsetCursor();
|
||||
}
|
||||
|
||||
emit TotalArtistCountUpdated_();
|
||||
Q_EMIT TotalArtistCountUpdated_();
|
||||
|
||||
}
|
||||
|
||||
@@ -307,7 +309,7 @@ void CollectionView::TotalAlbumCountUpdated(const int count) {
|
||||
unsetCursor();
|
||||
}
|
||||
|
||||
emit TotalAlbumCountUpdated_();
|
||||
Q_EMIT TotalAlbumCountUpdated_();
|
||||
|
||||
}
|
||||
|
||||
@@ -348,7 +350,7 @@ void CollectionView::mouseReleaseEvent(QMouseEvent *e) {
|
||||
QTreeView::mouseReleaseEvent(e);
|
||||
|
||||
if (total_song_count_ == 0) {
|
||||
emit ShowConfigDialog();
|
||||
Q_EMIT ShowConfigDialog();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -457,11 +459,7 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
||||
action_copy_to_device_->setVisible(regular_elements == regular_editable);
|
||||
#endif
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
action_delete_files_->setVisible(delete_files_);
|
||||
#else
|
||||
action_delete_files_->setVisible(false);
|
||||
#endif
|
||||
|
||||
action_show_in_various_->setVisible(songs_selected > 0);
|
||||
action_no_show_in_various_->setVisible(songs_selected > 0);
|
||||
@@ -472,11 +470,7 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
||||
action_copy_to_device_->setEnabled(regular_elements == regular_editable);
|
||||
#endif
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
action_delete_files_->setEnabled(delete_files_);
|
||||
#else
|
||||
action_delete_files_->setEnabled(false);
|
||||
#endif
|
||||
|
||||
context_menu_->popup(e->globalPos());
|
||||
|
||||
@@ -521,11 +515,7 @@ void CollectionView::SetShowInVarious(const bool on) {
|
||||
}
|
||||
}
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||
const QSet<QString> albums_set = QSet<QString>(albums.keyBegin(), albums.keyEnd());
|
||||
#else
|
||||
const QSet<QString> albums_set = QSet<QString>::fromList(albums.keys());
|
||||
#endif
|
||||
for (const QString &album : albums_set) {
|
||||
app_->collection_backend()->ForceCompilation(album, albums.values(album), on);
|
||||
}
|
||||
@@ -538,13 +528,13 @@ void CollectionView::Load() {
|
||||
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
|
||||
mimedata->clear_first_ = true;
|
||||
}
|
||||
emit AddToPlaylistSignal(q_mimedata);
|
||||
Q_EMIT AddToPlaylistSignal(q_mimedata);
|
||||
|
||||
}
|
||||
|
||||
void CollectionView::AddToPlaylist() {
|
||||
|
||||
emit AddToPlaylistSignal(model()->mimeData(selectedIndexes()));
|
||||
Q_EMIT AddToPlaylistSignal(model()->mimeData(selectedIndexes()));
|
||||
|
||||
}
|
||||
|
||||
@@ -554,7 +544,7 @@ void CollectionView::AddToPlaylistEnqueue() {
|
||||
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
|
||||
mimedata->enqueue_now_ = true;
|
||||
}
|
||||
emit AddToPlaylistSignal(q_mimedata);
|
||||
Q_EMIT AddToPlaylistSignal(q_mimedata);
|
||||
|
||||
}
|
||||
|
||||
@@ -564,7 +554,7 @@ void CollectionView::AddToPlaylistEnqueueNext() {
|
||||
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
|
||||
mimedata->enqueue_next_now_ = true;
|
||||
}
|
||||
emit AddToPlaylistSignal(q_mimedata);
|
||||
Q_EMIT AddToPlaylistSignal(q_mimedata);
|
||||
|
||||
}
|
||||
|
||||
@@ -574,7 +564,7 @@ void CollectionView::OpenInNewPlaylist() {
|
||||
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
|
||||
mimedata->open_in_new_playlist_ = true;
|
||||
}
|
||||
emit AddToPlaylistSignal(q_mimedata);
|
||||
Q_EMIT AddToPlaylistSignal(q_mimedata);
|
||||
|
||||
}
|
||||
|
||||
@@ -611,7 +601,7 @@ void CollectionView::SearchForThis() {
|
||||
CollectionItem *item = app_->collection_model()->IndexToItem(index);
|
||||
const CollectionModel::GroupBy group_by = app_->collection_model()->GetGroupBy()[item->container_level];
|
||||
while (!item->children.isEmpty()) {
|
||||
item = item->children.first();
|
||||
item = item->children.constFirst();
|
||||
}
|
||||
|
||||
switch (group_by) {
|
||||
@@ -731,7 +721,7 @@ void CollectionView::EditTracks() {
|
||||
}
|
||||
|
||||
void CollectionView::EditTagError(const QString &message) {
|
||||
emit Error(message);
|
||||
Q_EMIT Error(message);
|
||||
}
|
||||
|
||||
void CollectionView::RescanSongs() {
|
||||
@@ -773,7 +763,7 @@ void CollectionView::FilterReturnPressed() {
|
||||
|
||||
if (!currentIndex().isValid()) return;
|
||||
|
||||
emit doubleClicked(currentIndex());
|
||||
Q_EMIT doubleClicked(currentIndex());
|
||||
}
|
||||
|
||||
void CollectionView::ShowInBrowser() const {
|
||||
|
||||
@@ -69,7 +69,7 @@ class CollectionView : public AutoExpandingTreeView {
|
||||
int TotalArtists() const;
|
||||
int TotalAlbums() const;
|
||||
|
||||
public slots:
|
||||
public Q_SLOTS:
|
||||
void TotalSongCountUpdated(const int count);
|
||||
void TotalArtistCountUpdated(const int count);
|
||||
void TotalAlbumCountUpdated(const int count);
|
||||
@@ -82,7 +82,7 @@ class CollectionView : public AutoExpandingTreeView {
|
||||
|
||||
void EditTagError(const QString &message);
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void ShowConfigDialog();
|
||||
|
||||
void TotalSongCountUpdated_();
|
||||
@@ -97,7 +97,7 @@ class CollectionView : public AutoExpandingTreeView {
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void contextMenuEvent(QContextMenuEvent *e) override;
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void Load();
|
||||
void AddToPlaylist();
|
||||
void AddToPlaylistEnqueue();
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QImage>
|
||||
#include <QMutexLocker>
|
||||
#include <QSettings>
|
||||
|
||||
#include "core/filesystemwatcherinterface.h"
|
||||
@@ -70,9 +71,9 @@
|
||||
#endif
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
QStringList CollectionWatcher::sValidImages = QStringList() << QStringLiteral("jpg") << QStringLiteral("png") << QStringLiteral("gif") << QStringLiteral("jpeg");
|
||||
const QStringList CollectionWatcher::kIgnoredExtensions = QStringList() << QStringLiteral("tmp") << QStringLiteral("tar") << QStringLiteral("gz") << QStringLiteral("bz2") << QStringLiteral("xz") << QStringLiteral("tbz") << QStringLiteral("tgz") << QStringLiteral("z") << QStringLiteral("zip") << QStringLiteral("rar");
|
||||
|
||||
CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
||||
: QObject(parent),
|
||||
@@ -98,6 +99,8 @@ CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
||||
cue_parser_(new CueParser(backend_, this)),
|
||||
last_scan_time_(0) {
|
||||
|
||||
setObjectName(source_ == Song::Source::Collection ? QLatin1String(metaObject()->className()) : QStringLiteral("%1%2").arg(Song::DescriptionForSource(source_), QLatin1String(metaObject()->className())));
|
||||
|
||||
original_thread_ = thread();
|
||||
|
||||
rescan_timer_->setInterval(2s);
|
||||
@@ -135,10 +138,51 @@ void CollectionWatcher::Exit() {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
Stop();
|
||||
Abort();
|
||||
if (backend_) backend_->Close();
|
||||
moveToThread(original_thread_);
|
||||
emit ExitFinished();
|
||||
Q_EMIT ExitFinished();
|
||||
|
||||
}
|
||||
|
||||
void CollectionWatcher::Stop() {
|
||||
|
||||
QMutexLocker l(&mutex_stop_);
|
||||
stop_requested_ = true;
|
||||
|
||||
}
|
||||
|
||||
void CollectionWatcher::CancelStop() {
|
||||
|
||||
QMutexLocker l(&mutex_stop_);
|
||||
stop_requested_ = false;
|
||||
|
||||
}
|
||||
|
||||
bool CollectionWatcher::stop_requested() const {
|
||||
|
||||
QMutexLocker l(&mutex_stop_);
|
||||
return stop_requested_;
|
||||
|
||||
}
|
||||
|
||||
void CollectionWatcher::Abort() {
|
||||
|
||||
QMutexLocker l(&mutex_abort_);
|
||||
abort_requested_ = true;
|
||||
|
||||
}
|
||||
|
||||
bool CollectionWatcher::abort_requested() const {
|
||||
|
||||
QMutexLocker l(&mutex_abort_);
|
||||
return abort_requested_;
|
||||
|
||||
}
|
||||
|
||||
bool CollectionWatcher::stop_or_abort_requested() const {
|
||||
|
||||
return stop_requested() || abort_requested();
|
||||
|
||||
}
|
||||
|
||||
@@ -223,14 +267,14 @@ CollectionWatcher::ScanTransaction::ScanTransaction(CollectionWatcher *watcher,
|
||||
}
|
||||
|
||||
task_id_ = watcher_->task_manager_->StartTask(description);
|
||||
emit watcher_->ScanStarted(task_id_);
|
||||
Q_EMIT watcher_->ScanStarted(task_id_);
|
||||
|
||||
}
|
||||
|
||||
CollectionWatcher::ScanTransaction::~ScanTransaction() {
|
||||
|
||||
// If we're stopping then don't commit the transaction
|
||||
if (!watcher_->stop_requested_ && !watcher_->abort_requested_) {
|
||||
if (!watcher_->stop_or_abort_requested()) {
|
||||
CommitNewOrUpdatedSongs();
|
||||
}
|
||||
|
||||
@@ -256,35 +300,35 @@ void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
|
||||
|
||||
if (!deleted_songs.isEmpty()) {
|
||||
if (mark_songs_unavailable_ && watcher_->source() == Song::Source::Collection) {
|
||||
emit watcher_->SongsUnavailable(deleted_songs);
|
||||
Q_EMIT watcher_->SongsUnavailable(deleted_songs);
|
||||
}
|
||||
else {
|
||||
emit watcher_->SongsDeleted(deleted_songs);
|
||||
Q_EMIT watcher_->SongsDeleted(deleted_songs);
|
||||
}
|
||||
deleted_songs.clear();
|
||||
}
|
||||
|
||||
if (!new_songs.isEmpty()) {
|
||||
emit watcher_->NewOrUpdatedSongs(new_songs);
|
||||
Q_EMIT watcher_->NewOrUpdatedSongs(new_songs);
|
||||
new_songs.clear();
|
||||
}
|
||||
|
||||
if (!touched_songs.isEmpty()) {
|
||||
emit watcher_->SongsMTimeUpdated(touched_songs);
|
||||
Q_EMIT watcher_->SongsMTimeUpdated(touched_songs);
|
||||
touched_songs.clear();
|
||||
}
|
||||
|
||||
if (!readded_songs.isEmpty()) {
|
||||
emit watcher_->SongsReadded(readded_songs);
|
||||
Q_EMIT watcher_->SongsReadded(readded_songs);
|
||||
readded_songs.clear();
|
||||
}
|
||||
|
||||
if (!new_subdirs.isEmpty()) {
|
||||
emit watcher_->SubdirsDiscovered(new_subdirs);
|
||||
Q_EMIT watcher_->SubdirsDiscovered(new_subdirs);
|
||||
}
|
||||
|
||||
if (!touched_subdirs.isEmpty()) {
|
||||
emit watcher_->SubdirsMTimeUpdated(touched_subdirs);
|
||||
Q_EMIT watcher_->SubdirsMTimeUpdated(touched_subdirs);
|
||||
touched_subdirs.clear();
|
||||
}
|
||||
|
||||
@@ -306,7 +350,7 @@ void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
|
||||
new_subdirs.clear();
|
||||
|
||||
if (incremental_ || ignores_mtime_) {
|
||||
emit watcher_->UpdateLastSeen(dir_, expire_unavailable_songs_days_);
|
||||
Q_EMIT watcher_->UpdateLastSeen(dir_, expire_unavailable_songs_days_);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -317,7 +361,7 @@ SongList CollectionWatcher::ScanTransaction::FindSongsInSubdirectory(const QStri
|
||||
if (cached_songs_dirty_) {
|
||||
const SongList songs = watcher_->backend_->FindSongsInDirectory(dir_);
|
||||
for (const Song &song : songs) {
|
||||
const QString p = song.url().toLocalFile().section(QLatin1Char('/'), 0, -2);
|
||||
const QString p = song.url().toLocalFile().section(u'/', 0, -2);
|
||||
cached_songs_.insert(p, song);
|
||||
}
|
||||
cached_songs_dirty_ = false;
|
||||
@@ -336,7 +380,7 @@ bool CollectionWatcher::ScanTransaction::HasSongsWithMissingFingerprint(const QS
|
||||
if (cached_songs_missing_fingerprint_dirty_) {
|
||||
const SongList songs = watcher_->backend_->SongsWithMissingFingerprint(dir_);
|
||||
for (const Song &song : songs) {
|
||||
const QString p = song.url().toLocalFile().section(QLatin1Char('/'), 0, -2);
|
||||
const QString p = song.url().toLocalFile().section(u'/', 0, -2);
|
||||
cached_songs_missing_fingerprint_.insert(p, song);
|
||||
}
|
||||
cached_songs_missing_fingerprint_dirty_ = false;
|
||||
@@ -351,7 +395,7 @@ bool CollectionWatcher::ScanTransaction::HasSongsWithMissingLoudnessCharacterist
|
||||
if (cached_songs_missing_loudness_characteristics_dirty_) {
|
||||
const SongList songs = watcher_->backend_->SongsWithMissingLoudnessCharacteristics(dir_);
|
||||
for (const Song &song : songs) {
|
||||
const QString p = song.url().toLocalFile().section(QLatin1Char('/'), 0, -2);
|
||||
const QString p = song.url().toLocalFile().section(u'/', 0, -2);
|
||||
cached_songs_missing_loudness_characteristics_.insert(p, song);
|
||||
}
|
||||
cached_songs_missing_loudness_characteristics_dirty_ = false;
|
||||
@@ -407,7 +451,7 @@ CollectionSubdirectoryList CollectionWatcher::ScanTransaction::GetAllSubdirs() {
|
||||
|
||||
void CollectionWatcher::AddDirectory(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdirs) {
|
||||
|
||||
stop_requested_ = false;
|
||||
CancelStop();
|
||||
|
||||
watched_dirs_[dir.id] = dir;
|
||||
|
||||
@@ -434,16 +478,16 @@ void CollectionWatcher::AddDirectory(const CollectionDirectory &dir, const Colle
|
||||
transaction.SetKnownSubdirs(subdirs);
|
||||
transaction.AddToProgressMax(files_count);
|
||||
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
if (stop_or_abort_requested()) break;
|
||||
ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
|
||||
}
|
||||
if (!stop_requested_ && !abort_requested_) {
|
||||
if (!stop_or_abort_requested()) {
|
||||
last_scan_time_ = QDateTime::currentSecsSinceEpoch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit CompilationsNeedUpdating();
|
||||
Q_EMIT CompilationsNeedUpdating();
|
||||
|
||||
}
|
||||
|
||||
@@ -497,7 +541,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
||||
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
||||
while (it.hasNext()) {
|
||||
|
||||
if (stop_requested_ || abort_requested_) return;
|
||||
if (stop_or_abort_requested()) return;
|
||||
|
||||
QString child(it.next());
|
||||
QFileInfo child_info(child);
|
||||
@@ -516,7 +560,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
||||
else {
|
||||
QString ext_part(ExtensionPart(child));
|
||||
QString dir_part(DirectoryPart(child));
|
||||
if (kIgnoredExtensions.contains(child_info.suffix(), Qt::CaseInsensitive) || child_info.baseName() == QLatin1String("qt_temp")) {
|
||||
if (Song::kRejectedExtensions.contains(child_info.suffix(), Qt::CaseInsensitive) || child_info.baseName() == "qt_temp"_L1) {
|
||||
t->AddToProgress(1);
|
||||
}
|
||||
else if (sValidImages.contains(ext_part)) {
|
||||
@@ -532,7 +576,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
||||
}
|
||||
}
|
||||
|
||||
if (stop_requested_ || abort_requested_) return;
|
||||
if (stop_or_abort_requested()) return;
|
||||
|
||||
// Ask the database for a list of files in this directory
|
||||
SongList songs_in_db = t->FindSongsInSubdirectory(path);
|
||||
@@ -543,7 +587,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
||||
QStringList files_on_disk_copy = files_on_disk;
|
||||
for (const QString &file : files_on_disk_copy) {
|
||||
|
||||
if (stop_requested_ || abort_requested_) return;
|
||||
if (stop_or_abort_requested()) return;
|
||||
|
||||
// Associated CUE
|
||||
QString new_cue = CueParser::FindCueFilename(file);
|
||||
@@ -618,7 +662,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
||||
Chromaprinter chromaprinter(file);
|
||||
fingerprint = chromaprinter.CreateFingerprint();
|
||||
if (fingerprint.isEmpty()) {
|
||||
fingerprint = QLatin1String("NONE");
|
||||
fingerprint = "NONE"_L1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -645,11 +689,11 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
||||
Chromaprinter chromaprinter(file);
|
||||
fingerprint = chromaprinter.CreateFingerprint();
|
||||
if (fingerprint.isEmpty()) {
|
||||
fingerprint = QLatin1String("NONE");
|
||||
fingerprint = "NONE"_L1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (song_tracking_ && !fingerprint.isEmpty() && fingerprint != QLatin1String("NONE") && FindSongsByFingerprint(file, fingerprint, &matching_songs)) {
|
||||
if (song_tracking_ && !fingerprint.isEmpty() && fingerprint != "NONE"_L1 && FindSongsByFingerprint(file, fingerprint, &matching_songs)) {
|
||||
|
||||
// The song is in the database and still on disk.
|
||||
// Check the mtime to see if it's been changed since it was added.
|
||||
@@ -745,7 +789,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
||||
|
||||
// Recurse into the new subdirs that we found
|
||||
for (const CollectionSubdirectory &my_new_subdir : std::as_const(my_new_subdirs)) {
|
||||
if (stop_requested_ || abort_requested_) return;
|
||||
if (stop_or_abort_requested()) return;
|
||||
ScanSubdirectory(my_new_subdir.path, my_new_subdir, 0, t, true);
|
||||
}
|
||||
|
||||
@@ -942,7 +986,7 @@ void CollectionWatcher::AddChangedSong(const QString &file, const Song &matching
|
||||
qLog(Debug) << "Song" << file << "unchanged.";
|
||||
}
|
||||
else {
|
||||
qLog(Debug) << "Song" << file << changes.join(QLatin1String(", ")) << "changed.";
|
||||
qLog(Debug) << "Song" << file << changes.join(", "_L1) << "changed.";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1086,10 +1130,10 @@ void CollectionWatcher::RescanPathsNow() {
|
||||
|
||||
const QList<int> dirs = rescan_queue_.keys();
|
||||
for (const int dir : dirs) {
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
if (stop_or_abort_requested()) break;
|
||||
ScanTransaction transaction(this, dir, false, false, mark_songs_unavailable_);
|
||||
|
||||
const QStringList paths = rescan_queue_[dir];
|
||||
const QStringList paths = rescan_queue_.value(dir);
|
||||
|
||||
QMap<QString, quint64> subdir_files_count;
|
||||
for (const QString &path : paths) {
|
||||
@@ -1099,7 +1143,7 @@ void CollectionWatcher::RescanPathsNow() {
|
||||
}
|
||||
|
||||
for (const QString &path : paths) {
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
if (stop_or_abort_requested()) break;
|
||||
CollectionSubdirectory subdir;
|
||||
subdir.directory_id = dir;
|
||||
subdir.mtime = 0;
|
||||
@@ -1110,7 +1154,7 @@ void CollectionWatcher::RescanPathsNow() {
|
||||
|
||||
rescan_queue_.clear();
|
||||
|
||||
emit CompilationsNeedUpdating();
|
||||
Q_EMIT CompilationsNeedUpdating();
|
||||
|
||||
}
|
||||
|
||||
@@ -1144,7 +1188,7 @@ QString CollectionWatcher::PickBestArt(const QStringList &art_automatic_list) {
|
||||
QString biggest_path;
|
||||
|
||||
for (const QString &path : std::as_const(filtered)) {
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
if (stop_or_abort_requested()) break;
|
||||
|
||||
QImage image(path);
|
||||
if (image.isNull()) continue;
|
||||
@@ -1220,11 +1264,11 @@ void CollectionWatcher::FullScanNow() { PerformScan(false, true); }
|
||||
|
||||
void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mtimes) {
|
||||
|
||||
stop_requested_ = false;
|
||||
CancelStop();
|
||||
|
||||
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
||||
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
if (stop_or_abort_requested()) break;
|
||||
|
||||
ScanTransaction transaction(this, dir.id, incremental, ignore_mtimes, mark_songs_unavailable_);
|
||||
CollectionSubdirectoryList subdirs = transaction.GetAllSubdirs();
|
||||
@@ -1242,7 +1286,7 @@ void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mt
|
||||
transaction.AddToProgressMax(files_count);
|
||||
|
||||
for (const CollectionSubdirectory &subdir : std::as_const(subdirs)) {
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
if (stop_or_abort_requested()) break;
|
||||
ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
|
||||
}
|
||||
|
||||
@@ -1250,7 +1294,7 @@ void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mt
|
||||
|
||||
last_scan_time_ = QDateTime::currentSecsSinceEpoch();
|
||||
|
||||
emit CompilationsNeedUpdating();
|
||||
Q_EMIT CompilationsNeedUpdating();
|
||||
|
||||
}
|
||||
|
||||
@@ -1260,7 +1304,7 @@ quint64 CollectionWatcher::FilesCountForPath(ScanTransaction *t, const QString &
|
||||
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
||||
while (it.hasNext()) {
|
||||
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
if (stop_or_abort_requested()) break;
|
||||
|
||||
QString child = it.next();
|
||||
QFileInfo path_info(child);
|
||||
@@ -1294,7 +1338,7 @@ quint64 CollectionWatcher::FilesCountForSubdirs(ScanTransaction *t, const Collec
|
||||
|
||||
quint64 i = 0;
|
||||
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
if (stop_or_abort_requested()) break;
|
||||
const quint64 files_count = FilesCountForPath(t, subdir.path);
|
||||
subdir_files_count[subdir.path] = files_count;
|
||||
i += files_count;
|
||||
@@ -1312,17 +1356,17 @@ void CollectionWatcher::RescanSongsAsync(const SongList &songs) {
|
||||
|
||||
void CollectionWatcher::RescanSongs(const SongList &songs) {
|
||||
|
||||
stop_requested_ = false;
|
||||
CancelStop();
|
||||
|
||||
QStringList scanned_paths;
|
||||
for (const Song &song : songs) {
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
const QString song_path = song.url().toLocalFile().section(QLatin1Char('/'), 0, -2);
|
||||
if (stop_or_abort_requested()) break;
|
||||
const QString song_path = song.url().toLocalFile().section(u'/', 0, -2);
|
||||
if (scanned_paths.contains(song_path)) continue;
|
||||
ScanTransaction transaction(this, song.directory_id(), false, true, mark_songs_unavailable_);
|
||||
const CollectionSubdirectoryList subdirs = transaction.GetAllSubdirs();
|
||||
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
if (stop_or_abort_requested()) break;
|
||||
if (subdir.path != song_path) continue;
|
||||
qLog(Debug) << "Rescan for directory ID" << song.directory_id() << "directory" << subdir.path;
|
||||
quint64 files_count = FilesCountForPath(&transaction, subdir.path);
|
||||
@@ -1331,6 +1375,6 @@ void CollectionWatcher::RescanSongs(const SongList &songs) {
|
||||
}
|
||||
}
|
||||
|
||||
emit CompilationsNeedUpdating();
|
||||
Q_EMIT CompilationsNeedUpdating();
|
||||
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QMutex>
|
||||
|
||||
#include "collectiondirectory.h"
|
||||
#include "core/shared_ptr.h"
|
||||
@@ -46,6 +47,8 @@ class FileSystemWatcherInterface;
|
||||
class TaskManager;
|
||||
class CueParser;
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
class CollectionWatcher : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
@@ -64,14 +67,15 @@ class CollectionWatcher : public QObject {
|
||||
void SetRescanPausedAsync(const bool pause);
|
||||
void ReloadSettingsAsync();
|
||||
|
||||
void Stop() { stop_requested_ = true; }
|
||||
void Abort() { abort_requested_ = true; }
|
||||
void Stop();
|
||||
void CancelStop();
|
||||
void Abort();
|
||||
|
||||
void ExitAsync();
|
||||
|
||||
void RescanSongsAsync(const SongList &songs);
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void NewOrUpdatedSongs(const SongList &songs);
|
||||
void SongsMTimeUpdated(const SongList &songs);
|
||||
void SongsDeleted(const SongList &songs);
|
||||
@@ -85,7 +89,7 @@ class CollectionWatcher : public QObject {
|
||||
|
||||
void ScanStarted(const int task_id);
|
||||
|
||||
public slots:
|
||||
public Q_SLOTS:
|
||||
void AddDirectory(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdirs);
|
||||
void RemoveDirectory(const CollectionDirectory &dir);
|
||||
void SetRescanPaused(bool pause);
|
||||
@@ -166,7 +170,7 @@ class CollectionWatcher : public QObject {
|
||||
bool known_subdirs_dirty_;
|
||||
};
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void ReloadSettings();
|
||||
void Exit();
|
||||
void DirectoryChanged(const QString &subdir);
|
||||
@@ -178,6 +182,9 @@ class CollectionWatcher : public QObject {
|
||||
void RescanSongs(const SongList &songs);
|
||||
|
||||
private:
|
||||
bool stop_requested() const;
|
||||
bool abort_requested() const;
|
||||
bool stop_or_abort_requested() const;
|
||||
static bool FindSongsByPath(const SongList &songs, const QString &path, SongList *out);
|
||||
bool FindSongsByFingerprint(const QString &file, const QString &fingerprint, SongList *out);
|
||||
static bool FindSongsByFingerprint(const QString &file, const SongList &songs, const QString &fingerprint, SongList *out);
|
||||
@@ -231,7 +238,10 @@ class CollectionWatcher : public QObject {
|
||||
bool overwrite_playcount_;
|
||||
bool overwrite_rating_;
|
||||
|
||||
mutable QMutex mutex_stop_;
|
||||
bool stop_requested_;
|
||||
|
||||
mutable QMutex mutex_abort_;
|
||||
bool abort_requested_;
|
||||
|
||||
QMap<int, CollectionDirectory> watched_dirs_;
|
||||
@@ -245,21 +255,20 @@ class CollectionWatcher : public QObject {
|
||||
CueParser *cue_parser_;
|
||||
|
||||
static QStringList sValidImages;
|
||||
static const QStringList kIgnoredExtensions;
|
||||
|
||||
qint64 last_scan_time_;
|
||||
|
||||
};
|
||||
|
||||
inline QString CollectionWatcher::NoExtensionPart(const QString &fileName) {
|
||||
return fileName.contains(QLatin1Char('.')) ? fileName.section(QLatin1Char('.'), 0, -2) : QLatin1String("");
|
||||
return fileName.contains(u'.') ? fileName.section(u'.', 0, -2) : ""_L1;
|
||||
}
|
||||
// Thanks Amarok
|
||||
inline QString CollectionWatcher::ExtensionPart(const QString &fileName) {
|
||||
return fileName.contains(QLatin1Char('.')) ? fileName.mid(fileName.lastIndexOf(QLatin1Char('.')) + 1).toLower() : QLatin1String("");
|
||||
return fileName.contains(u'.') ? fileName.mid(fileName.lastIndexOf(u'.') + 1).toLower() : ""_L1;
|
||||
}
|
||||
inline QString CollectionWatcher::DirectoryPart(const QString &fileName) {
|
||||
return fileName.section(QLatin1Char('/'), 0, -2);
|
||||
return fileName.section(u'/', 0, -2);
|
||||
}
|
||||
|
||||
#endif // COLLECTIONWATCHER_H
|
||||
|
||||
@@ -116,7 +116,7 @@ void GroupByDialog::Reset() {
|
||||
|
||||
void GroupByDialog::accept() {
|
||||
|
||||
emit Accepted(CollectionModel::Grouping(
|
||||
Q_EMIT Accepted(CollectionModel::Grouping(
|
||||
p_->mapping_.get<tag_index>().find(ui_->combobox_first->currentIndex())->group_by,
|
||||
p_->mapping_.get<tag_index>().find(ui_->combobox_second->currentIndex())->group_by,
|
||||
p_->mapping_.get<tag_index>().find(ui_->combobox_third->currentIndex())->group_by),
|
||||
|
||||
@@ -43,14 +43,14 @@ class GroupByDialog : public QDialog {
|
||||
explicit GroupByDialog(QWidget *parent = nullptr);
|
||||
~GroupByDialog() override;
|
||||
|
||||
public slots:
|
||||
public Q_SLOTS:
|
||||
void CollectionGroupingChanged(const CollectionModel::Grouping g, const bool separate_albums_by_grouping);
|
||||
void accept() override;
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void Accepted(const CollectionModel::Grouping g, const bool separate_albums_by_grouping);
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
|
||||
@@ -44,6 +44,8 @@
|
||||
#include "savedgroupingmanager.h"
|
||||
#include "ui_savedgroupingmanager.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
const char *SavedGroupingManager::kSavedGroupingsSettingsGroup = "SavedGroupings";
|
||||
|
||||
SavedGroupingManager::SavedGroupingManager(const QString &saved_groupings_settings_group, QWidget *parent)
|
||||
@@ -165,7 +167,7 @@ void SavedGroupingManager::UpdateModel() {
|
||||
if (version == 1) {
|
||||
QStringList saved = s.childKeys();
|
||||
for (int i = 0; i < saved.size(); ++i) {
|
||||
if (saved.at(i) == QLatin1String("version")) continue;
|
||||
if (saved.at(i) == "version"_L1) continue;
|
||||
QByteArray bytes = s.value(saved.at(i)).toByteArray();
|
||||
QDataStream ds(&bytes, QIODevice::ReadOnly);
|
||||
CollectionModel::Grouping g;
|
||||
@@ -183,7 +185,7 @@ void SavedGroupingManager::UpdateModel() {
|
||||
else {
|
||||
QStringList saved = s.childKeys();
|
||||
for (int i = 0; i < saved.size(); ++i) {
|
||||
if (saved.at(i) == QLatin1String("version")) continue;
|
||||
if (saved.at(i) == "version"_L1) continue;
|
||||
s.remove(saved.at(i));
|
||||
}
|
||||
}
|
||||
@@ -207,7 +209,7 @@ void SavedGroupingManager::Remove() {
|
||||
}
|
||||
UpdateModel();
|
||||
|
||||
emit UpdateGroupByActions();
|
||||
Q_EMIT UpdateGroupByActions();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -51,10 +51,10 @@ class SavedGroupingManager : public QDialog {
|
||||
|
||||
static QString GroupByToString(const CollectionModel::GroupBy g);
|
||||
|
||||
signals:
|
||||
void UpdateGroupByActions();
|
||||
Q_SIGNALS:
|
||||
void UpdateGroupByActions();
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void UpdateButtonState();
|
||||
void Remove();
|
||||
|
||||
|
||||
@@ -48,13 +48,11 @@
|
||||
#cmakedefine INSTALL_TRANSLATIONS
|
||||
#define TRANSLATIONS_DIR "${CMAKE_INSTALL_PREFIX}/share/strawberry/translations"
|
||||
|
||||
#cmakedefine HAVE_QX11APPLICATION
|
||||
#cmakedefine HAVE_QPA_QPLATFORMNATIVEINTERFACE_H
|
||||
#cmakedefine HAVE_X11EXTRAS
|
||||
|
||||
#cmakedefine ENABLE_WIN32_CONSOLE
|
||||
|
||||
#cmakedefine HAVE_QX11APPLICATION
|
||||
|
||||
#cmakedefine HAVE_EBUR128
|
||||
|
||||
#endif // CONFIG_H_IN
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
|
||||
#include <QtGlobal>
|
||||
@@ -202,7 +203,7 @@ void ContextAlbum::DrawSpinner(QPainter *p) {
|
||||
|
||||
void ContextAlbum::DrawPreviousCovers(QPainter *p) {
|
||||
|
||||
for (SharedPtr<PreviousCover> previous_cover : previous_covers_) {
|
||||
for (SharedPtr<PreviousCover> previous_cover : std::as_const(previous_covers_)) {
|
||||
DrawImage(p, previous_cover->pixmap, previous_cover->opacity);
|
||||
}
|
||||
|
||||
@@ -220,7 +221,7 @@ void ContextAlbum::FadeCurrentCover(const qreal value) {
|
||||
void ContextAlbum::FadeCurrentCoverFinished() {
|
||||
|
||||
if (image_original_ == image_strawberry_) {
|
||||
emit FadeStopFinished();
|
||||
Q_EMIT FadeStopFinished();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -254,7 +255,7 @@ void ContextAlbum::ScaleCover() {
|
||||
|
||||
void ContextAlbum::ScalePreviousCovers() {
|
||||
|
||||
for (SharedPtr<PreviousCover> previous_cover : previous_covers_) {
|
||||
for (SharedPtr<PreviousCover> previous_cover : std::as_const(previous_covers_)) {
|
||||
QImage image = ImageUtils::ScaleImage(previous_cover->image, QSize(desired_height_, desired_height_), devicePixelRatioF(), true);
|
||||
if (image.isNull()) {
|
||||
previous_cover->pixmap = QPixmap();
|
||||
|
||||
@@ -78,18 +78,18 @@ class ContextAlbum : public QWidget {
|
||||
void ScaleCover();
|
||||
void ScalePreviousCovers();
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void FadeStopFinished();
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void Update() { update(); }
|
||||
void AutomaticCoverSearchDone();
|
||||
void FadeCurrentCover(const qreal value);
|
||||
void FadeCurrentCoverFinished();
|
||||
void FadePreviousCover(SharedPtr<PreviousCover> previous_cover);
|
||||
void FadePreviousCoverFinished(SharedPtr<PreviousCover> previous_cover);
|
||||
void FadePreviousCover(SharedPtr<ContextAlbum::PreviousCover> previous_cover);
|
||||
void FadePreviousCoverFinished(SharedPtr<ContextAlbum::PreviousCover> previous_cover);
|
||||
|
||||
public slots:
|
||||
public Q_SLOTS:
|
||||
void SearchCoverInProgress();
|
||||
|
||||
private:
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
@@ -65,6 +67,8 @@
|
||||
#include "contextview.h"
|
||||
#include "contextalbum.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
namespace {
|
||||
constexpr int kWidgetSpacing = 50;
|
||||
}
|
||||
@@ -293,11 +297,7 @@ void ContextView::AddActions() {
|
||||
void ContextView::ReloadSettings() {
|
||||
|
||||
QString default_font;
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
if (QFontDatabase::families().contains(QLatin1String(ContextSettingsPage::kDefaultFontFamily))) {
|
||||
#else
|
||||
if (QFontDatabase().families().contains(QLatin1String(ContextSettingsPage::kDefaultFontFamily))) {
|
||||
#endif
|
||||
default_font = QLatin1String(ContextSettingsPage::kDefaultFontFamily);
|
||||
}
|
||||
else {
|
||||
@@ -412,15 +412,15 @@ void ContextView::NoSong() {
|
||||
QString html;
|
||||
if (collectionview_->TotalSongs() == 1) html += tr("%1 song").arg(collectionview_->TotalSongs());
|
||||
else html += tr("%1 songs").arg(collectionview_->TotalSongs());
|
||||
html += QLatin1String("<br />");
|
||||
html += "<br />"_L1;
|
||||
|
||||
if (collectionview_->TotalArtists() == 1) html += tr("%1 artist").arg(collectionview_->TotalArtists());
|
||||
else html += tr("%1 artists").arg(collectionview_->TotalArtists());
|
||||
html += QLatin1String("<br />");
|
||||
html += "<br />"_L1;
|
||||
|
||||
if (collectionview_->TotalAlbums() == 1) html += tr("%1 album").arg(collectionview_->TotalAlbums());
|
||||
else html += tr("%1 albums").arg(collectionview_->TotalAlbums());
|
||||
html += QLatin1String("<br />");
|
||||
html += "<br />"_L1;
|
||||
|
||||
label_stop_summary_->setFont(font_normal_);
|
||||
label_stop_summary_->setText(html);
|
||||
@@ -429,10 +429,10 @@ void ContextView::NoSong() {
|
||||
|
||||
void ContextView::UpdateFonts() {
|
||||
|
||||
for (QLabel *l : labels_play_all_) {
|
||||
for (QLabel *l : std::as_const(labels_play_all_)) {
|
||||
l->setFont(font_normal_);
|
||||
}
|
||||
for (QTextEdit *e : textedit_play_) {
|
||||
for (QTextEdit *e : std::as_const(textedit_play_)) {
|
||||
e->setFont(font_normal_);
|
||||
}
|
||||
|
||||
@@ -454,7 +454,7 @@ void ContextView::SetSong() {
|
||||
widget_album_->hide();
|
||||
widget_album_changed = true;
|
||||
}
|
||||
if (widget_album_changed) emit AlbumEnabledChanged();
|
||||
if (widget_album_changed) Q_EMIT AlbumEnabledChanged();
|
||||
|
||||
if (action_show_data_->isChecked()) {
|
||||
widget_play_data_->show();
|
||||
@@ -620,11 +620,11 @@ void ContextView::UpdateSong(const Song &song) {
|
||||
|
||||
void ContextView::ResetSong() {
|
||||
|
||||
for (QLabel *l : labels_play_data_) {
|
||||
for (QLabel *l : std::as_const(labels_play_data_)) {
|
||||
l->clear();
|
||||
}
|
||||
|
||||
for (QTextEdit *l : textedit_play_) {
|
||||
for (QTextEdit *l : std::as_const(textedit_play_)) {
|
||||
l->clear();
|
||||
}
|
||||
|
||||
@@ -638,10 +638,10 @@ void ContextView::UpdateLyrics(const quint64 id, const QString &provider, const
|
||||
if (static_cast<qint64>(id) != lyrics_id_) return;
|
||||
|
||||
if (lyrics.isEmpty()) {
|
||||
lyrics_ = QLatin1String("No lyrics found.\n");
|
||||
lyrics_ = "No lyrics found.\n"_L1;
|
||||
}
|
||||
else {
|
||||
lyrics_ = lyrics + QLatin1String("\n\n(Lyrics from ") + provider + QLatin1String(")\n");
|
||||
lyrics_ = lyrics + "\n\n(Lyrics from "_L1 + provider + ")\n"_L1;
|
||||
}
|
||||
lyrics_id_ = -1;
|
||||
|
||||
|
||||
@@ -80,10 +80,10 @@ class ContextView : public QWidget {
|
||||
void SearchLyrics();
|
||||
void UpdateFonts();
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void AlbumEnabledChanged();
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void ActionShowAlbum();
|
||||
void ActionShowData();
|
||||
void ActionShowLyrics();
|
||||
@@ -92,7 +92,7 @@ class ContextView : public QWidget {
|
||||
void FadeStopFinished();
|
||||
void UpdateLyrics(const quint64 id, const QString &provider, const QString &lyrics);
|
||||
|
||||
public slots:
|
||||
public Q_SLOTS:
|
||||
void ReloadSettings();
|
||||
void Playing();
|
||||
void Stopped();
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include "application.h"
|
||||
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include <chrono>
|
||||
|
||||
@@ -68,6 +69,7 @@
|
||||
#include "lyrics/azlyricscomlyricsprovider.h"
|
||||
#include "lyrics/elyricsnetlyricsprovider.h"
|
||||
#include "lyrics/letraslyricsprovider.h"
|
||||
#include "lyrics/lyricfindlyricsprovider.h"
|
||||
|
||||
#include "scrobbler/audioscrobbler.h"
|
||||
#include "scrobbler/lastfmscrobbler.h"
|
||||
@@ -169,15 +171,16 @@ class ApplicationImpl {
|
||||
lyrics_providers_([app]() {
|
||||
LyricsProviders *lyrics_providers = new LyricsProviders(app);
|
||||
// Initialize the repository of lyrics providers.
|
||||
lyrics_providers->AddProvider(new GeniusLyricsProvider(app->network()));
|
||||
lyrics_providers->AddProvider(new OVHLyricsProvider(app->network()));
|
||||
lyrics_providers->AddProvider(new LoloLyricsProvider(app->network()));
|
||||
lyrics_providers->AddProvider(new MusixmatchLyricsProvider(app->network()));
|
||||
lyrics_providers->AddProvider(new ChartLyricsProvider(app->network()));
|
||||
lyrics_providers->AddProvider(new SongLyricsComLyricsProvider(app->network()));
|
||||
lyrics_providers->AddProvider(new AzLyricsComLyricsProvider(app->network()));
|
||||
lyrics_providers->AddProvider(new ElyricsNetLyricsProvider(app->network()));
|
||||
lyrics_providers->AddProvider(new LetrasLyricsProvider(app->network()));
|
||||
lyrics_providers->AddProvider(new GeniusLyricsProvider(lyrics_providers->network()));
|
||||
lyrics_providers->AddProvider(new OVHLyricsProvider(lyrics_providers->network()));
|
||||
lyrics_providers->AddProvider(new LoloLyricsProvider(lyrics_providers->network()));
|
||||
lyrics_providers->AddProvider(new MusixmatchLyricsProvider(lyrics_providers->network()));
|
||||
lyrics_providers->AddProvider(new ChartLyricsProvider(lyrics_providers->network()));
|
||||
lyrics_providers->AddProvider(new SongLyricsComLyricsProvider(lyrics_providers->network()));
|
||||
lyrics_providers->AddProvider(new AzLyricsComLyricsProvider(lyrics_providers->network()));
|
||||
lyrics_providers->AddProvider(new ElyricsNetLyricsProvider(lyrics_providers->network()));
|
||||
lyrics_providers->AddProvider(new LetrasLyricsProvider(lyrics_providers->network()));
|
||||
lyrics_providers->AddProvider(new LyricFindLyricsProvider(lyrics_providers->network()));
|
||||
lyrics_providers->ReloadSettings();
|
||||
return lyrics_providers;
|
||||
}),
|
||||
@@ -245,6 +248,8 @@ class ApplicationImpl {
|
||||
Application::Application(QObject *parent)
|
||||
: QObject(parent), p_(new ApplicationImpl(this)) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
|
||||
device_finders()->Init();
|
||||
collection()->Init();
|
||||
tag_reader_client();
|
||||
@@ -257,11 +262,11 @@ Application::~Application() {
|
||||
|
||||
qLog(Debug) << "Terminating application";
|
||||
|
||||
for (QThread *thread : threads_) {
|
||||
for (QThread *thread : std::as_const(threads_)) {
|
||||
thread->quit();
|
||||
}
|
||||
|
||||
for (QThread *thread : threads_) {
|
||||
for (QThread *thread : std::as_const(threads_)) {
|
||||
thread->wait();
|
||||
thread->deleteLater();
|
||||
}
|
||||
@@ -272,6 +277,8 @@ QThread *Application::MoveToNewThread(QObject *object) {
|
||||
|
||||
QThread *thread = new QThread(this);
|
||||
|
||||
thread->setObjectName(object->objectName());
|
||||
|
||||
MoveToThread(object, thread);
|
||||
|
||||
thread->start();
|
||||
@@ -340,9 +347,9 @@ void Application::ExitReceived() {
|
||||
|
||||
}
|
||||
|
||||
void Application::AddError(const QString &message) { emit ErrorAdded(message); }
|
||||
void Application::ReloadSettings() { emit SettingsChanged(); }
|
||||
void Application::OpenSettingsDialogAtPage(SettingsDialog::Page page) { emit SettingsDialogRequested(page); }
|
||||
void Application::AddError(const QString &message) { Q_EMIT ErrorAdded(message); }
|
||||
void Application::ReloadSettings() { Q_EMIT SettingsChanged(); }
|
||||
void Application::OpenSettingsDialogAtPage(SettingsDialog::Page page) { Q_EMIT SettingsDialogRequested(page); }
|
||||
|
||||
SharedPtr<TagReaderClient> Application::tag_reader_client() const { return p_->tag_reader_client_.ptr(); }
|
||||
SharedPtr<Database> Application::database() const { return p_->database_.ptr(); }
|
||||
|
||||
@@ -112,15 +112,15 @@ class Application : public QObject {
|
||||
QThread *MoveToNewThread(QObject *object);
|
||||
static void MoveToThread(QObject *object, QThread *thread);
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void ExitReceived();
|
||||
|
||||
public slots:
|
||||
public Q_SLOTS:
|
||||
void AddError(const QString &message);
|
||||
void ReloadSettings();
|
||||
void OpenSettingsDialogAtPage(SettingsDialog::Page page);
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void ErrorAdded(const QString &message);
|
||||
void SettingsChanged();
|
||||
void SettingsDialogRequested(const SettingsDialog::Page page);
|
||||
|
||||
@@ -206,7 +206,7 @@ bool CommandlineOptions::Parse() {
|
||||
|
||||
// Parse the arguments
|
||||
bool ok = false;
|
||||
forever {
|
||||
Q_FOREVER {
|
||||
#ifdef Q_OS_WIN32
|
||||
int c = getopt_long(argc_, argv_, L"hptusqrfv:c:alk:i:oyg:w:", kOptions, nullptr);
|
||||
#else
|
||||
|
||||
@@ -49,6 +49,8 @@
|
||||
#include "sqlquery.h"
|
||||
#include "scopedtransaction.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
const int Database::kSchemaVersion = 20;
|
||||
|
||||
namespace {
|
||||
@@ -63,14 +65,13 @@ QMutex Database::sNextConnectionIdMutex;
|
||||
Database::Database(Application *app, QObject *parent, const QString &database_name) :
|
||||
QObject(parent),
|
||||
app_(app),
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
||||
mutex_(QMutex::Recursive),
|
||||
#endif
|
||||
injected_database_name_(database_name),
|
||||
query_hash_(0),
|
||||
startup_schema_version_(-1),
|
||||
original_thread_(nullptr) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
|
||||
original_thread_ = thread();
|
||||
|
||||
{
|
||||
@@ -105,7 +106,7 @@ void Database::Exit() {
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
Close();
|
||||
moveToThread(original_thread_);
|
||||
emit ExitFinished();
|
||||
Q_EMIT ExitFinished();
|
||||
|
||||
}
|
||||
|
||||
@@ -137,7 +138,7 @@ QSqlDatabase Database::Connect() {
|
||||
//qLog(Debug) << "Opened database with connection id" << connection_id;
|
||||
|
||||
if (injected_database_name_.isNull()) {
|
||||
db.setDatabaseName(directory_ + QLatin1Char('/') + QLatin1String(kDatabaseFilename));
|
||||
db.setDatabaseName(directory_ + u'/' + QLatin1String(kDatabaseFilename));
|
||||
}
|
||||
else {
|
||||
db.setDatabaseName(injected_database_name_);
|
||||
@@ -161,7 +162,7 @@ QSqlDatabase Database::Connect() {
|
||||
// Attach external databases
|
||||
QStringList keys = attached_databases_.keys();
|
||||
for (const QString &key : std::as_const(keys)) {
|
||||
QString filename = attached_databases_[key].filename_;
|
||||
QString filename = attached_databases_.value(key).filename_;
|
||||
|
||||
if (!injected_database_name_.isNull()) filename = injected_database_name_;
|
||||
|
||||
@@ -182,7 +183,7 @@ QSqlDatabase Database::Connect() {
|
||||
// We might have to initialize the schema in some attached databases now, if they were deleted and don't match up with the main schema version.
|
||||
keys = attached_databases_.keys();
|
||||
for (const QString &key : std::as_const(keys)) {
|
||||
if (attached_databases_[key].is_temporary_ && attached_databases_[key].schema_.isEmpty()) {
|
||||
if (attached_databases_.value(key).is_temporary_ && attached_databases_.value(key).schema_.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
// Find out if there are any tables in this database
|
||||
@@ -258,7 +259,7 @@ void Database::RecreateAttachedDb(const QString &database_name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString filename = attached_databases_[database_name].filename_;
|
||||
const QString filename = attached_databases_.value(database_name).filename_;
|
||||
|
||||
QMutexLocker l(&mutex_);
|
||||
{
|
||||
@@ -353,7 +354,7 @@ void Database::UrlEncodeFilenameColumn(const QString &table, QSqlDatabase &db) {
|
||||
const int rowid = select.value(0).toInt();
|
||||
const QString filename = select.value(1).toString();
|
||||
|
||||
if (filename.isEmpty() || filename.contains(QLatin1String("://"))) {
|
||||
if (filename.isEmpty() || filename.contains("://"_L1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -377,8 +378,8 @@ void Database::ExecSchemaCommandsFromFile(QSqlDatabase &db, const QString &filen
|
||||
}
|
||||
QByteArray data = schema_file.readAll();
|
||||
QString schema = QString::fromUtf8(data);
|
||||
if (schema.contains(QLatin1String("\r\n"))) {
|
||||
schema = schema.replace(QLatin1String("\r\n"), QLatin1String("\n"));
|
||||
if (schema.contains("\r\n"_L1)) {
|
||||
schema = schema.replace("\r\n"_L1, "\n"_L1);
|
||||
}
|
||||
schema_file.close();
|
||||
ExecSchemaCommands(db, schema, schema_version, in_transaction);
|
||||
@@ -388,7 +389,8 @@ void Database::ExecSchemaCommandsFromFile(QSqlDatabase &db, const QString &filen
|
||||
void Database::ExecSchemaCommands(QSqlDatabase &db, const QString &schema, int schema_version, bool in_transaction) {
|
||||
|
||||
// Run each command
|
||||
QStringList commands = schema.split(QRegularExpression(QStringLiteral("; *\n\n")));
|
||||
static const QRegularExpression regex_split_commands(QStringLiteral("; *\n\n"));
|
||||
QStringList commands = schema.split(regex_split_commands);
|
||||
|
||||
// We don't want this list to reflect possible DB schema changes, so we initialize it before executing any statements.
|
||||
// If no outer transaction is provided the song tables need to be queried before beginning an inner transaction!
|
||||
@@ -414,7 +416,7 @@ void Database::ExecSongTablesCommands(QSqlDatabase &db, const QStringList &song_
|
||||
if (command.contains(QLatin1String(kMagicAllSongsTables))) {
|
||||
for (const QString &table : song_tables) {
|
||||
// Another horrible hack: device songs tables don't have matching _fts tables, so if this command tries to touch one, ignore it.
|
||||
if (table.startsWith(QLatin1String("device_")) && command.contains(QLatin1String(kMagicAllSongsTables) + QLatin1String("_fts"))) {
|
||||
if (table.startsWith("device_"_L1) && command.contains(QLatin1String(kMagicAllSongsTables) + "_fts"_L1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -450,7 +452,7 @@ QStringList Database::SongsTables(QSqlDatabase &db, const int schema_version) {
|
||||
// look for the tables in the main db
|
||||
const QStringList &tables = db.tables();
|
||||
for (const QString &table : tables) {
|
||||
if (table == QLatin1String("songs") || table.endsWith(QLatin1String("_songs"))) ret << table;
|
||||
if (table == "songs"_L1 || table.endsWith("_songs"_L1)) ret << table;
|
||||
}
|
||||
|
||||
// look for the tables in attached dbs
|
||||
@@ -481,8 +483,8 @@ void Database::ReportErrors(const SqlQuery &query) {
|
||||
if (sql_error.isValid()) {
|
||||
qLog(Error) << "Unable to execute SQL query:" << sql_error;
|
||||
qLog(Error) << "Failed SQL query:" << query.LastQuery();
|
||||
emit Error(tr("Unable to execute SQL query: %1").arg(sql_error.text()));
|
||||
emit Error(tr("Failed SQL query: %1").arg(query.LastQuery()));
|
||||
Q_EMIT Error(tr("Unable to execute SQL query: %1").arg(sql_error.text()));
|
||||
Q_EMIT Error(tr("Failed SQL query: %1").arg(query.LastQuery()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -502,7 +504,7 @@ bool Database::IntegrityCheck(const QSqlDatabase &db) {
|
||||
QString message = q.value(0).toString();
|
||||
|
||||
// If no errors are found, a single row with the value "ok" is returned
|
||||
if (message == QLatin1String("ok")) {
|
||||
if (message == "ok"_L1) {
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -34,9 +34,7 @@
|
||||
#include <QSqlQuery>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
# include <QRecursiveMutex>
|
||||
#endif
|
||||
#include <QRecursiveMutex>
|
||||
|
||||
#include "sqlquery.h"
|
||||
|
||||
@@ -67,11 +65,7 @@ class Database : public QObject {
|
||||
void Close();
|
||||
void ReportErrors(const SqlQuery &query);
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
QRecursiveMutex *Mutex() { return &mutex_; }
|
||||
#else
|
||||
QMutex *Mutex() { return &mutex_; }
|
||||
#endif
|
||||
|
||||
void RecreateAttachedDb(const QString &database_name);
|
||||
void ExecSchemaCommands(QSqlDatabase &db, const QString &schema, int schema_version, bool in_transaction = false);
|
||||
@@ -83,15 +77,15 @@ class Database : public QObject {
|
||||
void AttachDatabaseOnDbConnection(const QString &database_name, const AttachedDatabase &database, QSqlDatabase &db);
|
||||
void DetachDatabase(const QString &database_name);
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void ExitFinished();
|
||||
void Error(const QString &error);
|
||||
void Errors(const QStringList &errors);
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void Exit();
|
||||
|
||||
public slots:
|
||||
public Q_SLOTS:
|
||||
void DoBackup();
|
||||
|
||||
private:
|
||||
@@ -115,11 +109,7 @@ class Database : public QObject {
|
||||
|
||||
QString directory_;
|
||||
QMutex connect_mutex_;
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
QRecursiveMutex mutex_;
|
||||
#else
|
||||
QMutex mutex_;
|
||||
#endif
|
||||
|
||||
// This ID makes the QSqlDatabase name unique to the object as well as the thread
|
||||
int connection_id_;
|
||||
|
||||
@@ -99,7 +99,7 @@ void DeleteFiles::ProcessSomeFiles() {
|
||||
|
||||
task_manager_->SetTaskFinished(task_id_);
|
||||
|
||||
emit Finished(songs_with_errors_);
|
||||
Q_EMIT Finished(songs_with_errors_);
|
||||
|
||||
// Move back to the original thread so deleteLater() can get called in the main thread's event loop
|
||||
moveToThread(original_thread_);
|
||||
@@ -116,7 +116,7 @@ void DeleteFiles::ProcessSomeFiles() {
|
||||
for (; progress_ < n; ++progress_) {
|
||||
task_manager_->SetTaskProgress(task_id_, progress_, songs_.count());
|
||||
|
||||
const Song &song = songs_[progress_];
|
||||
const Song song = songs_.value(progress_);
|
||||
|
||||
MusicStorage::DeleteJob job;
|
||||
job.metadata_ = song;
|
||||
|
||||
@@ -44,10 +44,10 @@ class DeleteFiles : public QObject {
|
||||
void Start(const SongList &songs);
|
||||
void Start(const QStringList &filenames);
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void Finished(const SongList &songs_with_errors);
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void ProcessSomeFiles();
|
||||
|
||||
private:
|
||||
|
||||
@@ -114,11 +114,7 @@ bool FilesystemMusicStorage::DeleteFromStorage(const DeleteJob &job) {
|
||||
QFileInfo fileInfo(path);
|
||||
|
||||
if (job.use_trash_) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
return QFile::moveToTrash(path);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (fileInfo.isDir()) {
|
||||
|
||||
@@ -39,7 +39,7 @@ class FileSystemWatcherInterface : public QObject {
|
||||
|
||||
static FileSystemWatcherInterface *Create(QObject *parent = nullptr);
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void PathChanged(const QString &path);
|
||||
};
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
|
||||
namespace IconMapper {
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
struct IconProperties {
|
||||
explicit IconProperties() : min_size(0), max_size(0), allow_system_icon(true) {}
|
||||
IconProperties(const QStringList &_names, const int _min_size = 16, const int _max_size = 512, const bool _allow_system_icon = true) : names(_names), min_size(_min_size), max_size(_max_size), allow_system_icon(_allow_system_icon) {}
|
||||
@@ -38,104 +40,103 @@ struct IconProperties {
|
||||
|
||||
static const QMap<QString, IconProperties> iconmapper_ = { // clazy:exclude=non-pod-global-static
|
||||
|
||||
{ QStringLiteral("albums"), { {QStringLiteral("media-optical")}} },
|
||||
{ QStringLiteral("alsa"), { {}} },
|
||||
{ QStringLiteral("application-exit"), { {}} },
|
||||
{ QStringLiteral("applications-internet"), { {}} },
|
||||
{ QStringLiteral("bluetooth"), { {QStringLiteral("preferences-system-bluetooth"), QStringLiteral("bluetooth-active")}} },
|
||||
{ QStringLiteral("cdcase"), { {QStringLiteral("cdcover"), QStringLiteral("media-optical")}} },
|
||||
{ QStringLiteral("media-optical"), { {QStringLiteral("cd")}} },
|
||||
{ QStringLiteral("configure"), { {}} },
|
||||
{ QStringLiteral("device-ipod-nano"), { {}} },
|
||||
{ QStringLiteral("device-ipod"), { {}} },
|
||||
{ QStringLiteral("device-phone"), { {}} },
|
||||
{ QStringLiteral("device"), { {QStringLiteral("drive-removable-media-usb-pendrive")}} },
|
||||
{ QStringLiteral("device-usb-drive"), { {}} },
|
||||
{ QStringLiteral("device-usb-flash"), { {}} },
|
||||
{ QStringLiteral("dialog-error"), { {}} },
|
||||
{ QStringLiteral("dialog-information"), { {}} },
|
||||
{ QStringLiteral("dialog-ok-apply"), { {}} },
|
||||
{ QStringLiteral("dialog-password"), { {}} },
|
||||
{ QStringLiteral("dialog-warning"), { {}} },
|
||||
{ QStringLiteral("document-download"), { {QStringLiteral("download")}} },
|
||||
{ QStringLiteral("document-new"), { {}} },
|
||||
{ QStringLiteral("document-open-folder"), { {}} },
|
||||
{ QStringLiteral("document-open"), { {}} },
|
||||
{ QStringLiteral("document-save"), { {}} },
|
||||
{ QStringLiteral("document-search"), { {}} },
|
||||
{ QStringLiteral("document-open-remote"), { {}} },
|
||||
{ QStringLiteral("download"), { {QStringLiteral("applications-internet"), QStringLiteral("network-workgroup")}} },
|
||||
{ QStringLiteral("edit-clear-list"), { {QStringLiteral("edit-clear-list"), QStringLiteral("edit-clear-all")}} },
|
||||
{ QStringLiteral("edit-clear-locationbar-ltr"), { {QStringLiteral("edit-clear-locationbar-ltr")}} },
|
||||
{ QStringLiteral("edit-copy"), { {}} },
|
||||
{ QStringLiteral("edit-delete"), { {}} },
|
||||
{ QStringLiteral("edit-find"), { {}} },
|
||||
{ QStringLiteral("edit-redo"), { {}} },
|
||||
{ QStringLiteral("edit-rename"), { {}} },
|
||||
{ QStringLiteral("edit-undo"), { {}} },
|
||||
{ QStringLiteral("electrocompaniet"), { {}} },
|
||||
{ QStringLiteral("equalizer"), { {QStringLiteral("view-media-equalizer")}} },
|
||||
{ QStringLiteral("folder-new"), { {}} },
|
||||
{ QStringLiteral("folder"), { {}} },
|
||||
{ QStringLiteral("folder-sound"), { {QStringLiteral("folder-music")}} },
|
||||
{ QStringLiteral("footsteps"), { {QStringLiteral("go-jump")}} },
|
||||
{ QStringLiteral("go-down"), { {}} },
|
||||
{ QStringLiteral("go-home"), { {}} },
|
||||
{ QStringLiteral("go-jump"), { {}} },
|
||||
{ QStringLiteral("go-next"), { {}} },
|
||||
{ QStringLiteral("go-previous"), { {}} },
|
||||
{ QStringLiteral("go-up"), { {}} },
|
||||
{ QStringLiteral("gstreamer"), { {QStringLiteral("phonon-gstreamer")}} },
|
||||
{ QStringLiteral("headset"), { {QStringLiteral("audio-headset")}} },
|
||||
{ QStringLiteral("help-hint"), { {}} },
|
||||
{ QStringLiteral("intel"), { {}} },
|
||||
{ QStringLiteral("jack"), { {QStringLiteral("audio-input-line")}} },
|
||||
{ QStringLiteral("keyboard"), { {QStringLiteral("input-keyboard")}} },
|
||||
{ QStringLiteral("list-add"), { {}} },
|
||||
{ QStringLiteral("list-remove"), { {}} },
|
||||
{ QStringLiteral("love"), { {QStringLiteral("heart"), QStringLiteral("emblem-favorite")}} },
|
||||
{ QStringLiteral("mcintosh-player"), { {}} },
|
||||
{ QStringLiteral("mcintosh"), { {}} },
|
||||
{ QStringLiteral("mcintosh-text"), { {}} },
|
||||
{ QStringLiteral("media-eject"), { {}} },
|
||||
{ QStringLiteral("media-playback-pause"), { {QStringLiteral("media-pause")}} },
|
||||
{ QStringLiteral("media-playlist-repeat"), { {}} },
|
||||
{ QStringLiteral("media-playlist-shuffle"), { {QLatin1String("")}} },
|
||||
{ QStringLiteral("media-playback-start"), { {QStringLiteral("media-play"), QStringLiteral("media-playback-playing")}} },
|
||||
{ QStringLiteral("media-seek-backward"), { {}} },
|
||||
{ QStringLiteral("media-seek-forward"), { {}} },
|
||||
{ QStringLiteral("media-skip-backward"), { {}} },
|
||||
{ QStringLiteral("media-skip-forward"), { {}} },
|
||||
{ QStringLiteral("media-playback-stop"), { {QStringLiteral("media-stop")}} },
|
||||
{ QStringLiteral("moodbar"), { {QStringLiteral("preferences-desktop-icons")}} },
|
||||
{ QStringLiteral("nvidia"), { {}} },
|
||||
{ QStringLiteral("pulseaudio"), { {}} },
|
||||
{ QStringLiteral("realtek"), { {}} },
|
||||
{ QStringLiteral("scrobble-disabled"), { {}} },
|
||||
{ QStringLiteral("scrobble"), { {QStringLiteral("love")}} },
|
||||
{ QStringLiteral("search"), { {}} },
|
||||
{ QStringLiteral("soundcard"), { {QStringLiteral("audiocard"), QStringLiteral("audio-card")}} },
|
||||
{ QStringLiteral("speaker"), { {}} },
|
||||
{ QStringLiteral("star-grey"), { {}} },
|
||||
{ QStringLiteral("star"), { {}} },
|
||||
{ QStringLiteral("strawberry"), { {}} },
|
||||
{ QStringLiteral("subsonic"), { {}} },
|
||||
{ QStringLiteral("tidal"), { {}} },
|
||||
{ QStringLiteral("tools-wizard"), { {}} },
|
||||
{ QStringLiteral("view-choose"), { {}} },
|
||||
{ QStringLiteral("view-fullscreen"), { {}} },
|
||||
{ QStringLiteral("view-media-lyrics"), { {}} },
|
||||
{ QStringLiteral("view-media-playlist"), { {}} },
|
||||
{ QStringLiteral("view-media-visualization"), { {QStringLiteral("preferences-desktop-theme")}} },
|
||||
{ QStringLiteral("view-refresh"), { {}} },
|
||||
{ QStringLiteral("library-music"), { {QStringLiteral("vinyl")}} },
|
||||
{ QStringLiteral("vlc"), { {}} },
|
||||
{ QStringLiteral("zoom-in"), { {}} },
|
||||
{ QStringLiteral("zoom-out"), { {}, 0, 0 } }
|
||||
{ u"albums"_s, { {u"media-optical"_s}} },
|
||||
{ u"alsa"_s, { {}} },
|
||||
{ u"application-exit"_s, { {}} },
|
||||
{ u"applications-internet"_s, { {}} },
|
||||
{ u"bluetooth"_s, { {u"preferences-system-bluetooth"_s, u"bluetooth-active"_s}} },
|
||||
{ u"cdcase"_s, { {u"cdcover"_s, u"media-optical"_s}} },
|
||||
{ u"media-optical"_s, { {u"cd"_s}} },
|
||||
{ u"configure"_s, { {}} },
|
||||
{ u"device-ipod-nano"_s, { {}} },
|
||||
{ u"device-ipod"_s, { {}} },
|
||||
{ u"device-phone"_s, { {}} },
|
||||
{ u"device"_s, { {u"drive-removable-media-usb-pendrive"_s}} },
|
||||
{ u"device-usb-drive"_s, { {}} },
|
||||
{ u"device-usb-flash"_s, { {}} },
|
||||
{ u"dialog-error"_s, { {}} },
|
||||
{ u"dialog-information"_s, { {}} },
|
||||
{ u"dialog-ok-apply"_s, { {}} },
|
||||
{ u"dialog-password"_s, { {}} },
|
||||
{ u"dialog-warning"_s, { {}} },
|
||||
{ u"document-download"_s, { {u"download"_s}} },
|
||||
{ u"document-new"_s, { {}} },
|
||||
{ u"document-open-folder"_s, { {}} },
|
||||
{ u"document-open"_s, { {}} },
|
||||
{ u"document-save"_s, { {}} },
|
||||
{ u"document-search"_s, { {}} },
|
||||
{ u"document-open-remote"_s, { {}} },
|
||||
{ u"download"_s, { {u"applications-internet"_s, u"network-workgroup"_s}} },
|
||||
{ u"edit-clear-list"_s, { {u"edit-clear-list"_s, u"edit-clear-all"_s}} },
|
||||
{ u"edit-clear-locationbar-ltr"_s, { {u"edit-clear-locationbar-ltr"_s}} },
|
||||
{ u"edit-copy"_s, { {}} },
|
||||
{ u"edit-delete"_s, { {}} },
|
||||
{ u"edit-find"_s, { {}} },
|
||||
{ u"edit-redo"_s, { {}} },
|
||||
{ u"edit-rename"_s, { {}} },
|
||||
{ u"edit-undo"_s, { {}} },
|
||||
{ u"electrocompaniet"_s, { {}} },
|
||||
{ u"equalizer"_s, { {u"view-media-equalizer"_s}} },
|
||||
{ u"folder-new"_s, { {}} },
|
||||
{ u"folder"_s, { {}} },
|
||||
{ u"folder-sound"_s, { {u"folder-music"_s}} },
|
||||
{ u"footsteps"_s, { {u"go-jump"_s}} },
|
||||
{ u"go-down"_s, { {}} },
|
||||
{ u"go-home"_s, { {}} },
|
||||
{ u"go-jump"_s, { {}} },
|
||||
{ u"go-next"_s, { {}} },
|
||||
{ u"go-previous"_s, { {}} },
|
||||
{ u"go-up"_s, { {}} },
|
||||
{ u"gstreamer"_s, { {u"phonon-gstreamer"_s}} },
|
||||
{ u"headset"_s, { {u"audio-headset"_s}} },
|
||||
{ u"help-hint"_s, { {}} },
|
||||
{ u"intel"_s, { {}} },
|
||||
{ u"jack"_s, { {u"audio-input-line"_s}} },
|
||||
{ u"keyboard"_s, { {u"input-keyboard"_s}} },
|
||||
{ u"list-add"_s, { {}} },
|
||||
{ u"list-remove"_s, { {}} },
|
||||
{ u"love"_s, { {u"heart"_s, u"emblem-favorite"_s}} },
|
||||
{ u"mcintosh-player"_s, { {}} },
|
||||
{ u"mcintosh"_s, { {}} },
|
||||
{ u"mcintosh-text"_s, { {}} },
|
||||
{ u"media-eject"_s, { {}} },
|
||||
{ u"media-playback-pause"_s, { {u"media-pause"_s}} },
|
||||
{ u"media-playlist-repeat"_s, { {}} },
|
||||
{ u"media-playlist-shuffle"_s, { {""_L1}} },
|
||||
{ u"media-playback-start"_s, { {u"media-play"_s, u"media-playback-playing"_s}} },
|
||||
{ u"media-seek-backward"_s, { {}} },
|
||||
{ u"media-seek-forward"_s, { {}} },
|
||||
{ u"media-skip-backward"_s, { {}} },
|
||||
{ u"media-skip-forward"_s, { {}} },
|
||||
{ u"media-playback-stop"_s, { {u"media-stop"_s}} },
|
||||
{ u"moodbar"_s, { {u"preferences-desktop-icons"_s}} },
|
||||
{ u"nvidia"_s, { {}} },
|
||||
{ u"pulseaudio"_s, { {}} },
|
||||
{ u"realtek"_s, { {}} },
|
||||
{ u"scrobble-disabled"_s, { {}} },
|
||||
{ u"scrobble"_s, { {u"love"_s}} },
|
||||
{ u"search"_s, { {}} },
|
||||
{ u"soundcard"_s, { {u"audiocard"_s, u"audio-card"_s}} },
|
||||
{ u"speaker"_s, { {}} },
|
||||
{ u"star-grey"_s, { {}} },
|
||||
{ u"star"_s, { {}} },
|
||||
{ u"strawberry"_s, { {}} },
|
||||
{ u"subsonic"_s, { {}} },
|
||||
{ u"tidal"_s, { {}} },
|
||||
{ u"tools-wizard"_s, { {}} },
|
||||
{ u"view-choose"_s, { {}} },
|
||||
{ u"view-fullscreen"_s, { {}} },
|
||||
{ u"view-media-lyrics"_s, { {}} },
|
||||
{ u"view-media-playlist"_s, { {}} },
|
||||
{ u"view-media-visualization"_s, { {u"preferences-desktop-theme"_s}} },
|
||||
{ u"view-refresh"_s, { {}} },
|
||||
{ u"library-music"_s, { {u"vinyl"_s}} },
|
||||
{ u"vlc"_s, { {}} },
|
||||
{ u"zoom-in"_s, { {}} },
|
||||
{ u"zoom-out"_s, { {}, 0, 0 } }
|
||||
|
||||
};
|
||||
|
||||
} // namespace IconMapper
|
||||
|
||||
#endif // ICONMAPPER_H
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
#include <QDateTime>
|
||||
#include <QRandomGenerator>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
LocalRedirectServer::LocalRedirectServer(QObject *parent)
|
||||
: QTcpServer(parent),
|
||||
port_(0),
|
||||
@@ -86,8 +88,8 @@ void LocalRedirectServer::incomingConnection(qintptr socket_descriptor) {
|
||||
if (!tcp_socket->setSocketDescriptor(socket_descriptor)) {
|
||||
delete tcp_socket;
|
||||
close();
|
||||
error_ = QLatin1String("Unable to set socket descriptor");
|
||||
emit Finished();
|
||||
error_ = "Unable to set socket descriptor"_L1;
|
||||
Q_EMIT Finished();
|
||||
return;
|
||||
}
|
||||
socket_ = tcp_socket;
|
||||
@@ -114,7 +116,7 @@ void LocalRedirectServer::ReadyRead() {
|
||||
socket_ = nullptr;
|
||||
request_url_ = ParseUrlFromRequest(buffer_);
|
||||
close();
|
||||
emit Finished();
|
||||
Q_EMIT Finished();
|
||||
}
|
||||
else {
|
||||
QObject::connect(socket_, &QAbstractSocket::readyRead, this, &LocalRedirectServer::ReadyRead);
|
||||
@@ -129,9 +131,9 @@ void LocalRedirectServer::WriteTemplate() const {
|
||||
QString page_data = QString::fromUtf8(page_file.readAll());
|
||||
page_file.close();
|
||||
|
||||
QRegularExpression tr_regexp(QStringLiteral("tr\\(\"([^\"]+)\"\\)"));
|
||||
static const QRegularExpression tr_regexp(QStringLiteral("tr\\(\"([^\"]+)\"\\)"));
|
||||
qint64 offset = 0;
|
||||
forever {
|
||||
Q_FOREVER {
|
||||
QRegularExpressionMatch re_match = tr_regexp.match(page_data, offset);
|
||||
if (!re_match.hasMatch()) break;
|
||||
offset = re_match.capturedStart();
|
||||
@@ -151,7 +153,7 @@ void LocalRedirectServer::WriteTemplate() const {
|
||||
.pixmap(16)
|
||||
.toImage()
|
||||
.save(&image_buffer, "PNG");
|
||||
page_data.replace(QLatin1String("@IMAGE_DATA@"), QString::fromUtf8(image_buffer.data().toBase64()));
|
||||
page_data.replace("@IMAGE_DATA@"_L1, QString::fromUtf8(image_buffer.data().toBase64()));
|
||||
image_buffer.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -45,10 +45,10 @@ class LocalRedirectServer : public QTcpServer {
|
||||
const QUrl &request_url() const { return request_url_; }
|
||||
const QString &error() const { return error_; }
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void Finished();
|
||||
|
||||
public slots:
|
||||
public Q_SLOTS:
|
||||
void NewConnection();
|
||||
void incomingConnection(qintptr socket_descriptor) override;
|
||||
void Encrypted();
|
||||
|
||||
@@ -44,10 +44,10 @@ class MacFSListener : public FileSystemWatcherInterface {
|
||||
void RemovePath(const QString &path);
|
||||
void Clear();
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void PathChanged(const QString &path);
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void UpdateStream();
|
||||
|
||||
private:
|
||||
|
||||
@@ -32,11 +32,13 @@
|
||||
#include "core/logging.h"
|
||||
#include "scoped_nsobject.h"
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
MacFSListener::MacFSListener(QObject *parent)
|
||||
: FileSystemWatcherInterface(parent),
|
||||
run_loop_(nullptr),
|
||||
stream_(nullptr),
|
||||
update_timer_(new QTimer(this)) {
|
||||
run_loop_(nullptr),
|
||||
stream_(nullptr),
|
||||
update_timer_(new QTimer(this)) {
|
||||
|
||||
update_timer_->setSingleShot(true);
|
||||
update_timer_->setInterval(2000);
|
||||
@@ -57,10 +59,10 @@ void MacFSListener::EventStreamCallback(ConstFSEventStreamRef stream, void *user
|
||||
for (size_t i = 0; i < num_events; ++i) {
|
||||
QString path = QString::fromUtf8(paths[i]);
|
||||
qLog(Debug) << "Something changed at:" << path;
|
||||
while (path.endsWith(QLatin1Char('/'))) {
|
||||
while (path.endsWith(u'/')) {
|
||||
path.chop(1);
|
||||
}
|
||||
emit me->PathChanged(path);
|
||||
Q_EMIT me->PathChanged(path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -71,10 +71,10 @@ class SystemTrayIcon : public QObject {
|
||||
QPixmap CreateIcon(const QPixmap &icon, const QPixmap &grey_icon);
|
||||
void UpdateIcon();
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void ActionChanged();
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void ChangeVolume(const int delta);
|
||||
void SeekForward();
|
||||
void SeekBackward();
|
||||
|
||||
@@ -23,8 +23,9 @@
|
||||
#include "version.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
@@ -216,16 +217,13 @@
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_QTSPARKLE
|
||||
# if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
# include <qtsparkle-qt6/Updater>
|
||||
# else
|
||||
# include <qtsparkle-qt5/Updater>
|
||||
# endif
|
||||
# include <qtsparkle-qt6/Updater>
|
||||
#endif // HAVE_QTSPARKLE
|
||||
|
||||
using std::make_unique;
|
||||
using std::make_shared;
|
||||
using namespace std::chrono_literals;
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
const char *MainWindow::kSettingsGroup = "MainWindow";
|
||||
const char *MainWindow::kAllFilesFilterSpec = QT_TR_NOOP("All Files (*)");
|
||||
@@ -399,7 +397,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||
ui_->tabs->AddTab(tidal_view_, QStringLiteral("tidal"), IconLoader::Load(QStringLiteral("tidal"), true, 0, 32), tr("Tidal"));
|
||||
#endif
|
||||
#ifdef HAVE_SPOTIFY
|
||||
ui_->tabs->AddTab(spotify_view_, QLatin1String("spotify"), IconLoader::Load(QStringLiteral("spotify"), true, 0, 32), tr("Spotify"));
|
||||
ui_->tabs->AddTab(spotify_view_, QStringLiteral("spotify"), IconLoader::Load(QStringLiteral("spotify"), true, 0, 32), tr("Spotify"));
|
||||
#endif
|
||||
#ifdef HAVE_QOBUZ
|
||||
ui_->tabs->AddTab(qobuz_view_, QStringLiteral("qobuz"), IconLoader::Load(QStringLiteral("qobuz"), true, 0, 32), tr("Qobuz"));
|
||||
@@ -489,7 +487,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||
ui_->action_transcoder->setIcon(IconLoader::Load(QStringLiteral("tools-wizard")));
|
||||
ui_->action_update_collection->setIcon(IconLoader::Load(QStringLiteral("view-refresh")));
|
||||
ui_->action_full_collection_scan->setIcon(IconLoader::Load(QStringLiteral("view-refresh")));
|
||||
ui_->action_abort_collection_scan->setIcon(IconLoader::Load(QStringLiteral("dialog-error")));
|
||||
ui_->action_stop_collection_scan->setIcon(IconLoader::Load(QStringLiteral("dialog-error")));
|
||||
ui_->action_settings->setIcon(IconLoader::Load(QStringLiteral("configure")));
|
||||
ui_->action_import_data_from_last_fm->setIcon(IconLoader::Load(QStringLiteral("scrobble")));
|
||||
ui_->action_console->setIcon(IconLoader::Load(QStringLiteral("keyboard")));
|
||||
@@ -555,7 +553,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||
QObject::connect(ui_->action_jump, &QAction::triggered, ui_->playlist->view(), &PlaylistView::JumpToCurrentlyPlayingTrack);
|
||||
QObject::connect(ui_->action_update_collection, &QAction::triggered, &*app_->collection(), &SCollection::IncrementalScan);
|
||||
QObject::connect(ui_->action_full_collection_scan, &QAction::triggered, &*app_->collection(), &SCollection::FullScan);
|
||||
QObject::connect(ui_->action_abort_collection_scan, &QAction::triggered, &*app_->collection(), &SCollection::AbortScan);
|
||||
QObject::connect(ui_->action_stop_collection_scan, &QAction::triggered, &*app_->collection(), &SCollection::StopScan);
|
||||
#if defined(HAVE_GSTREAMER)
|
||||
QObject::connect(ui_->action_add_files_to_transcoder, &QAction::triggered, this, &MainWindow::AddFilesToTranscoder);
|
||||
ui_->action_add_files_to_transcoder->setIcon(IconLoader::Load(QStringLiteral("tools-wizard")));
|
||||
@@ -621,6 +619,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||
QObject::connect(&*app_->player(), &Player::VolumeChanged, ui_->volume, &VolumeSlider::SetValue);
|
||||
QObject::connect(&*app_->player(), &Player::ForceShowOSD, this, &MainWindow::ForceShowOSD);
|
||||
|
||||
QObject::connect(&*app_->playlist_manager(), &PlaylistManager::AllPlaylistsLoaded, &*app->player(), &Player::PlaylistsLoaded);
|
||||
QObject::connect(&*app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, this, &MainWindow::SongChanged);
|
||||
QObject::connect(&*app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, &*app_->player(), &Player::CurrentMetadataChanged);
|
||||
QObject::connect(&*app_->playlist_manager(), &PlaylistManager::EditingFinished, this, &MainWindow::PlaylistEditFinished);
|
||||
@@ -831,6 +830,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||
QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::StopAfter, ui_->action_stop_after_this_track, &QAction::trigger);
|
||||
QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::Next, ui_->action_next_track, &QAction::trigger);
|
||||
QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::Previous, ui_->action_previous_track, &QAction::trigger);
|
||||
QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::RestartOrPrevious, &*app_->player(), &Player::RestartOrPrevious);
|
||||
QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::IncVolume, &*app_->player(), &Player::VolumeUp);
|
||||
QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::DecVolume, &*app_->player(), &Player::VolumeDown);
|
||||
QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::Mute, &*app_->player(), &Player::Mute);
|
||||
@@ -1023,9 +1023,6 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||
|
||||
CommandlineOptionsReceived(options);
|
||||
|
||||
if (!options.contains_play_options()) {
|
||||
LoadPlaybackStatus();
|
||||
}
|
||||
if (app_->scrobbler()->enabled() && !app_->scrobbler()->offline()) {
|
||||
app_->scrobbler()->Submit();
|
||||
}
|
||||
@@ -1273,15 +1270,15 @@ void MainWindow::ReloadAllSettings() {
|
||||
|
||||
void MainWindow::RefreshStyleSheet() {
|
||||
QString contents(styleSheet());
|
||||
setStyleSheet(QLatin1String(""));
|
||||
setStyleSheet(""_L1);
|
||||
setStyleSheet(contents);
|
||||
}
|
||||
|
||||
void MainWindow::SaveSettings() {
|
||||
|
||||
SaveGeometry();
|
||||
SavePlaybackStatus();
|
||||
app_->player()->SaveVolume();
|
||||
app_->player()->SavePlaybackStatus();
|
||||
ui_->tabs->SaveSettings(QLatin1String(kSettingsGroup));
|
||||
ui_->playlist->view()->SaveSettings();
|
||||
app_->scrobbler()->WriteCache();
|
||||
@@ -1307,7 +1304,7 @@ void MainWindow::Exit() {
|
||||
else {
|
||||
if (app_->player()->engine()->is_fadeout_enabled()) {
|
||||
// To shut down the application when fadeout will be finished
|
||||
QObject::connect(&*app_->player()->engine(), &EngineBase::FadeoutFinishedSignal, this, &MainWindow::DoExit);
|
||||
QObject::connect(&*app_->player()->engine(), &EngineBase::Finished, this, &MainWindow::DoExit);
|
||||
if (app_->player()->GetState() == EngineBase::State::Playing) {
|
||||
app_->player()->Stop();
|
||||
ignore_close_ = true;
|
||||
@@ -1371,8 +1368,12 @@ void MainWindow::MediaStopped() {
|
||||
ui_->button_love->setEnabled(false);
|
||||
tray_icon_->LoveStateChanged(false);
|
||||
|
||||
track_position_timer_->stop();
|
||||
track_slider_timer_->stop();
|
||||
if (track_position_timer_->isActive()) {
|
||||
track_position_timer_->stop();
|
||||
}
|
||||
if (track_slider_timer_->isActive()) {
|
||||
track_slider_timer_->stop();
|
||||
}
|
||||
ui_->track_slider->SetStopped();
|
||||
tray_icon_->SetProgress(0);
|
||||
tray_icon_->SetStopped();
|
||||
@@ -1400,8 +1401,12 @@ void MainWindow::MediaPaused() {
|
||||
|
||||
ui_->action_play_pause->setEnabled(true);
|
||||
|
||||
track_position_timer_->stop();
|
||||
track_slider_timer_->stop();
|
||||
if (!track_position_timer_->isActive()) {
|
||||
track_position_timer_->start();
|
||||
}
|
||||
if (!track_slider_timer_->isActive()) {
|
||||
track_slider_timer_->start();
|
||||
}
|
||||
|
||||
tray_icon_->SetPaused();
|
||||
|
||||
@@ -1426,8 +1431,13 @@ void MainWindow::MediaPlaying() {
|
||||
ui_->track_slider->SetCanSeek(can_seek);
|
||||
tray_icon_->SetPlaying(enable_play_pause);
|
||||
|
||||
track_position_timer_->start();
|
||||
track_slider_timer_->start();
|
||||
if (!track_position_timer_->isActive()) {
|
||||
track_position_timer_->start();
|
||||
}
|
||||
if (!track_slider_timer_->isActive()) {
|
||||
track_slider_timer_->start();
|
||||
}
|
||||
|
||||
UpdateTrackPosition();
|
||||
|
||||
}
|
||||
@@ -1535,80 +1545,6 @@ void MainWindow::SaveGeometry() {
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::SavePlaybackStatus() {
|
||||
|
||||
Settings s;
|
||||
|
||||
s.beginGroup(Player::kSettingsGroup);
|
||||
s.setValue("playback_state", static_cast<int>(app_->player()->GetState()));
|
||||
if (app_->player()->GetState() == EngineBase::State::Playing || app_->player()->GetState() == EngineBase::State::Paused) {
|
||||
s.setValue("playback_playlist", app_->playlist_manager()->active()->id());
|
||||
s.setValue("playback_position", app_->player()->engine()->position_nanosec() / kNsecPerSec);
|
||||
}
|
||||
else {
|
||||
s.setValue("playback_playlist", -1);
|
||||
s.setValue("playback_position", 0);
|
||||
}
|
||||
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::LoadPlaybackStatus() {
|
||||
|
||||
Settings s;
|
||||
|
||||
s.beginGroup(BehaviourSettingsPage::kSettingsGroup);
|
||||
const bool resume_playback = s.value("resumeplayback", false).toBool();
|
||||
s.endGroup();
|
||||
|
||||
s.beginGroup(Player::kSettingsGroup);
|
||||
const EngineBase::State playback_state = static_cast<EngineBase::State>(s.value("playback_state", static_cast<int>(EngineBase::State::Empty)).toInt());
|
||||
s.endGroup();
|
||||
|
||||
if (resume_playback && playback_state != EngineBase::State::Empty && playback_state != EngineBase::State::Idle) {
|
||||
SharedPtr<QMetaObject::Connection> connection = make_shared<QMetaObject::Connection>();
|
||||
*connection = QObject::connect(&*app_->playlist_manager(), &PlaylistManager::AllPlaylistsLoaded, this, [this, connection]() {
|
||||
QObject::disconnect(*connection);
|
||||
QTimer::singleShot(400ms, this, &MainWindow::ResumePlayback);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::ResumePlayback() {
|
||||
|
||||
qLog(Debug) << "Resuming playback";
|
||||
|
||||
Settings s;
|
||||
s.beginGroup(Player::kSettingsGroup);
|
||||
const EngineBase::State playback_state = static_cast<EngineBase::State>(s.value("playback_state", static_cast<int>(EngineBase::State::Empty)).toInt());
|
||||
int playback_playlist = s.value("playback_playlist", -1).toInt();
|
||||
int playback_position = s.value("playback_position", 0).toInt();
|
||||
s.endGroup();
|
||||
|
||||
if (playback_playlist == app_->playlist_manager()->current()->id()) {
|
||||
// Set active to current to resume playback on correct playlist.
|
||||
app_->playlist_manager()->SetActiveToCurrent();
|
||||
if (playback_state == EngineBase::State::Paused) {
|
||||
SharedPtr<QMetaObject::Connection> connection = make_shared<QMetaObject::Connection>();
|
||||
*connection = QObject::connect(&*app_->player(), &Player::Playing, &*app_->player(), [this, connection]() {
|
||||
QObject::disconnect(*connection);
|
||||
QTimer::singleShot(300, &*app_->player(), &Player::PlayPauseHelper);
|
||||
});
|
||||
}
|
||||
app_->player()->Play(playback_position * kNsecPerSec);
|
||||
}
|
||||
|
||||
// Reset saved playback status so we don't resume again from the same position.
|
||||
s.beginGroup(Player::kSettingsGroup);
|
||||
s.setValue("playback_state", static_cast<int>(EngineBase::State::Empty));
|
||||
s.setValue("playback_playlist", -1);
|
||||
s.setValue("playback_position", 0);
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::PlayIndex(const QModelIndex &idx, Playlist::AutoScroll autoscroll) {
|
||||
|
||||
if (!idx.isValid()) return;
|
||||
@@ -1620,7 +1556,7 @@ void MainWindow::PlayIndex(const QModelIndex &idx, Playlist::AutoScroll autoscro
|
||||
}
|
||||
|
||||
app_->playlist_manager()->SetActiveToCurrent();
|
||||
app_->player()->PlayAt(row, 0, EngineBase::TrackChangeType::Manual, autoscroll, true);
|
||||
app_->player()->PlayAt(row, false, 0, EngineBase::TrackChangeType::Manual, autoscroll, true);
|
||||
|
||||
}
|
||||
|
||||
@@ -1637,14 +1573,14 @@ void MainWindow::PlaylistDoubleClick(const QModelIndex &idx) {
|
||||
switch (doubleclick_playlist_addmode_) {
|
||||
case BehaviourSettingsPage::PlaylistAddBehaviour::Play:
|
||||
app_->playlist_manager()->SetActiveToCurrent();
|
||||
app_->player()->PlayAt(source_idx.row(), 0, EngineBase::TrackChangeType::Manual, Playlist::AutoScroll::Never, true, true);
|
||||
app_->player()->PlayAt(source_idx.row(), false, 0, EngineBase::TrackChangeType::Manual, Playlist::AutoScroll::Never, true, true);
|
||||
break;
|
||||
|
||||
case BehaviourSettingsPage::PlaylistAddBehaviour::Enqueue:
|
||||
app_->playlist_manager()->current()->queue()->ToggleTracks(QModelIndexList() << source_idx);
|
||||
if (app_->player()->GetState() != EngineBase::State::Playing) {
|
||||
app_->playlist_manager()->SetActiveToCurrent();
|
||||
app_->player()->PlayAt(app_->playlist_manager()->current()->queue()->TakeNext(), 0, EngineBase::TrackChangeType::Manual, Playlist::AutoScroll::Never, true);
|
||||
app_->player()->PlayAt(app_->playlist_manager()->current()->queue()->TakeNext(), false, 0, EngineBase::TrackChangeType::Manual, Playlist::AutoScroll::Never, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1686,7 +1622,7 @@ void MainWindow::ToggleHide() {
|
||||
|
||||
void MainWindow::StopAfterCurrent() {
|
||||
app_->playlist_manager()->current()->StopAfter(app_->playlist_manager()->current()->current_row());
|
||||
emit StopAfterToggled(app_->playlist_manager()->active()->stop_after_current());
|
||||
Q_EMIT StopAfterToggled(app_->playlist_manager()->active()->stop_after_current());
|
||||
}
|
||||
|
||||
void MainWindow::showEvent(QShowEvent *e) {
|
||||
@@ -1896,7 +1832,8 @@ void MainWindow::AddToPlaylistFromAction(QAction *action) {
|
||||
SongList songs;
|
||||
|
||||
// Get the selected playlist items
|
||||
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
|
||||
const QModelIndexList proxy_indexes = ui_->playlist->view()->selectionModel()->selectedRows();
|
||||
for (const QModelIndex &proxy_index : proxy_indexes) {
|
||||
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
|
||||
if (!source_index.isValid()) continue;
|
||||
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
|
||||
@@ -1963,7 +1900,7 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
|
||||
playlist_stop_after_->setEnabled(source_index.isValid());
|
||||
|
||||
// Are any of the selected songs editable or queued?
|
||||
QModelIndexList selection = ui_->playlist->view()->selectionModel()->selectedRows();
|
||||
const QModelIndexList selection = ui_->playlist->view()->selectionModel()->selectedRows();
|
||||
bool cue_selected = false;
|
||||
qint64 selected = ui_->playlist->view()->selectionModel()->selectedRows().count();
|
||||
int editable = 0;
|
||||
@@ -2105,12 +2042,10 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
|
||||
playlist_copy_to_device_->setVisible(local_songs > 0);
|
||||
#endif
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
playlist_delete_->setVisible(delete_files_ && local_songs > 0);
|
||||
#endif
|
||||
|
||||
// Remove old item actions, if any.
|
||||
for (QAction *action : playlistitem_actions_) {
|
||||
for (QAction *action : std::as_const(playlistitem_actions_)) {
|
||||
playlist_menu_->removeAction(action);
|
||||
}
|
||||
|
||||
@@ -2120,7 +2055,7 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
|
||||
playlist_menu_->insertActions(playlistitem_actions_separator_, playlistitem_actions_);
|
||||
}
|
||||
|
||||
//if it isn't the first time we right click, we need to remove the menu previously created
|
||||
// If it isn't the first time we right click, we need to remove the menu previously created
|
||||
if (playlist_add_to_another_ != nullptr) {
|
||||
playlist_menu_->removeAction(playlist_add_to_another_);
|
||||
delete playlist_add_to_another_;
|
||||
@@ -2132,18 +2067,19 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
|
||||
QMenu *add_to_another_menu = new QMenu(tr("Add to another playlist"), this);
|
||||
add_to_another_menu->setIcon(IconLoader::Load(QStringLiteral("list-add")));
|
||||
|
||||
for (const PlaylistBackend::Playlist &playlist : app_->playlist_backend()->GetAllOpenPlaylists()) {
|
||||
// don't add the current playlist
|
||||
if (playlist.id != app_->playlist_manager()->current()->id()) {
|
||||
const QList<int> playlist_ids = app_->playlist_manager()->playlist_ids();
|
||||
for (const int playlist_id : playlist_ids) {
|
||||
// Don't add the current playlist
|
||||
if (playlist_id != app_->playlist_manager()->current()->id()) {
|
||||
QAction *existing_playlist = new QAction(this);
|
||||
existing_playlist->setText(playlist.name);
|
||||
existing_playlist->setData(playlist.id);
|
||||
existing_playlist->setText(app_->playlist_manager()->playlist_name(playlist_id));
|
||||
existing_playlist->setData(playlist_id);
|
||||
add_to_another_menu->addAction(existing_playlist);
|
||||
}
|
||||
}
|
||||
|
||||
add_to_another_menu->addSeparator();
|
||||
// add to a new playlist
|
||||
// Add to a new playlist
|
||||
QAction *new_playlist = new QAction(this);
|
||||
new_playlist->setText(tr("New playlist"));
|
||||
new_playlist->setData(-1); // fake id
|
||||
@@ -2177,7 +2113,8 @@ void MainWindow::RescanSongs() {
|
||||
|
||||
SongList songs;
|
||||
|
||||
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
|
||||
const QModelIndexList proxy_indexes = ui_->playlist->view()->selectionModel()->selectedRows();
|
||||
for (const QModelIndex &proxy_index : proxy_indexes) {
|
||||
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
|
||||
if (!source_index.isValid()) continue;
|
||||
PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(source_index.row()));
|
||||
@@ -2202,7 +2139,8 @@ void MainWindow::EditTracks() {
|
||||
SongList songs;
|
||||
PlaylistItemPtrList items;
|
||||
|
||||
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
|
||||
const QModelIndexList proxy_indexes = ui_->playlist->view()->selectionModel()->selectedRows();
|
||||
for (const QModelIndex &proxy_index : proxy_indexes) {
|
||||
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
|
||||
if (!source_index.isValid()) continue;
|
||||
PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(source_index.row()));
|
||||
@@ -2224,7 +2162,8 @@ void MainWindow::EditTracks() {
|
||||
|
||||
void MainWindow::EditTagDialogAccepted() {
|
||||
|
||||
for (PlaylistItemPtr item : edit_tag_dialog_->playlist_items()) {
|
||||
const PlaylistItemPtrList items = edit_tag_dialog_->playlist_items();
|
||||
for (PlaylistItemPtr item : items) {
|
||||
item->Reload();
|
||||
}
|
||||
|
||||
@@ -2243,13 +2182,13 @@ void MainWindow::RenumberTracks() {
|
||||
// Get the index list in order
|
||||
std::stable_sort(indexes.begin(), indexes.end());
|
||||
|
||||
// if first selected song has a track number set, start from that offset
|
||||
// If first selected song has a track number set, start from that offset
|
||||
if (!indexes.isEmpty()) {
|
||||
const Song first_song = app_->playlist_manager()->current()->item_at(indexes[0].row())->OriginalMetadata();
|
||||
if (first_song.track() > 0) track = first_song.track();
|
||||
}
|
||||
|
||||
for (const QModelIndex &proxy_index : indexes) {
|
||||
for (const QModelIndex &proxy_index : std::as_const(indexes)) {
|
||||
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
|
||||
if (!source_index.isValid()) continue;
|
||||
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
|
||||
@@ -2280,7 +2219,8 @@ void MainWindow::SelectionSetValue() {
|
||||
Playlist::Column column = static_cast<Playlist::Column>(playlist_menu_index_.column());
|
||||
QVariant column_value = app_->playlist_manager()->current()->data(playlist_menu_index_);
|
||||
|
||||
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
|
||||
const QModelIndexList proxy_indexes = ui_->playlist->view()->selectionModel()->selectedRows();
|
||||
for (const QModelIndex &proxy_index : proxy_indexes) {
|
||||
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
|
||||
if (!source_index.isValid()) continue;
|
||||
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
|
||||
@@ -2327,7 +2267,7 @@ void MainWindow::AddFile() {
|
||||
PlaylistParser parser(app_->collection_backend());
|
||||
|
||||
// Show dialog
|
||||
QStringList filenames = QFileDialog::getOpenFileNames(this, tr("Add file"), directory, QStringLiteral("%1 (%2);;%3;;%4").arg(tr("Music"), QLatin1String(FileView::kFileFilter), parser.filters(PlaylistParser::Type::Load), tr(kAllFilesFilterSpec)));
|
||||
const QStringList filenames = QFileDialog::getOpenFileNames(this, tr("Add file"), directory, QStringLiteral("%1 (%2);;%3;;%4").arg(tr("Music"), QLatin1String(FileView::kFileFilter), parser.filters(PlaylistParser::Type::Load), tr(kAllFilesFilterSpec)));
|
||||
|
||||
if (filenames.isEmpty()) return;
|
||||
|
||||
@@ -2394,7 +2334,8 @@ void MainWindow::ShowInCollection() {
|
||||
// Show the first valid selected track artist/album in CollectionView
|
||||
|
||||
SongList songs;
|
||||
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
|
||||
const QModelIndexList proxy_indexes = ui_->playlist->view()->selectionModel()->selectedRows();
|
||||
for (const QModelIndex &proxy_index : proxy_indexes) {
|
||||
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
|
||||
if (!source_index.isValid()) continue;
|
||||
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
|
||||
@@ -2405,7 +2346,7 @@ void MainWindow::ShowInCollection() {
|
||||
}
|
||||
QString search;
|
||||
if (!songs.isEmpty()) {
|
||||
search = QLatin1String("artist:") + songs.first().artist() + QLatin1String(" album:") + songs.first().album();
|
||||
search = "artist:"_L1 + songs.first().artist() + " album:"_L1 + songs.first().album();
|
||||
}
|
||||
collection_view_->filter_widget()->ShowInCollection(search);
|
||||
|
||||
@@ -2498,9 +2439,9 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
||||
break;
|
||||
|
||||
case CommandlineOptions::PlayerAction::ResizeWindow:{
|
||||
if (options.window_size().contains(QLatin1Char('x')) && options.window_size().length() >= 4) {
|
||||
QString str_w = options.window_size().left(options.window_size().indexOf(QLatin1Char('x')));
|
||||
QString str_h = options.window_size().right(options.window_size().length() - options.window_size().indexOf(QLatin1Char('x')) - 1);
|
||||
if (options.window_size().contains(u'x') && options.window_size().length() >= 4) {
|
||||
QString str_w = options.window_size().left(options.window_size().indexOf(u'x'));
|
||||
QString str_h = options.window_size().right(options.window_size().length() - options.window_size().indexOf(u'x') - 1);
|
||||
bool w_ok = false;
|
||||
bool h_ok = false;
|
||||
int w = str_w.toInt(&w_ok);
|
||||
@@ -2538,9 +2479,10 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
||||
if (!options.urls().empty()) {
|
||||
|
||||
#ifdef HAVE_TIDAL
|
||||
for (const QUrl &url : options.urls()) {
|
||||
if (url.scheme() == QLatin1String("tidal") && url.host() == QLatin1String("login")) {
|
||||
emit AuthorizationUrlReceived(url);
|
||||
const QList<QUrl> urls = options.urls();
|
||||
for (const QUrl &url : urls) {
|
||||
if (url.scheme() == "tidal"_L1 && url.host() == "login"_L1) {
|
||||
Q_EMIT AuthorizationUrlReceived(url);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2585,7 +2527,7 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
||||
app_->player()->SeekTo(app_->player()->engine()->position_nanosec() / kNsecPerSec + options.seek_by());
|
||||
}
|
||||
|
||||
if (options.play_track_at() != -1) app_->player()->PlayAt(options.play_track_at(), 0, EngineBase::TrackChangeType::Manual, Playlist::AutoScroll::Maybe, true);
|
||||
if (options.play_track_at() != -1) app_->player()->PlayAt(options.play_track_at(), false, 0, EngineBase::TrackChangeType::Manual, Playlist::AutoScroll::Maybe, true);
|
||||
|
||||
if (options.show_osd()) app_->player()->ShowOSD();
|
||||
|
||||
@@ -2617,8 +2559,8 @@ bool MainWindow::LoadUrl(const QString &url) {
|
||||
return true;
|
||||
}
|
||||
#ifdef HAVE_TIDAL
|
||||
if (url.startsWith(QLatin1String("tidal://login"))) {
|
||||
emit AuthorizationUrlReceived(QUrl(url));
|
||||
if (url.startsWith("tidal://login"_L1)) {
|
||||
Q_EMIT AuthorizationUrlReceived(QUrl(url));
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -2641,7 +2583,8 @@ void MainWindow::AddFilesToTranscoder() {
|
||||
|
||||
QStringList filenames;
|
||||
|
||||
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
|
||||
const QModelIndexList proxy_indexes = ui_->playlist->view()->selectionModel()->selectedRows();
|
||||
for (const QModelIndex &proxy_index : proxy_indexes) {
|
||||
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
|
||||
if (!source_index.isValid()) continue;
|
||||
PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(source_index.row()));
|
||||
@@ -2753,7 +2696,8 @@ void MainWindow::PlaylistMoveToCollection() {
|
||||
void MainWindow::PlaylistOrganizeSelected(const bool copy) {
|
||||
|
||||
SongList songs;
|
||||
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
|
||||
const QModelIndexList proxy_indexes = ui_->playlist->view()->selectionModel()->selectedRows();
|
||||
for (const QModelIndex &proxy_index : proxy_indexes) {
|
||||
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
|
||||
if (!source_index.isValid()) continue;
|
||||
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
|
||||
@@ -2775,7 +2719,8 @@ void MainWindow::PlaylistOrganizeSelected(const bool copy) {
|
||||
void MainWindow::PlaylistOpenInBrowser() {
|
||||
|
||||
QList<QUrl> urls;
|
||||
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
|
||||
const QModelIndexList proxy_indexes = ui_->playlist->view()->selectionModel()->selectedRows();
|
||||
for (const QModelIndex &proxy_index : proxy_indexes) {
|
||||
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
|
||||
if (!source_index.isValid()) continue;
|
||||
urls << QUrl(source_index.sibling(source_index.row(), static_cast<int>(Playlist::Column::Filename)).data().toString());
|
||||
@@ -2788,7 +2733,8 @@ void MainWindow::PlaylistOpenInBrowser() {
|
||||
void MainWindow::PlaylistCopyUrl() {
|
||||
|
||||
QList<QUrl> urls;
|
||||
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
|
||||
const QModelIndexList proxy_indexes = ui_->playlist->view()->selectionModel()->selectedRows();
|
||||
for (const QModelIndex &proxy_index : proxy_indexes) {
|
||||
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
|
||||
if (!source_index.isValid()) continue;
|
||||
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
|
||||
@@ -2819,7 +2765,7 @@ void MainWindow::PlaylistQueue() {
|
||||
|
||||
void MainWindow::PlaylistQueuePlayNext() {
|
||||
|
||||
QModelIndexList selected_rows = ui_->playlist->view()->selectionModel()->selectedRows();
|
||||
const QModelIndexList selected_rows = ui_->playlist->view()->selectionModel()->selectedRows();
|
||||
QModelIndexList indexes;
|
||||
indexes.reserve(selected_rows.count());
|
||||
for (const QModelIndex &proxy_index : selected_rows) {
|
||||
@@ -2849,7 +2795,8 @@ void MainWindow::PlaylistCopyToDevice() {
|
||||
|
||||
SongList songs;
|
||||
|
||||
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
|
||||
const QModelIndexList proxy_indexes = ui_->playlist->view()->selectionModel()->selectedRows();
|
||||
for (const QModelIndex &proxy_index : proxy_indexes) {
|
||||
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
|
||||
if (!source_index.isValid()) continue;
|
||||
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
|
||||
@@ -2964,7 +2911,7 @@ void MainWindow::CheckFullRescanRevisions() {
|
||||
int from = app_->database()->startup_schema_version();
|
||||
int to = app_->database()->current_schema_version();
|
||||
|
||||
// if we're restoring DB from scratch or nothing has changed, do nothing
|
||||
// If we're restoring DB from scratch or nothing has changed, do nothing
|
||||
if (from == 0 || from == to) {
|
||||
return;
|
||||
}
|
||||
@@ -2978,13 +2925,13 @@ void MainWindow::CheckFullRescanRevisions() {
|
||||
}
|
||||
}
|
||||
|
||||
// if we have any...
|
||||
// If we have any...
|
||||
if (!reasons.isEmpty()) {
|
||||
QString message = tr("The version of Strawberry you've just updated to requires a full collection rescan because of the new features listed below:") + QStringLiteral("<ul>");
|
||||
for (const QString &reason : reasons) {
|
||||
message += QLatin1String("<li>") + reason + QLatin1String("</li>");
|
||||
message += "<li>"_L1 + reason + "</li>"_L1;
|
||||
}
|
||||
message += QLatin1String("</ul>") + tr("Would you like to run a full rescan right now?");
|
||||
message += "</ul>"_L1 + tr("Would you like to run a full rescan right now?");
|
||||
if (QMessageBox::question(this, tr("Collection rescan notice"), message, QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) {
|
||||
app_->collection()->FullScan();
|
||||
}
|
||||
@@ -3015,11 +2962,7 @@ void MainWindow::Raise() {
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
bool MainWindow::nativeEvent(const QByteArray &eventType, void *message, qintptr *result) {
|
||||
# else
|
||||
bool MainWindow::nativeEvent(const QByteArray &eventType, void *message, long *result) {
|
||||
# endif
|
||||
|
||||
if (exit_count_ == 0 && message) {
|
||||
MSG *msg = static_cast<MSG*>(message);
|
||||
@@ -3050,7 +2993,8 @@ void MainWindow::AutoCompleteTags() {
|
||||
|
||||
// Get the selected songs and start fetching tags for them
|
||||
SongList songs;
|
||||
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
|
||||
const QModelIndexList proxy_indexes = ui_->playlist->view()->selectionModel()->selectedRows();
|
||||
for (const QModelIndex &proxy_index : proxy_indexes) {
|
||||
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
|
||||
if (!source_index.isValid()) continue;
|
||||
PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(source_index.row()));
|
||||
@@ -3075,7 +3019,7 @@ void MainWindow::AutoCompleteTags() {
|
||||
|
||||
void MainWindow::AutoCompleteTagsAccepted() {
|
||||
|
||||
for (PlaylistItemPtr item : autocomplete_tag_items_) {
|
||||
for (PlaylistItemPtr item : std::as_const(autocomplete_tag_items_)) {
|
||||
item->Reload();
|
||||
}
|
||||
autocomplete_tag_items_.clear();
|
||||
@@ -3180,7 +3124,7 @@ void MainWindow::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult
|
||||
song_ = song;
|
||||
album_cover_ = result.album_cover;
|
||||
|
||||
emit AlbumCoverReady(song, result.album_cover.image);
|
||||
Q_EMIT AlbumCoverReady(song, result.album_cover.image);
|
||||
|
||||
const bool enable_change_art = song.is_collection_song() && !song.effective_albumartist().isEmpty() && !song.album().isEmpty();
|
||||
album_cover_choice_controller_->show_cover_action()->setEnabled(result.success && result.type != AlbumCoverLoaderResult::Type::Unset);
|
||||
@@ -3208,7 +3152,7 @@ void MainWindow::GetCoverAutomatically() {
|
||||
!song_.effective_album().isEmpty();
|
||||
|
||||
if (search) {
|
||||
emit SearchCoverInProgress();
|
||||
Q_EMIT SearchCoverInProgress();
|
||||
album_cover_choice_controller_->SearchCoverAutomatically(song_);
|
||||
}
|
||||
|
||||
@@ -3267,7 +3211,8 @@ void MainWindow::PlaylistDelete() {
|
||||
SongList selected_songs;
|
||||
QStringList files;
|
||||
bool is_current_item = false;
|
||||
for (const QModelIndex &proxy_idx : ui_->playlist->view()->selectionModel()->selectedRows()) {
|
||||
const QModelIndexList proxy_indexes = ui_->playlist->view()->selectionModel()->selectedRows();
|
||||
for (const QModelIndex &proxy_idx : proxy_indexes) {
|
||||
QModelIndex source_idx = app_->playlist_manager()->current()->filter()->mapToSource(proxy_idx);
|
||||
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_idx.row());
|
||||
if (!item || !item->Metadata().url().isLocalFile()) continue;
|
||||
|
||||
@@ -120,18 +120,14 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
||||
void closeEvent(QCloseEvent *e) override;
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
#ifdef Q_OS_WIN
|
||||
# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result) override;
|
||||
# else
|
||||
bool nativeEvent(const QByteArray &eventType, void *message, long *result) override;
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// PlatformInterface
|
||||
void Activate() override;
|
||||
bool LoadUrl(const QString &url) override;
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void AlbumCoverReady(const Song &song, const QImage &image);
|
||||
void SearchCoverInProgress();
|
||||
// Signals that stop playing after track was toggled.
|
||||
@@ -139,7 +135,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
||||
|
||||
void AuthorizationUrlReceived(const QUrl &url);
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void FilePathChanged(const QString &path);
|
||||
|
||||
void EngineChanged(const EngineBase::Type enginetype);
|
||||
@@ -238,9 +234,6 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
||||
void ToggleSidebar(const bool checked);
|
||||
void ToggleSearchCoverAuto(const bool checked);
|
||||
void SaveGeometry();
|
||||
void SavePlaybackStatus();
|
||||
void LoadPlaybackStatus();
|
||||
void ResumePlayback();
|
||||
|
||||
void Exit();
|
||||
void DoExit();
|
||||
@@ -274,7 +267,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
||||
|
||||
void DeleteFilesFinished(const SongList &songs_with_errors);
|
||||
|
||||
public slots:
|
||||
public Q_SLOTS:
|
||||
void CommandlineOptionsReceived(const QByteArray &string_options);
|
||||
void Raise();
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="sidebar_layout">
|
||||
<layout class="QVBoxLayout" name="layout_left">
|
||||
@@ -77,7 +77,7 @@
|
||||
<item>
|
||||
<widget class="Line" name="line_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -102,7 +102,7 @@
|
||||
<item>
|
||||
<widget class="QFrame" name="player_controls">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
<enum>QFrame::Shape::NoFrame</enum>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="layout_player_controls">
|
||||
<property name="spacing">
|
||||
@@ -167,7 +167,7 @@
|
||||
</size>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::MenuButtonPopup</enum>
|
||||
<enum>QToolButton::ToolButtonPopupMode::MenuButtonPopup</enum>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
@@ -211,7 +211,7 @@
|
||||
<item>
|
||||
<widget class="Line" name="line_love">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -237,7 +237,7 @@
|
||||
<item>
|
||||
<widget class="Line" name="line_buttons">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -260,10 +260,10 @@
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
<enum>QSizePolicy::Policy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -276,7 +276,7 @@
|
||||
<item>
|
||||
<widget class="Line" name="line_volume">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -292,7 +292,7 @@
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -326,7 +326,7 @@
|
||||
<item>
|
||||
<widget class="Line" name="status_bar_line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -380,7 +380,7 @@
|
||||
<item>
|
||||
<widget class="QLabel" name="playlist_summary">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -391,7 +391,7 @@
|
||||
<item>
|
||||
<widget class="Line" name="line_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -401,7 +401,7 @@
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -524,7 +524,7 @@
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_update_collection"/>
|
||||
<addaction name="action_full_collection_scan"/>
|
||||
<addaction name="action_abort_collection_scan"/>
|
||||
<addaction name="action_stop_collection_scan"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_settings"/>
|
||||
<addaction name="action_import_data_from_last_fm"/>
|
||||
@@ -580,7 +580,7 @@
|
||||
<string>Ctrl+Q</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::QuitRole</enum>
|
||||
<enum>QAction::MenuRole::QuitRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_stop_after_this_track">
|
||||
@@ -644,7 +644,7 @@
|
||||
<string>Ctrl+P</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::PreferencesRole</enum>
|
||||
<enum>QAction::MenuRole::PreferencesRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_about_strawberry">
|
||||
@@ -659,7 +659,7 @@
|
||||
<string>F1</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::AboutRole</enum>
|
||||
<enum>QAction::MenuRole::AboutRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_shuffle">
|
||||
@@ -785,7 +785,7 @@
|
||||
<string>About &Qt</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::AboutQtRole</enum>
|
||||
<enum>QAction::MenuRole::AboutQtRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_mute">
|
||||
@@ -804,9 +804,12 @@
|
||||
<string>&Do a full collection rescan</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_abort_collection_scan">
|
||||
<action name="action_stop_collection_scan">
|
||||
<property name="text">
|
||||
<string>Abort collection scan</string>
|
||||
<string>Stop collection scan</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Stop collection scan</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_auto_complete_tags">
|
||||
|
||||
@@ -232,7 +232,7 @@ void MergedProxyModel::SubModelResetSlot() {
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
emit SubModelReset(proxy_parent, submodel);
|
||||
Q_EMIT SubModelReset(proxy_parent, submodel);
|
||||
|
||||
}
|
||||
|
||||
@@ -516,7 +516,7 @@ QAbstractItemModel *MergedProxyModel::GetModel(const QModelIndex &source_index)
|
||||
}
|
||||
|
||||
void MergedProxyModel::DataChanged(const QModelIndex &top_left, const QModelIndex &bottom_right) {
|
||||
emit dataChanged(mapFromSource(top_left), mapFromSource(bottom_right));
|
||||
Q_EMIT dataChanged(mapFromSource(top_left), mapFromSource(bottom_right));
|
||||
}
|
||||
|
||||
void MergedProxyModel::LayoutAboutToBeChanged() {
|
||||
@@ -535,8 +535,8 @@ void MergedProxyModel::LayoutChanged() {
|
||||
for (QAbstractItemModel *model : models) {
|
||||
if (!old_merge_points_.contains(model)) continue;
|
||||
|
||||
const int old_row = old_merge_points_[model].row();
|
||||
const int new_row = merge_points_[model].row();
|
||||
const int old_row = old_merge_points_.value(model).row();
|
||||
const int new_row = merge_points_.value(model).row();
|
||||
|
||||
if (old_row != new_row) {
|
||||
beginResetModel();
|
||||
|
||||
@@ -85,10 +85,10 @@ class MergedProxyModel : public QAbstractProxyModel {
|
||||
QModelIndexList mapFromSource(const QModelIndexList &source_indexes) const;
|
||||
QModelIndexList mapToSource(const QModelIndexList &proxy_indexes) const;
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void SubModelReset(const QModelIndex root, QAbstractItemModel *model);
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void SourceModelReset();
|
||||
void SubModelAboutToBeReset();
|
||||
void SubModelResetSlot();
|
||||
|
||||
@@ -81,6 +81,8 @@
|
||||
#include "smartplaylists/smartplaylistsearchterm.h"
|
||||
#include "smartplaylists/smartplaylistsitem.h"
|
||||
|
||||
#include "lyrics/lyricssearchresult.h"
|
||||
|
||||
void RegisterMetaTypes() {
|
||||
|
||||
qRegisterMetaType<const char*>("const char*");
|
||||
@@ -94,10 +96,6 @@ void RegisterMetaTypes() {
|
||||
qRegisterMetaType<QItemSelection>("QItemSelection");
|
||||
qRegisterMetaType<QMap<int, Qt::Alignment>>("ColumnAlignmentMap");
|
||||
qRegisterMetaType<QMap<int, int>>("ColumnAlignmentIntMap");
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
qRegisterMetaTypeStreamOperators<QMap<int, Qt::Alignment>>("ColumnAlignmentMap");
|
||||
qRegisterMetaTypeStreamOperators<QMap<int, int>>("ColumnAlignmentIntMap");
|
||||
#endif
|
||||
qRegisterMetaType<Song>("Song");
|
||||
qRegisterMetaType<SongList>("SongList");
|
||||
qRegisterMetaType<SongMap>("SongMap");
|
||||
@@ -131,10 +129,6 @@ void RegisterMetaTypes() {
|
||||
|
||||
qRegisterMetaType<Equalizer::Params>("Equalizer::Params");
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
qRegisterMetaTypeStreamOperators<Equalizer::Params>("Equalizer::Params");
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
qDBusRegisterMetaType<QByteArrayList>();
|
||||
qDBusRegisterMetaType<QImage>();
|
||||
@@ -167,4 +161,6 @@ void RegisterMetaTypes() {
|
||||
qRegisterMetaType<SmartPlaylistSearchTerm::DateType>("SmartPlaylistSearchTerm::DateType");
|
||||
qRegisterMetaType<SmartPlaylistsItem::Type>("SmartPlaylistsItem::Type");
|
||||
|
||||
qRegisterMetaType<LyricsSearchResults>("LyricsSearchResults");
|
||||
|
||||
}
|
||||
|
||||
44
src/core/mimedata.cpp
Normal file
44
src/core/mimedata.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "mimedata.h"
|
||||
|
||||
MimeData::MimeData(const bool clear, const bool play_now, const bool enqueue, const bool enqueue_next_now, const bool open_in_new_playlist, QObject *parent)
|
||||
: override_user_settings_(false),
|
||||
clear_first_(clear),
|
||||
play_now_(play_now),
|
||||
enqueue_now_(enqueue),
|
||||
enqueue_next_now_(enqueue_next_now),
|
||||
open_in_new_playlist_(open_in_new_playlist),
|
||||
name_for_new_playlist_(QString()),
|
||||
from_doubleclick_(false) {
|
||||
|
||||
Q_UNUSED(parent);
|
||||
|
||||
}
|
||||
|
||||
QString MimeData::get_name_for_new_playlist() const {
|
||||
return name_for_new_playlist_.isEmpty() ? tr("Playlist") : name_for_new_playlist_;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -21,9 +22,6 @@
|
||||
#ifndef MIMEDATA_H
|
||||
#define MIMEDATA_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QMimeData>
|
||||
#include <QString>
|
||||
|
||||
@@ -31,15 +29,7 @@ class MimeData : public QMimeData {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MimeData(const bool clear = false, const bool play_now = false, const bool enqueue = false, const bool enqueue_next_now = false, const bool open_in_new_playlist = false, QObject* = nullptr)
|
||||
: override_user_settings_(false),
|
||||
clear_first_(clear),
|
||||
play_now_(play_now),
|
||||
enqueue_now_(enqueue),
|
||||
enqueue_next_now_(enqueue_next_now),
|
||||
open_in_new_playlist_(open_in_new_playlist),
|
||||
name_for_new_playlist_(QString()),
|
||||
from_doubleclick_(false) {}
|
||||
explicit MimeData(const bool clear = false, const bool play_now = false, const bool enqueue = false, const bool enqueue_next_now = false, const bool open_in_new_playlist = false, QObject *parent = nullptr);
|
||||
|
||||
// If this is set then MainWindow will not touch any of the other flags.
|
||||
bool override_user_settings_;
|
||||
@@ -69,9 +59,7 @@ class MimeData : public QMimeData {
|
||||
|
||||
// Returns a pretty name for a playlist containing songs described by this MimeData object.
|
||||
// By pretty name we mean the value of 'name_for_new_playlist_' or generic "Playlist" string if the 'name_for_new_playlist_' attribute is empty.
|
||||
QString get_name_for_new_playlist() {
|
||||
return name_for_new_playlist_.isEmpty() ? tr("Playlist") : name_for_new_playlist_;
|
||||
}
|
||||
QString get_name_for_new_playlist() const;
|
||||
};
|
||||
|
||||
#endif // MIMEDATA_H
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user