Compare commits
189 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
8ddd309d5d | ||
|
|
8c8acbb546 | ||
|
|
fe30f27af3 | ||
|
|
d5d2eaba8a | ||
|
|
e8f64bfe8f | ||
|
|
79b03993b0 | ||
|
|
e3b8f9cb8c | ||
|
|
819463a865 | ||
|
|
c69777ca39 | ||
|
|
40f3e828aa | ||
|
|
dc8520ebec | ||
|
|
7ffbedf0dd | ||
|
|
826fad1ad4 | ||
|
|
1c4d3aebad | ||
|
|
ff6e93fc15 | ||
|
|
17e88bb97d | ||
|
|
d3dd26c596 | ||
|
|
c2a7509e0e | ||
|
|
079040b721 | ||
|
|
b80d239820 | ||
|
|
c3ff43dac2 | ||
|
|
4ac51afd8f | ||
|
|
26eab51698 | ||
|
|
388dcf2a1f | ||
|
|
9bfd14d067 | ||
|
|
061e0562d1 | ||
|
|
2ba20b013d | ||
|
|
dc36f87a6e | ||
|
|
e82ecb48b8 | ||
|
|
c56a134179 | ||
|
|
11028456ad | ||
|
|
81fcb02974 | ||
|
|
5ef4976c53 | ||
|
|
160356072f | ||
|
|
c10df5b634 | ||
|
|
70fdd5b0b3 | ||
|
|
6c1ec51fab | ||
|
|
66b1c22174 | ||
|
|
97138fd6b9 | ||
|
|
03c69be421 | ||
|
|
3a9a952cb0 | ||
|
|
3bd0331aa3 | ||
|
|
3ecf224d91 | ||
|
|
37743606a2 | ||
|
|
58587f4a9a | ||
|
|
464fde1851 | ||
|
|
2639498642 | ||
|
|
49f074737c | ||
|
|
3d53a8b434 | ||
|
|
e260433c7a | ||
|
|
88ef8bff0b | ||
|
|
fbdac36f6f | ||
|
|
da3876bd83 | ||
|
|
d303e700ae | ||
|
|
92a1173b9e | ||
|
|
9f7ebb1ac7 | ||
|
|
1a8690e1f2 | ||
|
|
6543e4c5da | ||
|
|
dd904fe3c2 | ||
|
|
c14cc6bf0b | ||
|
|
95c265ffd3 | ||
|
|
31c1ae68df | ||
|
|
f2eb0c3b6b | ||
|
|
32be33847c | ||
|
|
3100b0c044 | ||
|
|
f4ec3ab379 | ||
|
|
cdd7faa9bb | ||
|
|
e7b35aeaf7 | ||
|
|
696256eb5b | ||
|
|
8ad560ce0e | ||
|
|
1c71506f62 | ||
|
|
8bea6ec5b0 | ||
|
|
e8144487ee | ||
|
|
41d9d15dda | ||
|
|
124b97c024 | ||
|
|
98e0b45403 | ||
|
|
1f2b8d8bf6 | ||
|
|
8327751b91 | ||
|
|
6417f89596 | ||
|
|
2e53656f44 | ||
|
|
822cf0ad07 | ||
|
|
67f04a81b3 | ||
|
|
9232ad0125 | ||
|
|
0de87b3e1e | ||
|
|
74b8cd6156 | ||
|
|
ac959387fe | ||
|
|
ffd8ce9281 |
59
.github/workflows/build.yml
vendored
59
.github/workflows/build.yml
vendored
@@ -171,7 +171,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
fedora_version: [ '39', '40' ]
|
||||
fedora_version: [ '39', '40', '41' ]
|
||||
container:
|
||||
image: fedora:${{matrix.fedora_version}}
|
||||
steps:
|
||||
@@ -544,7 +544,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
ubuntu_version: [ 'focal', 'jammy', 'mantic', 'noble' ]
|
||||
ubuntu_version: [ 'focal', 'jammy', 'noble', 'oracular' ]
|
||||
container:
|
||||
image: ubuntu:${{matrix.ubuntu_version}}
|
||||
steps:
|
||||
@@ -633,7 +633,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
ubuntu_version: [ 'focal', 'jammy', 'mantic', 'noble' ]
|
||||
ubuntu_version: [ 'focal', 'jammy', 'noble', 'oracular' ]
|
||||
container:
|
||||
image: ubuntu:${{matrix.ubuntu_version}}
|
||||
steps:
|
||||
@@ -730,13 +730,27 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
runner: [ 'macos-12' ]
|
||||
runner: [ 'macos-13', 'macos-14' ]
|
||||
buildtype: [ 'release' ]
|
||||
|
||||
runs-on: ${{ matrix.runner }}
|
||||
|
||||
steps:
|
||||
|
||||
- name: Set MACOSX_DEPLOYMENT_TARGET
|
||||
run: |
|
||||
for i in 12 13 14 15; do
|
||||
if [ -d "/Library/Developer/CommandLineTools/SDKs/MacOSX${i}.sdk" ]; then
|
||||
echo "Using macOS SDK ${i}"
|
||||
echo "MACOSX_DEPLOYMENT_TARGET=${i}.0" >> $GITHUB_ENV
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Verify MACOSX_DEPLOYMENT_TARGET
|
||||
run: |
|
||||
test "${MACOSX_DEPLOYMENT_TARGET}" = "" && false || echo "MACOSX_DEPLOYMENT_TARGET: ${MACOSX_DEPLOYMENT_TARGET}"
|
||||
|
||||
- name: Set arch
|
||||
shell: bash
|
||||
run: echo "arch=$(uname -m)" >> $GITHUB_ENV
|
||||
@@ -784,7 +798,6 @@ jobs:
|
||||
|
||||
- name: Configure CMake
|
||||
env:
|
||||
MACOSX_DEPLOYMENT_TARGET: 12.0
|
||||
PKG_CONFIG_PATH: ${{env.prefix_path}}/lib/pkgconfig
|
||||
LDFLAGS: -L${{env.prefix_path}}/lib -Wl,-rpath,${{env.prefix_path}}/lib
|
||||
run: >
|
||||
@@ -801,7 +814,7 @@ jobs:
|
||||
-DICU_ROOT="${{env.prefix_path}}"
|
||||
-DFFTW3_DIR="${{env.prefix_path}}"
|
||||
-DAPPLE_DEVELOPER_ID=$(test '${{github.repository}}' = 'strawberrymusicplayer/strawberry' && test '${{github.event.pull_request.base.repo.full_name}}' = '${{github.event.pull_request.head.repo.full_name}}' && echo "383J84DVB6" || echo "")
|
||||
-DENABLE_SPOTIFY=OFF
|
||||
-DENABLE_SPOTIFY=$(test -f "${{env.prefix_path}}/lib/gstreamer-1.0/libgstspotify.dylib" && echo "ON" || echo "OFF")
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --config Release --parallel 4
|
||||
@@ -820,9 +833,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
|
||||
@@ -877,6 +895,20 @@ jobs:
|
||||
|
||||
steps:
|
||||
|
||||
- name: Set MACOSX_DEPLOYMENT_TARGET
|
||||
run: |
|
||||
for i in 12 13 14 15; do
|
||||
if [ -d "/Library/Developer/CommandLineTools/SDKs/MacOSX${i}.sdk" ]; then
|
||||
echo "Using macOS SDK ${i}"
|
||||
echo "MACOSX_DEPLOYMENT_TARGET=${i}.0" >> $GITHUB_ENV
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Verify MACOSX_DEPLOYMENT_TARGET
|
||||
run: |
|
||||
test "${MACOSX_DEPLOYMENT_TARGET}" = "" && false || echo "MACOSX_DEPLOYMENT_TARGET: ${MACOSX_DEPLOYMENT_TARGET}"
|
||||
|
||||
- name: Set arch
|
||||
shell: bash
|
||||
run: echo "arch=$(uname -m)" >> $GITHUB_ENV
|
||||
@@ -907,7 +939,6 @@ jobs:
|
||||
|
||||
- name: Configure CMake
|
||||
env:
|
||||
MACOSX_DEPLOYMENT_TARGET: 11.0
|
||||
PKG_CONFIG_PATH: ${{env.prefix_path}}/lib/pkgconfig
|
||||
LDFLAGS: -L${{env.prefix_path}}/lib -Wl,-rpath,${{env.prefix_path}}/lib
|
||||
run: >
|
||||
@@ -924,7 +955,7 @@ jobs:
|
||||
-DICU_ROOT="${{env.prefix_path}}"
|
||||
-DFFTW3_DIR="${{env.prefix_path}}"
|
||||
-DAPPLE_DEVELOPER_ID="383J84DVB6"
|
||||
-DENABLE_SPOTIFY=OFF
|
||||
-DENABLE_SPOTIFY=$(test -f "${{env.prefix_path}}/lib/gstreamer-1.0/libgstspotify.dylib" && echo "ON" || echo "OFF")
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --config Release --parallel 4
|
||||
@@ -967,11 +998,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:
|
||||
|
||||
134
.gitignore
vendored
134
.gitignore
vendored
@@ -1,120 +1,18 @@
|
||||
# This file is used to ignore files which are generated
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# Build
|
||||
build/
|
||||
bin/
|
||||
|
||||
# CMake
|
||||
CMakeLists.txt.user
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
CMakeScripts
|
||||
Makefile*
|
||||
Testing
|
||||
cmake_install.cmake
|
||||
install_manifest.txt
|
||||
compile_commands.json
|
||||
CTestTestfile.cmake
|
||||
_deps
|
||||
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
*.smod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
# Dump files
|
||||
*.core
|
||||
*.stackdump
|
||||
|
||||
# Qt
|
||||
*build-*
|
||||
moc_*.cpp
|
||||
moc_*.h
|
||||
qrc_*.cpp
|
||||
ui_*.h
|
||||
*.moc
|
||||
*.qm
|
||||
|
||||
# Temporary files
|
||||
*~
|
||||
*.autosave
|
||||
*.orig
|
||||
*.rej
|
||||
.*.kate-swp
|
||||
.swp.*
|
||||
.*.swp
|
||||
*.flc
|
||||
|
||||
# Directory files
|
||||
.directory
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# MinGW generated files
|
||||
*.Debug
|
||||
*.Release
|
||||
|
||||
# Package files
|
||||
*.spec
|
||||
*.nsi
|
||||
*.plist
|
||||
|
||||
# Stuff in dist
|
||||
maketarball.sh
|
||||
changelog
|
||||
|
||||
# Translations
|
||||
translations.pot
|
||||
zanata.xml
|
||||
.zanata-cache/
|
||||
|
||||
# QtCreator
|
||||
CMakeLists.txt.user*
|
||||
*.pro.user
|
||||
*.pro.user.*
|
||||
*creator.user*
|
||||
target_wrapper.*
|
||||
compile_commands.json
|
||||
|
||||
*.kdev4
|
||||
*.vscode
|
||||
*.code-workspace
|
||||
*.sublime-workspace
|
||||
|
||||
# MSVC
|
||||
CMakeSettings.json
|
||||
/build
|
||||
/bin
|
||||
/CMakeLists.txt.user
|
||||
/.kdev4
|
||||
/strawberry.kdev4
|
||||
/.vscode
|
||||
/.code-workspace
|
||||
/.sublime-workspace
|
||||
/.idea
|
||||
/.vs
|
||||
/out
|
||||
|
||||
# CLion
|
||||
/.idea
|
||||
/CMakeSettings.json
|
||||
/dist/scripts/maketarball.sh
|
||||
/dist/unix/strawberry.spec
|
||||
/dist/windows/strawberry.nsi
|
||||
/debian/control
|
||||
/debian/changelog
|
||||
/dist/macos/Info.plist
|
||||
|
||||
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})
|
||||
|
||||
@@ -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")
|
||||
@@ -105,7 +114,10 @@ find_package(Backtrace)
|
||||
if(Backtrace_FOUND)
|
||||
set(HAVE_BACKTRACE ON)
|
||||
endif()
|
||||
find_package(Boost REQUIRED)
|
||||
find_package(Boost CONFIG)
|
||||
if(NOT Boost_FOUND)
|
||||
find_package(Boost REQUIRED)
|
||||
endif()
|
||||
find_package(ICU COMPONENTS uc i18n REQUIRED)
|
||||
find_package(Protobuf CONFIG)
|
||||
if(NOT Protobuf_FOUND)
|
||||
@@ -491,6 +503,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)
|
||||
|
||||
48
Changelog
48
Changelog
@@ -2,7 +2,48 @@ Strawberry Music Player
|
||||
=======================
|
||||
ChangeLog
|
||||
|
||||
Version 1.1.0-rc2 (2024.07.09):
|
||||
Version 1.1.2-rc1 (2024.09.06):
|
||||
|
||||
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).
|
||||
* 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 marked text to be unmarked.
|
||||
* 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).
|
||||
* (macOS) Fixed missing Spotify.
|
||||
|
||||
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.
|
||||
|
||||
Version 1.1.1 (2024.07.22):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed compilation songs being split into different albums when using album grouping.
|
||||
* Fixed adding playlist columns not working when stretch mode is disabled (#1085).
|
||||
* Fixed resetting playlist columns.
|
||||
* Fixed adding songs to playlist adding all songs instead of filtered songs.
|
||||
* Fixed collection filter matching entire text instead of individual words.
|
||||
|
||||
Enhancements:
|
||||
* Use same code for collection and playlist filter search.
|
||||
|
||||
Version 1.1.0 (2024.07.14):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed crash when pressing CTRL + C (#1359).
|
||||
@@ -28,14 +69,13 @@ Version 1.1.0-rc2 (2024.07.09):
|
||||
* Only use playbin3 with GStreamer 1.24 and higher, not with GStreamer 1.22 or lower.
|
||||
* (macOS/Windows) Fixed dash and hls streaming, plugins were missing.
|
||||
* (Windows) Fixed incorrect colors in smart playlist wizard with Fusion in dark mode (#1399).
|
||||
* (Windows) Fixed update window blocking sponsor window on startup.
|
||||
|
||||
Enhancements:
|
||||
* Improve error messages when connecting and copying to devices.
|
||||
* Allow enter to be used with multiselection to add songs to playlist (#1360)
|
||||
* Add song progress to taskbar using D-Bus.
|
||||
* Use API to receive Radio Paradise channels.
|
||||
* Added letras lyrics provider.
|
||||
* Added Open Tidal API (openapi.tidal.com) cover provider.
|
||||
* Added button for fetching lyrics to tag editor (#1391).
|
||||
* Added option not to skip "A", "An" and "The” when sorting artist names in collection (#1393).
|
||||
* Improved album and title disc, remastered, etc matching and stripping (#1387).
|
||||
@@ -54,6 +94,8 @@ Version 1.1.0-rc2 (2024.07.09):
|
||||
* (Windows MSVC) Add back WASAPI2.
|
||||
|
||||
New features:
|
||||
* Letras lyrics provider.
|
||||
* Open Tidal API (openapi.tidal.com) cover provider.
|
||||
* Turbine analyzer.
|
||||
* WaveRubber analyzer.
|
||||
* Spotify streaming support.
|
||||
|
||||
@@ -14,11 +14,11 @@ 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
|
||||
* Ubuntu Unstable PPA: https://launchpad.net/~jonaski/+archive/ubuntu/strawberry-unstable
|
||||
* Translations: https://crowdin.com/project/strawberrymusicplayer/
|
||||
|
||||
### :bangbang: Opening an issue
|
||||
|
||||
@@ -64,7 +64,7 @@ Funding developers is a way to contribute to open source projects you appreciate
|
||||
|
||||
It has so far been tested to work on Linux, OpenBSD, FreeBSD, macOS and Windows.
|
||||
|
||||
**macOS releases are currently limited to sponsors. This is because Strawberry mainly has one contributor/developer and supporting macOS requires Apple hardware, building libraries Strawberry depends and a Apple developer account for signing releases. If you are sponsoring strawberry through Patreon, releases are available directly on Patreon, if you are sponsoring through GitHub, Ko-fi or Paypal, please e-mail support@strawberrymusicplayer.org for access to downloads.**
|
||||
**Access to macOS and Windows releases are currently restricted to sponsors, a 5 USD monthly sponsorship is required. You can sponsor strawberry through <a href="https://www.patreon.com/jonaskvinge">Patreon</a> for direct access to new releases. If you are sponsoring through GitHub, Ko-fi or PayPal, please e-mail support AT strawberrymusicplayer.org for access to downloads.**
|
||||
|
||||
### :heavy_exclamation_mark: Requirements
|
||||
|
||||
@@ -119,4 +119,4 @@ To compile on Windows with Visual Studio 2019 or 2022, see https://github.com/st
|
||||
|
||||
### :penguin: Packaging status
|
||||
|
||||
[](https://repology.org/metapackage/strawberry/versions)
|
||||
[](https://repology.org/metapackage/strawberry/versions)
|
||||
|
||||
@@ -44,7 +44,7 @@ macro(add_pot outfiles header pot)
|
||||
add_custom_command(
|
||||
OUTPUT ${pot}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMAND xgettext ${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,7 +1,7 @@
|
||||
set(STRAWBERRY_VERSION_MAJOR 1)
|
||||
set(STRAWBERRY_VERSION_MINOR 1)
|
||||
set(STRAWBERRY_VERSION_PATCH 0)
|
||||
set(STRAWBERRY_VERSION_PRERELEASE rc2)
|
||||
set(STRAWBERRY_VERSION_PATCH 2)
|
||||
set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
||||
|
||||
set(INCLUDE_GIT_REVISION OFF)
|
||||
|
||||
|
||||
3
crowdin.yml
Normal file
3
crowdin.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
files:
|
||||
- source: /src/translations/translations.pot
|
||||
translation: /src/translations/%locale_with_underscore%.po
|
||||
@@ -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;
|
||||
}
|
||||
1
dist/macos/macgstcopy.sh
vendored
1
dist/macos/macgstcopy.sh
vendored
@@ -111,6 +111,7 @@ libgstrtsp
|
||||
libgstsoup
|
||||
libgstspectrum
|
||||
libgstspeex
|
||||
libgstspotify
|
||||
libgsttaglib
|
||||
libgsttcp
|
||||
libgsttwolame
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
<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>
|
||||
@@ -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.1" date="2024-07-22"/>
|
||||
<release version="1.1.0" date="2024-07-14"/>
|
||||
<release version="1.0.23" date="2024-01-11"/>
|
||||
<release version="1.0.22" date="2023-12-09"/>
|
||||
<release version="1.0.21" date="2023-10-21"/>
|
||||
|
||||
34
dist/windows/strawberry.nsi.in
vendored
34
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"
|
||||
@@ -328,6 +329,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"
|
||||
@@ -450,6 +452,7 @@ Section "Strawberry" Strawberry
|
||||
File "liblzma.dll"
|
||||
File "libmp3lame.dll"
|
||||
File "libopenmpt.dll"
|
||||
File "utf8_validity.dll"
|
||||
File "mpcdec.dll"
|
||||
File "mpg123.dll"
|
||||
File "nghttp2.dll"
|
||||
@@ -526,13 +529,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 +834,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"
|
||||
@@ -898,6 +902,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"
|
||||
@@ -1020,6 +1025,7 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\liblzma.dll"
|
||||
Delete "$INSTDIR\libmp3lame.dll"
|
||||
Delete "$INSTDIR\libopenmpt.dll"
|
||||
Delete "$INSTDIR\utf8_validity.dll"
|
||||
Delete "$INSTDIR\mpcdec.dll"
|
||||
Delete "$INSTDIR\mpg123.dll"
|
||||
Delete "$INSTDIR\nghttp2.dll"
|
||||
@@ -1095,13 +1101,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,16 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
set(SOURCES gstfastspectrum.cpp gstmoodbarplugin.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(gstmoodbar STATIC ${SOURCES})
|
||||
|
||||
target_include_directories(gstmoodbar SYSTEM PRIVATE
|
||||
@@ -24,6 +15,15 @@ target_include_directories(gstmoodbar SYSTEM PRIVATE
|
||||
|
||||
target_include_directories(gstmoodbar PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
target_link_directories(gstmoodbar PRIVATE
|
||||
${GLIB_LIBRARY_DIRS}
|
||||
${GOBJECT_LIBRARY_DIRS}
|
||||
${GSTREAMER_LIBRARY_DIRS}
|
||||
${GSTREAMER_BASE_LIBRARY_DIRS}
|
||||
${GSTREAMER_AUDIO_LIBRARY_DIRS}
|
||||
${FFTW3_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(gstmoodbar PRIVATE
|
||||
${GLIB_LIBRARIES}
|
||||
${GOBJECT_LIBRARIES}
|
||||
|
||||
@@ -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>
|
||||
@@ -157,12 +159,13 @@ static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QStr
|
||||
break;
|
||||
}
|
||||
|
||||
for (const QString &line : message.split(QLatin1Char('\n'))) {
|
||||
const QStringList lines = message.split(QLatin1Char('\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,7 +196,8 @@ void SetLevels(const QString &levels) {
|
||||
|
||||
if (!sClassLevels) return;
|
||||
|
||||
for (const QString &item : levels.split(QLatin1Char(','))) {
|
||||
const QStringList items = levels.split(QLatin1Char(','));
|
||||
for (const QString &item : items) {
|
||||
const QStringList class_level = item.split(QLatin1Char(':'));
|
||||
|
||||
QString class_name;
|
||||
@@ -310,8 +314,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;
|
||||
}
|
||||
@@ -370,20 +374,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()
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "tagreaderbase.h"
|
||||
|
||||
TagReaderBase::TagReaderBase() = default;
|
||||
TagReaderBase::~TagReaderBase() = default;
|
||||
|
||||
QString TagReaderBase::ErrorString(const Result &result) {
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
class TagReaderBase {
|
||||
public:
|
||||
explicit TagReaderBase();
|
||||
~TagReaderBase() = default;
|
||||
virtual ~TagReaderBase();
|
||||
|
||||
class Result {
|
||||
public:
|
||||
|
||||
@@ -229,6 +229,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);
|
||||
|
||||
@@ -99,7 +99,6 @@ class TagReaderGME : public TagReaderBase {
|
||||
|
||||
public:
|
||||
explicit TagReaderGME();
|
||||
~TagReaderGME() = default;
|
||||
|
||||
bool IsMediaFile(const QString &filename) const override;
|
||||
|
||||
|
||||
@@ -798,13 +798,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 +825,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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1211,7 +1211,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());
|
||||
}
|
||||
|
||||
@@ -1870,3 +1870,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;
|
||||
|
||||
}
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
@@ -57,14 +55,14 @@ class FileRefFactory;
|
||||
class TagReaderTagLib : public TagReaderBase {
|
||||
public:
|
||||
explicit TagReaderTagLib();
|
||||
~TagReaderTagLib();
|
||||
~TagReaderTagLib() override;
|
||||
|
||||
static inline TagLib::String StdStringToTagLibString(const std::string &s) {
|
||||
return TagLib::String(s.c_str(), TagLib::String::UTF8);
|
||||
}
|
||||
|
||||
static inline std::string TagLibStringToStdString(const TagLib::String &s) {
|
||||
return std::string(s.toCString(), s.length());
|
||||
return std::string(s.toCString(true), s.length());
|
||||
}
|
||||
|
||||
static inline TagLib::String QStringToTagLibString(const QString &s) {
|
||||
@@ -77,9 +75,9 @@ class TagReaderTagLib : public TagReaderBase {
|
||||
|
||||
static inline void AssignTagLibStringToStdString(const TagLib::String &tstr, std::string *output) {
|
||||
|
||||
std::string stdstr = TagLibStringToStdString(tstr);
|
||||
boost::trim(stdstr);
|
||||
output->assign(stdstr);
|
||||
const QString qstr = TagLibStringToQString(tstr).trimmed();
|
||||
const QByteArray data = qstr.toUtf8();
|
||||
output->assign(data.constData(), data.size());
|
||||
|
||||
}
|
||||
|
||||
@@ -88,7 +86,7 @@ class TagReaderTagLib : public TagReaderBase {
|
||||
Result ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
||||
Result WriteFile(const QString &filename, const spb::tagreader::WriteFileRequest &request) const override;
|
||||
|
||||
Result LoadEmbeddedArt(const QString &filename, QByteArray &data) const;
|
||||
Result LoadEmbeddedArt(const QString &filename, QByteArray &data) const override;
|
||||
Result SaveEmbeddedArt(const QString &filename, const spb::tagreader::SaveEmbeddedArtRequest &request) const override;
|
||||
|
||||
Result SaveSongPlaycountToFile(const QString &filename, const uint playcount) const override;
|
||||
@@ -138,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_;
|
||||
|
||||
|
||||
@@ -46,8 +46,6 @@
|
||||
|
||||
TagReaderTagParser::TagReaderTagParser() = default;
|
||||
|
||||
TagReaderTagParser::~TagReaderTagParser() = default;
|
||||
|
||||
bool TagReaderTagParser::IsMediaFile(const QString &filename) const {
|
||||
|
||||
qLog(Debug) << "Checking for valid file" << filename;
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
class TagReaderTagParser : public TagReaderBase {
|
||||
public:
|
||||
explicit TagReaderTagParser();
|
||||
~TagReaderTagParser();
|
||||
|
||||
bool IsMediaFile(const QString &filename) const override;
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
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}
|
||||
@@ -8,6 +10,7 @@ target_include_directories(macdeploycheck PUBLIC
|
||||
${CMAKE_SOURCE_DIR}/ext/libstrawberry-common
|
||||
${CMAKE_BINARY_DIR}/src
|
||||
)
|
||||
target_link_directories(macdeploycheck PUBLIC ${GLIB_LIBRARY_DIRS})
|
||||
target_link_libraries(macdeploycheck PUBLIC
|
||||
"-framework AppKit"
|
||||
${GLIB_LIBRARIES}
|
||||
|
||||
@@ -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
|
||||
@@ -58,9 +61,11 @@ set(SOURCES
|
||||
utilities/filemanagerutils.cpp
|
||||
utilities/coverutils.cpp
|
||||
utilities/screenutils.cpp
|
||||
utilities/searchparserutils.cpp
|
||||
utilities/textencodingutils.cpp
|
||||
|
||||
filterparser/filterparser.cpp
|
||||
filterparser/filtertree.cpp
|
||||
|
||||
engine/enginebase.cpp
|
||||
engine/enginedevice.cpp
|
||||
engine/devicefinders.cpp
|
||||
@@ -96,7 +101,6 @@ set(SOURCES
|
||||
collection/collectionfilter.cpp
|
||||
collection/collectionplaylistitem.cpp
|
||||
collection/collectionquery.cpp
|
||||
collection/collectionqueryoptions.cpp
|
||||
collection/savedgroupingmanager.cpp
|
||||
collection/groupbydialog.cpp
|
||||
collection/collectiontask.cpp
|
||||
@@ -107,11 +111,12 @@ set(SOURCES
|
||||
playlist/playlistcontainer.cpp
|
||||
playlist/playlistdelegates.cpp
|
||||
playlist/playlistfilter.cpp
|
||||
playlist/playlistfilterparser.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
|
||||
|
||||
@@ -230,6 +243,8 @@ set(SOURCES
|
||||
widgets/busyindicator.cpp
|
||||
widgets/clickablelabel.cpp
|
||||
widgets/fancytabwidget.cpp
|
||||
widgets/fancytabbar.cpp
|
||||
widgets/fancytabdata.cpp
|
||||
widgets/favoritewidget.cpp
|
||||
widgets/fileview.cpp
|
||||
widgets/fileviewlist.cpp
|
||||
@@ -268,6 +283,7 @@ set(SOURCES
|
||||
streaming/streamingcollectionview.cpp
|
||||
streaming/streamingcollectionviewcontainer.cpp
|
||||
streaming/streamingsearchview.cpp
|
||||
streaming/streamsongmimedata.cpp
|
||||
|
||||
radios/radioservices.cpp
|
||||
radios/radiobackend.cpp
|
||||
@@ -279,6 +295,7 @@ set(SOURCES
|
||||
radios/radiochannel.cpp
|
||||
radios/somafmservice.cpp
|
||||
radios/radioparadiseservice.cpp
|
||||
radios/radiomimedata.cpp
|
||||
|
||||
scrobbler/audioscrobbler.cpp
|
||||
scrobbler/scrobblersettings.cpp
|
||||
@@ -294,6 +311,8 @@ set(SOURCES
|
||||
|
||||
organize/organize.cpp
|
||||
organize/organizeformat.cpp
|
||||
organize/organizeformatvalidator.cpp
|
||||
organize/organizesyntaxhighlighter.cpp
|
||||
organize/organizedialog.cpp
|
||||
organize/organizeerrordialog.cpp
|
||||
|
||||
@@ -396,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
|
||||
@@ -443,6 +467,7 @@ set(HEADERS
|
||||
lyrics/azlyricscomlyricsprovider.h
|
||||
lyrics/elyricsnetlyricsprovider.h
|
||||
lyrics/letraslyricsprovider.h
|
||||
lyrics/lyricfindlyricsprovider.h
|
||||
|
||||
settings/settingsdialog.h
|
||||
settings/settingspage.h
|
||||
@@ -476,6 +501,8 @@ set(HEADERS
|
||||
widgets/busyindicator.h
|
||||
widgets/clickablelabel.h
|
||||
widgets/fancytabwidget.h
|
||||
widgets/fancytabbar.h
|
||||
widgets/fancytabdata.h
|
||||
widgets/favoritewidget.h
|
||||
widgets/fileview.h
|
||||
widgets/fileviewlist.h
|
||||
@@ -495,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
|
||||
@@ -536,6 +563,8 @@ set(HEADERS
|
||||
scrobbler/lastfmimport.h
|
||||
|
||||
organize/organize.h
|
||||
organize/organizeformatvalidator.h
|
||||
organize/organizesyntaxhighlighter.h
|
||||
organize/organizedialog.h
|
||||
organize/organizeerrordialog.h
|
||||
|
||||
@@ -621,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)
|
||||
@@ -760,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
|
||||
@@ -838,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
|
||||
@@ -1000,95 +1030,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_SOURCE_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}
|
||||
@@ -1123,6 +1078,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}
|
||||
@@ -1151,11 +1116,13 @@ 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()
|
||||
|
||||
@@ -1168,6 +1135,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}
|
||||
@@ -1184,11 +1159,13 @@ 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()
|
||||
|
||||
@@ -1198,36 +1175,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()
|
||||
|
||||
@@ -1259,6 +1243,7 @@ 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()
|
||||
|
||||
|
||||
@@ -130,7 +130,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 +149,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,18 +200,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);
|
||||
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();
|
||||
|
||||
@@ -36,12 +36,14 @@
|
||||
#include "analyzerbase.h"
|
||||
#include "fht.h"
|
||||
|
||||
const int BlockAnalyzer::kHeight = 2;
|
||||
const int BlockAnalyzer::kWidth = 4;
|
||||
const int BlockAnalyzer::kMinRows = 3; // arbitrary
|
||||
const int BlockAnalyzer::kMinColumns = 32; // arbitrary
|
||||
const int BlockAnalyzer::kMaxColumns = 256; // must be 2**n
|
||||
const int BlockAnalyzer::kFadeSize = 90;
|
||||
namespace {
|
||||
constexpr int kHeight = 2;
|
||||
constexpr int kWidth = 4;
|
||||
constexpr int kMinRows = 3; // arbitrary
|
||||
constexpr int kMinColumns = 32; // arbitrary
|
||||
constexpr int kMaxColumns = 256; // must be 2**n
|
||||
constexpr int kFadeSize = 90;
|
||||
} // namespace
|
||||
|
||||
const char *BlockAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Block analyzer");
|
||||
|
||||
@@ -165,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;
|
||||
@@ -177,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());
|
||||
|
||||
@@ -43,13 +43,6 @@ class BlockAnalyzer : public AnalyzerBase {
|
||||
public:
|
||||
Q_INVOKABLE explicit BlockAnalyzer(QWidget*);
|
||||
|
||||
static const int kHeight;
|
||||
static const int kWidth;
|
||||
static const int kMinRows;
|
||||
static const int kMinColumns;
|
||||
static const int kMaxColumns;
|
||||
static const int kFadeSize;
|
||||
|
||||
static const char *kName;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -72,17 +72,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 +107,7 @@ void CollectionBackend::Exit() {
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
moveToThread(original_thread_);
|
||||
emit ExitFinished();
|
||||
Q_EMIT ExitFinished();
|
||||
|
||||
}
|
||||
|
||||
@@ -114,8 +118,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 +138,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 +149,7 @@ void CollectionBackend::GetAllSongs(const int id) {
|
||||
songs << song;
|
||||
}
|
||||
|
||||
emit GotSongs(songs, id);
|
||||
Q_EMIT GotSongs(songs, id);
|
||||
|
||||
}
|
||||
|
||||
@@ -189,7 +193,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 +321,7 @@ void CollectionBackend::UpdateTotalSongCount() {
|
||||
return;
|
||||
}
|
||||
|
||||
emit TotalSongCountUpdated(q.value(0).toInt());
|
||||
Q_EMIT TotalSongCountUpdated(q.value(0).toInt());
|
||||
|
||||
}
|
||||
|
||||
@@ -337,7 +341,7 @@ void CollectionBackend::UpdateTotalArtistCount() {
|
||||
return;
|
||||
}
|
||||
|
||||
emit TotalArtistCountUpdated(q.value(0).toInt());
|
||||
Q_EMIT TotalArtistCountUpdated(q.value(0).toInt());
|
||||
|
||||
}
|
||||
|
||||
@@ -357,7 +361,7 @@ void CollectionBackend::UpdateTotalAlbumCount() {
|
||||
return;
|
||||
}
|
||||
|
||||
emit TotalAlbumCountUpdated(q.value(0).toInt());
|
||||
Q_EMIT TotalAlbumCountUpdated(q.value(0).toInt());
|
||||
|
||||
}
|
||||
|
||||
@@ -395,7 +399,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 +441,7 @@ void CollectionBackend::RemoveDirectory(const CollectionDirectory &dir) {
|
||||
|
||||
transaction.Commit();
|
||||
|
||||
emit DirectoryDeleted(dir);
|
||||
Q_EMIT DirectoryDeleted(dir);
|
||||
|
||||
}
|
||||
|
||||
@@ -717,8 +721,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 +823,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 +871,7 @@ void CollectionBackend::DeleteSongs(const SongList &songs) {
|
||||
|
||||
transaction.Commit();
|
||||
|
||||
emit SongsDeleted(songs);
|
||||
Q_EMIT SongsDeleted(songs);
|
||||
|
||||
UpdateTotalSongCountAsync();
|
||||
UpdateTotalArtistCountAsync();
|
||||
@@ -894,10 +898,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();
|
||||
@@ -1410,7 +1414,7 @@ void CollectionBackend::CompilationsNeedUpdating() {
|
||||
transaction.Commit();
|
||||
|
||||
if (!changed_songs.isEmpty()) {
|
||||
emit SongsChanged(changed_songs);
|
||||
Q_EMIT SongsChanged(changed_songs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1497,7 +1501,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 +1510,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 {
|
||||
@@ -1616,7 +1621,7 @@ void CollectionBackend::UpdateEmbeddedAlbumArt(const QString &effective_albumart
|
||||
}
|
||||
|
||||
if (!songs.isEmpty()) {
|
||||
emit SongsChanged(songs);
|
||||
Q_EMIT SongsChanged(songs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1662,7 +1667,7 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis
|
||||
}
|
||||
|
||||
if (!songs.isEmpty()) {
|
||||
emit SongsChanged(songs);
|
||||
Q_EMIT SongsChanged(songs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1707,7 +1712,7 @@ void CollectionBackend::UnsetAlbumArt(const QString &effective_albumartist, cons
|
||||
}
|
||||
|
||||
if (!songs.isEmpty()) {
|
||||
emit SongsChanged(songs);
|
||||
Q_EMIT SongsChanged(songs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1753,7 +1758,7 @@ void CollectionBackend::ClearAlbumArt(const QString &effective_albumartist, cons
|
||||
}
|
||||
|
||||
if (!songs.isEmpty()) {
|
||||
emit SongsChanged(songs);
|
||||
Q_EMIT SongsChanged(songs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1802,7 +1807,7 @@ void CollectionBackend::ForceCompilation(const QString &album, const QStringList
|
||||
}
|
||||
|
||||
if (!songs.isEmpty()) {
|
||||
emit SongsChanged(songs);
|
||||
Q_EMIT SongsChanged(songs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1824,7 +1829,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 +1851,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 +1876,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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1920,7 +1925,7 @@ void CollectionBackend::DeleteAll() {
|
||||
t.Commit();
|
||||
}
|
||||
|
||||
emit DatabaseReset();
|
||||
Q_EMIT DatabaseReset();
|
||||
|
||||
}
|
||||
|
||||
@@ -2013,7 +2018,7 @@ void CollectionBackend::UpdateLastPlayed(const QString &artist, const QString &a
|
||||
}
|
||||
}
|
||||
|
||||
emit SongsStatisticsChanged(SongList() << songs);
|
||||
Q_EMIT SongsStatisticsChanged(SongList() << songs);
|
||||
|
||||
}
|
||||
|
||||
@@ -2039,7 +2044,7 @@ void CollectionBackend::UpdatePlayCount(const QString &artist, const QString &ti
|
||||
}
|
||||
}
|
||||
|
||||
emit SongsStatisticsChanged(SongList() << songs, save_tags);
|
||||
Q_EMIT SongsStatisticsChanged(SongList() << songs, save_tags);
|
||||
|
||||
}
|
||||
|
||||
@@ -2074,7 +2079,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);
|
||||
|
||||
|
||||
@@ -19,29 +19,31 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QVariant>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include <QSet>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "utilities/timeconstants.h"
|
||||
#include "utilities/searchparserutils.h"
|
||||
#include <QUrl>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "filterparser/filterparser.h"
|
||||
#include "filterparser/filtertree.h"
|
||||
#include "playlist/songmimedata.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
#include "collectionbackend.h"
|
||||
#include "collectionfilter.h"
|
||||
#include "collectionmodel.h"
|
||||
#include "collectionitem.h"
|
||||
|
||||
const QStringList CollectionFilter::Operators = QStringList() << QStringLiteral(":")
|
||||
<< QStringLiteral("=")
|
||||
<< QStringLiteral("==")
|
||||
<< QStringLiteral("<>")
|
||||
<< QStringLiteral("<")
|
||||
<< QStringLiteral("<=")
|
||||
<< QStringLiteral(">")
|
||||
<< QStringLiteral(">=");
|
||||
CollectionFilter::CollectionFilter(QObject *parent) : QSortFilterProxyModel(parent), query_hash_(0) {
|
||||
|
||||
CollectionFilter::CollectionFilter(QObject *parent) : QSortFilterProxyModel(parent) {}
|
||||
setSortLocaleAware(true);
|
||||
setDynamicSortFilter(true);
|
||||
setRecursiveFilteringEnabled(true);
|
||||
|
||||
}
|
||||
|
||||
bool CollectionFilter::filterAcceptsRow(const int source_row, const QModelIndex &source_parent) const {
|
||||
|
||||
@@ -52,284 +54,83 @@ bool CollectionFilter::filterAcceptsRow(const int source_row, const QModelIndex
|
||||
CollectionItem *item = model->IndexToItem(idx);
|
||||
if (!item) return false;
|
||||
|
||||
if (item->type == CollectionItem::Type::LoadingIndicator) return true;
|
||||
if (filter_string_.isEmpty()) return true;
|
||||
|
||||
if (item->type != CollectionItem::Type::Song) {
|
||||
return item->type == CollectionItem::Type::LoadingIndicator;
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QString filter_text = filterRegularExpression().pattern().remove(QLatin1Char('\\'));
|
||||
size_t hash = qHash(filter_string_);
|
||||
#else
|
||||
QString filter_text = filterRegExp().pattern();
|
||||
uint hash = qHash(filter_string_);
|
||||
#endif
|
||||
if (hash != query_hash_) {
|
||||
FilterParser p(filter_string_);
|
||||
filter_tree_.reset(p.parse());
|
||||
query_hash_ = hash;
|
||||
}
|
||||
|
||||
if (filter_text.isEmpty()) return true;
|
||||
return item->metadata.is_valid() && filter_tree_->accept(item->metadata);
|
||||
|
||||
filter_text = filter_text.replace(QRegularExpression(QStringLiteral("\\s*:\\s*")), QStringLiteral(":"))
|
||||
.replace(QRegularExpression(QStringLiteral("\\s*=\\s*")), QStringLiteral("="))
|
||||
.replace(QRegularExpression(QStringLiteral("\\s*==\\s*")), QStringLiteral("=="))
|
||||
.replace(QRegularExpression(QStringLiteral("\\s*<>\\s*")), QStringLiteral("<>"))
|
||||
.replace(QRegularExpression(QStringLiteral("\\s*<\\s*")), QStringLiteral("<"))
|
||||
.replace(QRegularExpression(QStringLiteral("\\s*>\\s*")), QStringLiteral(">"))
|
||||
.replace(QRegularExpression(QStringLiteral("\\s*<=\\s*")), QStringLiteral("<="))
|
||||
.replace(QRegularExpression(QStringLiteral("\\s*>=\\s*")), QStringLiteral(">="));
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
const QStringList tokens = filter_text.split(QRegularExpression(QStringLiteral("\\s+")), Qt::SkipEmptyParts);
|
||||
#else
|
||||
const QStringList tokens = filter_text.split(QRegularExpression(QStringLiteral("\\s+")), QString::SkipEmptyParts);
|
||||
#endif
|
||||
void CollectionFilter::SetFilterString(const QString &filter_string) {
|
||||
|
||||
filter_text.clear();
|
||||
filter_string_ = filter_string;
|
||||
setFilterFixedString(filter_string);
|
||||
|
||||
FilterList filters;
|
||||
static QRegularExpression operator_regex(QStringLiteral("(=|<[>=]?|>=?|!=)"));
|
||||
for (int i = 0; i < tokens.count(); ++i) {
|
||||
const QString &token = tokens[i];
|
||||
if (token.contains(QLatin1Char(':'))) {
|
||||
QString field = token.section(QLatin1Char(':'), 0, 0).remove(QLatin1Char(':')).trimmed();
|
||||
QString value = token.section(QLatin1Char(':'), 1, -1).remove(QLatin1Char(':')).trimmed();
|
||||
if (field.isEmpty() || value.isEmpty()) continue;
|
||||
if (Song::kTextSearchColumns.contains(field, Qt::CaseInsensitive) && value.count(QLatin1Char('"')) <= 2) {
|
||||
bool quotation_mark_start = false;
|
||||
bool quotation_mark_end = false;
|
||||
if (value.left(1) == QLatin1Char('"')) {
|
||||
value.remove(0, 1);
|
||||
quotation_mark_start = true;
|
||||
if (value.length() >= 1 && value.count(QLatin1Char('"')) == 1) {
|
||||
value = value.section(QLatin1Char(QLatin1Char('"')), 0, 0).remove(QLatin1Char('"')).trimmed();
|
||||
quotation_mark_end = true;
|
||||
}
|
||||
}
|
||||
for (int y = i + 1; y < tokens.count() && !quotation_mark_end; ++y) {
|
||||
QString next_value = tokens[y];
|
||||
if (!quotation_mark_start && ContainsOperators(next_value)) {
|
||||
break;
|
||||
}
|
||||
if (quotation_mark_start && next_value.contains(QLatin1Char('"'))) {
|
||||
next_value = next_value.section(QLatin1Char(QLatin1Char('"')), 0, 0).remove(QLatin1Char('"')).trimmed();
|
||||
quotation_mark_end = true;
|
||||
}
|
||||
value.append(QLatin1Char(' ') + next_value);
|
||||
i = y;
|
||||
}
|
||||
if (!field.isEmpty() && !value.isEmpty()) {
|
||||
filters.insert(field, Filter(field, value));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
QMimeData *CollectionFilter::mimeData(const QModelIndexList &indexes) const {
|
||||
|
||||
if (indexes.isEmpty()) return nullptr;
|
||||
|
||||
CollectionModel *collection_model = qobject_cast<CollectionModel*>(sourceModel());
|
||||
SongMimeData *data = new SongMimeData;
|
||||
data->backend = collection_model->backend();
|
||||
|
||||
QSet<int> song_ids;
|
||||
QList<QUrl> urls;
|
||||
for (const QModelIndex &idx : indexes) {
|
||||
const QModelIndex source_index = mapToSource(idx);
|
||||
CollectionItem *item = collection_model->IndexToItem(source_index);
|
||||
GetChildSongs(item, song_ids, urls, data->songs);
|
||||
}
|
||||
|
||||
data->setUrls(urls);
|
||||
data->name_for_new_playlist_ = PlaylistManager::GetNameForNewPlaylist(data->songs);
|
||||
|
||||
return data;
|
||||
|
||||
}
|
||||
|
||||
void CollectionFilter::GetChildSongs(CollectionItem *item, QSet<int> &song_ids, QList<QUrl> &urls, SongList &songs) const {
|
||||
|
||||
CollectionModel *collection_model = qobject_cast<CollectionModel*>(sourceModel());
|
||||
|
||||
switch (item->type) {
|
||||
case CollectionItem::Type::Container:{
|
||||
QList<CollectionItem*> children = item->children;
|
||||
std::sort(children.begin(), children.end(), std::bind(&CollectionModel::CompareItems, collection_model, std::placeholders::_1, std::placeholders::_2));
|
||||
for (CollectionItem *child : children) {
|
||||
GetChildSongs(child, song_ids, urls, songs);
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (token.contains(operator_regex)) {
|
||||
QRegularExpressionMatch re_match = operator_regex.match(token);
|
||||
if (re_match.hasMatch()) {
|
||||
const QString foperator = re_match.captured(0);
|
||||
const QString field = token.section(foperator, 0, 0).remove(foperator).trimmed();
|
||||
const QString value = token.section(foperator, 1, -1).remove(foperator).trimmed();
|
||||
if (value.isEmpty()) continue;
|
||||
if (Song::kNumericalSearchColumns.contains(field, Qt::CaseInsensitive)) {
|
||||
if (Song::kIntSearchColumns.contains(field, Qt::CaseInsensitive)) {
|
||||
bool ok = false;
|
||||
const int value_int = value.toInt(&ok);
|
||||
if (ok) {
|
||||
filters.insert(field, Filter(field, value_int, foperator));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (Song::kUIntSearchColumns.contains(field, Qt::CaseInsensitive)) {
|
||||
bool ok = false;
|
||||
const uint value_uint = value.toUInt(&ok);
|
||||
if (ok) {
|
||||
filters.insert(field, Filter(field, value_uint, foperator));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (field.compare(QLatin1String("length"), Qt::CaseInsensitive) == 0) {
|
||||
filters.insert(field, Filter(field, static_cast<qint64>(Utilities::ParseSearchTime(value)) * kNsecPerSec, foperator));
|
||||
continue;
|
||||
}
|
||||
else if (field.compare(QLatin1String("rating"), Qt::CaseInsensitive) == 0) {
|
||||
filters.insert(field, Filter(field, Utilities::ParseSearchRating(value), foperator));
|
||||
}
|
||||
case CollectionItem::Type::Song:{
|
||||
const QModelIndex idx = collection_model->ItemToIndex(item);
|
||||
if (filterAcceptsRow(idx.row(), idx.parent())) {
|
||||
urls << item->metadata.url();
|
||||
if (!song_ids.contains(item->metadata.id())) {
|
||||
song_ids.insert(item->metadata.id());
|
||||
songs << item->metadata;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!filter_text.isEmpty()) filter_text.append(QLatin1Char(' '));
|
||||
filter_text += token;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (filter_text.isEmpty() && filters.isEmpty()) return true;
|
||||
|
||||
return ItemMatchesFilters(item, filters, filter_text);
|
||||
|
||||
}
|
||||
|
||||
bool CollectionFilter::ItemMatchesFilters(CollectionItem *item, const FilterList &filters, const QString &filter_text) {
|
||||
|
||||
if (item->type == CollectionItem::Type::Song &&
|
||||
item->metadata.is_valid() &&
|
||||
ItemMetadataMatchesFilters(item->metadata, filters, filter_text)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (CollectionItem *child : std::as_const(item->children)) {
|
||||
if (ItemMatchesFilters(child, filters, filter_text)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
bool CollectionFilter::ItemMetadataMatchesFilters(const Song &metadata, const FilterList &filters, const QString &filter_text) {
|
||||
|
||||
for (FilterList::const_iterator it = filters.begin() ; it != filters.end() ; ++it) {
|
||||
const QString &field = it.key();
|
||||
const Filter &filter = it.value();
|
||||
const QVariant &value = filter.value;
|
||||
const QString &foperator = filter.foperator;
|
||||
if (field.isEmpty() || !value.isValid()) {
|
||||
continue;
|
||||
}
|
||||
const QVariant data = DataFromField(field, metadata);
|
||||
if (
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
value.metaType() != data.metaType()
|
||||
#else
|
||||
value.type() != data.type()
|
||||
#endif
|
||||
|| !FieldValueMatchesData(value, data, foperator)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return filter_text.isEmpty() || ItemMetadataMatchesFilterText(metadata, filter_text);
|
||||
|
||||
}
|
||||
|
||||
bool CollectionFilter::ItemMetadataMatchesFilterText(const Song &metadata, const QString &filter_text) {
|
||||
|
||||
return metadata.effective_albumartist().contains(filter_text, Qt::CaseInsensitive) ||
|
||||
metadata.artist().contains(filter_text, Qt::CaseInsensitive) ||
|
||||
metadata.album().contains(filter_text, Qt::CaseInsensitive) ||
|
||||
metadata.title().contains(filter_text, Qt::CaseInsensitive) ||
|
||||
metadata.composer().contains(filter_text, Qt::CaseInsensitive) ||
|
||||
metadata.performer().contains(filter_text, Qt::CaseInsensitive) ||
|
||||
metadata.grouping().contains(filter_text, Qt::CaseInsensitive) ||
|
||||
metadata.genre().contains(filter_text, Qt::CaseInsensitive) ||
|
||||
metadata.comment().contains(filter_text, Qt::CaseInsensitive);
|
||||
|
||||
}
|
||||
|
||||
QVariant CollectionFilter::DataFromField(const QString &field, const Song &metadata) {
|
||||
|
||||
if (field == QLatin1String("albumartist")) return metadata.effective_albumartist();
|
||||
if (field == QLatin1String("artist")) return metadata.artist();
|
||||
if (field == QLatin1String("album")) return metadata.album();
|
||||
if (field == QLatin1String("title")) return metadata.title();
|
||||
if (field == QLatin1String("composer")) return metadata.composer();
|
||||
if (field == QLatin1String("performer")) return metadata.performer();
|
||||
if (field == QLatin1String("grouping")) return metadata.grouping();
|
||||
if (field == QLatin1String("genre")) return metadata.genre();
|
||||
if (field == QLatin1String("comment")) return metadata.comment();
|
||||
if (field == QLatin1String("track")) return metadata.track();
|
||||
if (field == QLatin1String("year")) return metadata.year();
|
||||
if (field == QLatin1String("length")) return metadata.length_nanosec();
|
||||
if (field == QLatin1String("samplerate")) return metadata.samplerate();
|
||||
if (field == QLatin1String("bitdepth")) return metadata.bitdepth();
|
||||
if (field == QLatin1String("bitrate")) return metadata.bitrate();
|
||||
if (field == QLatin1String("rating")) return metadata.rating();
|
||||
if (field == QLatin1String("playcount")) return metadata.playcount();
|
||||
if (field == QLatin1String("skipcount")) return metadata.skipcount();
|
||||
|
||||
return QVariant();
|
||||
|
||||
}
|
||||
|
||||
bool CollectionFilter::FieldValueMatchesData(const QVariant &value, const QVariant &data, const QString &foperator) {
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
switch (value.metaType().id()) {
|
||||
#else
|
||||
switch (value.userType()) {
|
||||
#endif
|
||||
case QMetaType::QString:{
|
||||
const QString str_value = value.toString();
|
||||
const QString str_data = data.toString();
|
||||
return str_data.contains(str_value, Qt::CaseInsensitive);
|
||||
}
|
||||
case QMetaType::Int:{
|
||||
return FieldIntValueMatchesData(value.toInt(), foperator, data.toInt());
|
||||
}
|
||||
case QMetaType::UInt:{
|
||||
return FieldUIntValueMatchesData(value.toUInt(), foperator, data.toUInt());
|
||||
}
|
||||
case QMetaType::LongLong:{
|
||||
return FieldLongLongValueMatchesData(value.toLongLong(), foperator, data.toLongLong());
|
||||
}
|
||||
case QMetaType::Float:{
|
||||
return FieldFloatValueMatchesData(value.toFloat(), foperator, data.toFloat());
|
||||
}
|
||||
default:{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool CollectionFilter::FieldNumericalValueMatchesData(const T value, const QString &foperator, const T data) {
|
||||
|
||||
if (foperator == QLatin1Char('=') || foperator == QLatin1String("==")) {
|
||||
return data == value;
|
||||
}
|
||||
if (foperator == QLatin1String("!=") || foperator == QLatin1String("<>")) {
|
||||
return data != value;
|
||||
}
|
||||
if (foperator == QLatin1Char('<')) {
|
||||
return data < value;
|
||||
}
|
||||
if (foperator == QLatin1Char('>')) {
|
||||
return data > value;
|
||||
}
|
||||
if (foperator == QLatin1String(">=")) {
|
||||
return data >= value;
|
||||
}
|
||||
if (foperator == QLatin1String("<=")) {
|
||||
return data <= value;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
bool CollectionFilter::FieldIntValueMatchesData(const int value, const QString &foperator, const int data) {
|
||||
|
||||
return FieldNumericalValueMatchesData(value, foperator, data);
|
||||
|
||||
}
|
||||
|
||||
bool CollectionFilter::FieldUIntValueMatchesData(const uint value, const QString &foperator, const uint data) {
|
||||
|
||||
return FieldNumericalValueMatchesData(value, foperator, data);
|
||||
|
||||
}
|
||||
|
||||
bool CollectionFilter::FieldLongLongValueMatchesData(const qint64 value, const QString &foperator, const qint64 data) {
|
||||
|
||||
return FieldNumericalValueMatchesData(value, foperator, data);
|
||||
|
||||
}
|
||||
|
||||
bool CollectionFilter::FieldFloatValueMatchesData(const float value, const QString &foperator, const float data) {
|
||||
|
||||
return FieldNumericalValueMatchesData(value, foperator, data);
|
||||
|
||||
}
|
||||
|
||||
bool CollectionFilter::ContainsOperators(const QString &token) {
|
||||
|
||||
for (const QString &foperator : Operators) {
|
||||
if (token.contains(foperator, Qt::CaseInsensitive)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@@ -22,14 +22,14 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QScopedPointer>
|
||||
#include <QSet>
|
||||
#include <QList>
|
||||
#include <QUrl>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "filterparser/filtertree.h"
|
||||
|
||||
class CollectionItem;
|
||||
|
||||
@@ -39,31 +39,24 @@ class CollectionFilter : public QSortFilterProxyModel {
|
||||
public:
|
||||
explicit CollectionFilter(QObject *parent = nullptr);
|
||||
|
||||
void SetFilterString(const QString &filter_string);
|
||||
QString filter_string() const { return filter_string_; }
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(const int source_row, const QModelIndex &source_parent) const override;
|
||||
QMimeData *mimeData(const QModelIndexList &indexes) const override;
|
||||
|
||||
private:
|
||||
static const QStringList Operators;
|
||||
struct Filter {
|
||||
public:
|
||||
Filter(const QString &_field = QString(), const QVariant &_value = QVariant(), const QString &_foperator = QString()) : field(_field), value(_value), foperator(_foperator) {}
|
||||
QString field;
|
||||
QVariant value;
|
||||
QString foperator;
|
||||
};
|
||||
using FilterList = QMap<QString, Filter>;
|
||||
static bool ItemMatchesFilters(CollectionItem *item, const FilterList &filters, const QString &filter_text);
|
||||
static bool ItemMetadataMatchesFilters(const Song &metadata, const FilterList &filters, const QString &filter_text);
|
||||
static bool ItemMetadataMatchesFilterText(const Song &metadata, const QString &filter_text);
|
||||
static QVariant DataFromField(const QString &field, const Song &metadata);
|
||||
static bool FieldValueMatchesData(const QVariant &value, const QVariant &data, const QString &foperator);
|
||||
template<typename T>
|
||||
static bool FieldNumericalValueMatchesData(const T value, const QString &foperator, const T data);
|
||||
static bool FieldIntValueMatchesData(const int value, const QString &foperator, const int data);
|
||||
static bool FieldUIntValueMatchesData(const uint value, const QString &foperator, const uint data);
|
||||
static bool FieldLongLongValueMatchesData(const qint64 value, const QString &foperator, const qint64 data);
|
||||
static bool FieldFloatValueMatchesData(const float value, const QString &foperator, const float data);
|
||||
static bool ContainsOperators(const QString &token);
|
||||
void GetChildSongs(CollectionItem *item, QSet<int> &song_ids, QList<QUrl> &urls, SongList &songs) const;
|
||||
|
||||
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_;
|
||||
};
|
||||
|
||||
#endif // COLLECTIONFILTER_H
|
||||
|
||||
@@ -52,14 +52,19 @@
|
||||
#include "collectionmodel.h"
|
||||
#include "collectionfilter.h"
|
||||
#include "collectionquery.h"
|
||||
#include "filterparser/filterparser.h"
|
||||
#include "savedgroupingmanager.h"
|
||||
#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"
|
||||
|
||||
namespace {
|
||||
constexpr int kFilterDelay = 500; // msec
|
||||
}
|
||||
|
||||
CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
||||
: QWidget(parent),
|
||||
ui_(new Ui_CollectionFilterWidget),
|
||||
@@ -71,47 +76,19 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
||||
group_by_menu_(nullptr),
|
||||
collection_menu_(nullptr),
|
||||
group_by_group_(nullptr),
|
||||
filter_delay_(new QTimer(this)),
|
||||
timer_filter_delay_(new QTimer(this)),
|
||||
filter_applies_to_model_(true),
|
||||
delay_behaviour_(DelayBehaviour::DelayedOnLargeLibraries) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
|
||||
QString available_fields = Song::kTextSearchColumns.join(QLatin1String(", "));
|
||||
available_fields += QLatin1String(", ") + Song::kNumericalSearchColumns.join(QLatin1String(", "));
|
||||
ui_->search_field->setToolTip(FilterParser::ToolTip());
|
||||
|
||||
ui_->search_field->setToolTip(
|
||||
QLatin1String("<html><head/><body><p>") +
|
||||
tr("Prefix a word with a field name to limit the search to that field, e.g.:") +
|
||||
QLatin1Char(' ') +
|
||||
QLatin1String("<span style=\"font-weight:600;\">") +
|
||||
tr("artist") +
|
||||
QLatin1String(":</span><span style=\"font-style:italic;\">Strawbs</span> ") +
|
||||
tr("searches the collection for all artists that contain the word %1. ").arg(QLatin1String("Strawbs")) +
|
||||
QLatin1String("</p><p>") +
|
||||
tr("Search terms for numerical fields can be prefixed with %1 or %2 to refine the search, e.g.: ")
|
||||
.arg(QLatin1String(" =, !=, <, >, <="), QLatin1String(">=")) +
|
||||
QLatin1String("<span style=\"font-weight:600;\">") +
|
||||
tr("rating") +
|
||||
QLatin1String("</span>") +
|
||||
QLatin1String(":>=") +
|
||||
QLatin1String("<span style=\"font-weight:italic;\">4</span>") +
|
||||
QObject::connect(ui_->search_field, &SearchField::returnPressed, this, &CollectionFilterWidget::ReturnPressed);
|
||||
QObject::connect(timer_filter_delay_, &QTimer::timeout, this, &CollectionFilterWidget::FilterDelayTimeout);
|
||||
|
||||
QLatin1String("</p><p><span style=\"font-weight:600;\">") +
|
||||
tr("Available fields") +
|
||||
QLatin1String(": ") +
|
||||
QLatin1String("</span>") +
|
||||
QLatin1String("<span style=\"font-style:italic;\">") +
|
||||
available_fields +
|
||||
QLatin1String("</span>.") +
|
||||
QLatin1String("</p></body></html>")
|
||||
);
|
||||
|
||||
QObject::connect(ui_->search_field, &QSearchField::returnPressed, this, &CollectionFilterWidget::ReturnPressed);
|
||||
QObject::connect(filter_delay_, &QTimer::timeout, this, &CollectionFilterWidget::FilterDelayTimeout);
|
||||
|
||||
filter_delay_->setInterval(kFilterDelay);
|
||||
filter_delay_->setSingleShot(true);
|
||||
timer_filter_delay_->setInterval(kFilterDelay);
|
||||
timer_filter_delay_->setSingleShot(true);
|
||||
|
||||
// Icons
|
||||
ui_->options->setIcon(IconLoader::Load(QStringLiteral("configure")));
|
||||
@@ -150,7 +127,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();
|
||||
@@ -181,7 +158,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); } );
|
||||
}
|
||||
|
||||
@@ -502,12 +479,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;
|
||||
|
||||
@@ -529,10 +506,10 @@ void CollectionFilterWidget::FilterTextChanged(const QString &text) {
|
||||
const bool delay = (delay_behaviour_ == DelayBehaviour::AlwaysDelayed) || (delay_behaviour_ == DelayBehaviour::DelayedOnLargeLibraries && !text.isEmpty() && text.length() < 3 && model_->total_song_count() >= 100000);
|
||||
|
||||
if (delay) {
|
||||
filter_delay_->start();
|
||||
timer_filter_delay_->start();
|
||||
}
|
||||
else {
|
||||
filter_delay_->stop();
|
||||
timer_filter_delay_->stop();
|
||||
FilterDelayTimeout();
|
||||
}
|
||||
|
||||
@@ -541,7 +518,7 @@ void CollectionFilterWidget::FilterTextChanged(const QString &text) {
|
||||
void CollectionFilterWidget::FilterDelayTimeout() {
|
||||
|
||||
if (filter_applies_to_model_) {
|
||||
filter_->setFilterFixedString(ui_->search_field->text());
|
||||
filter_->SetFilterString(ui_->search_field->text());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -51,8 +51,6 @@ class CollectionFilterWidget : public QWidget {
|
||||
explicit CollectionFilterWidget(QWidget *parent = nullptr);
|
||||
~CollectionFilterWidget() override;
|
||||
|
||||
static const int kFilterDelay = 500; // msec
|
||||
|
||||
enum class DelayBehaviour {
|
||||
AlwaysInstant,
|
||||
DelayedOnLargeLibraries,
|
||||
@@ -88,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();
|
||||
@@ -101,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();
|
||||
@@ -128,7 +126,7 @@ class CollectionFilterWidget : public QWidget {
|
||||
QActionGroup *group_by_group_;
|
||||
QHash<QAction*, int> filter_max_ages_;
|
||||
|
||||
QTimer *filter_delay_;
|
||||
QTimer *timer_filter_delay_;
|
||||
|
||||
bool filter_applies_to_model_;
|
||||
DelayBehaviour delay_behaviour_;
|
||||
|
||||
@@ -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,8 @@
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
const int CollectionModel::kPrettyCoverSize = 32;
|
||||
namespace {
|
||||
constexpr char kPixmapDiskCacheDir[] = "pixmapcache";
|
||||
@@ -98,10 +101,10 @@ 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_->setDynamicSortFilter(true);
|
||||
filter_->setSortLocaleAware(true);
|
||||
filter_->sort(0);
|
||||
|
||||
if (app_) {
|
||||
@@ -135,11 +138,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();
|
||||
@@ -148,7 +151,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();
|
||||
@@ -268,7 +271,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);
|
||||
|
||||
}
|
||||
|
||||
@@ -391,12 +394,13 @@ QVariant CollectionModel::data(const CollectionItem *item, const int role) const
|
||||
Qt::ItemFlags CollectionModel::flags(const QModelIndex &idx) const {
|
||||
|
||||
switch (IndexToItem(idx)->type) {
|
||||
case CollectionItem::Type::Song:
|
||||
case CollectionItem::Type::Container:
|
||||
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled;
|
||||
case CollectionItem::Type::Divider:
|
||||
case CollectionItem::Type::Root:
|
||||
case CollectionItem::Type::LoadingIndicator:
|
||||
case CollectionItem::Type::Divider:
|
||||
return Qt::ItemIsEnabled | Qt::ItemNeverHasChildren;
|
||||
case CollectionItem::Type::Container:
|
||||
case CollectionItem::Type::Song:
|
||||
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled;
|
||||
case CollectionItem::Type::Root:
|
||||
default:
|
||||
return Qt::ItemIsEnabled;
|
||||
}
|
||||
@@ -515,7 +519,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;
|
||||
@@ -607,7 +611,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);
|
||||
@@ -623,18 +627,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -649,7 +653,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;
|
||||
|
||||
@@ -707,7 +711,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();
|
||||
@@ -990,7 +994,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);
|
||||
|
||||
}
|
||||
|
||||
@@ -1164,7 +1168,8 @@ QString CollectionModel::SortText(QString text) {
|
||||
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;
|
||||
|
||||
@@ -1316,7 +1321,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.effective_albumartist().isEmpty()) {
|
||||
if (IsAlbumGroupBy(group_by) && !has_unique_album_identifier && !song.is_compilation() && !song.effective_albumartist().isEmpty()) {
|
||||
key.prepend(QLatin1Char('-'));
|
||||
key.prepend(TextOrUnknown(song.effective_albumartist()));
|
||||
has_unique_album_identifier = true;
|
||||
@@ -1532,21 +1537,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);
|
||||
|
||||
}
|
||||
|
||||
@@ -1554,18 +1559,6 @@ void CollectionModel::ClearDiskCache() {
|
||||
if (sIconCache) sIconCache->clear();
|
||||
}
|
||||
|
||||
void CollectionModel::ExpandAll(CollectionItem *item) const {
|
||||
|
||||
if (!root_) return;
|
||||
|
||||
if (!item) item = root_;
|
||||
|
||||
for (CollectionItem *child : item->children) {
|
||||
ExpandAll(child);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CollectionModel::RowsInserted(const QModelIndex &parent, const int first, const int last) {
|
||||
|
||||
SongList songs;
|
||||
@@ -1578,7 +1571,7 @@ void CollectionModel::RowsInserted(const QModelIndex &parent, const int first, c
|
||||
}
|
||||
|
||||
if (!songs.isEmpty()) {
|
||||
emit SongsAdded(songs);
|
||||
Q_EMIT SongsAdded(songs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1594,7 +1587,7 @@ void CollectionModel::RowsRemoved(const QModelIndex &parent, const int first, co
|
||||
songs << item->metadata;
|
||||
}
|
||||
|
||||
emit SongsRemoved(songs);
|
||||
Q_EMIT SongsRemoved(songs);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -142,6 +142,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
CollectionFilterOptions filter_options;
|
||||
};
|
||||
|
||||
SharedPtr<CollectionBackend> backend() const { return backend_; }
|
||||
CollectionFilter *filter() const { return filter_; }
|
||||
|
||||
void Init();
|
||||
@@ -198,9 +199,9 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
SongList GetChildSongs(const QModelIndex &idx) const;
|
||||
SongList GetChildSongs(const QModelIndexList &indexes) const;
|
||||
|
||||
void ExpandAll(CollectionItem *item = nullptr) const;
|
||||
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);
|
||||
@@ -208,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);
|
||||
|
||||
@@ -250,10 +251,9 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
static QUrl AlbumIconPixmapDiskCacheKey(const QString &cache_key);
|
||||
QVariant AlbumIcon(const QModelIndex &idx);
|
||||
void ClearItemPixmapCache(CollectionItem *item);
|
||||
bool CompareItems(const CollectionItem *a, const CollectionItem *b) const;
|
||||
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>
|
||||
@@ -35,7 +37,6 @@
|
||||
|
||||
#include "collectionquery.h"
|
||||
#include "collectionfilteroptions.h"
|
||||
#include "utilities/searchparserutils.h"
|
||||
|
||||
CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const CollectionFilterOptions &filter_options)
|
||||
: SqlQuery(db),
|
||||
@@ -45,7 +46,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;
|
||||
@@ -63,7 +64,7 @@ void CollectionQuery::AddWhere(const QString &column, const QVariant &value, con
|
||||
|
||||
// Ignore 'literal' for IN
|
||||
if (op.compare(QLatin1String("IN"), Qt::CaseInsensitive) == 0) {
|
||||
QStringList values = value.toStringList();
|
||||
const QStringList values = value.toStringList();
|
||||
QStringList final_values;
|
||||
final_values.reserve(values.count());
|
||||
for (const QString &single_value : values) {
|
||||
@@ -135,7 +136,7 @@ bool CollectionQuery::Exec() {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2023, 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef COLLECTIONQUERYOPTIONS_H
|
||||
#define COLLECTIONQUERYOPTIONS_H
|
||||
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
|
||||
class CollectionQueryOptions {
|
||||
public:
|
||||
|
||||
explicit CollectionQueryOptions();
|
||||
|
||||
struct Where {
|
||||
explicit Where(const QString &_column = QString(), const QVariant &_value = QString(), const QString &_op = QString()) : column(_column), value(_value), op(_op) {}
|
||||
QString column;
|
||||
QVariant value;
|
||||
QString op;
|
||||
};
|
||||
|
||||
enum class CompilationRequirement {
|
||||
None,
|
||||
On,
|
||||
Off
|
||||
};
|
||||
|
||||
QString column_spec() const { return column_spec_; }
|
||||
CompilationRequirement compilation_requirement() const { return compilation_requirement_; }
|
||||
bool query_have_compilations() const { return query_have_compilations_; }
|
||||
|
||||
void set_column_spec(const QString &column_spec) { column_spec_ = column_spec; }
|
||||
void set_compilation_requirement(const CompilationRequirement compilation_requirement) { compilation_requirement_ = compilation_requirement; }
|
||||
void set_query_have_compilations(const bool query_have_compilations) { query_have_compilations_ = query_have_compilations; }
|
||||
|
||||
QList<Where> where_clauses() const { return where_clauses_; }
|
||||
void AddWhere(const QString &column, const QVariant &value, const QString &op = QStringLiteral("="));
|
||||
|
||||
private:
|
||||
QString column_spec_;
|
||||
CompilationRequirement compilation_requirement_;
|
||||
bool query_have_compilations_;
|
||||
QList<Where> where_clauses_;
|
||||
};
|
||||
|
||||
#endif // COLLECTIONQUERYOPTIONS_H
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -538,13 +540,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 +556,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 +566,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,13 +576,13 @@ 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);
|
||||
|
||||
}
|
||||
|
||||
void CollectionView::SearchForThis() {
|
||||
|
||||
QModelIndex current = currentIndex();
|
||||
const QModelIndex current = currentIndex();
|
||||
const QVariant role_type = model()->data(current, CollectionModel::Role_Type);
|
||||
if (!role_type.isValid()) {
|
||||
return;
|
||||
@@ -599,7 +601,7 @@ void CollectionView::SearchForThis() {
|
||||
if (!songs.isEmpty()) {
|
||||
last_selected_song_ = songs.last();
|
||||
}
|
||||
search = QStringLiteral("title:%1").arg(last_selected_song_.title());
|
||||
search = QStringLiteral("title:\"%1\"").arg(last_selected_song_.title());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -609,28 +611,29 @@ void CollectionView::SearchForThis() {
|
||||
|
||||
case CollectionItem::Type::Container:{
|
||||
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.constFirst();
|
||||
}
|
||||
|
||||
int container_level = item->container_level;
|
||||
CollectionModel::GroupBy container_group_by = app_->collection_model()->GetGroupBy()[container_level];
|
||||
|
||||
switch (container_group_by) {
|
||||
switch (group_by) {
|
||||
case CollectionModel::GroupBy::AlbumArtist:
|
||||
search = QStringLiteral("albumartist:%1").arg(item->metadata.effective_albumartist());
|
||||
search = QStringLiteral("albumartist:\"%1\"").arg(item->metadata.effective_albumartist());
|
||||
break;
|
||||
case CollectionModel::GroupBy::Artist:
|
||||
search = QStringLiteral("artist:%1").arg(item->metadata.artist());
|
||||
search = QStringLiteral("artist:\"%1\"").arg(item->metadata.artist());
|
||||
break;
|
||||
case CollectionModel::GroupBy::Album:
|
||||
case CollectionModel::GroupBy::AlbumDisc:
|
||||
search = QStringLiteral("album:%1").arg(item->metadata.album());
|
||||
search = QStringLiteral("album:\"%1\"").arg(item->metadata.album());
|
||||
break;
|
||||
case CollectionModel::GroupBy::YearAlbum:
|
||||
case CollectionModel::GroupBy::YearAlbumDisc:
|
||||
search = QStringLiteral("year:%1 album:%2").arg(item->metadata.year()).arg(item->metadata.album());
|
||||
search = QStringLiteral("year:%1 album:\"%2\"").arg(item->metadata.year()).arg(item->metadata.album());
|
||||
break;
|
||||
case CollectionModel::GroupBy::OriginalYearAlbum:
|
||||
case CollectionModel::GroupBy::OriginalYearAlbumDisc:
|
||||
search = QStringLiteral("year:%1 album:%2").arg(item->metadata.effective_originalyear()).arg(item->metadata.album());
|
||||
search = QStringLiteral("year:%1 album:\"%2\"").arg(item->metadata.effective_originalyear()).arg(item->metadata.album());
|
||||
break;
|
||||
case CollectionModel::GroupBy::Year:
|
||||
search = QStringLiteral("year:%1").arg(item->metadata.year());
|
||||
@@ -639,16 +642,16 @@ void CollectionView::SearchForThis() {
|
||||
search = QStringLiteral("year:%1").arg(item->metadata.effective_originalyear());
|
||||
break;
|
||||
case CollectionModel::GroupBy::Genre:
|
||||
search = QStringLiteral("genre:%1").arg(item->metadata.genre());
|
||||
search = QStringLiteral("genre:\"%1\"").arg(item->metadata.genre());
|
||||
break;
|
||||
case CollectionModel::GroupBy::Composer:
|
||||
search = QStringLiteral("composer:%1").arg(item->metadata.composer());
|
||||
search = QStringLiteral("composer:\"%1\"").arg(item->metadata.composer());
|
||||
break;
|
||||
case CollectionModel::GroupBy::Performer:
|
||||
search = QStringLiteral("performer:%1").arg(item->metadata.performer());
|
||||
search = QStringLiteral("performer:\"%1\"").arg(item->metadata.performer());
|
||||
break;
|
||||
case CollectionModel::GroupBy::Grouping:
|
||||
search = QStringLiteral("grouping:%1").arg(item->metadata.grouping());
|
||||
search = QStringLiteral("grouping:\"%1\"").arg(item->metadata.grouping());
|
||||
break;
|
||||
case CollectionModel::GroupBy::Samplerate:
|
||||
search = QStringLiteral("samplerate:%1").arg(item->metadata.samplerate());
|
||||
@@ -730,7 +733,7 @@ void CollectionView::EditTracks() {
|
||||
}
|
||||
|
||||
void CollectionView::EditTagError(const QString &message) {
|
||||
emit Error(message);
|
||||
Q_EMIT Error(message);
|
||||
}
|
||||
|
||||
void CollectionView::RescanSongs() {
|
||||
@@ -772,7 +775,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"
|
||||
@@ -72,7 +73,6 @@
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
QStringList CollectionWatcher::sValidImages = QStringList() << QStringLiteral("jpg") << QStringLiteral("png") << QStringLiteral("gif") << QStringLiteral("jpeg");
|
||||
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 +98,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 +137,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 +266,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 +299,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 +349,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_);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -407,7 +450,7 @@ CollectionSubdirectoryList CollectionWatcher::ScanTransaction::GetAllSubdirs() {
|
||||
|
||||
void CollectionWatcher::AddDirectory(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdirs) {
|
||||
|
||||
stop_requested_ = false;
|
||||
CancelStop();
|
||||
|
||||
watched_dirs_[dir.id] = dir;
|
||||
|
||||
@@ -421,25 +464,29 @@ void CollectionWatcher::AddDirectory(const CollectionDirectory &dir, const Colle
|
||||
last_scan_time_ = QDateTime::currentSecsSinceEpoch();
|
||||
}
|
||||
else {
|
||||
// We can do an incremental scan - looking at the mtimes of each subdirectory and only rescan if the directory has changed.
|
||||
ScanTransaction transaction(this, dir.id, true, false, mark_songs_unavailable_);
|
||||
QMap<QString, quint64> subdir_files_count;
|
||||
const quint64 files_count = FilesCountForSubdirs(&transaction, subdirs, subdir_files_count);
|
||||
transaction.SetKnownSubdirs(subdirs);
|
||||
transaction.AddToProgressMax(files_count);
|
||||
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
|
||||
if (scan_on_startup_) ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
|
||||
|
||||
if (monitor_) AddWatch(dir, subdir.path);
|
||||
if (monitor_) {
|
||||
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||
AddWatch(dir, subdir.path);
|
||||
}
|
||||
}
|
||||
if (scan_on_startup_) {
|
||||
// We can do an incremental scan - looking at the mtimes of each subdirectory and only rescan if the directory has changed.
|
||||
ScanTransaction transaction(this, dir.id, true, false, mark_songs_unavailable_);
|
||||
QMap<QString, quint64> subdir_files_count;
|
||||
const quint64 files_count = FilesCountForSubdirs(&transaction, subdirs, subdir_files_count);
|
||||
transaction.SetKnownSubdirs(subdirs);
|
||||
transaction.AddToProgressMax(files_count);
|
||||
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||
if (stop_or_abort_requested()) break;
|
||||
ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
|
||||
}
|
||||
if (!stop_or_abort_requested()) {
|
||||
last_scan_time_ = QDateTime::currentSecsSinceEpoch();
|
||||
}
|
||||
}
|
||||
|
||||
last_scan_time_ = QDateTime::currentSecsSinceEpoch();
|
||||
|
||||
}
|
||||
|
||||
emit CompilationsNeedUpdating();
|
||||
Q_EMIT CompilationsNeedUpdating();
|
||||
|
||||
}
|
||||
|
||||
@@ -493,7 +540,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);
|
||||
@@ -512,7 +559,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() == QLatin1String("qt_temp")) {
|
||||
t->AddToProgress(1);
|
||||
}
|
||||
else if (sValidImages.contains(ext_part)) {
|
||||
@@ -528,7 +575,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);
|
||||
@@ -539,7 +586,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);
|
||||
@@ -741,7 +788,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);
|
||||
}
|
||||
|
||||
@@ -797,6 +844,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
|
||||
t->deleted_songs << old_cue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
|
||||
@@ -1008,8 +1056,8 @@ void CollectionWatcher::RemoveDirectory(const CollectionDirectory &dir) {
|
||||
watched_dirs_.remove(dir.id);
|
||||
|
||||
// Stop watching the directory's subdirectories
|
||||
QStringList subdir_paths = subdir_mapping_.keys(dir);
|
||||
for (const QString &subdir_path : std::as_const(subdir_paths)) {
|
||||
const QStringList subdir_paths = subdir_mapping_.keys(dir);
|
||||
for (const QString &subdir_path : subdir_paths) {
|
||||
fs_watcher_->RemovePath(subdir_path);
|
||||
subdir_mapping_.remove(subdir_path);
|
||||
}
|
||||
@@ -1030,8 +1078,8 @@ bool CollectionWatcher::FindSongsByPath(const SongList &songs, const QString &pa
|
||||
|
||||
bool CollectionWatcher::FindSongsByFingerprint(const QString &file, const QString &fingerprint, SongList *out) {
|
||||
|
||||
SongList songs = backend_->GetSongsByFingerprint(fingerprint);
|
||||
for (const Song &song : std::as_const(songs)) {
|
||||
const SongList songs = backend_->GetSongsByFingerprint(fingerprint);
|
||||
for (const Song &song : songs) {
|
||||
QString filename = song.url().toLocalFile();
|
||||
QFileInfo info(filename);
|
||||
// Allow mulitiple songs in different directories with the same fingerprint.
|
||||
@@ -1081,10 +1129,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) {
|
||||
@@ -1094,7 +1142,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;
|
||||
@@ -1105,7 +1153,7 @@ void CollectionWatcher::RescanPathsNow() {
|
||||
|
||||
rescan_queue_.clear();
|
||||
|
||||
emit CompilationsNeedUpdating();
|
||||
Q_EMIT CompilationsNeedUpdating();
|
||||
|
||||
}
|
||||
|
||||
@@ -1139,7 +1187,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;
|
||||
@@ -1215,14 +1263,14 @@ 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());
|
||||
CollectionSubdirectoryList subdirs = transaction.GetAllSubdirs();
|
||||
|
||||
if (subdirs.isEmpty()) {
|
||||
qLog(Debug) << "Collection directory wasn't in subdir list.";
|
||||
@@ -1237,7 +1285,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);
|
||||
}
|
||||
|
||||
@@ -1245,7 +1293,7 @@ void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mt
|
||||
|
||||
last_scan_time_ = QDateTime::currentSecsSinceEpoch();
|
||||
|
||||
emit CompilationsNeedUpdating();
|
||||
Q_EMIT CompilationsNeedUpdating();
|
||||
|
||||
}
|
||||
|
||||
@@ -1255,7 +1303,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);
|
||||
@@ -1289,7 +1337,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;
|
||||
@@ -1307,17 +1355,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;
|
||||
if (stop_or_abort_requested()) break;
|
||||
const QString song_path = song.url().toLocalFile().section(QLatin1Char('/'), 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);
|
||||
@@ -1326,6 +1374,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"
|
||||
@@ -64,14 +65,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 +87,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 +168,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 +180,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 +236,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,7 +253,6 @@ class CollectionWatcher : public QObject {
|
||||
CueParser *cue_parser_;
|
||||
|
||||
static QStringList sValidImages;
|
||||
static QStringList kIgnoredExtensions;
|
||||
|
||||
qint64 last_scan_time_;
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -207,7 +207,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();
|
||||
|
||||
|
||||
@@ -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,7 +67,9 @@
|
||||
#include "contextview.h"
|
||||
#include "contextalbum.h"
|
||||
|
||||
const int ContextView::kWidgetSpacing = 50;
|
||||
namespace {
|
||||
constexpr int kWidgetSpacing = 50;
|
||||
}
|
||||
|
||||
ContextView::ContextView(QWidget *parent)
|
||||
: QWidget(parent),
|
||||
@@ -427,10 +431,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_);
|
||||
}
|
||||
|
||||
@@ -452,7 +456,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();
|
||||
@@ -547,7 +551,10 @@ void ContextView::SetSong() {
|
||||
|
||||
void ContextView::UpdateSong(const Song &song) {
|
||||
|
||||
textedit_top_->SetText(QStringLiteral("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song, QStringLiteral("<br />"), true), Utilities::ReplaceMessage(summary_fmt_, song, QStringLiteral("<br />"), true)));
|
||||
const QString top_text = QStringLiteral("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song, QStringLiteral("<br />"), true), Utilities::ReplaceMessage(summary_fmt_, song, QStringLiteral("<br />"), true));
|
||||
if (top_text != textedit_top_->Text()) {
|
||||
textedit_top_->SetText(top_text);
|
||||
}
|
||||
|
||||
if (action_show_data_->isChecked()) {
|
||||
if (song.filetype() != song_playing_.filetype()) label_filetype_->setText(song.TextForFiletype());
|
||||
@@ -615,11 +622,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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
@@ -101,8 +101,6 @@ class ContextView : public QWidget {
|
||||
void AlbumCoverLoaded(const Song &song, const QImage &image);
|
||||
|
||||
private:
|
||||
static const int kWidgetSpacing;
|
||||
|
||||
Application *app_;
|
||||
CollectionView *collectionview_;
|
||||
AlbumCoverChoiceController *album_cover_choice_controller_;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -46,7 +46,9 @@
|
||||
|
||||
#include <getopt.h>
|
||||
|
||||
const char *CommandlineOptions::kHelpText =
|
||||
namespace {
|
||||
|
||||
constexpr char kHelpText[] =
|
||||
"%1: strawberry [%2] [%3]\n"
|
||||
"\n"
|
||||
"%4:\n"
|
||||
@@ -83,7 +85,9 @@ const char *CommandlineOptions::kHelpText =
|
||||
" --log-levels <levels> %33\n"
|
||||
" --version %34\n";
|
||||
|
||||
const char *CommandlineOptions::kVersionText = "Strawberry %1";
|
||||
constexpr char kVersionText[] = "Strawberry %1";
|
||||
|
||||
} // namespace
|
||||
|
||||
CommandlineOptions::CommandlineOptions(int argc, char **argv)
|
||||
: argc_(argc),
|
||||
@@ -202,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
|
||||
|
||||
@@ -41,9 +41,6 @@ class CommandlineOptions {
|
||||
public:
|
||||
explicit CommandlineOptions(int argc = 0, char **argv = nullptr);
|
||||
|
||||
static const char *kHelpText;
|
||||
static const char *kVersionText;
|
||||
|
||||
// Don't change the values or order, these get serialised and sent to
|
||||
// possibly a different version of Strawberry
|
||||
enum class UrlListAction {
|
||||
|
||||
@@ -71,6 +71,8 @@ Database::Database(Application *app, QObject *parent, const QString &database_na
|
||||
startup_schema_version_(-1),
|
||||
original_thread_(nullptr) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
|
||||
original_thread_ = thread();
|
||||
|
||||
{
|
||||
@@ -105,7 +107,7 @@ void Database::Exit() {
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
Close();
|
||||
moveToThread(original_thread_);
|
||||
emit ExitFinished();
|
||||
Q_EMIT ExitFinished();
|
||||
|
||||
}
|
||||
|
||||
@@ -161,7 +163,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 +184,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 +260,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_);
|
||||
{
|
||||
@@ -388,7 +390,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!
|
||||
@@ -481,8 +484,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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -83,15 +83,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:
|
||||
|
||||
@@ -34,7 +34,9 @@
|
||||
#include "deletefiles.h"
|
||||
#include "musicstorage.h"
|
||||
|
||||
const int DeleteFiles::kBatchSize = 50;
|
||||
namespace {
|
||||
constexpr int kBatchSize = 50;
|
||||
}
|
||||
|
||||
DeleteFiles::DeleteFiles(SharedPtr<TaskManager> task_manager, SharedPtr<MusicStorage> storage, const bool use_trash, QObject *parent)
|
||||
: QObject(parent),
|
||||
@@ -97,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_);
|
||||
@@ -114,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;
|
||||
|
||||
@@ -41,15 +41,13 @@ class DeleteFiles : public QObject {
|
||||
explicit DeleteFiles(SharedPtr<TaskManager> task_manager, SharedPtr<MusicStorage> storage, const bool use_trash, QObject *parent = nullptr);
|
||||
~DeleteFiles() override;
|
||||
|
||||
static const int kBatchSize;
|
||||
|
||||
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:
|
||||
|
||||
@@ -39,7 +39,7 @@ class FileSystemWatcherInterface : public QObject {
|
||||
|
||||
static FileSystemWatcherInterface *Create(QObject *parent = nullptr);
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void PathChanged(const QString &path);
|
||||
};
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ void LocalRedirectServer::incomingConnection(qintptr socket_descriptor) {
|
||||
delete tcp_socket;
|
||||
close();
|
||||
error_ = QLatin1String("Unable to set socket descriptor");
|
||||
emit Finished();
|
||||
Q_EMIT Finished();
|
||||
return;
|
||||
}
|
||||
socket_ = tcp_socket;
|
||||
@@ -114,7 +114,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 +129,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();
|
||||
|
||||
@@ -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();
|
||||
@@ -57,7 +57,6 @@ class LocalRedirectServer : public QTcpServer {
|
||||
void ReadyRead();
|
||||
|
||||
private:
|
||||
bool GenerateCertificate();
|
||||
void WriteTemplate() const;
|
||||
QUrl ParseUrlFromRequest(const QByteArray &request) const;
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -34,9 +34,9 @@
|
||||
|
||||
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);
|
||||
@@ -60,7 +60,7 @@ void MacFSListener::EventStreamCallback(ConstFSEventStreamRef stream, void *user
|
||||
while (path.endsWith(QLatin1Char('/'))) {
|
||||
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>
|
||||
|
||||
@@ -399,16 +400,16 @@ 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"));
|
||||
#endif
|
||||
|
||||
// Add the playing widget to the fancy tab widget
|
||||
ui_->tabs->addBottomWidget(ui_->widget_playing);
|
||||
ui_->tabs->setBackgroundPixmap(QPixmap(QStringLiteral(":/pictures/sidebar-background.png")));
|
||||
ui_->tabs->Load(QLatin1String(kSettingsGroup));
|
||||
ui_->tabs->AddBottomWidget(ui_->widget_playing);
|
||||
ui_->tabs->SetBackgroundPixmap(QPixmap(QStringLiteral(":/pictures/sidebar-background.png")));
|
||||
ui_->tabs->LoadSettings(QLatin1String(kSettingsGroup));
|
||||
|
||||
track_position_timer_->setInterval(kTrackPositionUpdateTimeMs);
|
||||
QObject::connect(track_position_timer_, &QTimer::timeout, this, &MainWindow::UpdateTrackPosition);
|
||||
@@ -489,7 +490,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 +556,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 +622,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);
|
||||
@@ -844,7 +846,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||
#endif
|
||||
|
||||
// Fancy tabs
|
||||
QObject::connect(ui_->tabs, &FancyTabWidget::CurrentChanged, this, &MainWindow::TabSwitched);
|
||||
QObject::connect(ui_->tabs, &FancyTabWidget::CurrentTabChanged, this, &MainWindow::TabSwitched);
|
||||
|
||||
// Context
|
||||
QObject::connect(&*app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, context_view_, &ContextView::SongChanged);
|
||||
@@ -1023,9 +1025,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();
|
||||
}
|
||||
@@ -1071,17 +1070,25 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||
#endif
|
||||
|
||||
{
|
||||
bool asked_permission = true;
|
||||
Settings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
constexpr char do_not_show_sponsor_message_key[] = "do_not_show_sponsor_message";
|
||||
const bool do_not_show_sponsor_message = s.value(do_not_show_sponsor_message_key, false).toBool();
|
||||
#ifdef HAVE_QTSPARKLE
|
||||
s.beginGroup("QtSparkle");
|
||||
asked_permission = s.value("asked_permission", false).toBool();
|
||||
s.endGroup();
|
||||
if (!do_not_show_sponsor_message) {
|
||||
MessageDialog *sponsor_message = new MessageDialog(this);
|
||||
sponsor_message->set_settings_group(QLatin1String(kSettingsGroup));
|
||||
sponsor_message->set_do_not_show_message_again(QLatin1String(do_not_show_sponsor_message_key));
|
||||
sponsor_message->setAttribute(Qt::WA_DeleteOnClose);
|
||||
sponsor_message->ShowMessage(tr("Sponsoring Strawberry"), tr("Strawberry is free and open source software. If you like Strawberry, please consider sponsoring the project. For more information about sponsorship see our website %1").arg(QStringLiteral("<a href= \"https://www.strawberrymusicplayer.org/\">www.strawberrymusicplayer.org</a>")), IconLoader::Load(QStringLiteral("dialog-information")));
|
||||
#endif
|
||||
if (asked_permission) {
|
||||
s.beginGroup(kSettingsGroup);
|
||||
constexpr char do_not_show_sponsor_message_key[] = "do_not_show_sponsor_message";
|
||||
const bool do_not_show_sponsor_message = s.value(do_not_show_sponsor_message_key, false).toBool();
|
||||
s.endGroup();
|
||||
if (!do_not_show_sponsor_message) {
|
||||
MessageDialog *sponsor_message = new MessageDialog(this);
|
||||
sponsor_message->set_settings_group(QLatin1String(kSettingsGroup));
|
||||
sponsor_message->set_do_not_show_message_again(QLatin1String(do_not_show_sponsor_message_key));
|
||||
sponsor_message->setAttribute(Qt::WA_DeleteOnClose);
|
||||
sponsor_message->ShowMessage(tr("Sponsoring Strawberry"), tr("Strawberry is free and open source software. If you like Strawberry, please consider sponsoring the project. For more information about sponsorship see our website %1").arg(QStringLiteral("<a href= \"https://www.strawberrymusicplayer.org/\">www.strawberrymusicplayer.org</a>")), IconLoader::Load(QStringLiteral("dialog-information")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1272,8 +1279,8 @@ void MainWindow::RefreshStyleSheet() {
|
||||
void MainWindow::SaveSettings() {
|
||||
|
||||
SaveGeometry();
|
||||
SavePlaybackStatus();
|
||||
app_->player()->SaveVolume();
|
||||
app_->player()->SavePlaybackStatus();
|
||||
ui_->tabs->SaveSettings(QLatin1String(kSettingsGroup));
|
||||
ui_->playlist->view()->SaveSettings();
|
||||
app_->scrobbler()->WriteCache();
|
||||
@@ -1299,7 +1306,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;
|
||||
@@ -1363,8 +1370,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();
|
||||
@@ -1392,8 +1403,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();
|
||||
|
||||
@@ -1418,8 +1433,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();
|
||||
|
||||
}
|
||||
@@ -1527,80 +1547,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;
|
||||
@@ -1612,7 +1558,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);
|
||||
|
||||
}
|
||||
|
||||
@@ -1629,14 +1575,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;
|
||||
}
|
||||
@@ -1644,7 +1590,7 @@ void MainWindow::PlaylistDoubleClick(const QModelIndex &idx) {
|
||||
}
|
||||
|
||||
void MainWindow::VolumeWheelEvent(const int delta) {
|
||||
ui_->volume->setValue(ui_->volume->value() + delta / 30);
|
||||
ui_->volume->HandleWheel(delta);
|
||||
}
|
||||
|
||||
void MainWindow::ToggleShowHide() {
|
||||
@@ -1678,7 +1624,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) {
|
||||
@@ -1888,7 +1834,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());
|
||||
@@ -1955,7 +1902,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;
|
||||
@@ -2102,7 +2049,7 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
|
||||
#endif
|
||||
|
||||
// Remove old item actions, if any.
|
||||
for (QAction *action : playlistitem_actions_) {
|
||||
for (QAction *action : std::as_const(playlistitem_actions_)) {
|
||||
playlist_menu_->removeAction(action);
|
||||
}
|
||||
|
||||
@@ -2112,7 +2059,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_;
|
||||
@@ -2124,18 +2071,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
|
||||
@@ -2169,7 +2117,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()));
|
||||
@@ -2194,7 +2143,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()));
|
||||
@@ -2216,7 +2166,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();
|
||||
}
|
||||
|
||||
@@ -2235,13 +2186,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());
|
||||
@@ -2272,7 +2223,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());
|
||||
@@ -2319,7 +2271,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;
|
||||
|
||||
@@ -2386,7 +2338,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());
|
||||
@@ -2530,9 +2483,10 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
||||
if (!options.urls().empty()) {
|
||||
|
||||
#ifdef HAVE_TIDAL
|
||||
for (const QUrl &url : options.urls()) {
|
||||
const QList<QUrl> urls = options.urls();
|
||||
for (const QUrl &url : urls) {
|
||||
if (url.scheme() == QLatin1String("tidal") && url.host() == QLatin1String("login")) {
|
||||
emit AuthorizationUrlReceived(url);
|
||||
Q_EMIT AuthorizationUrlReceived(url);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2577,7 +2531,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();
|
||||
|
||||
@@ -2610,7 +2564,7 @@ bool MainWindow::LoadUrl(const QString &url) {
|
||||
}
|
||||
#ifdef HAVE_TIDAL
|
||||
if (url.startsWith(QLatin1String("tidal://login"))) {
|
||||
emit AuthorizationUrlReceived(QUrl(url));
|
||||
Q_EMIT AuthorizationUrlReceived(QUrl(url));
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -2633,7 +2587,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()));
|
||||
@@ -2745,7 +2700,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());
|
||||
@@ -2767,7 +2723,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());
|
||||
@@ -2780,7 +2737,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());
|
||||
@@ -2811,7 +2769,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) {
|
||||
@@ -2841,7 +2799,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());
|
||||
@@ -2956,7 +2915,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;
|
||||
}
|
||||
@@ -2970,7 +2929,7 @@ 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) {
|
||||
@@ -3042,7 +3001,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()));
|
||||
@@ -3067,7 +3027,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();
|
||||
@@ -3172,7 +3132,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);
|
||||
@@ -3200,7 +3160,7 @@ void MainWindow::GetCoverAutomatically() {
|
||||
!song_.effective_album().isEmpty();
|
||||
|
||||
if (search) {
|
||||
emit SearchCoverInProgress();
|
||||
Q_EMIT SearchCoverInProgress();
|
||||
album_cover_choice_controller_->SearchCoverAutomatically(song_);
|
||||
}
|
||||
|
||||
@@ -3259,7 +3219,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;
|
||||
|
||||
@@ -131,7 +131,7 @@ class MainWindow : public QMainWindow, public 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 +139,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 +238,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 +271,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*");
|
||||
@@ -167,4 +169,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
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <cmath>
|
||||
|
||||
#include <QApplication>
|
||||
@@ -141,7 +142,7 @@ Mpris2::Mpris2(Application *app, QObject *parent)
|
||||
data_dirs.append(QStringLiteral("/usr/share"));
|
||||
}
|
||||
|
||||
for (const QString &data_dir : data_dirs) {
|
||||
for (const QString &data_dir : std::as_const(data_dirs)) {
|
||||
const QString desktopfilepath = QStringLiteral("%1/applications/%2.desktop").arg(data_dir, QGuiApplication::desktopFileName());
|
||||
if (QFile::exists(desktopfilepath)) {
|
||||
desktopfilepath_ = desktopfilepath;
|
||||
@@ -240,7 +241,7 @@ QString Mpris2::DesktopEntryAbsolutePath() const {
|
||||
|
||||
}
|
||||
|
||||
QString Mpris2::DesktopEntry() const { return QGuiApplication::desktopFileName() + QLatin1String(".desktop"); }
|
||||
QString Mpris2::DesktopEntry() const { return QGuiApplication::desktopFileName(); }
|
||||
|
||||
QStringList Mpris2::SupportedUriSchemes() const {
|
||||
|
||||
@@ -288,7 +289,7 @@ QStringList Mpris2::SupportedMimeTypes() const {
|
||||
|
||||
}
|
||||
|
||||
void Mpris2::Raise() { emit RaiseMainWindow(); }
|
||||
void Mpris2::Raise() { Q_EMIT RaiseMainWindow(); }
|
||||
|
||||
void Mpris2::Quit() { QCoreApplication::quit(); }
|
||||
|
||||
@@ -618,7 +619,7 @@ MprisPlaylistList Mpris2::GetPlaylists(quint32 index, quint32 max_count, const Q
|
||||
|
||||
Q_UNUSED(order);
|
||||
|
||||
QList<Playlist*> playlists = app_->playlist_manager()->GetAllPlaylists();
|
||||
const QList<Playlist*> playlists = app_->playlist_manager()->GetAllPlaylists();
|
||||
MprisPlaylistList ret;
|
||||
ret.reserve(playlists.count());
|
||||
for (Playlist *p : playlists) {
|
||||
@@ -642,7 +643,7 @@ void Mpris2::PlaylistChangedSlot(Playlist *playlist) {
|
||||
mpris_playlist.id = MakePlaylistPath(playlist->id());
|
||||
mpris_playlist.name = app_->playlist_manager()->GetPlaylistName(playlist->id());
|
||||
|
||||
emit PlaylistChanged(mpris_playlist);
|
||||
Q_EMIT PlaylistChanged(mpris_playlist);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -33,9 +33,8 @@
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QtDBus>
|
||||
#include <QDBusObjectPath>
|
||||
#include <QDBusArgument>
|
||||
#include <qdbusextratypes.h>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "engine/enginebase.h"
|
||||
@@ -191,7 +190,7 @@ class Mpris2 : public QObject {
|
||||
void ActivatePlaylist(const QDBusObjectPath &playlist_id);
|
||||
MprisPlaylistList GetPlaylists(quint32 index, quint32 max_count, const QString &order, bool reverse_order);
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
// Player
|
||||
void Seeked(const qint64 position);
|
||||
|
||||
@@ -206,7 +205,7 @@ class Mpris2 : public QObject {
|
||||
// Playlist
|
||||
void PlaylistChanged(const MprisPlaylist &playlist);
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result = AlbumCoverLoaderResult());
|
||||
void EngineStateChanged(EngineBase::State newState);
|
||||
void VolumeChanged();
|
||||
|
||||
65
src/core/mutex_protected.h
Normal file
65
src/core/mutex_protected.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MUTEX_PROTECTED_H
|
||||
#define MUTEX_PROTECTED_H
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#include <QMutex>
|
||||
#include <QMutexLocker>
|
||||
|
||||
template<typename T>
|
||||
class mutex_protected : public boost::noncopyable {
|
||||
public:
|
||||
mutex_protected(const mutex_protected &value) : value_(value.value()) {}
|
||||
mutex_protected(const T value) : value_(value) {}
|
||||
~mutex_protected() {}
|
||||
|
||||
T value() const {
|
||||
QMutexLocker l(&mutex_);
|
||||
return value_;
|
||||
}
|
||||
|
||||
T operator==(const mutex_protected &value) const {
|
||||
QMutexLocker l(&mutex_);
|
||||
return value == value_;
|
||||
}
|
||||
|
||||
T operator==(const T value) const {
|
||||
QMutexLocker l(&mutex_);
|
||||
return value == value_;
|
||||
}
|
||||
|
||||
void operator=(const mutex_protected &value) {
|
||||
QMutexLocker l(&mutex_);
|
||||
value_ = value.value();
|
||||
}
|
||||
|
||||
void operator=(const T value) {
|
||||
QMutexLocker l(&mutex_);
|
||||
value_ = value;
|
||||
}
|
||||
|
||||
private:
|
||||
T value_;
|
||||
mutable QMutex mutex_;
|
||||
};
|
||||
|
||||
#endif // MUTEX_PROTECTED_H
|
||||
@@ -42,7 +42,7 @@ class NetworkTimeouts : public QObject {
|
||||
protected:
|
||||
void timerEvent(QTimerEvent *e) override;
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void ReplyFinished();
|
||||
|
||||
private:
|
||||
|
||||
@@ -85,6 +85,8 @@ Player::Player(Application *app, QObject *parent)
|
||||
analyzer_(nullptr),
|
||||
equalizer_(nullptr),
|
||||
timer_save_volume_(new QTimer(this)),
|
||||
playlists_loaded_(false),
|
||||
play_requested_(false),
|
||||
stream_change_type_(EngineBase::TrackChangeType::First),
|
||||
autoscroll_(Playlist::AutoScroll::Maybe),
|
||||
last_state_(EngineBase::State::Empty),
|
||||
@@ -99,6 +101,8 @@ Player::Player(Application *app, QObject *parent)
|
||||
volume_increment_(5),
|
||||
play_offset_nanosec_(0) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
|
||||
Settings s;
|
||||
s.beginGroup(BackendSettingsPage::kSettingsGroup);
|
||||
EngineBase::Type enginetype = EngineBase::TypeFromName(s.value("engine", EngineBase::Name(EngineBase::Type::GStreamer)).toString().toLower());
|
||||
@@ -156,7 +160,7 @@ EngineBase::Type Player::CreateEngine(EngineBase::Type enginetype) {
|
||||
qFatal("Failed to create engine!");
|
||||
}
|
||||
|
||||
emit EngineChanged(use_enginetype);
|
||||
Q_EMIT EngineChanged(use_enginetype);
|
||||
|
||||
return use_enginetype;
|
||||
|
||||
@@ -245,6 +249,80 @@ void Player::SaveVolume() {
|
||||
|
||||
}
|
||||
|
||||
void Player::SavePlaybackStatus() {
|
||||
|
||||
Settings s;
|
||||
|
||||
s.beginGroup(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 Player::PlaylistsLoaded() {
|
||||
|
||||
playlists_loaded_ = true;
|
||||
|
||||
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::Playing || playback_state == EngineBase::State::Paused)) {
|
||||
ResumePlayback();
|
||||
}
|
||||
else if (play_requested_) {
|
||||
Play();
|
||||
}
|
||||
|
||||
play_requested_ = false;
|
||||
|
||||
}
|
||||
|
||||
void Player::ResumePlayback() {
|
||||
|
||||
qLog(Debug) << "Resuming playback";
|
||||
|
||||
Settings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
const EngineBase::State playback_state = static_cast<EngineBase::State>(s.value("playback_state", static_cast<int>(EngineBase::State::Empty)).toInt());
|
||||
const int playback_playlist = s.value("playback_playlist", -1).toInt();
|
||||
const 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::Playing) {
|
||||
Play(playback_position * kNsecPerSec);
|
||||
}
|
||||
else if (playback_state == EngineBase::State::Paused) {
|
||||
PlayWithPause(playback_position * kNsecPerSec);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset saved playback status so we don't resume again from the same position.
|
||||
s.beginGroup(kSettingsGroup);
|
||||
s.setValue("playback_state", static_cast<int>(EngineBase::State::Empty));
|
||||
s.setValue("playback_playlist", -1);
|
||||
s.setValue("playback_position", 0);
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
|
||||
|
||||
if (loading_async_.contains(result.media_url_)) {
|
||||
@@ -285,7 +363,7 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
|
||||
if (is_current) {
|
||||
InvalidSongRequested(result.media_url_);
|
||||
}
|
||||
emit Error(result.error_);
|
||||
Q_EMIT Error(result.error_);
|
||||
break;
|
||||
|
||||
case UrlHandler::LoadResult::Type::NoMoreTracks:
|
||||
@@ -354,7 +432,7 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
|
||||
|
||||
if (is_current) {
|
||||
qLog(Debug) << "Playing song" << current_item->Metadata().title() << result.stream_url_ << "position" << play_offset_nanosec_;
|
||||
engine_->Play(result.media_url_, result.stream_url_, stream_change_type_, song.has_cue(), song.beginning_nanosec(), song.end_nanosec(), play_offset_nanosec_, song.ebur128_integrated_loudness_lufs());
|
||||
engine_->Play(result.media_url_, result.stream_url_, pause_, stream_change_type_, song.has_cue(), song.beginning_nanosec(), song.end_nanosec(), play_offset_nanosec_, song.ebur128_integrated_loudness_lufs());
|
||||
current_item_ = current_item;
|
||||
play_offset_nanosec_ = 0;
|
||||
}
|
||||
@@ -421,12 +499,12 @@ void Player::NextItem(const EngineBase::TrackChangeFlags change, const Playlist:
|
||||
if (i == -1) {
|
||||
app_->playlist_manager()->active()->set_current_row(i);
|
||||
app_->playlist_manager()->active()->reset_last_played();
|
||||
emit PlaylistFinished();
|
||||
Q_EMIT PlaylistFinished();
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
PlayAt(i, 0, change, autoscroll, false, true);
|
||||
PlayAt(i, false, 0, change, autoscroll, false, true);
|
||||
|
||||
}
|
||||
|
||||
@@ -461,11 +539,10 @@ void Player::PlayPlaylistInternal(const EngineBase::TrackChangeFlags change, con
|
||||
if (i == -1) i = app_->playlist_manager()->active()->last_played_row();
|
||||
if (i == -1) i = 0;
|
||||
|
||||
PlayAt(i, 0, change, autoscroll, true);
|
||||
PlayAt(i, false, 0, change, autoscroll, true);
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool Player::HandleStopAfter(const Playlist::AutoScroll autoscroll) {
|
||||
|
||||
if (app_->playlist_manager()->active()->stop_after_current()) {
|
||||
@@ -502,7 +579,7 @@ void Player::PlayPause(const quint64 offset_nanosec, const Playlist::AutoScroll
|
||||
switch (engine_->state()) {
|
||||
case EngineBase::State::Paused:
|
||||
UnPause();
|
||||
emit Resumed();
|
||||
Q_EMIT Resumed();
|
||||
break;
|
||||
|
||||
case EngineBase::State::Playing:{
|
||||
@@ -527,7 +604,7 @@ void Player::PlayPause(const quint64 offset_nanosec, const Playlist::AutoScroll
|
||||
int i = app_->playlist_manager()->active()->current_row();
|
||||
if (i == -1) i = app_->playlist_manager()->active()->last_played_row();
|
||||
if (i == -1) i = 0;
|
||||
PlayAt(i, offset_nanosec, EngineBase::TrackChangeType::First, autoscroll, true);
|
||||
PlayAt(i, false, offset_nanosec, EngineBase::TrackChangeType::First, autoscroll, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -543,7 +620,8 @@ void Player::UnPause() {
|
||||
if (time >= 30) { // Stream URL might be expired.
|
||||
qLog(Debug) << "Re-requesting stream URL for" << song.url();
|
||||
play_offset_nanosec_ = engine_->position_nanosec();
|
||||
HandleLoadResult(url_handlers_[song.url().scheme()]->StartLoading(song.url()));
|
||||
UrlHandler *url_handler = url_handlers_.value(song.url().scheme());
|
||||
HandleLoadResult(url_handler->StartLoading(song.url()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -606,7 +684,7 @@ void Player::PreviousItem(const EngineBase::TrackChangeFlags change) {
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
if (last_pressed_previous_.isValid() && last_pressed_previous_.secsTo(now) >= 2) {
|
||||
last_pressed_previous_ = now;
|
||||
PlayAt(app_->playlist_manager()->active()->current_row(), 0, change, Playlist::AutoScroll::Always, false, true);
|
||||
PlayAt(app_->playlist_manager()->active()->current_row(), false, 0, change, Playlist::AutoScroll::Always, false, true);
|
||||
return;
|
||||
}
|
||||
last_pressed_previous_ = now;
|
||||
@@ -616,11 +694,11 @@ void Player::PreviousItem(const EngineBase::TrackChangeFlags change) {
|
||||
app_->playlist_manager()->active()->set_current_row(i, Playlist::AutoScroll::Always, false);
|
||||
if (i == -1) {
|
||||
Stop();
|
||||
PlayAt(i, 0, change, Playlist::AutoScroll::Always, true);
|
||||
PlayAt(i, false, 0, change, Playlist::AutoScroll::Always, true);
|
||||
return;
|
||||
}
|
||||
|
||||
PlayAt(i, 0, change, Playlist::AutoScroll::Always, false);
|
||||
PlayAt(i, false, 0, change, Playlist::AutoScroll::Always, false);
|
||||
|
||||
}
|
||||
|
||||
@@ -637,21 +715,21 @@ void Player::EngineStateChanged(const EngineBase::State state) {
|
||||
case EngineBase::State::Paused:
|
||||
pause_time_ = QDateTime::currentDateTime();
|
||||
play_offset_nanosec_ = engine_->position_nanosec();
|
||||
emit Paused();
|
||||
Q_EMIT Paused();
|
||||
break;
|
||||
case EngineBase::State::Playing:
|
||||
pause_time_ = QDateTime();
|
||||
play_offset_nanosec_ = 0;
|
||||
emit Playing();
|
||||
Q_EMIT Playing();
|
||||
break;
|
||||
case EngineBase::State::Error:
|
||||
emit Error();
|
||||
Q_EMIT Error();
|
||||
[[fallthrough]];
|
||||
case EngineBase::State::Empty:
|
||||
case EngineBase::State::Idle:
|
||||
pause_time_ = QDateTime();
|
||||
play_offset_nanosec_ = 0;
|
||||
emit Stopped();
|
||||
Q_EMIT Stopped();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -671,7 +749,7 @@ void Player::SetVolumeFromSlider(const int value) {
|
||||
if (volume != volume_) {
|
||||
volume_ = volume;
|
||||
engine_->SetVolume(volume);
|
||||
emit VolumeChanged(volume);
|
||||
Q_EMIT VolumeChanged(volume);
|
||||
timer_save_volume_->start();
|
||||
}
|
||||
|
||||
@@ -682,7 +760,7 @@ void Player::SetVolumeFromEngine(const uint volume) {
|
||||
const uint new_volume = qBound(0U, volume, 100U);
|
||||
if (new_volume != volume_) {
|
||||
volume_ = new_volume;
|
||||
emit VolumeChanged(new_volume);
|
||||
Q_EMIT VolumeChanged(new_volume);
|
||||
timer_save_volume_->start();
|
||||
}
|
||||
|
||||
@@ -694,7 +772,7 @@ void Player::SetVolume(const uint volume) {
|
||||
if (new_volume != volume_) {
|
||||
volume_ = new_volume;
|
||||
engine_->SetVolume(new_volume);
|
||||
emit VolumeChanged(new_volume);
|
||||
Q_EMIT VolumeChanged(new_volume);
|
||||
timer_save_volume_->start();
|
||||
}
|
||||
|
||||
@@ -718,13 +796,13 @@ void Player::VolumeDown() {
|
||||
|
||||
}
|
||||
|
||||
void Player::PlayAt(const int index, const quint64 offset_nanosec, EngineBase::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform) {
|
||||
void Player::PlayAt(const int index, const bool pause, const quint64 offset_nanosec, EngineBase::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform) {
|
||||
|
||||
pause_time_ = QDateTime();
|
||||
pause_time_ = pause ? QDateTime::currentDateTime() : QDateTime();
|
||||
play_offset_nanosec_ = offset_nanosec;
|
||||
|
||||
if (current_item_ && change & EngineBase::TrackChangeType::Manual && engine_->position_nanosec() != engine_->length_nanosec()) {
|
||||
emit TrackSkipped(current_item_);
|
||||
Q_EMIT TrackSkipped(current_item_);
|
||||
}
|
||||
|
||||
if (current_item_ && app_->playlist_manager()->active()->has_item_at(index) && current_item_->Metadata().IsOnSameAlbum(app_->playlist_manager()->active()->item_at(index)->Metadata())) {
|
||||
@@ -748,13 +826,15 @@ void Player::PlayAt(const int index, const quint64 offset_nanosec, EngineBase::T
|
||||
return;
|
||||
}
|
||||
|
||||
pause_ = pause;
|
||||
stream_change_type_ = change;
|
||||
autoscroll_ = autoscroll;
|
||||
HandleLoadResult(url_handlers_[url.scheme()]->StartLoading(url));
|
||||
UrlHandler *url_handler = url_handlers_.value(url.scheme());
|
||||
HandleLoadResult(url_handler->StartLoading(url));
|
||||
}
|
||||
else {
|
||||
qLog(Debug) << "Playing song" << current_item_->Metadata().title() << url << "position" << offset_nanosec;
|
||||
engine_->Play(current_item_->Url(), url, change, current_item_->Metadata().has_cue(), current_item_->effective_beginning_nanosec(), current_item_->effective_end_nanosec(), offset_nanosec, current_item_->effective_ebur128_integrated_loudness_lufs());
|
||||
engine_->Play(current_item_->Url(), url, pause, change, current_item_->Metadata().has_cue(), current_item_->effective_beginning_nanosec(), current_item_->effective_end_nanosec(), offset_nanosec, current_item_->effective_ebur128_integrated_loudness_lufs());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -781,7 +861,7 @@ void Player::SeekTo(const quint64 seconds) {
|
||||
qLog(Debug) << "Track seeked to" << nanosec << "ns - updating scrobble point";
|
||||
app_->playlist_manager()->active()->UpdateScrobblePoint(nanosec);
|
||||
|
||||
emit Seeked(nanosec / 1000);
|
||||
Q_EMIT Seeked(nanosec / 1000);
|
||||
|
||||
if (seconds == 0) {
|
||||
app_->playlist_manager()->active()->InformOfCurrentSongChange(false);
|
||||
@@ -852,6 +932,11 @@ void Player::Pause() { engine_->Pause(); }
|
||||
|
||||
void Player::Play(const quint64 offset_nanosec) {
|
||||
|
||||
if (!playlists_loaded_) {
|
||||
play_requested_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (GetState()) {
|
||||
case EngineBase::State::Playing:
|
||||
SeekTo(offset_nanosec);
|
||||
@@ -866,12 +951,25 @@ void Player::Play(const quint64 offset_nanosec) {
|
||||
|
||||
}
|
||||
|
||||
void Player::PlayWithPause(const quint64 offset_nanosec) {
|
||||
|
||||
pause_time_ = QDateTime();
|
||||
play_offset_nanosec_ = offset_nanosec;
|
||||
app_->playlist_manager()->SetActivePlaylist(app_->playlist_manager()->current_id());
|
||||
if (app_->playlist_manager()->active()->rowCount() == 0) return;
|
||||
int i = app_->playlist_manager()->active()->current_row();
|
||||
if (i == -1) i = app_->playlist_manager()->active()->last_played_row();
|
||||
if (i == -1) i = 0;
|
||||
PlayAt(i, true, offset_nanosec, EngineBase::TrackChangeType::First, Playlist::AutoScroll::Always, true);
|
||||
|
||||
}
|
||||
|
||||
void Player::ShowOSD() {
|
||||
if (current_item_) emit ForceShowOSD(current_item_->Metadata(), false);
|
||||
if (current_item_) Q_EMIT ForceShowOSD(current_item_->Metadata(), false);
|
||||
}
|
||||
|
||||
void Player::TogglePrettyOSD() {
|
||||
if (current_item_) emit ForceShowOSD(current_item_->Metadata(), true);
|
||||
if (current_item_) Q_EMIT ForceShowOSD(current_item_->Metadata(), true);
|
||||
}
|
||||
|
||||
void Player::TrackAboutToEnd() {
|
||||
@@ -909,10 +1007,11 @@ void Player::TrackAboutToEnd() {
|
||||
if (url_handlers_.contains(url.scheme())) {
|
||||
if (loading_async_.contains(url)) return;
|
||||
autoscroll_ = Playlist::AutoScroll::Maybe;
|
||||
UrlHandler::LoadResult result = url_handlers_[url.scheme()]->StartLoading(url);
|
||||
UrlHandler *url_handler = url_handlers_.value(url.scheme());
|
||||
const UrlHandler::LoadResult result = url_handler->StartLoading(url);
|
||||
switch (result.type_) {
|
||||
case UrlHandler::LoadResult::Type::Error:
|
||||
emit Error(result.error_);
|
||||
Q_EMIT Error(result.error_);
|
||||
return;
|
||||
case UrlHandler::LoadResult::Type::NoMoreTracks:
|
||||
return;
|
||||
@@ -945,12 +1044,12 @@ void Player::FatalError() {
|
||||
}
|
||||
|
||||
void Player::ValidSongRequested(const QUrl &url) {
|
||||
emit SongChangeRequestProcessed(url, true);
|
||||
Q_EMIT SongChangeRequestProcessed(url, true);
|
||||
}
|
||||
|
||||
void Player::InvalidSongRequested(const QUrl &url) {
|
||||
|
||||
if (greyout_) emit SongChangeRequestProcessed(url, false);
|
||||
if (greyout_) Q_EMIT SongChangeRequestProcessed(url, false);
|
||||
|
||||
if (!continue_on_error_) {
|
||||
FatalError();
|
||||
@@ -1013,5 +1112,5 @@ void Player::UrlHandlerDestroyed(QObject *object) {
|
||||
}
|
||||
|
||||
void Player::HandleAuthentication() {
|
||||
emit Authenticated();
|
||||
Q_EMIT Authenticated();
|
||||
}
|
||||
|
||||
@@ -64,13 +64,16 @@ class PlayerInterface : public QObject {
|
||||
virtual void RegisterUrlHandler(UrlHandler *handler) = 0;
|
||||
virtual void UnregisterUrlHandler(UrlHandler *handler) = 0;
|
||||
|
||||
public slots:
|
||||
public Q_SLOTS:
|
||||
virtual void ReloadSettings() = 0;
|
||||
virtual void LoadVolume() = 0;
|
||||
virtual void SaveVolume() = 0;
|
||||
virtual void SavePlaybackStatus() = 0;
|
||||
|
||||
virtual void PlaylistsLoaded() = 0;
|
||||
|
||||
// Manual track change to the specified track
|
||||
virtual void PlayAt(const int index, const quint64 offset_nanosec, EngineBase::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform = false) = 0;
|
||||
virtual void PlayAt(const int index, const bool pause, const quint64 offset_nanosec, EngineBase::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform = false) = 0;
|
||||
|
||||
// If there's currently a song playing, pause it, otherwise play the track that was playing last, or the first one on the playlist
|
||||
virtual void PlayPause(const quint64 offset_nanosec = 0, const Playlist::AutoScroll autoscroll = Playlist::AutoScroll::Always) = 0;
|
||||
@@ -98,10 +101,11 @@ class PlayerInterface : public QObject {
|
||||
virtual void Pause() = 0;
|
||||
virtual void Stop(const bool stop_after = false) = 0;
|
||||
virtual void Play(const quint64 offset_nanosec = 0) = 0;
|
||||
virtual void PlayWithPause(const quint64 offset_nanosec) = 0;
|
||||
virtual void PlayHelper() = 0;
|
||||
virtual void ShowOSD() = 0;
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void Playing();
|
||||
void Paused();
|
||||
// Emitted only when playback is manually resumed
|
||||
@@ -154,12 +158,14 @@ class Player : public PlayerInterface {
|
||||
void SetAnalyzer(AnalyzerContainer *analyzer) { analyzer_ = analyzer; }
|
||||
void SetEqualizer(SharedPtr<Equalizer> equalizer) { equalizer_ = equalizer; }
|
||||
|
||||
public slots:
|
||||
public Q_SLOTS:
|
||||
void ReloadSettings() override;
|
||||
void LoadVolume() override;
|
||||
void SaveVolume() override;
|
||||
void SavePlaybackStatus() override;
|
||||
void PlaylistsLoaded() override;
|
||||
|
||||
void PlayAt(const int index, const quint64 offset_nanosec, EngineBase::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform = false) override;
|
||||
void PlayAt(const int index, const bool pause, const quint64 offset_nanosec, EngineBase::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform = false) override;
|
||||
void PlayPause(const quint64 offset_nanosec = 0, const Playlist::AutoScroll autoscroll = Playlist::AutoScroll::Always) override;
|
||||
void PlayPauseHelper() override { PlayPause(play_offset_nanosec_); }
|
||||
void RestartOrPrevious() override;
|
||||
@@ -182,16 +188,17 @@ class Player : public PlayerInterface {
|
||||
void Stop(const bool stop_after = false) override;
|
||||
void StopAfterCurrent();
|
||||
void Play(const quint64 offset_nanosec = 0) override;
|
||||
void PlayWithPause(const quint64 offset_nanosec) override;
|
||||
void PlayHelper() override { Play(); }
|
||||
void ShowOSD() override;
|
||||
void TogglePrettyOSD();
|
||||
|
||||
void HandleAuthentication();
|
||||
|
||||
signals:
|
||||
Q_SIGNALS:
|
||||
void EngineChanged(const EngineBase::Type Type);
|
||||
|
||||
private slots:
|
||||
private Q_SLOTS:
|
||||
void EngineStateChanged(const EngineBase::State);
|
||||
void EngineMetadataReceived(const EngineMetadata &engine_metadata);
|
||||
void TrackAboutToEnd();
|
||||
@@ -211,6 +218,8 @@ class Player : public PlayerInterface {
|
||||
void HandleLoadResult(const UrlHandler::LoadResult &result);
|
||||
|
||||
private:
|
||||
void ResumePlayback();
|
||||
|
||||
// Returns true if we were supposed to stop after this track.
|
||||
bool HandleStopAfter(const Playlist::AutoScroll autoscroll);
|
||||
|
||||
@@ -226,8 +235,12 @@ class Player : public PlayerInterface {
|
||||
SharedPtr<Equalizer> equalizer_;
|
||||
QTimer *timer_save_volume_;
|
||||
|
||||
bool playlists_loaded_;
|
||||
bool play_requested_;
|
||||
|
||||
PlaylistItemPtr current_item_;
|
||||
|
||||
bool pause_;
|
||||
EngineBase::TrackChangeFlags stream_change_type_;
|
||||
Playlist::AutoScroll autoscroll_;
|
||||
EngineBase::State last_state_;
|
||||
|
||||
34
src/core/potranslator.cpp
Normal file
34
src/core/potranslator.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 <QString>
|
||||
|
||||
#include "potranslator.h"
|
||||
|
||||
PoTranslator::PoTranslator(QObject *parent) : QTranslator(parent) {}
|
||||
|
||||
QString PoTranslator::translate(const char *context, const char *source_text, const char *disambiguation, const int n) const {
|
||||
|
||||
QString ret = QTranslator::translate(context, source_text, disambiguation, n);
|
||||
if (!ret.isEmpty()) return ret;
|
||||
return QTranslator::translate(nullptr, source_text, disambiguation, n);
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -22,6 +23,7 @@
|
||||
#define POTRANSLATOR_H
|
||||
|
||||
#include <QTranslator>
|
||||
#include <QString>
|
||||
|
||||
// We convert from .po files to .qm files, which loses context information.
|
||||
// This translator tries loading strings with an empty context if it can't find any others.
|
||||
@@ -30,12 +32,8 @@ class PoTranslator : public QTranslator {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PoTranslator(QObject *parent = nullptr) : QTranslator(parent) {}
|
||||
QString translate(const char *context, const char *source_text, const char *disambiguation = nullptr, int n = -1) const override {
|
||||
QString ret = QTranslator::translate(context, source_text, disambiguation, n);
|
||||
if (!ret.isEmpty()) return ret;
|
||||
return QTranslator::translate(nullptr, source_text, disambiguation, n);
|
||||
}
|
||||
explicit PoTranslator(QObject *parent = nullptr);
|
||||
QString translate(const char *context, const char *source_text, const char *disambiguation = nullptr, int n = -1) const override;
|
||||
};
|
||||
|
||||
#endif // POTRANSLATOR_H
|
||||
|
||||
@@ -113,11 +113,11 @@ void SystemTrayIcon::Clicked(const QSystemTrayIcon::ActivationReason reason) {
|
||||
switch (reason) {
|
||||
case QSystemTrayIcon::DoubleClick:
|
||||
case QSystemTrayIcon::Trigger:
|
||||
emit ShowHide();
|
||||
Q_EMIT ShowHide();
|
||||
break;
|
||||
|
||||
case QSystemTrayIcon::MiddleClick:
|
||||
emit PlayPause();
|
||||
Q_EMIT PlayPause();
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user