Compare commits

..

214 Commits

Author SHA1 Message Date
Jonas Kvinge
5a90643ba8 Release 1.1.3 2024-09-21 22:12:57 +02:00
Jonas Kvinge
4a1ab2f004 CI: Upload PPA on 1.1 branch 2024-09-21 22:07:15 +02:00
Jonas Kvinge
36be755a78 filterparser: Optimize filter term
Fixes #1536
2024-09-21 21:55:46 +02:00
Jonas Kvinge
ffd8c88b1f FilterParser: Add check for empty column 2024-09-21 21:45:33 +02:00
Jonas Kvinge
84ff163b34 appdata: Update description 2024-09-21 21:31:17 +02:00
Jonas Kvinge
811e625618 GeniusLyricsProvider: Convert client ID and secret from base64
Fixes #1554
2024-09-21 21:31:05 +02:00
Jonas Kvinge
037b0d7dea CI: Fix source upload path 2024-09-21 21:30:44 +02:00
Jonas Kvinge
d578d3c66d GstEngine: Don't set state to play if already playing 2024-09-21 21:30:31 +02:00
Jonas Kvinge
2644dbd5ab GstEngine: Use beginning nanosec on play
Fixes #1549
2024-09-21 21:30:18 +02:00
Jonas Kvinge
245103dc30 SpotifySettingsPage: Fix gst reg lookup leak 2024-09-21 21:29:28 +02:00
Strawberry Bot
eb30c654c5 Update translations 2024-09-12 22:12:34 +02:00
Jonas Kvinge
3b925c24ad Turn on git revision 2024-09-12 21:57:12 +02:00
Jonas Kvinge
beefdabc64 Release 1.1.2 2024-09-12 19:49:52 +02:00
Jonas Kvinge
91e06cadf3 Update Changelog 2024-09-12 19:49:23 +02:00
Jonas Kvinge
4ea5eb8292 GstEnginePipeline: Set volume internal in notify volume callback
Fixes #1541
2024-09-10 17:04:24 +02:00
Jonas Kvinge
af06f8e70a Playlist: Remove flawed sorting logic for filename
Possible fix for #1538
2024-09-08 13:12:13 +02:00
Jonas Kvinge
3238e295ba Update Changelog 2024-09-07 16:45:33 +02:00
Jonas Kvinge
26fbd5c144 Update 3rdparty/README.md 2024-09-07 16:42:43 +02:00
Jonas Kvinge
e64a2b98c4 Turn on git revision 2024-09-07 01:17:57 +02:00
Jonas Kvinge
42417e5554 Release 1.1.2-rc1 2024-09-06 23:08:22 +02:00
Jonas Kvinge
82e613206e CollectionQuery: Add const 2024-09-06 22:58:21 +02:00
Jonas Kvinge
f1038152e9 Update Changelog 2024-09-06 22:31:12 +02:00
Jonas Kvinge
2597a8faed CMake: Add option to turn off debug output 2024-09-03 23:41:49 +02:00
Jonas Kvinge
8008ec895a logging: Respect QT_NO_INFO_OUTPUT 2024-09-03 23:10:55 +02:00
Jonas Kvinge
d85d25b931 TagReaderTagLib: Use Strawberry for editor 2024-09-03 23:10:16 +02:00
Jonas Kvinge
de62552ad1 AlbumCoverManager: Queue album cover loading using timer
Helps reduce memory growth.
2024-09-03 22:02:05 +02:00
Jonas Kvinge
155485173b AlbumCoverLoader: Process tasks using timer
Helps reduce memory growth.
2024-09-03 22:01:13 +02:00
Strawberry Bot
82079fcf70 Update translations 2024-09-03 01:56:28 +02:00
Jonas Kvinge
3b2eea5292 nsi: Add utf8_validity.dll 2024-09-02 23:56:26 +02:00
Jonas Kvinge
1f2ad9c177 CI: Manually sign libutf8_validity.dylib 2024-09-02 23:51:47 +02:00
Jonas Kvinge
cc6e5a6c69 TagReaderTagLib: Handle multiple mbids for MP4
Fixes #1531
2024-09-02 23:48:25 +02:00
Jonas Kvinge
c77c7a247a ListenBrainzScrobbler: Split work mbids 2024-09-02 23:46:13 +02:00
Jonas Kvinge
552440f50e Add mutexes 2024-09-02 22:27:45 +02:00
Jonas Kvinge
2a9ccd7480 Set object names 2024-09-02 22:26:36 +02:00
Jonas Kvinge
f265c055a5 metatypes: Add LyricsSearchResults 2024-09-02 22:25:12 +02:00
Jonas Kvinge
837e5388ea CMake: Use target_link_directories 2024-08-27 19:39:53 +02:00
Jonas Kvinge
9883dc6925 TemporaryFile: Use int 2024-08-25 18:09:09 +02:00
Jonas Kvinge
e3ad00e930 CI: Adjust manual codesign 2024-08-25 18:05:20 +02:00
Jonas Kvinge
6428ae8b3a GPodDevice: Use own temporary file class
QTemporaryFile keeps files open.

Fixes #1527
2024-08-25 17:59:56 +02:00
Jonas Kvinge
07c182d5b8 Add temporary file class 2024-08-25 17:58:27 +02:00
Jonas Kvinge
64aa15842c Organize: Correct debug message 2024-08-25 17:30:09 +02:00
Jonas Kvinge
19c8da06e6 PlaylistTest: Replace forever with Q_FOREVER 2024-08-25 16:58:56 +02:00
Jonas Kvinge
7dd959f4a1 CMake: Skip pot on MSVC 2024-08-25 15:40:18 +02:00
Jonas Kvinge
148ae530d8 Playlist: Add missing reference for Columns 2024-08-25 06:24:55 +02:00
Jonas Kvinge
e75698ee9a CMake: Add QT_NO_KEYWORDS 2024-08-25 06:24:26 +02:00
Jonas Kvinge
80bea31b98 Replace forever with Q_FOREVER 2024-08-25 06:24:02 +02:00
Jonas Kvinge
4ea57d1181 searchfield_mac: Replace emit with Q_EMIT 2024-08-25 06:23:20 +02:00
Jonas Kvinge
854847ca8a nsi: Update ffmpeg DLL's 2024-08-25 05:59:32 +02:00
Jonas Kvinge
bd39e7cb0d smartplaylists: Move classes to own files 2024-08-25 05:49:41 +02:00
Jonas Kvinge
20ef621a20 Rename SearchField 2024-08-25 05:48:37 +02:00
Jonas Kvinge
145c276c97 PlayingWidget: Remove unused variables 2024-08-25 05:46:53 +02:00
Jonas Kvinge
1c5d0dceb1 Replace emit with Q_EMIT 2024-08-25 05:46:17 +02:00
Jonas Kvinge
359f320b06 MacFSListener: Formatting 2024-08-25 05:45:33 +02:00
Jonas Kvinge
108d522dcf OrganizeFormat: Move to own classes 2024-08-25 03:09:11 +02:00
Jonas Kvinge
96e746c508 AlbumCoverLoaderOptions: Fix incorrect enum number 2024-08-25 02:08:12 +02:00
Jonas Kvinge
b2c862e7d5 CollectionModel: Use chrono literals 2024-08-25 02:07:31 +02:00
Jonas Kvinge
9334fe9f24 Remove const from qHash 2024-08-25 01:37:46 +02:00
Jonas Kvinge
b964385024 AlbumCoverLoader: Remove const 2024-08-25 01:16:29 +02:00
Jonas Kvinge
3f3059c98b Replace QLatin1String with QStringLiteral 2024-08-25 01:08:25 +02:00
Jonas Kvinge
8da616491d Replace emit with Q_EMIT 2024-08-25 01:06:30 +02:00
Jonas Kvinge
cb0db8750f CollectionModelUpdate: Remove reference from enum 2024-08-24 23:20:20 +02:00
Jonas Kvinge
08224443e3 MainWindow: Don't use the playlists backend on right click
Fixes #1478
2024-08-24 22:43:16 +02:00
Jonas Kvinge
5c2989196f MainWindow: Fix comments 2024-08-24 22:40:20 +02:00
Jonas Kvinge
4c4c84e104 PlaylistManager: Add methods for accessing playlists 2024-08-24 22:39:55 +02:00
Jonas Kvinge
232399ea28 DynamicPlaylistControls: Make background follow system colors
Fixes #1483
2024-08-24 22:07:46 +02:00
Jonas Kvinge
9d22e4ec07 SongLoader: Use Song::kRejectedExtensions
Fixes #1525
2024-08-24 21:12:19 +02:00
Jonas Kvinge
ee5bc16e47 CollectionWatcher: Use Song::kRejectedExtensions 2024-08-24 21:10:52 +02:00
Jonas Kvinge
74f0f885b9 Song: Add rejected extensions 2024-08-24 21:10:27 +02:00
Jonas Kvinge
b914d9aaba Update translations.pot 2024-08-24 21:09:22 +02:00
Jonas Kvinge
136f150d67 Translations: Remove deprecated sort option 2024-08-24 21:08:00 +02:00
Jonas Kvinge
5b50cbb61b Update .gitignore 2024-08-24 21:03:53 +02:00
Jonas Kvinge
cb3a9bf195 Update translations.pot 2024-08-24 20:44:46 +02:00
Strawberry Bot
cb847951e6 Update translations 2024-08-24 20:43:21 +02:00
Jonas Kvinge
11228f0634 Player: Add missing override 2024-08-24 20:38:08 +02:00
Jonas Kvinge
a0889d60f1 Update translations.pot 2024-08-24 20:21:45 +02:00
Jonas Kvinge
0055ebe8a7 CMake: Move translations.pot to src dir 2024-08-24 20:21:37 +02:00
Jonas Kvinge
58c7a9b9cc Translations: Add --no-location to xgettext 2024-08-24 20:21:20 +02:00
Strawberry Bot
6afc081ff0 Update translations 2024-08-24 20:15:38 +02:00
Jonas Kvinge
2c0ad2fc88 Move lyrics providers to own thread 2024-08-24 20:07:36 +02:00
Jonas Kvinge
77e934beab SpotifyService: Use LoginError 2024-08-24 19:29:00 +02:00
Jonas Kvinge
368022ec43 concurrentrun_test: Remove QtConcurrent include 2024-08-24 19:28:44 +02:00
Jonas Kvinge
69f8ca95bc Add missing reference 2024-08-24 19:28:15 +02:00
Jonas Kvinge
dde8661e93 Use QDateTime::currentSecsSinceEpoch() 2024-08-24 17:28:29 +02:00
Jonas Kvinge
2604e1a0ff Use multi-arg 2024-08-24 17:27:47 +02:00
Jonas Kvinge
e8471bcc66 MusixmatchCoverProvider: Use static QRegularExpression 2024-08-24 17:27:05 +02:00
Jonas Kvinge
d230dd7365 Use fully-qualified namespaces in slot parameters 2024-08-24 17:25:56 +02:00
Jonas Kvinge
74dce24e91 Mpris2: Remove QtDBus include 2024-08-24 17:24:56 +02:00
Jonas Kvinge
bc667a6474 Use static QRegularExpression 2024-08-24 17:23:10 +02:00
Jonas Kvinge
a2cae06582 Remove QtConcurrent include 2024-08-24 17:01:53 +02:00
Jonas Kvinge
5212587055 TagReaderGME: Mark variable unused 2024-08-24 17:00:00 +02:00
Jonas Kvinge
efd42bc68f MusicBrainzClient: Remove unneeded values from qDeleteAll 2024-08-23 20:40:36 +02:00
Jonas Kvinge
ebaa2e7918 BlockAnalyzer: Replace value with at 2024-08-23 20:32:56 +02:00
Jonas Kvinge
7ebcc73a49 More const detach fixes 2024-08-23 20:30:59 +02:00
Jonas Kvinge
be09011bb7 CollectionWatcher: Use mutex for stop and abort 2024-08-23 20:22:18 +02:00
Jonas Kvinge
2778a55e8e SpotifySettingsPage: Update Wiki page URL 2024-08-23 19:16:17 +02:00
Jonas Kvinge
9b5fe3bfd6 GstEnginePipeline: Rename PlaybinProbe to PadProbe 2024-08-23 00:17:33 +02:00
Jonas Kvinge
91eef0d695 GstEnginePipeline: Sort variables 2024-08-23 00:08:14 +02:00
Jonas Kvinge
88704efad8 Add lyricfind.com lyrics provider 2024-08-18 20:35:09 +02:00
Jonas Kvinge
f4e4483392 HtmlLyricsProvider: Remove extra QRegularExpression 2024-08-18 19:58:57 +02:00
Jonas Kvinge
63102bce23 Update Changelog 2024-08-17 23:13:30 +02:00
Jonas Kvinge
8890a3dd0f Delay play until playlists have finished loading
Fixes #1465
2024-08-17 22:38:48 +02:00
Jonas Kvinge
3d9dec2c27 nsi: Add libbrotlienc.dll 2024-08-17 15:39:35 +02:00
Jonas Kvinge
c96ad61c80 CMake: Add QT_NO_SHOW_OLD_QT_WRAP_CPP_WARNING 2024-08-17 14:31:51 +02:00
Jonas Kvinge
acd74d5b54 README: Remove buildbot URL 2024-08-12 23:16:24 +02:00
Strawberry Bot
4808188964 Update translations 2024-08-12 22:36:10 +02:00
Jonas Kvinge
1bb045b3b0 Mpris2: Remove .desktop file extension in DesktopEntry
According to the specifications it should be the desktop entry without .desktop file extension: https://specifications.freedesktop.org/mpris-spec/latest/Media_Player.html#Property:DesktopEntry

Fixes #1516
2024-08-12 21:49:25 +02:00
Jonas Kvinge
bdca60c0ad Add missing const 2024-08-12 18:12:26 +02:00
Jonas Kvinge
8d9c135498 DeviceManager: Remove no longer relevant comment 2024-08-12 01:09:59 +02:00
Jonas Kvinge
0f76482916 GioLister: Remove undef signals 2024-08-12 01:09:33 +02:00
Jonas Kvinge
38eb86bdee CMake: Add QT_NO_SIGNALS_SLOTS_KEYWORDS 2024-08-12 01:09:01 +02:00
Jonas Kvinge
cbce9892d5 Replace slots with Q_SLOTS 2024-08-12 01:06:15 +02:00
Jonas Kvinge
f624b7a331 Add cpp files for classes with only header files 2024-08-12 00:48:16 +02:00
Jonas Kvinge
1ebcd61a75 PlaylistListView: Fix incorrect header guard 2024-08-11 23:30:03 +02:00
Jonas Kvinge
358da72ffe Replace signals with Q_SIGNALS 2024-08-11 23:23:12 +02:00
Jonas Kvinge
9666feca37 GstEngine: Rename variable 2024-08-11 18:40:07 +02:00
Jonas Kvinge
03eb52eac8 GstEngine: Ensure no fading is done with exclusive mode 2024-08-11 17:37:23 +02:00
Jonas Kvinge
6562cc710c GstEngine: Disconnect old pipelines
Fixes #1518
2024-08-11 15:53:41 +02:00
Jonas Kvinge
222001bc13 GstEnginePipeline: Fix buffering 2024-08-11 14:52:00 +02:00
Jonas Kvinge
e93f14248a Update appdata xml file 2024-08-11 00:44:27 +02:00
Jonas Kvinge
7119f1bc81 Add filename and url to text search columns 2024-08-11 00:12:41 +02:00
Jonas Kvinge
548fa3f6ee Wait for set state to finish before deleting pipeline
Setting state to GST_STATE_NULL sometimes blocks, to fix this use the threadpool to set the state to NULL and wait with deleting the pipeline until the state is changed.
This fixes blocking the main thread when switching Spotify songs.
2024-08-10 18:22:56 +02:00
Jonas Kvinge
8ddd309d5d FilterTree: Use Song::PrettyTitle for title
Uses the filename instead if the title tag is emtpy.
2024-08-10 12:43:38 +02:00
Jonas Kvinge
8c8acbb546 GstEnginePipeline: Rename variables 2024-08-09 19:29:12 +02:00
Jonas Kvinge
fe30f27af3 GstEngine: Simplify use of State 2024-08-09 19:26:15 +02:00
Jonas Kvinge
d5d2eaba8a CollectionWatcher: Make const 2024-08-08 17:13:14 +02:00
Jonas Kvinge
e8f64bfe8f CollectionWatcher: Formatting 2024-08-08 17:12:51 +02:00
Jonas Kvinge
79b03993b0 Update .gitignore 2024-08-08 17:12:23 +02:00
Jonas Kvinge
e3b8f9cb8c Windows7ThumbBar: Fix namespace 2024-08-07 01:15:26 +02:00
Jonas Kvinge
819463a865 Use anonymous namespace for constants 2024-08-07 00:52:58 +02:00
Jonas Kvinge
c69777ca39 ContextView: Only update top text when changed 2024-08-06 23:24:58 +02:00
Jonas Kvinge
40f3e828aa ResizableTextEdit: Store current text 2024-08-06 23:23:27 +02:00
Jonas Kvinge
dc8520ebec Move translations.pot to binary directory 2024-08-06 23:05:26 +02:00
Jonas Kvinge
7ffbedf0dd AnalyzerContainer: Fix setting default analyzer 2024-08-06 22:59:26 +02:00
Jonas Kvinge
826fad1ad4 SpotifyRequest: Limit returned albums to queried artists 2024-08-05 18:24:47 +02:00
Jonas Kvinge
1c4d3aebad Rename enums 2024-08-05 17:53:07 +02:00
Jonas Kvinge
ff6e93fc15 CollectionWatcher: Only start transaction with scan on startup
Fixes #1469
2024-08-03 00:44:47 +02:00
Jonas Kvinge
17e88bb97d Add const 2024-08-02 23:35:52 +02:00
Jonas Kvinge
d3dd26c596 GstEnginePipeline: Set Spotify bitrate to 320 2024-08-01 23:22:19 +02:00
Jonas Kvinge
c2a7509e0e Update Changelog 2024-08-01 21:56:44 +02:00
Jonas Kvinge
079040b721 CI: Build on macos-13 for x86 and macos-14 for arm 2024-07-31 20:16:37 +02:00
Strawberry Bot
b80d239820 Update translations 2024-07-31 17:26:35 +02:00
Jonas Kvinge
c3ff43dac2 CI: Build macOS public with spotify 2024-07-30 19:59:09 +02:00
Jonas Kvinge
4ac51afd8f macgstcopy: Add spotify plugin 2024-07-30 19:59:09 +02:00
luzpaz
26eab51698 README: Tweak Repology badge
Use 3 column format + Add header
2024-07-30 16:56:24 +02:00
Jonas Kvinge
388dcf2a1f Fix dtor compile warnings with Clang 2024-07-30 16:34:20 +02:00
Jonas Kvinge
9bfd14d067 FancyTabBar: Add missing override 2024-07-30 16:33:50 +02:00
Jonas Kvinge
061e0562d1 CollectionView: Fix search for this
Fixes #1510
2024-07-29 20:23:42 +02:00
Jonas Kvinge
2ba20b013d Move fancy tabbar classes into separate files 2024-07-29 18:19:41 +02:00
Jonas Kvinge
dc36f87a6e FancyTabWidget: Remove use of QTabBar::tabText
Fixes #1476
Fixes #1389
2024-07-29 17:15:33 +02:00
Jonas Kvinge
e82ecb48b8 PlaylistTabBar: Don't use QTabBar::tabText
Fixes #1499
2024-07-29 16:17:46 +02:00
Jonas Kvinge
c56a134179 Update translations.pot 2024-07-29 13:02:37 +02:00
Jonas Kvinge
11028456ad LocalRedirectServer: Remove unused declaration 2024-07-29 01:53:16 +02:00
Jonas Kvinge
81fcb02974 OpenTidalCoverProvider: Only login when required
Fixes #1500
2024-07-29 00:48:16 +02:00
Jonas Kvinge
5ef4976c53 SpotifyRequest: Remove unused variable 2024-07-28 14:55:42 +02:00
Jonas Kvinge
160356072f TagReaderTagLib: Add missing override 2024-07-28 02:35:35 +02:00
Strawberry Bot
c10df5b634 Update French translations 2024-07-28 01:26:26 +02:00
Jonas Kvinge
70fdd5b0b3 Use anonymous namespace instead of static for constants 2024-07-28 01:24:56 +02:00
Jonas Kvinge
6c1ec51fab README: Add translations URL 2024-07-25 02:30:38 +02:00
Strawberry Bot
66b1c22174 Update translations 2024-07-25 02:27:38 +02:00
Strawberry Bot
97138fd6b9 Update Crowdin configuration file 2024-07-25 01:34:54 +02:00
Jonas Kvinge
03c69be421 Rename translation files 2024-07-25 01:29:40 +02:00
Jonas Kvinge
3a9a952cb0 Add translations.pot 2024-07-24 22:39:05 +02:00
Eri the Switch
3bd0331aa3 Use accumulator for seeking via scrolling
This results in much smoother experience on my touchpad.
The value of 120 was chosen as the most common for mice,
according to Qt 6 documentation.
2024-07-24 20:02:33 +02:00
Eri the Switch
3ecf224d91 Use accumulator for volume scrolling events
This results in much smoother experience on my touchpad.
2024-07-24 20:02:33 +02:00
Jonas Kvinge
37743606a2 CollectionModel: Remove unused ExpandAll function 2024-07-24 19:58:53 +02:00
Jonas Kvinge
58587f4a9a Turn on git revision 2024-07-23 01:14:57 +02:00
Jonas Kvinge
464fde1851 Release 1.1.1 2024-07-22 23:44:17 +02:00
Jonas Kvinge
2639498642 Update Changelog 2024-07-22 23:37:46 +02:00
Jonas Kvinge
49f074737c Remove placeholder text 2024-07-22 20:48:46 +02:00
Jonas Kvinge
3d53a8b434 AppearanceSettingsPage: Remove translatable 2024-07-22 19:00:24 +02:00
Jonas Kvinge
e260433c7a settings: Remove translatable 2024-07-22 18:58:18 +02:00
Jonas Kvinge
88ef8bff0b Update .gitignore 2024-07-22 18:29:43 +02:00
Jonas Kvinge
fbdac36f6f PlaylistView: Adjust initial header layout 2024-07-20 15:19:50 +02:00
Jonas Kvinge
da3876bd83 StretchHeaderView: Properly implement reset 2024-07-20 15:19:28 +02:00
Jonas Kvinge
d303e700ae CollectionFilter: Override mimedata function 2024-07-20 01:55:53 +02:00
Jonas Kvinge
92a1173b9e Remove unused FilterParserRatingComparatorDecorator 2024-07-19 18:18:02 +02:00
Jonas Kvinge
9f7ebb1ac7 CI: Remove Ubuntu mantic and add oracular for PPA 2024-07-19 18:14:10 +02:00
Jonas Kvinge
1a8690e1f2 StretchHeaderView: Make sure section size never is zero
Fixes #1085
2024-07-19 17:51:49 +02:00
Jonas Kvinge
6543e4c5da Use common filter parser for collection and playlist 2024-07-19 17:29:05 +02:00
Jonas Kvinge
dd904fe3c2 Remove unused CollectionQueryOptions class 2024-07-18 02:06:48 +02:00
ajtribick
c14cc6bf0b CMake: Use result of find_program instead of calling xgettext directly 2024-07-18 00:20:25 +02:00
Jonas Kvinge
95c265ffd3 CollectionFilter: Match individual words 2024-07-17 01:41:25 +02:00
Jonas Kvinge
31c1ae68df EditTagDialog: Fix build without MusicBrainz
Fixes #1492
2024-07-17 00:00:17 +02:00
Jonas Kvinge
f2eb0c3b6b CollectionModel: Add ItemNeverHasChildren 2024-07-15 14:28:29 +02:00
Jonas Kvinge
32be33847c CollectionFilter: Move early return 2024-07-15 14:16:56 +02:00
Jonas Kvinge
3100b0c044 CollectionFilter: Use recursive filtering
Fixes #1486
Fixes #1487
2024-07-15 13:44:50 +02:00
Jonas Kvinge
f4ec3ab379 CollectionModel: Don't append artist if song is compilation 2024-07-14 20:21:08 +02:00
Jonas Kvinge
cdd7faa9bb CI: Add Fedora 41 and Ubuntu Oracular 2024-07-14 17:47:43 +02:00
Jonas Kvinge
e7b35aeaf7 CMake: Use find_package CONFIG for Boost 2024-07-14 17:47:43 +02:00
Jonas Kvinge
696256eb5b README: Update macOS and Windows download info 2024-07-14 17:46:11 +02:00
Mikel Pérez
8ad560ce0e simplify CreateElementForMimeType + good practices
suggestions from gstreamer dev slomo on gst's matrix:
- whole static_pad_templates loop can be avoided with
  gst_element_factory_can_src_any_caps
- ffmpeg elements have been av* prefixed for a while now
- should be looking for Muxer instead of Codec/Muxer,
  Encoder/Audio instead of Codec/Encoder/Audio,
  and there are constants for that
2024-07-14 17:24:42 +02:00
Jonas Kvinge
1c71506f62 Turn off git revision 2024-07-14 17:20:58 +02:00
Jonas Kvinge
8bea6ec5b0 Release 1.1.0 2024-07-14 14:55:14 +02:00
Jonas Kvinge
e8144487ee Update Changelog 2024-07-14 14:48:29 +02:00
Jonas Kvinge
41d9d15dda MainWindow: Only show sponsor dialog if update dialog is answered 2024-07-13 18:24:47 +02:00
Jonas Kvinge
124b97c024 Turn on git revision 2024-07-10 21:58:29 +02:00
Jonas Kvinge
98e0b45403 Release 1.1.0-rc4 2024-07-10 20:07:01 +02:00
Jonas Kvinge
1f2b8d8bf6 Rename playlist filter classes 2024-07-10 18:27:17 +02:00
Jonas Kvinge
8327751b91 CollectionFilter: Optimize use of QRegularExpression
Possible fix for #1482
2024-07-09 22:06:42 +02:00
Jonas Kvinge
6417f89596 CollectionFilter: Add std::as_const 2024-07-09 18:06:46 +02:00
Jonas Kvinge
2e53656f44 Turn on git revision 2024-07-09 17:52:21 +02:00
Jonas Kvinge
822cf0ad07 Release 1.1.0-rc3 2024-07-09 16:23:10 +02:00
Jonas Kvinge
67f04a81b3 Playlist: Add data changed when setting current row 2024-07-09 16:21:09 +02:00
Jonas Kvinge
9232ad0125 TagReaderTagLib: Use QString for converting TagLib::String
Converting directly to std::string does not seem to work correctly.
2024-07-09 15:56:07 +02:00
Jonas Kvinge
0de87b3e1e TagReaderTagLib: Use UTF-8 when converting to CString
Fixes #1481
2024-07-09 15:06:07 +02:00
Jonas Kvinge
74b8cd6156 StretchHeaderView: Formatting 2024-07-09 11:35:01 +02:00
Jonas Kvinge
ac959387fe StretchHeaderView: Fix infinite loop
Fixes #1480
2024-07-09 11:14:31 +02:00
Jonas Kvinge
ffd8ce9281 Turn on git revision 2024-07-09 10:44:11 +02:00
Jonas Kvinge
d8052b295f Release 1.1.0-rc2 2024-07-09 04:39:48 +02:00
Jonas Kvinge
625929133c Rename analyzers and add turbine analyzer 2024-07-09 04:39:48 +02:00
Jonas Kvinge
79c28e7e1d sqlite_test: Remove fts5 2024-07-09 03:24:13 +02:00
Jonas Kvinge
01f4a79f07 schema: Bump schema version to 20 2024-07-09 03:19:29 +02:00
Jonas Kvinge
47c5a2215e device-schema: Remove fts5 table 2024-07-09 03:18:43 +02:00
Jonas Kvinge
bb6e38630f Turn on git revision 2024-07-09 03:18:04 +02:00
621 changed files with 96713 additions and 123692 deletions

View File

@@ -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:
@@ -628,12 +628,12 @@ jobs:
upload-ubuntu-ppa:
name: Upload Ubuntu PPA
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.event_name == 'release' || (github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')))
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.event_name == 'release' || (github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/1.1')))
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
ubuntu_version: [ 'focal', 'jammy', '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:
@@ -1517,7 +1548,7 @@ jobs:
upload_path="${{secrets.RELEASES_PATH}}/"
else
distro=$(echo "$i" | cut -d '/' -f 2)
if [ "$(echo "$i" | grep '-' || true)" = "" ]; then
if [ -z "$(echo "${distro}" | grep '-' || true)" ]; then
upload_path="${{secrets.BUILDS_PATH}}/${distro}/"
else
distro_name=$(echo "${distro}" | cut -d '-' -f 1)

134
.gitignore vendored
View File

@@ -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

17
3rdparty/README.md vendored
View File

@@ -2,20 +2,27 @@
============================================
KDSingleApplication
-----------------
This is a small static library used by Strawberry to prevent it from starting twice per user session.
-------------------
A small library used by Strawberry to prevent it from starting twice per user session.
If the user tries to start strawberry twice, the main window will maximize instead of starting another instance.
It is also used to pass command-line options through to the first instance.
This 3rdparty copy is used only if KDSingleApplication 1.1 or higher is not found on the system.
URL: https://github.com/KDAB/KDSingleApplication/
SPMediaKeyTap
-------------
Used on macOS to exclusively enable strawberry to grab global media shortcuts.
Can safely be deleted on other platforms.
A library used on macOS to exclusively grab global media shortcuts.
The library is no longer maintained by the original author.
The directory can safely be deleted on other platforms.
getopt
------
getopt included only when compiling on Windows.
getopt included on Windows for command line options parsing with Unicode support .
The directory can safely be deleted on other platforms.
URL: https://github.com/ludvikjerabek/getopt-win

View File

@@ -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})

View File

@@ -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)

View File

@@ -2,7 +2,58 @@ Strawberry Music Player
=======================
ChangeLog
Version 1.1.0-rc1:
Version 1.1.3 (2024.09.21):
Bugfixes:
* Fixed gstreamer registry lookup leak in Spotify settings.
* Fixed all songs in a CUE sheet starting playback at the zero position (#1549).
* Fixed playback going to pause and back to play on song change.
* Fixed Genius Lyrics login not working (#1554).
* Fixed slow collection filter search.
Version 1.1.2 (2024.09.12):
Bugfixes:
* Fixed Tidal Open API cover provider to only login when needed instead of on startup.
* Fixed KDE added keyboard accelerator characters (ampersands) appearing in sidebar (#1400, #1389, #1476).
* Fixed KDE added keyboard accelerator characters (ampersands) appearing when editing playlist name (#1499).
* Fixed collection "Search for this" adding prefix without value (#1510).
* Fixed play (-p) command line option not working on startup (#1465).
* Fixed scan transaction being started when "Update the collection when Strawberry starts" option is unchecked (#1469)
* Fixed Spotify bitrate being limited 128kbit/s.
* Fixed Spotify returning too many artists and albums.
* Fixed manually switching Spotify songs blocking UI.
* Fixed analyzer not being set.
* Fixed context top text being updated causing selected text to be unselected.
* Fixed filter search to use filename for songs with empty title.
* Fixed missing developer in Appstream appdata file.
* Fixed MPRIS2 DesktopEntry to return desktop file entry without ".desktop" (#1516)
* Fixed WavPack .wvc accepted as valid audio files (#1525).
* Fixed dynamic playlist controls not following system colors (#1483).
* Fixed freeze on playlist right click (#1478).
* Fixed copying songs to a iPod device keeping too many files open (#1527).
* Fixed MBIDs from MP4 being parsed incorrectly causing ListenBrainz errors (#1531).
* Fixed playlist sorting after filename (#1538).
Enhancements:
* Improved volume adjustment and track seeking using touchpad (#1498).
* Use own thread for lyrics parsing.
* Added url and filename columns to collection and playlist filter search.
* (macOS) Added Spotify.
Version 1.1.1 (2024.07.22):
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 +79,13 @@ Version 1.1.0-rc1:
* 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 +104,9 @@ Version 1.1.0-rc1:
* (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.

View File

@@ -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
[![Packaging status](https://repology.org/badge/vertical-allrepos/strawberry.svg?exclude_unsupported=1)](https://repology.org/metapackage/strawberry/versions)
[![Packaging status](https://repology.org/badge/vertical-allrepos/strawberry.svg?columns=3&header=Strawberry&exclude_unsupported=1)](https://repology.org/metapackage/strawberry/versions)

View File

@@ -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}
)

View File

@@ -1,7 +1,7 @@
set(STRAWBERRY_VERSION_MAJOR 1)
set(STRAWBERRY_VERSION_MINOR 1)
set(STRAWBERRY_VERSION_PATCH 0)
set(STRAWBERRY_VERSION_PRERELEASE rc1)
set(STRAWBERRY_VERSION_PATCH 3)
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
set(INCLUDE_GIT_REVISION OFF)

3
crowdin.yml Normal file
View File

@@ -0,0 +1,3 @@
files:
- source: /src/translations/translations.pot
translation: /src/translations/%locale_with_underscore%.po

View File

@@ -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>

View File

@@ -94,9 +94,4 @@ CREATE INDEX idx_device_%deviceid_songs_album ON device_%deviceid_songs (album);
CREATE INDEX idx_device_%deviceid_songs_comp_artist ON device_%deviceid_songs (compilation_effective, artist);
CREATE VIRTUAL TABLE device_%deviceid_fts USING fts5(
ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment,
tokenize = "unicode61 remove_diacritics 1"
);
UPDATE devices SET schema_version=5 WHERE ROWID=%deviceid;

View File

@@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS schema_version (
DELETE FROM schema_version;
INSERT INTO schema_version (version) VALUES (19);
INSERT INTO schema_version (version) VALUES (20);
CREATE TABLE IF NOT EXISTS directories (
path TEXT NOT NULL,

View 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;
}

View File

@@ -111,6 +111,7 @@ libgstrtsp
libgstsoup
libgstspectrum
libgstspeex
libgstspotify
libgsttaglib
libgsttcp
libgsttwolame

View File

@@ -12,11 +12,14 @@
<summary>A music player and collection organizer</summary>
<url type="homepage">https://www.strawberrymusicplayer.org/</url>
<url type="bugtracker">https://github.com/strawberrymusicplayer/strawberry/</url>
<translation type="qt">strawberry</translation>
<developer id="net.jkvinge.jonas">
<name>Jonas Kvinge</name>
</developer>
<translation type="gettext">strawberry</translation>
<content_rating type="oars-1.1" />
<description>
<p>
Strawberry is a music player and music collection organizer. It is aimed at music collectors and audiophiles. With Strawberry you can play and manage your digital music collection, or stream your favorite radios. It also has unofficial streaming support for Tidal and Qobuz. Strawberry is free software released under GPL. The source code is available on GitHub. It's written in C++ using the Qt toolkit and GStreamer. Strawberry is compatible with both Qt version 5 and 6.
Strawberry is a music player and music collection organizer. It is aimed at music collectors and audiophiles. Strawberry is free software released under GPL. It's written in C++ using the Qt framework and GStreamer.
</p>
<p>Features:</p>
<ul>
@@ -30,12 +33,11 @@
<li>Automatically retrieve tags from MusicBrainz</li>
<li>Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify</li>
<li>Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com and elyrics.net</li>
<li>Support for multiple backends</li>
<li>Audio analyzer and equalizer</li>
<li>Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic</li>
<li>Scrobbler with support for Last.fm, Libre.fm and ListenBrainz</li>
<li>Streaming support for Subsonic-compatible servers</li>
<li>Unofficial streaming support for Tidal and Qobuz</li>
<li>Unofficial streaming support for Tidal, Spotify and Qobuz</li>
</ul>
</description>
<screenshots>
@@ -50,6 +52,10 @@
</screenshots>
<update_contact>eclipseo@fedoraproject.org</update_contact>
<releases>
<release version="1.1.3" date="2024-09-21"/>
<release version="1.1.2" date="2024-09-12"/>
<release version="1.1.1" date="2024-07-22"/>
<release version="1.1.0" date="2024-07-14"/>
<release version="1.0.23" date="2024-01-11"/>
<release version="1.0.22" date="2023-12-09"/>
<release version="1.0.21" date="2023-10-21"/>

View File

@@ -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"

View File

@@ -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}

View File

@@ -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}

View File

@@ -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

View File

@@ -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);

View File

@@ -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();

View File

@@ -41,7 +41,7 @@ void _MessageReplyBase::Abort() {
finished_ = true;
success_ = false;
emit Finished();
Q_EMIT Finished();
qLog(Debug) << "Releasing ID" << id() << "(aborted)";
semaphore_.release();

View File

@@ -45,7 +45,7 @@ class _MessageReplyBase : public QObject {
void Abort();
signals:
Q_SIGNALS:
void Finished();
protected:

View File

@@ -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:

View File

@@ -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()

View File

@@ -32,6 +32,7 @@
#include "tagreaderbase.h"
TagReaderBase::TagReaderBase() = default;
TagReaderBase::~TagReaderBase() = default;
QString TagReaderBase::ErrorString(const Result &result) {

View File

@@ -34,7 +34,7 @@
class TagReaderBase {
public:
explicit TagReaderBase();
~TagReaderBase() = default;
virtual ~TagReaderBase();
class Result {
public:

View File

@@ -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);

View File

@@ -99,7 +99,6 @@ class TagReaderGME : public TagReaderBase {
public:
explicit TagReaderGME();
~TagReaderGME() = default;
bool IsMediaFile(const QString &filename) const override;

View File

@@ -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;
}

View File

@@ -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_;

View File

@@ -46,8 +46,6 @@
TagReaderTagParser::TagReaderTagParser() = default;
TagReaderTagParser::~TagReaderTagParser() = default;
bool TagReaderTagParser::IsMediaFile(const QString &filename) const {
qLog(Debug) << "Checking for valid file" << filename;

View File

@@ -37,7 +37,6 @@
class TagReaderTagParser : public TagReaderBase {
public:
explicit TagReaderTagParser();
~TagReaderTagParser();
bool IsMediaFile(const QString &filename) const override;

View File

@@ -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}

View File

@@ -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()

View File

@@ -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());

View File

@@ -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
@@ -72,9 +77,10 @@ set(SOURCES
analyzer/analyzercontainer.cpp
analyzer/blockanalyzer.cpp
analyzer/boomanalyzer.cpp
analyzer/turbineanalyzer.cpp
analyzer/sonogramanalyzer.cpp
analyzer/waverubberanalyzer.cpp
analyzer/rainbowanalyzer.cpp
analyzer/sonogram.cpp
analyzer/waverubber.cpp
equalizer/equalizer.cpp
equalizer/equalizerslider.cpp
@@ -95,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
@@ -106,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
@@ -119,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
@@ -139,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
@@ -194,6 +207,7 @@ set(SOURCES
lyrics/azlyricscomlyricsprovider.cpp
lyrics/elyricsnetlyricsprovider.cpp
lyrics/letraslyricsprovider.cpp
lyrics/lyricfindlyricsprovider.cpp
providers/musixmatchprovider.cpp
@@ -229,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
@@ -267,6 +283,7 @@ set(SOURCES
streaming/streamingcollectionview.cpp
streaming/streamingcollectionviewcontainer.cpp
streaming/streamingsearchview.cpp
streaming/streamsongmimedata.cpp
radios/radioservices.cpp
radios/radiobackend.cpp
@@ -278,6 +295,7 @@ set(SOURCES
radios/radiochannel.cpp
radios/somafmservice.cpp
radios/radioparadiseservice.cpp
radios/radiomimedata.cpp
scrobbler/audioscrobbler.cpp
scrobbler/scrobblersettings.cpp
@@ -293,6 +311,8 @@ set(SOURCES
organize/organize.cpp
organize/organizeformat.cpp
organize/organizeformatvalidator.cpp
organize/organizesyntaxhighlighter.cpp
organize/organizedialog.cpp
organize/organizeerrordialog.cpp
@@ -331,9 +351,10 @@ set(HEADERS
analyzer/analyzercontainer.h
analyzer/blockanalyzer.h
analyzer/boomanalyzer.h
analyzer/turbineanalyzer.h
analyzer/sonogramanalyzer.h
analyzer/waverubberanalyzer.h
analyzer/rainbowanalyzer.h
analyzer/sonogram.h
analyzer/waverubber.h
equalizer/equalizer.h
equalizer/equalizerslider.h
@@ -394,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
@@ -441,6 +467,7 @@ set(HEADERS
lyrics/azlyricscomlyricsprovider.h
lyrics/elyricsnetlyricsprovider.h
lyrics/letraslyricsprovider.h
lyrics/lyricfindlyricsprovider.h
settings/settingsdialog.h
settings/settingspage.h
@@ -474,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
@@ -493,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
@@ -534,6 +563,8 @@ set(HEADERS
scrobbler/lastfmimport.h
organize/organize.h
organize/organizeformatvalidator.h
organize/organizesyntaxhighlighter.h
organize/organizedialog.h
organize/organizeerrordialog.h
@@ -619,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)
@@ -758,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
@@ -836,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
@@ -998,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}
@@ -1121,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}
@@ -1149,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()
@@ -1166,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}
@@ -1182,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()
@@ -1196,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()
@@ -1257,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()

View File

@@ -39,9 +39,10 @@
#include "analyzerbase.h"
#include "blockanalyzer.h"
#include "boomanalyzer.h"
#include "turbineanalyzer.h"
#include "sonogramanalyzer.h"
#include "waverubberanalyzer.h"
#include "rainbowanalyzer.h"
#include "sonogram.h"
#include "waverubber.h"
#include "core/logging.h"
#include "core/shared_ptr.h"
@@ -88,10 +89,11 @@ AnalyzerContainer::AnalyzerContainer(QWidget *parent)
AddAnalyzerType<BlockAnalyzer>();
AddAnalyzerType<BoomAnalyzer>();
AddAnalyzerType<NyanCatAnalyzer>();
AddAnalyzerType<TurbineAnalyzer>();
AddAnalyzerType<SonogramAnalyzer>();
AddAnalyzerType<WaveRubberAnalyzer>();
AddAnalyzerType<RainbowDashAnalyzer>();
AddAnalyzerType<Sonogram>();
AddAnalyzerType<WaveRubber>();
AddAnalyzerType<NyanCatAnalyzer>();
disable_action_ = context_menu_->addAction(tr("No analyzer"), this, &AnalyzerContainer::DisableAnalyzer);
disable_action_->setCheckable(true);
@@ -128,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) {
@@ -147,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();
@@ -198,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;
}
}

View File

@@ -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();

View File

@@ -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");
@@ -136,7 +138,7 @@ void BlockAnalyzer::transform(Scope &s) {
}
void BlockAnalyzer::analyze(QPainter &p, const Scope &s, bool new_frame) {
void BlockAnalyzer::analyze(QPainter &p, const Scope &s, const bool new_frame) {
// y = 2 3 2 1 0 2
// . . . . # .
@@ -165,11 +167,12 @@ void BlockAnalyzer::analyze(QPainter &p, const Scope &s, 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, 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());

View File

@@ -43,18 +43,11 @@ 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:
void transform(Scope&) override;
void analyze(QPainter &p, const Scope&, bool new_frame) override;
void analyze(QPainter &p, const Scope &s, const bool new_frame) override;
void resizeEvent(QResizeEvent*) override;
virtual void paletteChange(const QPalette&);
void framerateChanged() override;

View File

@@ -45,9 +45,9 @@ class BoomAnalyzer : public AnalyzerBase {
static const char *kName;
void transform(Scope &s) override;
void analyze(QPainter &p, const Scope&, const bool new_frame) 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);

View File

@@ -41,18 +41,21 @@
#include "fht.h"
#include "analyzerbase.h"
const char *NyanCatAnalyzer::kName = "Nyanalyzer Cat";
const char *RainbowDashAnalyzer::kName = "Rainbow Dash";
RainbowAnalyzer::RainbowType RainbowAnalyzer::rainbowtype;
const int RainbowAnalyzer::kHeight[] = { 21, 33 };
const int RainbowAnalyzer::kWidth[] = { 34, 53 };
const int RainbowAnalyzer::kFrameCount[] = { 6, 16 };
const int RainbowAnalyzer::kRainbowHeight[] = { 21, 16 };
const int RainbowAnalyzer::kRainbowOverlap[] = { 13, 15 };
const int RainbowAnalyzer::kSleepingHeight[] = { 24, 33 };
const char *NyanCatAnalyzer::kName = "Nyanalyzer Cat";
const char *RainbowDashAnalyzer::kName = "Rainbow Dash";
const float RainbowAnalyzer::kPixelScale = 0.02F;
RainbowAnalyzer::RainbowType RainbowAnalyzer::rainbowtype;
namespace {
constexpr int kFrameIntervalMs = 150;
constexpr int kRainbowHeight[] = { 21, 16 };
constexpr int kRainbowOverlap[] = { 13, 15 };
constexpr float kPixelScale = 0.02F;
} // namespace
RainbowAnalyzer::RainbowAnalyzer(const RainbowType rbtype, QWidget *parent)
: AnalyzerBase(parent, 9),
@@ -106,7 +109,7 @@ void RainbowAnalyzer::resizeEvent(QResizeEvent *e) {
}
void RainbowAnalyzer::analyze(QPainter &p, const Scope &s, bool new_frame) {
void RainbowAnalyzer::analyze(QPainter &p, const Scope &s, const bool new_frame) {
// Discard the second half of the transform
const int scope_size = static_cast<int>(s.size() / 2);

View File

@@ -49,44 +49,37 @@ class RainbowAnalyzer : public AnalyzerBase {
Dash = 1
};
RainbowAnalyzer(const RainbowType rbtype, QWidget *parent);
explicit RainbowAnalyzer(const RainbowType rbtype, QWidget *parent);
protected:
void transform(Scope&) override;
void analyze(QPainter &p, const Scope&, bool new_frame) override;
void transform(Scope &s) override;
void analyze(QPainter &p, const Scope &s, const bool new_frame) override;
void timerEvent(QTimerEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
private:
static const int kRainbowBands = 6;
static const int kHistorySize = 128;
static RainbowType rainbowtype;
static const int kHeight[];
static const int kWidth[];
static const int kFrameCount[];
static const int kRainbowHeight[];
static const int kRainbowOverlap[];
static const int kSleepingHeight[];
static const int kHistorySize = 128;
static const int kRainbowBands = 6;
static const float kPixelScale;
static const int kFrameIntervalMs = 150;
static RainbowType rainbowtype;
inline QRect SourceRect(RainbowType _rainbowtype) const {
inline QRect SourceRect(const RainbowType _rainbowtype) const {
return QRect(0, kHeight[_rainbowtype] * frame_, kWidth[_rainbowtype], kHeight[_rainbowtype]);
}
inline QRect SleepingSourceRect(RainbowType _rainbowtype) const {
inline QRect SleepingSourceRect(const RainbowType _rainbowtype) const {
return QRect(0, kHeight[_rainbowtype] * kFrameCount[_rainbowtype], kWidth[_rainbowtype], kSleepingHeight[_rainbowtype]);
}
inline QRect DestRect(RainbowType _rainbowtype) const {
inline QRect DestRect(const RainbowType _rainbowtype) const {
return QRect(width() - kWidth[_rainbowtype], (height() - kHeight[_rainbowtype]) / 2, kWidth[_rainbowtype], kHeight[_rainbowtype]);
}
inline QRect SleepingDestRect(RainbowType _rainbowtype) const {
inline QRect SleepingDestRect(const RainbowType _rainbowtype) const {
return QRect(width() - kWidth[_rainbowtype], (height() - kSleepingHeight[_rainbowtype]) / 2, kWidth[_rainbowtype], kSleepingHeight[_rainbowtype]);
}

View File

@@ -26,14 +26,14 @@
#include "engine/enginebase.h"
#include "sonogram.h"
#include "sonogramanalyzer.h"
const char *Sonogram::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Sonogram");
const char *SonogramAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Sonogram");
Sonogram::Sonogram(QWidget *parent)
SonogramAnalyzer::SonogramAnalyzer(QWidget *parent)
: AnalyzerBase(parent, 9) {}
void Sonogram::resizeEvent(QResizeEvent *e) {
void SonogramAnalyzer::resizeEvent(QResizeEvent *e) {
Q_UNUSED(e)
@@ -42,7 +42,7 @@ void Sonogram::resizeEvent(QResizeEvent *e) {
}
void Sonogram::analyze(QPainter &p, const Scope &s, bool new_frame) {
void SonogramAnalyzer::analyze(QPainter &p, const Scope &s, const bool new_frame) {
if (!new_frame || engine_->state() == EngineBase::State::Paused) {
p.drawPixmap(0, 0, canvas_);
@@ -81,7 +81,7 @@ void Sonogram::analyze(QPainter &p, const Scope &s, bool new_frame) {
}
void Sonogram::transform(Scope &scope) {
void SonogramAnalyzer::transform(Scope &scope) {
fht_->power2(scope.data());
fht_->scale(scope.data(), 1.0 / 256);
@@ -89,6 +89,6 @@ void Sonogram::transform(Scope &scope) {
}
void Sonogram::demo(QPainter &p) {
void SonogramAnalyzer::demo(QPainter &p) {
analyze(p, Scope(fht_->size(), 0), new_frame_);
}

View File

@@ -21,24 +21,25 @@
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SONOGRAM_H
#define SONOGRAM_H
#ifndef SONOGRAMANALYZER_H
#define SONOGRAMANALYZER_H
#include <QPixmap>
#include <QPainter>
#include "analyzerbase.h"
class Sonogram : public AnalyzerBase {
class SonogramAnalyzer : public AnalyzerBase {
Q_OBJECT
public:
Q_INVOKABLE explicit Sonogram(QWidget *parent);
Q_INVOKABLE explicit SonogramAnalyzer(QWidget *parent);
static const char *kName;
protected:
void resizeEvent(QResizeEvent *e) override;
void analyze(QPainter &p, const Scope &s, bool new_frame) override;
void analyze(QPainter &p, const Scope &s, const bool new_frame) override;
void transform(Scope &scope) override;
void demo(QPainter &p) override;
@@ -46,4 +47,4 @@ class Sonogram : public AnalyzerBase {
QPixmap canvas_;
};
#endif // SONOGRAM_H
#endif // SONOGRAMANALYZER_H

View File

@@ -0,0 +1,100 @@
/*
Strawberry Music Player
This file was part of Clementine.
Copyright 2003, Stanislav Karchebny <berkus@users.sf.net>
Copyright 2003, Max Howell <max.howell@methylblue.com>
Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Copyright 2014, John Maguire <john.maguire@gmail.com>
Clementine 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.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <cmath>
#include <algorithm>
#include <QPainter>
#include "turbineanalyzer.h"
#include "engine/enginebase.h"
const char *TurbineAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Turbine");
TurbineAnalyzer::TurbineAnalyzer(QWidget *parent) : BoomAnalyzer(parent) {}
void TurbineAnalyzer::analyze(QPainter &p, const Scope &scope, const bool new_frame) {
if (!new_frame || engine_->state() == EngineBase::State::Paused) {
p.drawPixmap(0, 0, canvas_);
return;
}
const uint hd2 = height() / 2;
const uint kMaxHeight = hd2 - 1;
QPainter canvas_painter(&canvas_);
canvas_.fill(palette().color(QPalette::Window));
AnalyzerBase::interpolate(scope, scope_);
for (uint i = 0, x = 0, y = 0; i < static_cast<uint>(bands_); ++i, x += kColumnWidth + 1) {
float h = static_cast<float>(std::min(log10(scope_[i] * 256.0) * F_ * 0.5, kMaxHeight * 1.0));
if (h > bar_height_[i]) {
bar_height_[i] = h;
if (h > peak_height_[i]) {
peak_height_[i] = h;
peak_speed_[i] = 0.01;
}
else {
goto peak_handling;
}
}
else {
if (bar_height_[i] > 0.0) {
bar_height_[i] -= K_barHeight_; // 1.4
if (bar_height_[i] < 0.0) bar_height_[i] = 0.0;
}
peak_handling:
if (peak_height_[i] > 0.0) {
peak_height_[i] -= peak_speed_[i];
peak_speed_[i] *= F_peakSpeed_; // 1.12
peak_height_[i] = std::max(0.0, std::max(bar_height_[i], peak_height_[i]));
}
}
y = hd2 - static_cast<uint>(bar_height_[i]);
canvas_painter.drawPixmap(static_cast<int>(x + 1), static_cast<int>(y), barPixmap_, 0, static_cast<int>(y), -1, -1);
canvas_painter.drawPixmap(static_cast<int>(x + 1), static_cast<int>(hd2), barPixmap_, 0, static_cast<int>(bar_height_[i]), -1, -1);
canvas_painter.setPen(fg_);
if (bar_height_[i] > 0) {
canvas_painter.drawRect(static_cast<int>(x), static_cast<int>(y), kColumnWidth - 1, static_cast<int>(bar_height_[i]) * 2 - 1);
}
const uint x2 = x + kColumnWidth - 1;
canvas_painter.setPen(palette().color(QPalette::Midlight));
y = hd2 - static_cast<uint>(peak_height_[i]);
canvas_painter.drawLine(static_cast<int>(x), static_cast<int>(y), static_cast<int>(x2), static_cast<int>(y));
y = hd2 + static_cast<uint>(peak_height_[i]);
canvas_painter.drawLine(static_cast<int>(x), static_cast<int>(y), static_cast<int>(x2), static_cast<int>(y));
}
p.drawPixmap(0, 0, canvas_);
}

View File

@@ -0,0 +1,41 @@
/*
Strawberry Music Player
This file was part of Clementine.
Copyright 2003, Stanislav Karchebny <berkus@users.sf.net>
Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Copyright 2014, John Maguire <john.maguire@gmail.com>
Clementine 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.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TURBINEANALYZER_H
#define TURBINEANALYZER_H
#include "boomanalyzer.h"
class QPainter;
class TurbineAnalyzer : public BoomAnalyzer {
Q_OBJECT
public:
Q_INVOKABLE explicit TurbineAnalyzer(QWidget *parent);
void analyze(QPainter &p, const Scope &scope, const bool new_frame);
static const char *kName;
};
#endif // TURBINEANALYZER_H

View File

@@ -19,14 +19,14 @@
#include <QPainter>
#include <QResizeEvent>
#include "engine/enginebase.h"
#include "waverubber.h"
#include "waverubberanalyzer.h"
const char *WaveRubber::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "WaveRubber");
const char *WaveRubberAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "WaveRubber");
WaveRubber::WaveRubber(QWidget *parent)
WaveRubberAnalyzer::WaveRubberAnalyzer(QWidget *parent)
: AnalyzerBase(parent, 9) {}
void WaveRubber::resizeEvent(QResizeEvent *e) {
void WaveRubberAnalyzer::resizeEvent(QResizeEvent *e) {
Q_UNUSED(e)
@@ -35,7 +35,7 @@ void WaveRubber::resizeEvent(QResizeEvent *e) {
}
void WaveRubber::analyze(QPainter &p, const Scope &s, const bool new_frame) {
void WaveRubberAnalyzer::analyze(QPainter &p, const Scope &s, const bool new_frame) {
if (!new_frame || engine_->state() == EngineBase::State::Paused) {
p.drawPixmap(0, 0, canvas_);
@@ -82,11 +82,11 @@ void WaveRubber::analyze(QPainter &p, const Scope &s, const bool new_frame) {
}
void WaveRubber::transform(Scope &s) {
void WaveRubberAnalyzer::transform(Scope &s) {
// No need transformation for waveform analyzer
Q_UNUSED(s);
}
void WaveRubber::demo(QPainter &p) {
void WaveRubberAnalyzer::demo(QPainter &p) {
analyze(p, Scope(fht_->size(), 0), new_frame_);
}

View File

@@ -22,11 +22,11 @@
#include "analyzerbase.h"
class WaveRubber : public AnalyzerBase {
class WaveRubberAnalyzer : public AnalyzerBase {
Q_OBJECT
public:
Q_INVOKABLE explicit WaveRubber(QWidget *parent);
Q_INVOKABLE explicit WaveRubberAnalyzer(QWidget *parent);
static const char *kName;

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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(" =, !=, &lt;, &gt;, &lt;="), QLatin1String("&gt;=")) +
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());
}
}

View File

@@ -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_;

View File

@@ -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/>

View File

@@ -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;
};

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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) {}

View File

@@ -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;
};

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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();

View File

@@ -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();
}

View File

@@ -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_;

View File

@@ -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),

View File

@@ -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:

View File

@@ -207,7 +207,7 @@ void SavedGroupingManager::Remove() {
}
UpdateModel();
emit UpdateGroupByActions();
Q_EMIT UpdateGroupByActions();
}

View File

@@ -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();

View File

@@ -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();

View File

@@ -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:

View File

@@ -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();
}

View File

@@ -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_;

View File

@@ -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(); }

View File

@@ -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);

View File

@@ -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

View File

@@ -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 {

View File

@@ -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()));
}
}

View File

@@ -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:

View File

@@ -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;

View File

@@ -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:

View File

@@ -39,7 +39,7 @@ class FileSystemWatcherInterface : public QObject {
static FileSystemWatcherInterface *Create(QObject *parent = nullptr);
signals:
Q_SIGNALS:
void PathChanged(const QString &path);
};

View File

@@ -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();

View File

@@ -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;

View File

@@ -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:

View File

@@ -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);
}
}

View File

@@ -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();

View File

@@ -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;

View File

@@ -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();

View File

@@ -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 &amp;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>&amp;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">

View File

@@ -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();

View File

@@ -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();

View File

@@ -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");
}

Some files were not shown because too many files have changed in this diff Show More