Compare commits

...

193 Commits

Author SHA1 Message Date
Jonas Kvinge ea1e4541c0 CI: Use MSVC protoc from debug to workaround a crash 2024-06-09 23:03:57 +02:00
Guzpido 50572ac40f WaveRubber: a simple waveform analyzer
This kinda elastic analyzer uses no transformations and is colored by a gradient based on the user QT "Highlight" palette.

WaveRubber: a kinda elastic waveform analyzer

Update src/analyzer/waverubber.cpp

better pointer declaration

Co-authored-by: Jonas Kvinge <jonas@jkvinge.net>
2024-06-09 18:01:28 +02:00
Guzpido fd81036909 GstEnginePipeline: Divide samples and format by channels for buffer duration 2024-06-09 15:56:29 +02:00
Jonas Kvinge 0e27886e28 CI: Add Boost_INCLUDE_DIR for MSVC build 2024-06-09 14:12:38 +02:00
whatwareweb 45bad3be04 Fix integer underflow bug 2024-06-08 11:49:35 +02:00
Jonas Kvinge 30b268dc3a Remove msvc toolset 2024-06-08 00:48:43 +02:00
jj ef99f0ef36 Add volume increment setting 2024-06-06 21:53:16 +02:00
Jonas Kvinge e357ba0125 GstEngine: Check individual classes when parsing outputs 2024-06-04 19:43:54 +02:00
Jonas Kvinge 36b75a5928 CI: Manually codesign libraries 2024-06-03 17:13:28 +02:00
Jonas Kvinge 64d3ea2804 Udisks2Lister: Fix mountpoint 2024-06-03 17:12:23 +02:00
Jonas Kvinge 0a93affeef SongLoader: Use QObject::connect 2024-06-03 00:00:20 +02:00
Jonas Kvinge 402d13a322 CI: Bump macOS to 12 2024-06-02 12:49:05 +02:00
Jonas Kvinge adf0efc859 CI: Remove Fedora 38 2024-06-02 12:48:23 +02:00
Jonas Kvinge d1c65fd273 Bump default LSMinimumSystemVersion to 12.0 2024-06-02 12:30:48 +02:00
Jonas Kvinge 8a27c6a52f GstEnginePipeline: Use playbin3 with GStreamer 1.24 and higher
playbin3 is buggy with GStreamer 1.22, for some reason the bug is only reproducible on Gnome.

https://forum.strawberrymusicplayer.org/topic/1506/buffering-forever/23
2024-06-02 12:09:38 +02:00
Jonas Kvinge d7cc52bc99 EngineBase: Use fully qualified namespace in StateChanged
Makes sure the metatype matches with Qt 5:
QObject::connect: Cannot queue arguments of type 'State'
(Make sure 'State' is registered using qRegisterMetaType().)

Fixes #1446
2024-05-30 20:09:32 +02:00
Jonas Kvinge f0f5300891 ParserBase: Make the path absolute and try canonical path
Somehow I got this mixed up in commit 2953f9e :(

Fixes #1448
2024-05-29 00:18:39 +02:00
Jonas Kvinge 6e90e72b4a CollectionModel: Add content to fake header for pixmap cache
QNetworkCacheMetaData requires this now.
2024-05-26 02:49:26 +02:00
Jonas Kvinge c655963483 Use checkStateChanged(Qt::CheckState) with Qt >= 6.7 2024-05-24 02:13:48 +02:00
Jonas Kvinge 9f2e4ac312 CueParser: Detect and handle different text encodings
Fixes #1429
2024-05-19 01:49:37 +02:00
Jonas Kvinge 9e25366f85 Add function for detecting text encoding 2024-05-19 01:47:44 +02:00
Jonas Kvinge c102d8731a Require ICU 2024-05-19 01:45:19 +02:00
Jonas Kvinge 0983ba1339 MoodbarLoader: Add header name for disk cache 2024-05-13 00:44:37 +02:00
dependabot[bot] 0a99eca7cd Bump apple-actions/import-codesign-certs from 2 to 3
Bumps [apple-actions/import-codesign-certs](https://github.com/apple-actions/import-codesign-certs) from 2 to 3.
- [Release notes](https://github.com/apple-actions/import-codesign-certs/releases)
- [Commits](https://github.com/apple-actions/import-codesign-certs/compare/v2...v3)

---
updated-dependencies:
- dependency-name: apple-actions/import-codesign-certs
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-13 00:27:33 +02:00
Jonas Kvinge 116bbec73e Replace qPrintable with qUtf8Printable
Fixes #1440
2024-05-13 00:05:58 +02:00
Jonas Kvinge bf19540f8d Set LSMinimumSystemVersion from MACOSX_DEPLOYMENT_TARGET
Fixes #1436
2024-05-12 23:10:43 +02:00
Jonas Kvinge dff3ae7410 CI: Remove --skip-broken from dnf for Fedora 2024-05-12 21:41:38 +02:00
Jonas Kvinge 76614bcde0 Only apply collection directories changes on save 2024-05-12 21:40:51 +02:00
Jonas Kvinge 2953f9eefc ParserBase: Use original paths 2024-05-12 21:38:59 +02:00
Jonas Kvinge 4a24605361 FileViewList: Use original paths instead of canonical paths 2024-05-12 21:38:15 +02:00
Jonas Kvinge decabe8d47 CommandlineOptions: Use original paths instead of canonical paths 2024-05-12 21:37:54 +02:00
Jonas Kvinge 51adcf0f1e MainWindow: Use original paths instead of canonical paths 2024-05-12 21:37:32 +02:00
Jonas Kvinge 2a6a07fef6 CollectionBackendTest: Remove use of QFileInfo::canonicalFilePath 2024-05-12 21:36:52 +02:00
Jonas Kvinge 315cf63118 CI: Remove Windows 32 bit 2024-05-11 23:23:56 +02:00
Wedone e28d362aad Update fr.po 2024-05-04 16:22:51 +02:00
Jonas Kvinge e0d9b8f715 ResizableTextEdit: Remove tab 2024-05-04 16:21:59 +02:00
Jonas Kvinge eff6b75c43 nsi: Remove /SOLID from SetCompressor for debug 2024-04-24 02:25:05 +02:00
Robert Gingras e8be0adf37 TagReaderTagLib: Remove redundant ID3v2 validity check
TagLib will have created a valid ID3v2 tag on this file by this point in the code, due to the way it handles the tag() method for WAV::File. Thus the null pointer check is redundant and the hasID3v2() call is at best redundant and at worst will cause tags to not save when they otherwise should have
2024-04-24 01:23:51 +02:00
Robert Gingras 9f4a82bb62 TagReaderTagLib: Remove file_mpeg argument from the SetEmbeddedArt ID3v2 overload 2024-04-24 01:23:51 +02:00
Robert Gingras 8d7e14f21d Song: Added WAV to list of supported filetypes 2024-04-24 01:23:51 +02:00
Robert Gingras d03d3622aa TagReaderTagLib: Have RIFF WAV files save ID3v2 tags, when applicable 2024-04-24 01:23:51 +02:00
Robert Gingras 821c32992d TagReaderTagLib: Refactor ID3v2 saving to a dedicated function 2024-04-24 01:23:51 +02:00
Robert Gingras b52cf9f3cd TagReaderTagLib: Reposition ParseID3v2Tag 2024-04-24 01:23:51 +02:00
Robert Gingras ab8e687f96 TagReaderTagLib: Add id3v2 parsing for RIFF WAV files 2024-04-24 01:23:51 +02:00
Robert Gingras 90703703aa TagReaderTagLib: Make id3v2 parsing reusable 2024-04-24 01:23:51 +02:00
Jonas Kvinge 176984afe0 RadioPlaylistItem: Set correct col position for InitFromQuery
Fixes #1430
2024-04-23 22:34:00 +02:00
Jonas Kvinge 542efa17ff Fix position for song and internet playlist items
Fixes #1430
2024-04-23 20:18:28 +02:00
Jonas Kvinge 6a2c2dbba1 Song: Fix loading length 2024-04-23 20:16:41 +02:00
Jonas Kvinge 426de61525 Add const and std::as_const 2024-04-23 17:15:42 +02:00
Jonas Kvinge 24c8d06d41 SongPlaylistItem: Use static_cast 2024-04-23 17:00:10 +02:00
Jonas Kvinge 227f5e5176 Replace QStringLiteral with QLatin1String 2024-04-23 16:57:49 +02:00
Jonas Kvinge 92e39a3e21 GlobalShortcut: Use optional 2024-04-23 16:54:54 +02:00
Jonas Kvinge fb2300e2fa EBUR128State: Add missing const reference 2024-04-23 16:54:22 +02:00
Jonas Kvinge 78becae5e9 AlbumCoverLoader: Use fully qualified namespace in slot 2024-04-23 16:53:40 +02:00
Jonas Kvinge d10eb4370e Player: Use chrono_literals 2024-04-23 16:53:12 +02:00
Jonas Kvinge 9c92ef941f CollectionModel: Remove redundant const_cast 2024-04-23 16:52:17 +02:00
Jonas Kvinge 7aefe3d71b Change QList<QString> to QStringList 2024-04-23 16:51:42 +02:00
Jonas Kvinge 398db964b8 Use QDateTime::currentSecsSinceEpoch 2024-04-23 16:48:51 +02:00
Jonas Kvinge 579349b104 ResizableTextEdit: Add Q_OBJECT macro 2024-04-23 16:44:44 +02:00
Jonas Kvinge 5f9a83871d Playlist: Cast to int 2024-04-23 02:45:27 +02:00
Jonas Kvinge da0c5e67c5 Playlist: Use optional::has_value 2024-04-23 02:45:11 +02:00
Jonas Kvinge a0cfde18c3 PlaylistSequence: Use constexpr 2024-04-23 02:44:48 +02:00
Jonas Kvinge 0ad4889936 PlaylistSequence: Remove unused member variable 2024-04-23 02:44:36 +02:00
Jonas Kvinge 36e809d530 InternetPlaylistItem: Cast to int 2024-04-23 02:18:24 +02:00
Jonas Kvinge 78096658e2 Chromaprinter: Remove useless cast 2024-04-23 02:18:08 +02:00
Jonas Kvinge 3edd218e3a Remove redundant casts 2024-04-23 01:58:08 +02:00
Jonas Kvinge 569bf6335b CollectionView: Add action_search_for_this_ to initialization list 2024-04-23 01:57:16 +02:00
Jonas Kvinge 7b8919d706 CommandlineOptions: Add const 2024-04-23 01:56:55 +02:00
Jonas Kvinge 147fd87d8c MainWindow: Only pass progress to UpdateTaskbarProgress 2024-04-23 01:56:30 +02:00
Jonas Kvinge ac0926d40b Song: Add ColumnIndex helper function 2024-04-23 01:55:57 +02:00
Jonas Kvinge 105d472f5d README: Fix badge 2024-04-21 22:35:15 +02:00
Jonas Kvinge c1a49da385 tests: Use QStringLiteral 2024-04-21 19:37:39 +02:00
Jonas Kvinge c3f596e64e CI: Add devel-tools-building repo for Leap 2024-04-21 17:03:06 +02:00
Jonas Kvinge adfda84c41 nsi: Bump icu from 74 to 75 2024-04-21 16:44:07 +02:00
Jonas Kvinge 345cc118a3 CI: Use different mirror for Mageia 2024-04-21 16:10:26 +02:00
Jonas Kvinge df070ac0cf Optimize `Song::InitFromQuery`
Use `QSqlQuery::value(int)` or `QSqlRecord::value(int)` instead of `QSqlQuery::value(QString)`.
Make `SqlRow` use `QSqlRecord` directly instead iterating over all columns.
2024-04-21 15:42:29 +02:00
Jonas Kvinge 7b88be2635 CollectionModel: Only set grouping if it's selected 2024-04-21 15:42:29 +02:00
Kientz Arnaud c30a39d29a Fix infinitive in french translations 2024-04-16 00:24:59 +02:00
Jonas Kvinge 36db41a1f0 Add sidebar background 2024-04-13 23:47:48 +02:00
Jonas Kvinge 8b249dc06a QSearchField: Remove NSSearchField workaround
This was a workaround for QTBUG-124160.
2024-04-13 05:39:19 +02:00
Jonas Kvinge 0c6872b352 Disable automatic conversions from 8-bit strings 2024-04-13 05:05:33 +02:00
Jonas Kvinge 58944993b8 Use QStringLiteral 2024-04-09 23:20:26 +02:00
Olivier HUMBERT 3cfffa5fbb Adds French to the menu item 2024-04-07 19:26:23 +02:00
Jonas Kvinge 4873b8b413 CI: Move OpenMandriva if false 2024-04-06 21:24:19 +02:00
Jonas Kvinge 0b85f5192c CI: Enable Mageia 2024-04-06 21:19:03 +02:00
Jonas Kvinge 3e9a1776a1 CI: Run upload and attach independent of failure 2024-04-06 21:10:10 +02:00
Jonas Kvinge e1fbe9ae54 Resolve song from collection using track with Cue in XSPF
Fixes #1181
2024-04-04 22:22:02 +02:00
Jonas Kvinge f48d1a8017 OrganizeDialog: Don't save settings unless button is pressed
Fixes #1404
2024-04-04 21:43:57 +02:00
rimasx 0debc90695 Create estonian translation
I hope this is OK. Strawberry compiled without any problems and Estonian was automatically selected. I mostly used the Clementine translation, which I tweaked a bit. Many thanks to the Clementine translators, especially Priit Jõerüüt.
2024-04-04 21:29:48 +02:00
Jonas Kvinge 8d42ea7cfd README: Remove link to zanata 2024-04-04 21:18:57 +02:00
Jonas Kvinge 1dae80a633 Add scrobbler option for stripping "remastered" etc
Fixes #1387
2024-04-04 21:17:07 +02:00
Jonas Kvinge d398c86b0c Make showing song progress on taskbar optional 2024-04-04 16:49:53 +02:00
Jonas Kvinge 70809e0647 MainWindow: Add error dialog when file deletion fails
Fixes #1384
2024-04-03 21:37:20 +02:00
Jonas Kvinge 4c1a5168f0 CollectionModel: Reset model before deletion 2024-04-03 21:17:20 +02:00
Jonas Kvinge f9acfbc224 SimpleTreeModel: Handle null root 2024-04-03 21:17:20 +02:00
Jonas Kvinge 5f78e1a983 MergedProxyModel: Fix beginRemoveRows first
Fixes #1314
2024-04-03 21:17:06 +02:00
Jonas Kvinge 7bc5579fb7 Song: Check that filetype is supported for writing tags
Fixes #1413
2024-04-03 20:45:52 +02:00
Jonas Kvinge 57750efcb2 CI: Remove openSUSE Leap 15.5
Protobuf is broken
2024-04-03 20:09:07 +02:00
Jonas Kvinge a33ee1cda9 CI: Use GCC 13 for Leap 2024-04-03 20:08:05 +02:00
Jonas Kvinge cd20a0679a CI: Replace qwindowsvistastyle with qmodernwindowsstyle 2024-04-02 17:37:58 +02:00
Jonas Kvinge 20e546e02b nsi: Replace qwindowsvistastyle with qmodernwindowsstyle 2024-04-02 17:37:38 +02:00
Jonas Kvinge f5547f093e Player: Use timer for saving volume
Fixes #1272
2024-04-02 17:16:29 +02:00
Jonas Kvinge c00d95242d Utilities: Handle missing XDG_DATA_DIRS variable 2024-04-02 16:39:48 +02:00
Jonas Kvinge 05c4d23df6 Utilities: Remove `--new-window` parameter from dolphin
Fixes #1412
2024-04-02 00:48:29 +02:00
Jonas Kvinge 06fa17f33f CI: Run apt upgrade for debian/ubuntu 2024-04-01 00:38:21 +02:00
Jonas Kvinge 194285289c Update Changelog 2024-03-31 22:56:46 +02:00
Jonas Kvinge a61fa61330 CI: Disable OpenMandriva 2024-03-31 01:49:30 +01:00
Jonas Kvinge 68c922ee12 SmartPlaylistWizard: Set classic style if using fusion on Windows
Workaround a Qt bug.

Fixes #1399
2024-03-29 02:54:25 +01:00
Jonas Kvinge d1042b276b GstEnginePipeline: Set volume_set_ to false in ElementRemovedCallback 2024-03-24 19:44:47 +01:00
Jonas Kvinge 9bbffe150f GstEnginePipeline: Add back volume sync for auto
We need to remove the volume sync when the element is deleted on "deep-element-removed", then re-add it on the next "deep-element-added" that isn't a fakesink.

Fixes #1123
2024-03-24 19:36:32 +01:00
Jonas Kvinge b95be526d3 HtmlLyricsProvider: Use QNetworkRequest::UserAgentHeader 2024-03-24 07:04:07 +01:00
Jonas Kvinge 165f9d769b MusixmatchCoverProvider: Fix parsing 2024-03-24 06:59:01 +01:00
Jonas Kvinge a0ea75b74e NetworkAccessManager: Use QNetworkRequest::setHeader 2024-03-24 06:58:33 +01:00
Jonas Kvinge 4075f92eec OpenTidalCoverProvider: Adjust settings 2024-03-24 05:27:43 +01:00
Jonas Kvinge 035aff5454 Add Open Tidal cover provider 2024-03-24 05:23:35 +01:00
Jonas Kvinge 52dc7ad259 CI: Add Fedora 41 2024-03-23 14:18:17 +01:00
Jonas Kvinge c3c83f608c CI: Add Leap 15.6 and remove 15.4 2024-03-23 14:05:11 +01:00
Strawbs Bot ffba351a16 Update translations 2024-03-23 01:43:24 +01:00
Jonas Kvinge a12623e142 Update Changelog 2024-03-23 01:04:29 +01:00
Jonas Kvinge 1a691a103e Fix Qt 5 and mpris2 build errors 2024-03-22 20:26:13 +01:00
Jonas Kvinge 5e725e0bbe Fix playlist shuffle
- Shuffle all indexes
- Use persistent indexes to store play history
- Update virtual items to keep original shuffle order when the playlist is reordered
- Make sure to always set virtual index on manual shuffle
- Ignore repeat and shuffle when dynamic playlist is activated

Fixes #707
Fixes #1381
Fixes #1366
Fixes #1353
2024-03-22 20:00:12 +01:00
Jonas Kvinge 93c2fa4c73 MusixmatchLyricsProvider: Parse metadata 2024-03-17 23:41:05 +01:00
Jonas Kvinge f412fb21d6 SettingsPage: Double spinboxes are double, not int 2024-03-17 21:56:39 +01:00
Jonas Kvinge bd4b6c1f01 main: Add `QCoreApplication::setQuitLockEnabled(false);`
Fixes #1401
2024-03-17 21:46:15 +01:00
Jonas Kvinge d1839d87e7 MusixmatchLyricsProvider: Fix parsing lyrics 2024-03-16 22:46:45 +01:00
Jonas Kvinge 1fc163eb5f Playlist: Comments formatting 2024-03-13 21:59:17 +01:00
Reverier-Xu cd2b3cb73e mpris2: Fix mpris:trackid type with Plasma 6 2024-03-13 21:31:25 +01:00
Reverier-Xu 88b5cf2461 mpris2: Fix mpris:trackid type with Plasma 6 2024-03-13 21:31:25 +01:00
Jonas Kvinge 2ccb0af75e Song: Only include mpris when built with DBUS 2024-03-13 18:15:51 +01:00
Célestin Matte 27ee6e7643 EditTagDialog: Add button to fetch lyrics
Co-Authored-By: Jonas Kvinge <jonas@jkvinge.net>
2024-03-13 17:54:19 +01:00
Jonas Kvinge a3207a5703 macgstcopy: Update gstreamer plugins list 2024-03-12 18:25:06 +01:00
Jonas Kvinge 72ff64a7f8 nsi: Remove unused gstreamer plugins 2024-03-12 16:29:48 +01:00
Jonas Kvinge 9c06d1d0ae nsi: Add gstreamer dsd plugin 2024-03-12 00:52:49 +01:00
Jonas Kvinge f11afd4414 GstEnginePipeline: Add default to switch 2024-03-12 00:40:11 +01:00
Sami Boukortt 2aa70b6ab8 Add an option not to skip “The” when sorting artist names 2024-03-11 23:34:42 +01:00
Jonas Kvinge 4626a6f609 GstEnginePipeline: Use playbin3 with gstreamer >= 1.22 2024-03-08 18:52:22 +01:00
Jonas Kvinge 9152f8559f Song: Split remastered and explicit regex 2024-03-03 01:50:05 +01:00
Jonas Kvinge 7f4c61b15a Improve album and title disc, remastered, etc matching
Don't partial remove things like "(Mono / Remastered)".

Fixes #1387
2024-03-02 19:48:19 +01:00
Jonas Kvinge b365131363 Playlist: Remove veto listeners
We have never used this, it's basically dead code.
2024-02-28 23:00:24 +01:00
Jonas Kvinge a6ea4dd0d7 Remove unused includes 2024-02-28 21:37:14 +01:00
Jonas Kvinge 9c6649f077 Add letras lyrics provider 2024-02-28 21:33:30 +01:00
Jonas Kvinge 04ba202e12 HtmlLyricsProvider: Use browser-like user-agent 2024-02-25 04:32:09 +01:00
Jonas Kvinge 352a6c5691 Remove lyricsmode.com provider
They have a "Verifying you are human" thing now.
2024-02-25 04:23:04 +01:00
Jonas Kvinge 72bccad82d Add accessible name for QToolButton css
Make sure it does not apply to other buttons.

Fixes #1255
2024-02-25 02:50:40 +01:00
Jonas Kvinge 6d52a2b409 QSearchField: Replace QToolButton with QPushButton 2024-02-25 02:46:51 +01:00
Jonas Kvinge 9b1035a5f2 Translations: Fix generating pot with NMake
Using GETTEXT_XGETTEXT_EXECUTABLE causes issues with NMake, possibly because of spaces in path.
2024-02-24 19:45:58 +01:00
Jonas Kvinge ce7c3e8039 CI: Use gh instead of hub 2024-02-24 01:18:02 +01:00
Jonas Kvinge 9baec288c3 CI: Set FFTW3_DIR 2024-02-23 22:57:00 +01:00
Jonas Kvinge 82894b94f4 CI: Set vsversion to 2022 2024-02-23 21:59:06 +01:00
Jonas Kvinge fb00d68aa7 Update Changelog 2024-02-20 22:47:55 +01:00
Jonas Kvinge 12288a2622 BackendSettingsPage: Fix enabling/disabling exclusive mode 2024-02-20 01:22:40 +01:00
Jonas Kvinge f84ce3f1d1 Add exclusive mode option for WASAPI 2024-02-20 01:08:00 +01:00
Jonas Kvinge 306b3f72d8 SettingsPage: Pass on scroll event to page
If the settings widget does not have focus, pass the event to the page for scrolling down instead of changing the setting.

Fixes #1380
2024-02-19 23:07:27 +01:00
buckmelanoma 593a04d047 InternetSearchView: Use DescriptionForSource instead of TextForSource
DescriptionForSource provides uppercase names in the filter menu instead of the lowercase names provided by TextForSource
2024-02-18 20:14:01 +01:00
buckmelanoma 667548f3ed InternetSongsView: Use DescriptionForSource instead of TextForSource
DescriptionForSource provides uppercase names in the filter menu instead of the lowercase names provided by TextForSource
2024-02-18 20:14:01 +01:00
Jonas Kvinge 8f89bf6402 Replace tabs with spaces 2024-02-18 14:24:20 +01:00
Jonas Kvinge ff28e7c86e Add ASIO device finder 2024-02-17 00:40:55 +01:00
Jonas Kvinge 67cc69179b nsi: Update gstreamer plugins 2024-02-16 21:51:45 +01:00
Jonas Kvinge 06fac2b7a3 CI: Copy all gstreamer plugins for mingw and msvc 2024-02-16 21:51:45 +01:00
Jonas Kvinge a354f6bdc5 GstEnginePipeline: Set device-clsid 2024-02-16 21:38:33 +01:00
Jonas Kvinge cb44c71733 DirectSoundDeviceFinder: Add waveformsink 2024-02-16 21:38:08 +01:00
Jonas Kvinge 6b1c14f875 GstEngine: Make sure asiosink is detected 2024-02-16 00:29:17 +01:00
Jonas Kvinge 7770aba877 GstEngine: Add pipewiresink 2024-02-14 18:46:23 +01:00
Jonas Kvinge 05381096aa RadioParadiseService: Use API to receive streams 2024-02-12 16:57:51 +01:00
Jonas Kvinge 6bdd9ad4dd GstEnginePipeline: Only hard-code playbin3 with gst 1.22 2024-02-11 23:52:22 +01:00
Jonas Kvinge 19836e8898 CI: Fix variable typo 2024-02-11 12:24:29 +01:00
Jonas Kvinge 5e4b193260 CI: Fix greping variable 2024-02-11 12:17:04 +01:00
Jonas Kvinge 923d0f2b7a CI: Use common SSH upload job 2024-02-11 12:12:54 +01:00
Jonas Kvinge 56f1a93c4e CI: Fix tag name variable typo 2024-02-11 01:19:39 +01:00
Jonas Kvinge 0168182af5 CddaDevice: Add missing override 2024-02-11 00:14:19 +01:00
Jonas Kvinge 679f0e1cd8 CI: Trigger on release 2024-02-11 00:10:36 +01:00
Adam Hill dd6b9bb38d MainWindow: Add function to display progress on taskbar 2024-02-09 21:48:12 +01:00
Jonas Kvinge 5bd8f35dc0 Update Changelog 2024-02-07 01:46:12 +01:00
Jonas Kvinge 53fc939e35 ScrobblingAPI20: Ignore permission related error
Last.fm returns permission denied error when servers are overloaded, ignore this error instead.

Fixes #442
2024-02-07 01:34:46 +01:00
Strawbs Bot 42d6c79710 Update translations 2024-02-01 21:43:21 +01:00
Jonas Kvinge 882869255b CI: Remove Ubuntu Lunar 2024-01-30 18:42:32 +01:00
Jonas Kvinge 19903751c1 CI: Codesign png.framework for macOS private 2024-01-27 17:39:26 +01:00
Jonas Kvinge 836074fb60 CI: Manually codesign png framework 2024-01-26 18:58:10 +01:00
Jonas Kvinge 5865a70c2e CI: Add --skip-broken to dnf install for Fedora 2024-01-26 00:29:25 +01:00
Jacob Henner fa057ee9d3 CollectionView: Add "search for this"
Add selection search in the collection view, to allow users to quickly
search for similarly-named songs, artists, albums, etc.
2024-01-25 19:16:14 +01:00
Jonas Kvinge 2d88fcb249 CI: Add Ubuntu Noble 2024-01-24 23:05:46 +01:00
Jonas Kvinge 920bb04b00 CMakeLists: Find TagLib using CMake 2024-01-24 21:30:10 +01:00
Jonas Kvinge a915e62e2c GPodDevice: Fix error message 2024-01-24 19:52:24 +01:00
Jonas Kvinge 226a6c50e0 Add better error messages for device and organize
Fixes #1364
2024-01-24 19:27:30 +01:00
Jonas Kvinge 269f13de76 MtpLoader: Allow empty artist 2024-01-24 19:21:02 +01:00
Jonas Kvinge 3ee796a663 macgstcopy: Add adaptivedemux2 and mpeg mux/demux 2024-01-22 19:26:21 +01:00
Jonas Kvinge d24af2f4db nsi: Add adaptivedemux2 and mpeg demux/mux plugins 2024-01-22 16:53:43 +01:00
Enrique Garcia f86c3cfd91 SubsonicSettingsPage: Add recommended to MD5 authentication 2024-01-21 04:25:31 +01:00
William Andrea 11061bdd07 CONTRIBUTING: Clarify "Commit messages" section.
Specifically:
- Clarify what "class" is referring to
- Use an actual example, which requires moving out two bits of explanation
2024-01-19 23:43:44 +01:00
Jonas Kvinge f901f802bb CollectionView: Implement add to playlist with enter
Fixes #1360
2024-01-19 23:04:03 +01:00
Jonas Kvinge a98c209101 AutoExpandingTreeView: Remove doubleClicked on enter 2024-01-19 23:03:37 +01:00
Jonas Kvinge 6a459682ca Playlist: Check for valid index
Fixes #1359
2024-01-19 22:37:14 +01:00
Jonas Kvinge 1a7194b3a9 Turn on git revision 2024-01-11 22:32:36 +01:00
493 changed files with 26473 additions and 15902 deletions

View File

@ -1,5 +1,10 @@
name: Build name: Build
on: [push, pull_request] on:
push:
pull_request:
types: [opened, synchronize, reopened]
release:
types: [published]
jobs: jobs:
@ -10,11 +15,14 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
opensuse_version: [ 'tumbleweed', 'leap:15.4', 'leap:15.5' ] opensuse_version: [ 'tumbleweed', 'leap:15.5', 'leap:15.6' ]
qt_version: [ '5', '6' ] qt_version: [ '5', '6' ]
container: container:
image: opensuse/${{matrix.opensuse_version}} image: opensuse/${{matrix.opensuse_version}}
steps: steps:
- name: Add devel tools building repo
if: matrix.opensuse_version != 'tumbleweed'
run: zypper -n ar -c -f -n 'devel-tools-building' https://download.opensuse.org/repositories/devel:tools:building/$(echo "${{matrix.opensuse_version}}" | cut -d ':' -f 2)/ devel-tools-building
- name: Add tagparser repo - name: Add tagparser repo
if: matrix.opensuse_version == 'tumbleweed' if: matrix.opensuse_version == 'tumbleweed'
run: zypper -n ar -c -f -n 'repo-tagparser' https://download.opensuse.org/repositories/home:/mkittler/openSUSE_Tumbleweed/ repo-tagparser run: zypper -n ar -c -f -n 'repo-tagparser' https://download.opensuse.org/repositories/home:/mkittler/openSUSE_Tumbleweed/ repo-tagparser
@ -23,12 +31,15 @@ jobs:
- name: Upgrade packages - name: Upgrade packages
if: matrix.opensuse_version == 'tumbleweed' if: matrix.opensuse_version == 'tumbleweed'
run: zypper -n --gpg-auto-import-keys dup run: zypper -n --gpg-auto-import-keys dup
- name: Upgrade packages
if: matrix.opensuse_version != 'tumbleweed'
run: zypper -n --gpg-auto-import-keys up
- name: Install gcc - name: Install gcc
if: matrix.opensuse_version == 'tumbleweed' if: matrix.opensuse_version == 'tumbleweed'
run: zypper -n --gpg-auto-import-keys in gcc gcc-c++ run: zypper -n --gpg-auto-import-keys in gcc gcc-c++
- name: Install gcc10 - name: Install gcc 13
if: matrix.opensuse_version != 'tumbleweed' if: matrix.opensuse_version != 'tumbleweed'
run: zypper -n --gpg-auto-import-keys in gcc10 gcc10-c++ run: zypper -n --gpg-auto-import-keys in gcc13 gcc13-c++
- name: Install packages - name: Install packages
run: > run: >
zypper -n --gpg-auto-import-keys in zypper -n --gpg-auto-import-keys in
@ -134,35 +145,27 @@ jobs:
if: matrix.opensuse_version != 'tumbleweed' if: matrix.opensuse_version != 'tumbleweed'
working-directory: build working-directory: build
env: env:
CC: gcc-10 CC: gcc-13
CXX: g++-10 CXX: g++-13
run: rpmbuild -ba ../dist/unix/strawberry.spec run: rpmbuild -ba ../dist/unix/strawberry.spec
- name: Set opensuse subdir - name: Set subdir
run: echo "opensuse_subdir=$(echo ${{matrix.opensuse_version}} | sed 's/leap:/lp/g' | sed 's/\.//g')" > $GITHUB_ENV id: set-subdir
- name: Upload artifacts run: echo "subdir=$(echo ${{matrix.opensuse_version}} | sed 's/leap:/lp/g' | sed 's/\.//g')" > $GITHUB_OUTPUT
if: matrix.opensuse_version != 'tumbleweed' - name: Upload source
if: matrix.opensuse_version == 'tumbleweed' && matrix.qt_version == '6'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: opensuse-${{env.opensuse_subdir}}-qt${{matrix.qt_version}} name: source
path: | path: |
/usr/src/packages/SOURCES/*.xz /usr/src/packages/SOURCES/*.xz
- name: Upload rpm
if: matrix.opensuse_version != 'tumbleweed' && matrix.qt_version == '6'
uses: actions/upload-artifact@v4
with:
name: opensuse-${{steps.set-subdir.outputs.subdir}}
path: |
/usr/src/packages/SRPMS/*.rpm /usr/src/packages/SRPMS/*.rpm
/usr/src/packages/RPMS/x86_64/*.rpm /usr/src/packages/RPMS/x86_64/*.rpm
- name: SSH key setup
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/source ${{secrets.BUILDS_PATH}}/opensuse/${{env.opensuse_subdir}}
- name: rsync source
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci') && matrix.opensuse_version == 'tumbleweed' && matrix.qt_version == '6'
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var /usr/src/packages/SOURCES/*.xz ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/source/
- name: rsync rpms
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci') && matrix.opensuse_version != 'tumbleweed'
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var /usr/src/packages/SRPMS/*.rpm /usr/src/packages/RPMS/x86_64/*.rpm ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/opensuse/${{env.opensuse_subdir}}/
build-fedora: build-fedora:
@ -172,7 +175,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
fedora_version: [ '38', '39', '40' ] fedora_version: [ '39', '40', '41' ]
container: container:
image: fedora:${{matrix.fedora_version}} image: fedora:${{matrix.fedora_version}}
steps: steps:
@ -256,23 +259,11 @@ jobs:
path: | path: |
/github/home/rpmbuild/SRPMS/*.rpm /github/home/rpmbuild/SRPMS/*.rpm
/github/home/rpmbuild/RPMS/x86_64/*.rpm /github/home/rpmbuild/RPMS/x86_64/*.rpm
- name: SSH key setup
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/fedora/${{matrix.fedora_version}}
- name: rsync
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var /github/home/rpmbuild/SRPMS/*.rpm /github/home/rpmbuild/RPMS/x86_64/*.rpm ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/fedora/${{matrix.fedora_version}}/
build-openmandriva: build-openmandriva:
name: Build OpenMandriva name: Build OpenMandriva
if: github.repository != 'strawberrymusicplayer/strawberry-private' if: github.repository != 'strawberrymusicplayer/strawberry-private' && false
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
@ -360,29 +351,18 @@ jobs:
working-directory: build working-directory: build
run: rpmbuild -ba ../dist/unix/strawberry.spec run: rpmbuild -ba ../dist/unix/strawberry.spec
- name: Upload artifacts - name: Upload artifacts
if: matrix.openmandriva_version != 'cooker'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: openmandriva-${{matrix.openmandriva_version}} name: openmandriva-${{matrix.openmandriva_version}}
path: | path: |
/github/home/rpmbuild/SRPMS/*.rpm /github/home/rpmbuild/SRPMS/*.rpm
/github/home/rpmbuild/RPMS/x86_64/*.rpm /github/home/rpmbuild/RPMS/x86_64/*.rpm
- name: SSH key setup
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.openmandriva_version != 'cooker' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.openmandriva_version != 'cooker' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/openmandriva/${{matrix.openmandriva_version}}
- name: rsync
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.openmandriva_version != 'cooker' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var /github/home/rpmbuild/SRPMS/*.rpm /github/home/rpmbuild/RPMS/x86_64/*.rpm ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/openmandriva/${{matrix.openmandriva_version}}/
build-mageia: build-mageia:
name: Build Mageia name: Build Mageia
if: github.repository != 'strawberrymusicplayer/strawberry-private' && false if: github.repository != 'strawberrymusicplayer/strawberry-private'
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
@ -391,8 +371,14 @@ jobs:
container: container:
image: mageia:${{matrix.mageia_version}} image: mageia:${{matrix.mageia_version}}
steps: steps:
- name: Set media
run: |
urpmi.removemedia "Core Release"
urpmi.removemedia "Core Updates"
urpmi.addmedia "Core Release" "https://mirrors.kernel.org/mageia/distrib/${{matrix.mageia_version}}/x86_64/media/core/release/"
urpmi.addmedia "Core Updates" "https://mirrors.kernel.org/mageia/distrib/${{matrix.mageia_version}}/x86_64/media/core/updates/"
- name: Update repositories - name: Update repositories
run: urpmi.update --auto -a run: urpmi.update -a
- name: Upgrade packages - name: Upgrade packages
run: urpmi --auto --auto-update run: urpmi --auto --auto-update
- name: Install dependencies - name: Install dependencies
@ -426,16 +412,19 @@ jobs:
lib64fftw-devel lib64fftw-devel
lib64dbus-devel lib64dbus-devel
lib64appstream-devel lib64appstream-devel
lib64qt6core-devel
lib64qt6gui-devel
lib64qt6widgets-devel
lib64qt6network-devel
lib64qt6concurrent-devel
lib64qt6sql-devel
lib64qt6dbus-devel
lib64qt6help-devel
lib64qt6test-devel
protobuf-compiler protobuf-compiler
desktop-file-utils desktop-file-utils
appstream-util appstream-util
hicolor-icon-theme hicolor-icon-theme
- name: Install Qt 5
if: matrix.mageia_version == '8'
run: urpmi --auto --force urpmi-debuginfo-install lib64qt5core-devel lib64qt5gui-devel lib64qt5widgets-devel lib64qt5network-devel lib64qt5concurrent-devel lib64qt5sql-devel lib64qt5dbus-devel lib64qt5help-devel lib64qt5test-devel lib64qt5x11extras-devel
- name: Install Qt 6
if: matrix.mageia_version == 'cauldron'
run: urpmi --auto --force urpmi-debuginfo-install lib64qt6core-devel lib64qt6gui-devel lib64qt6widgets-devel lib64qt6network-devel lib64qt6concurrent-devel lib64qt6sql-devel lib64qt6dbus-devel lib64qt6help-devel lib64qt6test-devel
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
@ -468,18 +457,6 @@ jobs:
path: | path: |
/github/home/rpmbuild/SRPMS/*.rpm /github/home/rpmbuild/SRPMS/*.rpm
/github/home/rpmbuild/RPMS/x86_64/*.rpm /github/home/rpmbuild/RPMS/x86_64/*.rpm
- name: SSH key setup
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/mageia/${{matrix.mageia_version}}
- name: rsync
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var /github/home/rpmbuild/SRPMS/*.rpm /github/home/rpmbuild/RPMS/x86_64/*.rpm ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/mageia/${{matrix.mageia_version}}/
build-debian: build-debian:
@ -493,8 +470,10 @@ jobs:
container: container:
image: debian:${{matrix.debian_version}} image: debian:${{matrix.debian_version}}
steps: steps:
- name: Update repositories - name: Update packages
run: apt update -y run: apt update -y
- name: Upgrade packages
run: apt upgrade -y
- name: Install packages - name: Install packages
env: env:
DEBIAN_FRONTEND: noninteractive DEBIAN_FRONTEND: noninteractive
@ -502,7 +481,6 @@ jobs:
apt install -y apt install -y
build-essential build-essential
dh-make dh-make
ssh
git git
make make
cmake cmake
@ -512,7 +490,6 @@ jobs:
fakeroot fakeroot
gettext gettext
lsb-release lsb-release
rsync
dpkg-dev dpkg-dev
libglib2.0-dev libglib2.0-dev
libdbus-1-dev libdbus-1-dev
@ -564,18 +541,6 @@ jobs:
with: with:
name: debian-${{matrix.debian_version}} name: debian-${{matrix.debian_version}}
path: "*.deb" path: "*.deb"
- name: SSH key setup
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/debian/${{matrix.debian_version}}
- name: rsync
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var *.deb ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/debian/${{matrix.debian_version}}/
build-ubuntu: build-ubuntu:
@ -585,12 +550,14 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
ubuntu_version: [ 'focal', 'jammy', 'lunar', 'mantic' ] ubuntu_version: [ 'focal', 'jammy', 'mantic', 'noble' ]
container: container:
image: ubuntu:${{matrix.ubuntu_version}} image: ubuntu:${{matrix.ubuntu_version}}
steps: steps:
- name: Update repositories - name: Update packages
run: apt update -y run: apt update -y
- name: Upgrade packages
run: apt upgrade -y
- name: Install packages - name: Install packages
env: env:
DEBIAN_FRONTEND: noninteractive DEBIAN_FRONTEND: noninteractive
@ -598,7 +565,6 @@ jobs:
apt install -y apt install -y
build-essential build-essential
dh-make dh-make
ssh
git git
make make
cmake cmake
@ -610,7 +576,6 @@ jobs:
curl curl
gettext gettext
lsb-release lsb-release
rsync
dpkg-dev dpkg-dev
libglib2.0-dev libglib2.0-dev
libboost-dev libboost-dev
@ -665,40 +630,29 @@ jobs:
path: | path: |
*.deb *.deb
*.ddeb *.ddeb
- name: SSH key setup
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/ubuntu/${{matrix.ubuntu_version}}
- name: rsync
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var *.deb *.ddeb ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/ubuntu/${{matrix.ubuntu_version}}/
upload-ubuntu-ppa: upload-ubuntu-ppa:
name: Upload Ubuntu PPA name: Upload Ubuntu PPA
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (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')))
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
ubuntu_version: [ 'focal', 'jammy', 'lunar', 'mantic' ] ubuntu_version: [ 'focal', 'jammy', 'mantic', 'noble' ]
container: container:
image: ubuntu:${{matrix.ubuntu_version}} image: ubuntu:${{matrix.ubuntu_version}}
steps: steps:
- name: Update repositories - name: Update packages
run: apt update -y run: apt update -y
- name: Upgrade packages
run: apt upgrade -y
- name: Install packages - name: Install packages
env: env:
DEBIAN_FRONTEND: noninteractive DEBIAN_FRONTEND: noninteractive
run: > run: >
apt install -y apt install -y
git git
ssh
build-essential build-essential
dh-make dh-make
make make
@ -733,6 +687,11 @@ jobs:
gstreamer1.0-alsa gstreamer1.0-alsa
gstreamer1.0-pulseaudio gstreamer1.0-pulseaudio
protobuf-compiler protobuf-compiler
- name: Install keyboxd
if: matrix.ubuntu_version == 'noble'
env:
DEBIAN_FRONTEND: noninteractive
run: apt install -y keyboxd
- name: Install Qt 5 - name: Install Qt 5
if: matrix.ubuntu_version == 'focal' if: matrix.ubuntu_version == 'focal'
env: env:
@ -762,15 +721,11 @@ jobs:
gpg_private_key: ${{secrets.UBUNTU_PPA_GPG_PRIVATE_KEY}} gpg_private_key: ${{secrets.UBUNTU_PPA_GPG_PRIVATE_KEY}}
- name: dpkg-buildpackage - name: dpkg-buildpackage
run: dpkg-buildpackage -S -d -k573D197B5EA20EDF run: dpkg-buildpackage -S -d -k573D197B5EA20EDF
- name: Set is release
run: echo "is_release=$(grep '^\s*set\s*(\s*INCLUDE_GIT_REVISION\s\+OFF\s*)\s*$' cmake/Version.cmake >/dev/null 2>&1 && echo 1 || echo 0)" >> $GITHUB_ENV
- name: Get release version
run: echo "release_version=$(git describe --tags --exact-match ${GITHUB_SHA} 2>/dev/null | head -1)" >> $GITHUB_ENV
- name: Upload Unstable PPA - name: Upload Unstable PPA
if: env.is_release != '1' && env.release_version == '' if: github.event_name == 'push'
run: dput ppa:jonaski/strawberry-unstable ../*_source.changes run: dput ppa:jonaski/strawberry-unstable ../*_source.changes
- name: Upload Stable PPA - name: Upload Stable PPA
if: env.is_release == '1' && env.release_version != '' if: github.event_name == 'release'
run: dput ppa:jonaski/strawberry ../*_source.changes run: dput ppa:jonaski/strawberry ../*_source.changes
@ -781,7 +736,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
runner: [ 'macos-11' ] runner: [ 'macos-12' ]
buildtype: [ 'release' ] buildtype: [ 'release' ]
runs-on: ${{ matrix.runner }} runs-on: ${{ matrix.runner }}
@ -813,7 +768,7 @@ jobs:
- name: Import certificate file - name: Import certificate file
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
uses: apple-actions/import-codesign-certs@v2 uses: apple-actions/import-codesign-certs@v3
with: with:
p12-file-base64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE }} p12-file-base64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE }}
p12-password: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }} p12-password: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }}
@ -835,7 +790,7 @@ jobs:
- name: Configure CMake - name: Configure CMake
env: env:
MACOSX_DEPLOYMENT_TARGET: 11.0 MACOSX_DEPLOYMENT_TARGET: 12.0
PKG_CONFIG_PATH: ${{env.prefix_path}}/lib/pkgconfig PKG_CONFIG_PATH: ${{env.prefix_path}}/lib/pkgconfig
LDFLAGS: -L${{env.prefix_path}}/lib -Wl,-rpath,${{env.prefix_path}}/lib LDFLAGS: -L${{env.prefix_path}}/lib -Wl,-rpath,${{env.prefix_path}}/lib
run: > run: >
@ -869,10 +824,10 @@ jobs:
working-directory: build working-directory: build
run: make deploy run: make deploy
- name: Codesign libsoup - 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
working-directory: build working-directory: build
run: codesign -s 383J84DVB6 -f strawberry.app/Contents/Frameworks/{libsoup-3.0.0.dylib,libnghttp2.14.dylib,libpsl.5.dylib,libpcre2-16.0.dylib,libpng16.16.dylib,libzstd.1.dylib} strawberry.app 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
- name: Deploy check - name: Deploy check
working-directory: build working-directory: build
@ -888,34 +843,29 @@ jobs:
run: make dmg run: make dmg
- name: SSH key setup - name: SSH key setup
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos') if: github.repository == 'strawberrymusicplayer/strawberry' && (github.event_name == 'release' || (github.event_name == 'push' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos')))
uses: shimataro/ssh-key-action@v2 uses: shimataro/ssh-key-action@v2
with: with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}} known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }} key: ${{ secrets.SSH_KEY }}
- name: Set is release
run: echo "is_release=$(grep '^\s*set\s*(\s*INCLUDE_GIT_REVISION\s\+OFF\s*)\s*$' cmake/Version.cmake >/dev/null 2>&1 && echo 1 || echo 0)" >> $GITHUB_ENV
- name: Get release version
run: echo "release_version=$(git describe --tags --exact-match ${GITHUB_SHA} 2>/dev/null | head -1)" >> $GITHUB_ENV
- name: Set Upload path - name: Set Upload path
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos') id: set-upload-path
if: github.repository == 'strawberrymusicplayer/strawberry' && (github.event_name == 'release' || (github.event_name == 'push' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos')))
run: | run: |
if [ "${{env.is_release}}" = "1" ] && ! [ "${{env.release_version}}" = "" ]; then if [ "${{github.event_name}}" = "release" ]; then
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/stable_releases/macos" >> $GITHUB_ENV echo "upload_path=${{secrets.DOWNLOADS_PATH}}/stable_releases/macos" >> $GITHUB_OUTPUT
else else
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/development_releases/macos" >> $GITHUB_ENV echo "upload_path=${{secrets.DOWNLOADS_PATH}}/development_releases/macos" >> $GITHUB_OUTPUT
fi fi
- name: Create server path - name: Create server path
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos') if: steps.set-upload-path.outputs.upload_path != ''
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{env.upload_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 - name: rsync
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos') if: steps.set-upload-path.outputs.upload_path != ''
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var build/*.dmg ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{env.upload_path}}/ 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-macos-private: build-macos-private:
@ -996,6 +946,10 @@ jobs:
working-directory: build working-directory: build
run: make deploy run: make deploy
- name: Manually Codesign
working-directory: build
run: codesign -s 383J84DVB6 -f strawberry.app/Contents/Frameworks/png.framework/png strawberry.app
- name: Deploy check - name: Deploy check
working-directory: build working-directory: build
run: make deploycheck run: make deploycheck
@ -1008,22 +962,20 @@ jobs:
working-directory: build working-directory: build
run: make dmg run: make dmg
- name: Set is release
run: echo "is_release=$(grep '^\s*set\s*(\s*INCLUDE_GIT_REVISION\s\+OFF\s*)\s*$' cmake/Version.cmake >/dev/null 2>&1 && echo 1 || echo 0)" >> $GITHUB_ENV
- name: Set Upload path - name: Set Upload path
id: set-upload-path
run: | run: |
if [ "${{env.is_release}}" = "1" ]; then if [ "${{github.event_name}}" = "release" ]; then
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/stable_releases/macos" >> $GITHUB_ENV echo "upload_path=${{secrets.DOWNLOADS_PATH}}/stable_releases/macos" >> $GITHUB_OUTPUT
else else
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/development_releases/macos" >> $GITHUB_ENV echo "upload_path=${{secrets.DOWNLOADS_PATH}}/development_releases/macos" >> $GITHUB_OUTPUT
fi fi
- name: Create server path - name: Create server path
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{env.upload_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 - name: rsync
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var build/*.dmg ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{env.upload_path}}/ 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: build-windows-mingw:
@ -1033,7 +985,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
arch: [ 'i686', 'x86_64' ] arch: [ 'x86_64' ]
buildtype: [ 'debug', 'release' ] buildtype: [ 'debug', 'release' ]
container: container:
image: jonaski/strawberry-mxe-${{matrix.arch}}-${{matrix.buildtype}} image: jonaski/strawberry-mxe-${{matrix.arch}}-${{matrix.buildtype}}
@ -1095,7 +1047,7 @@ jobs:
- name: Copy Qt styles - name: Copy Qt styles
working-directory: build working-directory: build
run: cp /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/qt6/plugins/styles/qwindowsvistastyle.dll ${GITHUB_WORKSPACE}/build/styles/ run: cp /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/qt6/plugins/styles/qmodernwindowsstyle.dll ${GITHUB_WORKSPACE}/build/styles/
- name: Copy Qt TLS plugins - name: Copy Qt TLS plugins
working-directory: build working-directory: build
@ -1111,7 +1063,7 @@ jobs:
- name: Copy gstreamer plugins - name: Copy gstreamer plugins
working-directory: build working-directory: build
run: cp /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/bin/gstreamer-1.0/{libgstaes.dll,libgstaiff.dll,libgstapetag.dll,libgstapp.dll,libgstasf.dll,libgstasfmux.dll,libgstaudioconvert.dll,libgstaudiofx.dll,libgstaudiomixer.dll,libgstaudioparsers.dll,libgstaudiorate.dll,libgstaudioresample.dll,libgstaudiotestsrc.dll,libgstautodetect.dll,libgstbs2b.dll,libgstcoreelements.dll,libgstdash.dll,libgstdirectsound.dll,libgstequalizer.dll,libgstfaac.dll,libgstfaad.dll,libgstfdkaac.dll,libgstflac.dll,libgstgio.dll,libgstgme.dll,libgsthls.dll,libgsticydemux.dll,libgstid3demux.dll,libgstid3tag.dll,libgstisomp4.dll,libgstlame.dll,libgstlibav.dll,libgstmpg123.dll,libgstmusepack.dll,libgstogg.dll,libgstopenmpt.dll,libgstopus.dll,libgstopusparse.dll,libgstpbtypes.dll,libgstplayback.dll,libgstreplaygain.dll,libgstrtp.dll,libgstrtsp.dll,libgstsoup.dll,libgstspectrum.dll,libgstspeex.dll,libgsttaglib.dll,libgsttcp.dll,libgsttwolame.dll,libgsttypefindfunctions.dll,libgstudp.dll,libgstvolume.dll,libgstvorbis.dll,libgstwasapi.dll,libgstwavenc.dll,libgstwavpack.dll,libgstwavparse.dll,libgstxingmux.dll} ${GITHUB_WORKSPACE}/build/gstreamer-plugins/ run: cp /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/bin/gstreamer-1.0/*.dll ${GITHUB_WORKSPACE}/build/gstreamer-plugins/
- name: Copy extra binaries - name: Copy extra binaries
working-directory: build working-directory: build
@ -1196,21 +1148,6 @@ jobs:
name: windows-mingw-${{matrix.arch}}-${{matrix.buildtype}} name: windows-mingw-${{matrix.arch}}-${{matrix.buildtype}}
path: build/StrawberrySetup*.exe path: build/StrawberrySetup*.exe
- name: SSH key setup
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/windows/mingw
- name: rsync
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var build/StrawberrySetup*.exe ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/windows/mingw/
build-windows-msvc: build-windows-msvc:
name: Build Windows MSVC name: Build Windows MSVC
@ -1219,8 +1156,8 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
arch: [ 'x86', 'x86_64' ] arch: [ 'x86_64' ]
buildtype: [ 'debug', 'release' ] buildtype: [ 'release' ]
steps: steps:
- name: Set prefix path - name: Set prefix path
@ -1248,6 +1185,21 @@ jobs:
working-directory: downloads working-directory: downloads
run: tar -C /c -xf strawberry-msvc-${{matrix.arch}}-${{matrix.buildtype}}.tar.xz run: tar -C /c -xf strawberry-msvc-${{matrix.arch}}-${{matrix.buildtype}}.tar.xz
- name: Download Windows MSVC dependencies (debug)
if: matrix.buildtype == 'release'
shell: cmd
working-directory: downloads
run: curl -f -O -L https://github.com/strawberrymusicplayer/strawberry-msvc-dependencies/releases/latest/download/strawberry-msvc-${{matrix.arch}}-debug.tar.xz
- name: Use protoc from debug
if: matrix.buildtype == 'release'
shell: bash
working-directory: downloads
run: |
mv -v /c/strawberry_msvc_${{matrix.arch}}_${{matrix.buildtype}}/bin/abseil_dll.dll /c/strawberry_msvc_${{matrix.arch}}_${{matrix.buildtype}}/bin/abseil_dll.dll_
tar -xvf strawberry-msvc-${{matrix.arch}}-debug.tar.xz strawberry_msvc_${{matrix.arch}}_debug/bin/{libprotobufd.dll,libprotocd.dll,protoc.exe,abseil_dll.dll,zlibd.dll}
cp -v strawberry_msvc_${{matrix.arch}}_debug/bin/{libprotobufd.dll,libprotocd.dll,protoc.exe,abseil_dll.dll,zlibd.dll} /c/strawberry_msvc_${{matrix.arch}}_${{matrix.buildtype}}/bin/
- name: Update PATH - name: Update PATH
run: echo "${{env.prefix_path_backslash}}\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append run: echo "${{env.prefix_path_backslash}}\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
@ -1310,8 +1262,7 @@ jobs:
with: with:
arch: ${{matrix.arch}} arch: ${{matrix.arch}}
sdk: 10.0.20348.0 sdk: 10.0.20348.0
vsversion: 17 vsversion: 2022
toolset: 14.3
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -1352,6 +1303,8 @@ jobs:
-DUSE_TAGLIB=ON -DUSE_TAGLIB=ON
-DPKG_CONFIG_EXECUTABLE="${{env.prefix_path_forwardslash}}/bin/pkg-config.exe" -DPKG_CONFIG_EXECUTABLE="${{env.prefix_path_forwardslash}}/bin/pkg-config.exe"
-DICU_ROOT="${{env.prefix_path_forwardslash}}" -DICU_ROOT="${{env.prefix_path_forwardslash}}"
-DFFTW3_DIR="${{env.prefix_path_forwardslash}}"
-DBoost_INCLUDE_DIR="${{env.prefix_path_forwardslash}}/include"
- name: Run Make - name: Run Make
shell: cmd shell: cmd
@ -1359,6 +1312,11 @@ jobs:
CL: "/MP" CL: "/MP"
run: cmake --build build --config "${{env.cmake_buildtype}}" --parallel 4 run: cmake --build build --config "${{env.cmake_buildtype}}" --parallel 4
- name: Move back abseil dll
if: matrix.buildtype == 'release'
shell: bash
run: mv -v /c/strawberry_msvc_${{matrix.arch}}_${{matrix.buildtype}}/bin/abseil_dll.dll_ /c/strawberry_msvc_${{matrix.arch}}_${{matrix.buildtype}}/bin/abseil_dll.dll
- name: Copy extra binaries - name: Copy extra binaries
shell: cmd shell: cmd
working-directory: build working-directory: build
@ -1397,7 +1355,7 @@ jobs:
- name: Copy Qt styles - name: Copy Qt styles
shell: cmd shell: cmd
working-directory: build working-directory: build
run: copy ${{env.prefix_path_backslash}}\plugins\styles\qwindowsvistastyle*.dll .\styles\ run: copy ${{env.prefix_path_backslash}}\plugins\styles\qmodernwindowsstyle*.dll .\styles\
- name: Copy Qt TLS plugins - name: Copy Qt TLS plugins
shell: cmd shell: cmd
@ -1417,66 +1375,7 @@ jobs:
- name: Copy gstreamer plugins - name: Copy gstreamer plugins
shell: cmd shell: cmd
working-directory: build working-directory: build
run: | run: copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\*.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstaes.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstaiff.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstapetag.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstapp.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstasf.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstasfmux.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstaudioconvert.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstaudiofx.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstaudiomixer.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstaudioparsers.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstaudiorate.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstaudioresample.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstaudiotestsrc.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstautodetect.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstbs2b.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstcoreelements.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstdash.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstdirectsound.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstequalizer.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstfaac.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstfaad.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstfdkaac.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstflac.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstgio.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstgme.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gsthls.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gsticydemux.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstid3demux.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstid3tag.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstisomp4.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstlame.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstlibav.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstmpg123.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstmusepack.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstogg.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstopenmpt.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstopus.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstopusparse.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstpbtypes.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstplayback.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstreplaygain.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstrtp.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstrtsp.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstsoup.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstspectrum.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstspeex.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gsttaglib.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gsttcp.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gsttwolame.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gsttypefindfunctions.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstudp.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstvolume.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstvorbis.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstwasapi.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstwasapi2.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstwavenc.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstwavpack.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstwavparse.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstxingmux.dll .\gstreamer-plugins\
- name: Download copydlldeps.sh - name: Download copydlldeps.sh
shell: bash shell: bash
@ -1557,38 +1456,9 @@ jobs:
path: build/StrawberrySetup*.exe path: build/StrawberrySetup*.exe
rsync-windows-msvc-builds: upload:
name: Rsync Windows MSVC builds name: Upload
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci') if: (success() || failure()) && github.repository == 'strawberrymusicplayer/strawberry' && (github.event_name == 'release' || (github.event_name == 'push' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')))
runs-on: ubuntu-latest
needs:
- build-windows-msvc
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: builds
pattern: windows-msvc-*
- name: View files
run: find builds
- name: SSH key setup
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{secrets.SSH_KEY}}
- name: Create server path
shell: bash
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/windows/msvc
- name: rsync
shell: bash
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var builds/*/StrawberrySetup-*-msvc-*.exe ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/windows/msvc/
upload-release:
name: Upload release
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- build-opensuse - build-opensuse
@ -1598,55 +1468,79 @@ jobs:
- build-windows-mingw - build-windows-mingw
- build-windows-msvc - build-windows-msvc
steps: steps:
- name: Install packages - name: Install packages
env: env:
DEBIAN_FRONTEND: noninteractive DEBIAN_FRONTEND: noninteractive
run: sudo apt install -y git rsync hub run: sudo apt install -y git rsync
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set is release
run: echo "is_release=$(grep '^\s*set\s*(\s*INCLUDE_GIT_REVISION\s\+OFF\s*)\s*$' cmake/Version.cmake >/dev/null 2>&1 && echo 1 || echo 0)" >> $GITHUB_ENV
- name: Get release version
run: echo "release_version=$(git describe --tags --exact-match ${GITHUB_SHA} 2>/dev/null | head -1)" >> $GITHUB_ENV
- name: Show release version
if: env.release_version != ''
run: echo "Release version:" ${{env.release_version}}
- name: Show release assets
if: env.release_version != ''
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
run: hub release show -f "%as" ${{env.release_version}}
- name: Download artifacts - name: Download artifacts
if: env.release_version != ''
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
path: artifacts path: artifacts
- name: SSH key setup - name: SSH key setup
if: env.release_version != ''
uses: shimataro/ssh-key-action@v2 uses: shimataro/ssh-key-action@v2
with: with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}} known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{secrets.SSH_KEY}} key: ${{secrets.SSH_KEY}}
- name: Upload
run: |
for i in $(find artifacts -type f); do
if [ "${{github.event_name}}" = "release" ]; then
upload_path="${{secrets.RELEASES_PATH}}/"
else
distro=$(echo "$i" | cut -d '/' -f 2)
if [ "$(echo "$i" | grep '-' || true)" = "" ]; then
upload_path="${{secrets.BUILDS_PATH}}/${distro}/"
else
distro_name=$(echo "${distro}" | cut -d '-' -f 1)
distro_version=$(echo "${distro}" | cut -d '-' -f 2)
upload_path="${{secrets.BUILDS_PATH}}/${distro_name}/${distro_version}/"
fi
fi
ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${upload_path}
rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var $i ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${upload_path}/
done
attach:
name: Attach to release
if: (success() || failure()) && github.event_name == 'release'
runs-on: ubuntu-latest
needs:
- build-opensuse
- build-fedora
- build-mageia
- build-debian
- build-ubuntu
- build-windows-mingw
- build-windows-msvc
steps:
- name: Install packages
env:
DEBIAN_FRONTEND: noninteractive
run: sudo apt install -y git jq gh
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Show release assets
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
run: gh release view "${{github.event.release.tag_name}}" --json assets | jq -r '.assets[].name'
- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Add artifacts to release - name: Add artifacts to release
if: env.is_release == '1' && env.release_version != ''
env: env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
run: | run: |
echo "Release version: ${{env.release_version}}" echo "Release version: ${{github.event.release.tag_name}}"
filenames=() filenames=()
files=() files=()
a_files=()
for i in $(find artifacts -type f); do for i in $(find artifacts -type f); do
filename=$(basename $i) filename=$(basename $i)
if [[ ${filenames[@]} =~ ${filename} ]]; then if [[ ${filenames[@]} =~ ${filename} ]]; then
@ -1654,22 +1548,16 @@ jobs:
continue continue
fi fi
filenames+=("${filename}") filenames+=("${filename}")
existing_asset=$(hub release show -f "%as" ${{env.release_version}} | tr -d '[:blank:]' | grep ".*/${filename}\$" 2>/dev/null || true) existing_asset=$(gh release view "${{github.event.release.tag_name}}" --json assets | jq -r '.assets[].name' | tr -d '[:blank:]' | grep ".*/${filename}\$" 2>/dev/null || true)
if [ "${existing_asset}" = "" ]; then if [ "${existing_asset}" = "" ]; then
echo "Adding file: ${filename}" echo "Adding file: ${filename}"
files+=("$i") files+=("${i}")
a_files+=("-a" "${i}")
else else
echo "Release already has file: ${filename}" echo "Release already has file: ${filename}"
fi fi
done done
files_list="${files[@]}" files_list="${files[@]}"
a_files_list="${a_files[@]}"
if ! [ "${files_list}" = "" ]; then if ! [ "${files_list}" = "" ]; then
echo "Uploading files: ${files_list}" echo "Adding files to GitHub release"
rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var ${files_list} ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.RELEASES_PATH}}/ gh release upload "${{github.event.release.tag_name}}" ${files_list}
fi
if ! [ "${a_files_list}" = "" ]; then
echo "Adding files to GitHub release: ${files_list}"
hub release edit -m "Strawberry ${{env.release_version}}" ${a_files_list} "${{env.release_version}}"
fi fi

View File

@ -62,7 +62,7 @@ enum ENUM_ORDERING {
// //
// //
// Ansi structures and functions follow // Ansi structures and functions follow
// //
// //
@ -409,7 +409,7 @@ int _getopt_long_only_r_a(int argc, char *const *argv, const char *options, cons
// //
// //
// Unicode Structures and Functions // Unicode Structures and Functions
// //
// //
@ -750,4 +750,4 @@ int _getopt_long_r_w(int argc, wchar_t *const *argv, const wchar_t *options, con
int _getopt_long_only_r_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d); int _getopt_long_only_r_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d);
int _getopt_long_only_r_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d) { int _getopt_long_only_r_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d) {
return _getopt_internal_r_w(argc, argv, options, long_options, opt_index, 1, d, 0); return _getopt_internal_r_w(argc, argv, options, long_options, opt_index, 1, d, 0);
} }

View File

@ -99,8 +99,6 @@ if(CCACHE_EXECUTABLE)
SET_PROPERTY(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_EXECUTABLE}) SET_PROPERTY(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_EXECUTABLE})
endif() endif()
option(USE_ICU "Use ICU" ON)
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
find_package(Backtrace) find_package(Backtrace)
@ -108,14 +106,7 @@ if(Backtrace_FOUND)
set(HAVE_BACKTRACE ON) set(HAVE_BACKTRACE ON)
endif() endif()
find_package(Boost REQUIRED) find_package(Boost REQUIRED)
if(USE_ICU) find_package(ICU COMPONENTS uc i18n REQUIRED)
find_package(ICU COMPONENTS uc i18n REQUIRED)
if(ICU_FOUND)
set(HAVE_ICU ON)
endif()
else()
find_package(Iconv)
endif()
find_package(Protobuf CONFIG) find_package(Protobuf CONFIG)
if(NOT Protobuf_FOUND) if(NOT Protobuf_FOUND)
find_package(Protobuf REQUIRED) find_package(Protobuf REQUIRED)
@ -266,25 +257,19 @@ if(X11_FOUND)
endif(X11_FOUND) endif(X11_FOUND)
option(USE_TAGLIB "Build with TagLib" OFF) option(USE_TAGLIB "Build with TagLib" ON)
option(USE_TAGPARSER "Build with TagParser" OFF) option(USE_TAGPARSER "Build with TagParser" OFF)
if(NOT USE_TAGLIB AND NOT USE_TAGPARSER)
set(USE_TAGLIB ON)
endif()
# TAGLIB # TAGLIB
if(USE_TAGLIB) if(USE_TAGLIB)
pkg_check_modules(TAGLIB REQUIRED taglib>=1.11.1) find_package(TagLib 2.0)
if(TAGLIB_FOUND) if(TARGET TagLib::TagLib)
find_path(HAVE_TAGLIB_DSFFILE_H taglib/dsffile.h) set(TAGLIB_FOUND ON)
find_path(HAVE_TAGLIB_DSDIFFFILE_H taglib/dsdifffile.h) set(TAGLIB_LIBRARIES TagLib::TagLib)
if(HAVE_TAGLIB_DSFFILE_H) set(HAVE_TAGLIB_DSFFILE ON)
set(HAVE_TAGLIB_DSFFILE ON) set(HAVE_TAGLIB_DSDIFFFILE ON)
endif(HAVE_TAGLIB_DSFFILE_H) else()
if(HAVE_TAGLIB_DSDIFFFILE_H) pkg_check_modules(TAGLIB REQUIRED taglib>=1.11.1)
set(HAVE_TAGLIB_DSDIFFFILE ON)
endif(HAVE_TAGLIB_DSDIFFFILE_H)
endif() endif()
endif() endif()
@ -514,6 +499,8 @@ add_definitions(
-DQT_NO_CAST_TO_ASCII -DQT_NO_CAST_TO_ASCII
-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT -DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT
-DQT_NO_FOREACH -DQT_NO_FOREACH
-DQT_ASCII_CAST_WARNINGS
-DQT_NO_CAST_FROM_ASCII
) )
if(WIN32) if(WIN32)

View File

@ -48,24 +48,27 @@ small as possible.
### Commit messages ### Commit messages
The first line should start with "Class:", which referer to the class The first line should start with the name of the class that is changed
that is changed. Don't use a trailing period after the first line. followed by a colon then a short explanation of the commit.
If this change affects more than one class, omit the class and write a Don't use a trailing period after the first line.
If this change affects more than one class, omit the class name and write a
more general message. more general message.
You only need to include a main description (body) for larger changes You only need to include a main description (body) for larger changes
where the one line is not enough to describe everything. where the one line is not enough to describe everything.
The main description starts after two newlines, it is normal prose and The main description starts after two newlines, it is normal prose and
should use normal punctuation and capital letters where appropriate. should use normal punctuation and capital letters where appropriate.
It should explain exactly what's changed, why it's changed,
and what bugs were fixed.
An example of the expected format for git commit messages is as follows: An example of the expected format for git commit messages is as follows:
``` ```
class: Short explanation of the commit StretchHeaderView: Set default section size
Longer explanation explaining exactly what's changed, why it's changed, As of Qt 6.6.1, style changes are resetting the column sizes. To prevent this, we set a default section size.
and what bugs were fixed.
Fixes #1234 Fixes #1328
``` ```

View File

@ -2,6 +2,42 @@ Strawberry Music Player
======================= =======================
ChangeLog ChangeLog
Unreleased:
Bugfixes:
* Fixed crash when pressing CTRL + C (#1359).
* Pass on scroll events to page in settings to avoid changing settings when scrolling with mouse (#1380).
* Fixed misredered playlist search field with Wayland using scaling (#1255).
* Fixed Azlyrics lyrics provider because of website changes.
* Fixed Musixmatch lyrics provider because of website changes.
* Fixed application exiting when closing file dialog (#1401).
* Fixed playlist shuffle randomness (#707).
* Fixed playlist shuffle order always the same when restarting playback (#1381).
* Fixed dynamic random mix not always ignoring shuffle and repeat mode (#1366).
* Fixed manual shuffle while playing not setting current song to new index (##1353).
* Fixed volume sync with PA when output is set to auto (#1123).
* Fixed mpris:trackid type with KDE 6 (#1397).
* (macOS/Windows) Fixed dash and hls streaming, plugins were missing.
* (Windows) Fixed incorrect colors in smart playlist wizard with Fusion in dark mode (#1399).
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).
* (Unix) Add experimental GStreamer pipewire support.
* (Windows) Add experimental exclusive mode for WASAPI.
* (Windows MSVC) Add ASIO support.
* (Windows MSVC) Add back WASAPI2.
Removed features:
* Removed now broken lyricsmode.com lyrics provider because of website changes.
Version 1.0.23 (2024.01.11): Version 1.0.23 (2024.01.11):
Bugfixes: Bugfixes:

View File

@ -1,4 +1,4 @@
:strawberry: Strawberry Music Player [![Build Status](https://github.com/strawberrymusicplayer/strawberry/workflows/build/badge.svg)](https://github.com/strawberrymusicplayer/strawberry/actions) :strawberry: Strawberry Music Player [![Build Status](https://github.com/strawberrymusicplayer/strawberry/workflows/Build/badge.svg)](https://github.com/strawberrymusicplayer/strawberry/actions)
======================= =======================
[![Sponsor](https://img.shields.io/badge/-Sponsor-green?logo=github)](https://github.com/sponsors/jonaski) [![Sponsor](https://img.shields.io/badge/-Sponsor-green?logo=github)](https://github.com/sponsors/jonaski)
[![Patreon](https://img.shields.io/badge/patreon-donate-green.svg)](https://patreon.com/jonaskvinge) [![Patreon](https://img.shields.io/badge/patreon-donate-green.svg)](https://patreon.com/jonaskvinge)
@ -19,7 +19,6 @@ Resources:
* openSUSE buildservice: https://build.opensuse.org/package/show/home:jonaski:audio/strawberry * openSUSE buildservice: https://build.opensuse.org/package/show/home:jonaski:audio/strawberry
* Ubuntu PPA: https://launchpad.net/~jonaski/+archive/ubuntu/strawberry * Ubuntu PPA: https://launchpad.net/~jonaski/+archive/ubuntu/strawberry
* Ubuntu Unstable PPA: https://launchpad.net/~jonaski/+archive/ubuntu/strawberry-unstable * Ubuntu Unstable PPA: https://launchpad.net/~jonaski/+archive/ubuntu/strawberry-unstable
* Translations: https://translate.zanata.org/iteration/view/strawberry/master
### :bangbang: Opening an issue ### :bangbang: Opening an issue
@ -29,7 +28,7 @@ Resources:
* We do not take feature requests from users on GitHub. Any issues related to feature requests will be closed. This does not necessarily mean that we won't add new features, but we don't have time to take feature requests or answer questions about new features from users. It is still possible to suggest or discuss new features on the forum (https://forum.strawberrymusicplayer.org/). * We do not take feature requests from users on GitHub. Any issues related to feature requests will be closed. This does not necessarily mean that we won't add new features, but we don't have time to take feature requests or answer questions about new features from users. It is still possible to suggest or discuss new features on the forum (https://forum.strawberrymusicplayer.org/).
* We do not maintain the Flatpak package. Do not report issues related to Flatpak unless the issue can be reproduced with a native package, use Flatpak support instead https://flatpak.org/about/ * We do not maintain the Flatpak package. Do not report issues related to Flatpak unless the issue can be reproduced with a native package, use Flatpak support instead https://flatpak.org/about/
### :moneybag: Sponsoring ### :moneybag: Sponsoring
The program is free software, released under GPL. If you like this program and can make use of it, consider sponsoring or donating to help fund the project. The program is free software, released under GPL. If you like this program and can make use of it, consider sponsoring or donating to help fund the project.
There are currently 4 options for sponsoring: There are currently 4 options for sponsoring:
@ -54,7 +53,7 @@ Funding developers is a way to contribute to open source projects you appreciate
* Edit tags on audio files * Edit tags on audio files
* Fetch tags from MusicBrainz * Fetch tags from MusicBrainz
* Album cover art from [Last.fm](https://www.last.fm/), [Musicbrainz](https://musicbrainz.org/), [Discogs](https://www.discogs.com/), [Musixmatch](https://www.musixmatch.com/), [Deezer](https://www.deezer.com/), [Tidal](https://www.tidal.com/), [Qobuz](https://www.qobuz.com/) and [Spotify](https://www.spotify.com/) * Album cover art from [Last.fm](https://www.last.fm/), [Musicbrainz](https://musicbrainz.org/), [Discogs](https://www.discogs.com/), [Musixmatch](https://www.musixmatch.com/), [Deezer](https://www.deezer.com/), [Tidal](https://www.tidal.com/), [Qobuz](https://www.qobuz.com/) and [Spotify](https://www.spotify.com/)
* Song lyrics from [Genius](https://genius.com/), [Musixmatch](https://www.musixmatch.com/), [ChartLyrics](http://www.chartlyrics.com/), [lyrics.ovh](https://lyrics.ovh/), [lololyrics.com](https://www.lololyrics.com/), [songlyrics.com](https://www.songlyrics.com/), [azlyrics.com](https://www.azlyrics.com/), [elyrics.net](https://www.elyrics.net/) and [lyricsmode.com](https://www.lyricsmode.com/) * Song lyrics from [Genius](https://genius.com/), [Musixmatch](https://www.musixmatch.com/), [ChartLyrics](http://www.chartlyrics.com/), [lyrics.ovh](https://lyrics.ovh/), [lololyrics.com](https://www.lololyrics.com/), [songlyrics.com](https://www.songlyrics.com/), [azlyrics.com](https://www.azlyrics.com/) and [elyrics.net](https://www.elyrics.net/)
* Support for multiple backends * Support for multiple backends
* Audio analyzer * Audio analyzer
* Audio equalizer * Audio equalizer
@ -97,7 +96,7 @@ Optional dependencies:
You should also install the gstreamer plugins base and good, and optionally bad, ugly and libav to support all audio formats. You should also install the gstreamer plugins base and good, and optionally bad, ugly and libav to support all audio formats.
### :wrench: Compiling from source ### :wrench: Compiling from source
### Get the code: ### Get the code:
@ -118,6 +117,6 @@ Strawberry is backwards compatible with Qt 5, to compile with Qt 5 use:
To compile on Windows with Visual Studio 2019 or 2022, see https://github.com/strawberrymusicplayer/strawberry-msvc To compile on Windows with Visual Studio 2019 or 2022, see https://github.com/strawberrymusicplayer/strawberry-msvc
### :penguin: Packaging status ### :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?exclude_unsupported=1)](https://repology.org/metapackage/strawberry/versions)

View File

@ -1,21 +1,33 @@
find_program(GETTEXT_XGETTEXT_EXECUTABLE xgettext) find_program(GETTEXT_XGETTEXT_EXECUTABLE xgettext REQUIRED)
if(NOT GETTEXT_XGETTEXT_EXECUTABLE) find_program(CAT_EXECUTABLE cat REQUIRED)
message(FATAL_ERROR "Could not find xgettext executable")
endif(NOT GETTEXT_XGETTEXT_EXECUTABLE)
set (XGETTEXT_OPTIONS list(APPEND XGETTEXT_OPTIONS
--qt --qt
--keyword=tr:1,2c --keyword=tr:1,2c
--keyword=tr --flag=tr:1:pass-c-format --flag=tr:1:pass-qt-format --keyword=tr
--keyword=trUtf8 --flag=tr:1:pass-c-format --flag=tr:1:pass-qt-format --flag=tr:1:pass-c-format
--keyword=translate:2,3c --flag=tr:1:pass-qt-format
--keyword=translate:2 --flag=translate:2:pass-c-format --flag=translate:2:pass-qt-format --keyword=trUtf8
--keyword=QT_TR_NOOP --flag=QT_TR_NOOP:1:pass-c-format --flag=QT_TR_NOOP:1:pass-qt-format --flag=tr:1:pass-c-format
--keyword=QT_TRANSLATE_NOOP:2 --flag=QT_TRANSLATE_NOOP:2:pass-c-format --flag=QT_TRANSLATE_NOOP:2:pass-qt-format --flag=tr:1:pass-qt-format
--keyword=_ --flag=_:1:pass-c-format --flag=_:1:pass-qt-format --keyword=translate:2,3c
--keyword=N_ --flag=N_:1:pass-c-format --flag=N_:1:pass-qt-format --keyword=translate:2
--from-code=utf-8 --flag=translate:2:pass-c-format
) --flag=translate:2:pass-qt-format
--keyword=QT_TR_NOOP
--flag=QT_TR_NOOP:1:pass-c-format
--flag=QT_TR_NOOP:1:pass-qt-format
--keyword=QT_TRANSLATE_NOOP:2
--flag=QT_TRANSLATE_NOOP:2:pass-c-format
--flag=QT_TRANSLATE_NOOP:2:pass-qt-format
--keyword=_
--flag=_:1:pass-c-format
--flag=_:1:pass-qt-format
--keyword=N_
--flag=N_:1:pass-c-format
--flag=N_:1:pass-qt-format
--from-code=utf-8
)
execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/translations) execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/translations)
@ -32,7 +44,7 @@ macro(add_pot outfiles header pot)
add_custom_command( add_custom_command(
OUTPUT ${pot} OUTPUT ${pot}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND ${GETTEXT_XGETTEXT_EXECUTABLE} ${XGETTEXT_OPTIONS} -s -C --omit-header --output=${CMAKE_CURRENT_BINARY_DIR}/pot.temp ${add_pot_sources} COMMAND xgettext ${XGETTEXT_OPTIONS} -s -C --omit-header --output="${CMAKE_CURRENT_BINARY_DIR}/pot.temp" ${add_pot_sources}
COMMAND cat ${header} ${CMAKE_CURRENT_BINARY_DIR}/pot.temp > ${pot} COMMAND cat ${header} ${CMAKE_CURRENT_BINARY_DIR}/pot.temp > ${pot}
DEPENDS ${add_pot_sources} ${header} DEPENDS ${add_pot_sources} ${header}
) )

View File

@ -3,7 +3,7 @@ set(STRAWBERRY_VERSION_MINOR 0)
set(STRAWBERRY_VERSION_PATCH 23) set(STRAWBERRY_VERSION_PATCH 23)
#set(STRAWBERRY_VERSION_PRERELEASE rc1) #set(STRAWBERRY_VERSION_PRERELEASE rc1)
set(INCLUDE_GIT_REVISION OFF) set(INCLUDE_GIT_REVISION ON)
set(majorminorpatch "${STRAWBERRY_VERSION_MAJOR}.${STRAWBERRY_VERSION_MINOR}.${STRAWBERRY_VERSION_PATCH}") set(majorminorpatch "${STRAWBERRY_VERSION_MAJOR}.${STRAWBERRY_VERSION_MINOR}.${STRAWBERRY_VERSION_PATCH}")

View File

@ -42,5 +42,6 @@
<file>pictures/star-off.png</file> <file>pictures/star-off.png</file>
<file>mood/sample.mood</file> <file>mood/sample.mood</file>
<file>text/ghosts.txt</file> <file>text/ghosts.txt</file>
<file>pictures/sidebar-background.png</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -290,8 +290,8 @@
<file>icons/48x48/moodbar.png</file> <file>icons/48x48/moodbar.png</file>
<file>icons/48x48/love.png</file> <file>icons/48x48/love.png</file>
<file>icons/48x48/subsonic.png</file> <file>icons/48x48/subsonic.png</file>
<file>icons/48x48/tidal.png</file> <file>icons/48x48/tidal.png</file>
<file>icons/48x48/qobuz.png</file> <file>icons/48x48/qobuz.png</file>
<file>icons/48x48/multimedia-player-ipod-standard-black.png</file> <file>icons/48x48/multimedia-player-ipod-standard-black.png</file>
<file>icons/48x48/radio.png</file> <file>icons/48x48/radio.png</file>
<file>icons/48x48/somafm.png</file> <file>icons/48x48/somafm.png</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -35,32 +35,32 @@
background-color: %palette-base; background-color: %palette-base;
} }
QToolButton { QToolButton[accessibleName="MenuPopupToolButton"] {
border: 2px solid transparent; border: 2px solid transparent;
border-radius: 3px; border-radius: 3px;
padding: 1px; padding: 1px;
} }
QToolButton:hover { QToolButton:hover[accessibleName="MenuPopupToolButton"] {
border: 2px solid %palette-highlight; border: 2px solid %palette-highlight;
background-color: %palette-highlight-lighter; background-color: %palette-highlight-lighter;
} }
QToolButton:pressed { QToolButton:pressed[accessibleName="MenuPopupToolButton"] {
border: 2px solid %palette-highlight-darker; border: 2px solid %palette-highlight-darker;
background-color: %palette-highlight-lighter; background-color: %palette-highlight-lighter;
} }
QToolButton[popupMode="MenuButtonPopup"], QToolButton:hover[popupMode="MenuButtonPopup"], QToolButton:pressed[popupMode="MenuButtonPopup"] { QToolButton[popupMode="MenuButtonPopup"][accessibleName="MenuPopupToolButton"], QToolButton:hover[popupMode="MenuButtonPopup"][accessibleName="MenuPopupToolButton"], QToolButton:pressed[popupMode="MenuButtonPopup"][accessibleName="MenuPopupToolButton"] {
padding-right: 16px; padding-right: 16px;
} }
/* For backwards compatibility with Qt 5 as it does not support property name */ /* For backwards compatibility with Qt 5 as it does not support property name */
QToolButton[popupMode="1"], QToolButton:hover[popupMode="1"], QToolButton:pressed[popupMode="1"] { QToolButton[popupMode="1"][accessibleName="MenuPopupToolButton"], QToolButton:hover[popupMode="1"][accessibleName="MenuPopupToolButton"], QToolButton:pressed[popupMode="1"][accessibleName="MenuPopupToolButton"] {
padding-right: 16px; padding-right: 16px;
} }
QToolButton::menu-button { QToolButton::menu-button[accessibleName="MenuPopupToolButton"] {
width: 16px; width: 16px;
border: none; border: none;
} }

2
debian/control.in vendored
View File

@ -53,7 +53,7 @@ Description: music player and music collection organizer
- Edit tags on audio files - Edit tags on audio files
- Automatically retrieve tags from MusicBrainz - Automatically retrieve tags from MusicBrainz
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify - Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
- Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com, elyrics.net and lyricsmode.com - Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com and elyrics.net
- Audio analyzer - Audio analyzer
- Audio equalizer - Audio equalizer
- Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic - Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic

5
dist/CMakeLists.txt vendored
View File

@ -4,6 +4,11 @@ if(RPM_DISTRO AND RPM_DATE)
endif(RPM_DISTRO AND RPM_DATE) endif(RPM_DISTRO AND RPM_DATE)
if(APPLE) if(APPLE)
if(DEFINED ENV{MACOSX_DEPLOYMENT_TARGET})
set(LSMinimumSystemVersion $ENV{MACOSX_DEPLOYMENT_TARGET})
else()
set(LSMinimumSystemVersion 12.0)
endif()
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist.in ${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist.in ${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist)
endif(APPLE) endif(APPLE)

View File

@ -33,7 +33,7 @@
<key>LSApplicationCategoryType</key> <key>LSApplicationCategoryType</key>
<string>public.app-category.music</string> <string>public.app-category.music</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>11.0</string> <string>@LSMinimumSystemVersion@</string>
<key>SUFeedURL</key> <key>SUFeedURL</key>
<string>https://www.strawberrymusicplayer.org/sparkle-macos</string> <string>https://www.strawberrymusicplayer.org/sparkle-macos</string>
<key>SUPublicEDKey</key> <key>SUPublicEDKey</key>

View File

@ -62,6 +62,7 @@ cp -v -f "${GST_PLUGIN_SCANNER}" "${bundledir}/Contents/PlugIns/" || exit 1
install_name_tool -add_rpath "@loader_path/../Frameworks" "${bundledir}/Contents/PlugIns/$(basename ${GST_PLUGIN_SCANNER})" || exit 1 install_name_tool -add_rpath "@loader_path/../Frameworks" "${bundledir}/Contents/PlugIns/$(basename ${GST_PLUGIN_SCANNER})" || exit 1
gst_plugins=" gst_plugins="
libgstadaptivedemux2
libgstaes libgstaes
libgstaiff libgstaiff
libgstapetag libgstapetag
@ -70,16 +71,14 @@ libgstasf
libgstasfmux libgstasfmux
libgstaudioconvert libgstaudioconvert
libgstaudiofx libgstaudiofx
libgstaudiomixer
libgstaudioparsers libgstaudioparsers
libgstaudiorate
libgstaudioresample libgstaudioresample
libgstaudiotestsrc
libgstautodetect libgstautodetect
libgstbs2b libgstbs2b
libgstcdio libgstcdio
libgstcoreelements libgstcoreelements
libgstdash libgstdash
libgstdsd
libgstequalizer libgstequalizer
libgstfaac libgstfaac
libgstfaad libgstfaad
@ -92,6 +91,10 @@ libgstid3demux
libgstid3tag libgstid3tag
libgstisomp4 libgstisomp4
libgstlame libgstlame
libgstmpegpsdemux
libgstmpegpsmux
libgstmpegtsdemux
libgstmpegtsmux
libgstlibav libgstlibav
libgstmpg123 libgstmpg123
libgstmusepack libgstmusepack

View File

@ -29,7 +29,7 @@
<li>Edit tags on audio files</li> <li>Edit tags on audio files</li>
<li>Automatically retrieve tags from MusicBrainz</li> <li>Automatically retrieve tags from MusicBrainz</li>
<li>Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify</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, elyrics.net and lyricsmode.com</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>Support for multiple backends</li>
<li>Audio analyzer and equalizer</li> <li>Audio analyzer and equalizer</li>
<li>Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic</li> <li>Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic</li>

View File

@ -3,8 +3,10 @@ Version=1.0
Type=Application Type=Application
Name=Strawberry Name=Strawberry
GenericName=Strawberry Music Player GenericName=Strawberry Music Player
GenericName[fr]=Lecteur de musique Strawberry
GenericName[ru]=Музыкальный проигрыватель Strawberry GenericName[ru]=Музыкальный проигрыватель Strawberry
Comment=Plays music Comment=Plays music
Comment[fr]=Joue de la musique
Comment[ru]=Прослушивание музыки Comment[ru]=Прослушивание музыки
Exec=strawberry %U Exec=strawberry %U
TryExec=strawberry TryExec=strawberry

View File

@ -99,7 +99,7 @@ Features:
- Edit tags on audio files - Edit tags on audio files
- Automatically retrieve tags from MusicBrainz - Automatically retrieve tags from MusicBrainz
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify - Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
- Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com, elyrics.net and lyricsmode.com - Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com and elyrics.net
- Support for multiple backends - Support for multiple backends
- Audio analyzer - Audio analyzer
- Audio equalizer - Audio equalizer

View File

@ -109,7 +109,11 @@
Unicode True Unicode True
!ifdef debug
SetCompressor lzma
!else
SetCompressor /SOLID lzma SetCompressor /SOLID lzma
!endif
!include "MUI2.nsh" !include "MUI2.nsh"
!include "FileAssociation.nsh" !include "FileAssociation.nsh"
@ -282,8 +286,10 @@ Section "Strawberry" Strawberry
File "libgstaudio-1.0-0.dll" File "libgstaudio-1.0-0.dll"
File "libgstbadaudio-1.0-0.dll" File "libgstbadaudio-1.0-0.dll"
File "libgstbase-1.0-0.dll" File "libgstbase-1.0-0.dll"
File "libgstcodecparsers-1.0-0.dll"
File "libgstfft-1.0-0.dll" File "libgstfft-1.0-0.dll"
File "libgstisoff-1.0-0.dll" File "libgstisoff-1.0-0.dll"
File "libgstmpegts-1.0-0.dll"
File "libgstnet-1.0-0.dll" File "libgstnet-1.0-0.dll"
File "libgstpbutils-1.0-0.dll" File "libgstpbutils-1.0-0.dll"
File "libgstreamer-1.0-0.dll" File "libgstreamer-1.0-0.dll"
@ -422,8 +428,10 @@ Section "Strawberry" Strawberry
File "gstaudio-1.0-0.dll" File "gstaudio-1.0-0.dll"
File "gstbadaudio-1.0-0.dll" File "gstbadaudio-1.0-0.dll"
File "gstbase-1.0-0.dll" File "gstbase-1.0-0.dll"
File "gstcodecparsers-1.0-0.dll"
File "gstfft-1.0-0.dll" File "gstfft-1.0-0.dll"
File "gstisoff-1.0-0.dll" File "gstisoff-1.0-0.dll"
File "gstmpegts-1.0-0.dll"
File "gstnet-1.0-0.dll" File "gstnet-1.0-0.dll"
File "gstpbutils-1.0-0.dll" File "gstpbutils-1.0-0.dll"
File "gstreamer-1.0-0.dll" File "gstreamer-1.0-0.dll"
@ -493,7 +501,7 @@ Section "Strawberry" Strawberry
; Common files ; Common files
File "icudt74.dll" File "icudt75.dll"
File "libfftw3-3.dll" File "libfftw3-3.dll"
!ifdef debug !ifdef debug
File "libprotobufd.dll" File "libprotobufd.dll"
@ -501,8 +509,8 @@ Section "Strawberry" Strawberry
File "libprotobuf.dll" File "libprotobuf.dll"
!endif !endif
!ifdef msvc && debug !ifdef msvc && debug
File "icuin74d.dll" File "icuin75d.dll"
File "icuuc74d.dll" File "icuuc75d.dll"
File "Qt6Concurrentd.dll" File "Qt6Concurrentd.dll"
File "Qt6Cored.dll" File "Qt6Cored.dll"
File "Qt6Guid.dll" File "Qt6Guid.dll"
@ -510,8 +518,8 @@ Section "Strawberry" Strawberry
File "Qt6Sqld.dll" File "Qt6Sqld.dll"
File "Qt6Widgetsd.dll" File "Qt6Widgetsd.dll"
!else !else
File "icuin74.dll" File "icuin75.dll"
File "icuuc74.dll" File "icuuc75.dll"
File "Qt6Concurrent.dll" File "Qt6Concurrent.dll"
File "Qt6Core.dll" File "Qt6Core.dll"
File "Qt6Gui.dll" File "Qt6Gui.dll"
@ -584,9 +592,9 @@ SectionEnd
Section "Qt styles" styles Section "Qt styles" styles
SetOutPath "$INSTDIR\styles" SetOutPath "$INSTDIR\styles"
!ifdef msvc && debug !ifdef msvc && debug
File "/oname=qwindowsvistastyled.dll" "styles\qwindowsvistastyled.dll" File "/oname=qmodernwindowsstyled.dll" "styles\qmodernwindowsstyled.dll"
!else !else
File "/oname=qwindowsvistastyle.dll" "styles\qwindowsvistastyle.dll" File "/oname=qmodernwindowsstyle.dll" "styles\qmodernwindowsstyle.dll"
!endif !endif
SectionEnd SectionEnd
@ -627,6 +635,7 @@ Section "Gstreamer plugins" gstreamer-plugins
SetOutPath "$INSTDIR\gstreamer-plugins" SetOutPath "$INSTDIR\gstreamer-plugins"
!ifdef mingw !ifdef mingw
File "/oname=libgstadaptivedemux2.dll" "gstreamer-plugins\libgstadaptivedemux2.dll"
File "/oname=libgstaes.dll" "gstreamer-plugins\libgstaes.dll" File "/oname=libgstaes.dll" "gstreamer-plugins\libgstaes.dll"
File "/oname=libgstaiff.dll" "gstreamer-plugins\libgstaiff.dll" File "/oname=libgstaiff.dll" "gstreamer-plugins\libgstaiff.dll"
File "/oname=libgstapetag.dll" "gstreamer-plugins\libgstapetag.dll" File "/oname=libgstapetag.dll" "gstreamer-plugins\libgstapetag.dll"
@ -635,16 +644,14 @@ Section "Gstreamer plugins" gstreamer-plugins
File "/oname=libgstasfmux.dll" "gstreamer-plugins\libgstasfmux.dll" File "/oname=libgstasfmux.dll" "gstreamer-plugins\libgstasfmux.dll"
File "/oname=libgstaudioconvert.dll" "gstreamer-plugins\libgstaudioconvert.dll" File "/oname=libgstaudioconvert.dll" "gstreamer-plugins\libgstaudioconvert.dll"
File "/oname=libgstaudiofx.dll" "gstreamer-plugins\libgstaudiofx.dll" File "/oname=libgstaudiofx.dll" "gstreamer-plugins\libgstaudiofx.dll"
File "/oname=libgstaudiomixer.dll" "gstreamer-plugins\libgstaudiomixer.dll"
File "/oname=libgstaudioparsers.dll" "gstreamer-plugins\libgstaudioparsers.dll" File "/oname=libgstaudioparsers.dll" "gstreamer-plugins\libgstaudioparsers.dll"
File "/oname=libgstaudiorate.dll" "gstreamer-plugins\libgstaudiorate.dll"
File "/oname=libgstaudioresample.dll" "gstreamer-plugins\libgstaudioresample.dll" File "/oname=libgstaudioresample.dll" "gstreamer-plugins\libgstaudioresample.dll"
File "/oname=libgstaudiotestsrc.dll" "gstreamer-plugins\libgstaudiotestsrc.dll"
File "/oname=libgstautodetect.dll" "gstreamer-plugins\libgstautodetect.dll" File "/oname=libgstautodetect.dll" "gstreamer-plugins\libgstautodetect.dll"
File "/oname=libgstbs2b.dll" "gstreamer-plugins\libgstbs2b.dll" File "/oname=libgstbs2b.dll" "gstreamer-plugins\libgstbs2b.dll"
File "/oname=libgstcoreelements.dll" "gstreamer-plugins\libgstcoreelements.dll" File "/oname=libgstcoreelements.dll" "gstreamer-plugins\libgstcoreelements.dll"
File "/oname=libgstdash.dll" "gstreamer-plugins\libgstdash.dll" File "/oname=libgstdash.dll" "gstreamer-plugins\libgstdash.dll"
File "/oname=libgstdirectsound.dll" "gstreamer-plugins\libgstdirectsound.dll" File "/oname=libgstdirectsound.dll" "gstreamer-plugins\libgstdirectsound.dll"
File "/oname=libgstdsd.dll" "gstreamer-plugins\libgstdsd.dll"
File "/oname=libgstequalizer.dll" "gstreamer-plugins\libgstequalizer.dll" File "/oname=libgstequalizer.dll" "gstreamer-plugins\libgstequalizer.dll"
File "/oname=libgstfaac.dll" "gstreamer-plugins\libgstfaac.dll" File "/oname=libgstfaac.dll" "gstreamer-plugins\libgstfaac.dll"
File "/oname=libgstfaad.dll" "gstreamer-plugins\libgstfaad.dll" File "/oname=libgstfaad.dll" "gstreamer-plugins\libgstfaad.dll"
@ -659,6 +666,10 @@ Section "Gstreamer plugins" gstreamer-plugins
File "/oname=libgstisomp4.dll" "gstreamer-plugins\libgstisomp4.dll" File "/oname=libgstisomp4.dll" "gstreamer-plugins\libgstisomp4.dll"
File "/oname=libgstlame.dll" "gstreamer-plugins\libgstlame.dll" File "/oname=libgstlame.dll" "gstreamer-plugins\libgstlame.dll"
File "/oname=libgstlibav.dll" "gstreamer-plugins\libgstlibav.dll" File "/oname=libgstlibav.dll" "gstreamer-plugins\libgstlibav.dll"
File "/oname=libgstmpegpsdemux.dll" "gstreamer-plugins\libgstmpegpsdemux.dll"
File "/oname=libgstmpegpsmux.dll" "gstreamer-plugins\libgstmpegpsmux.dll"
File "/oname=libgstmpegtsdemux.dll" "gstreamer-plugins\libgstmpegtsdemux.dll"
File "/oname=libgstmpegtsmux.dll" "gstreamer-plugins\libgstmpegtsmux.dll"
File "/oname=libgstmpg123.dll" "gstreamer-plugins\libgstmpg123.dll" File "/oname=libgstmpg123.dll" "gstreamer-plugins\libgstmpg123.dll"
File "/oname=libgstmusepack.dll" "gstreamer-plugins\libgstmusepack.dll" File "/oname=libgstmusepack.dll" "gstreamer-plugins\libgstmusepack.dll"
File "/oname=libgstogg.dll" "gstreamer-plugins\libgstogg.dll" File "/oname=libgstogg.dll" "gstreamer-plugins\libgstogg.dll"
@ -681,6 +692,7 @@ Section "Gstreamer plugins" gstreamer-plugins
File "/oname=libgstvolume.dll" "gstreamer-plugins\libgstvolume.dll" File "/oname=libgstvolume.dll" "gstreamer-plugins\libgstvolume.dll"
File "/oname=libgstvorbis.dll" "gstreamer-plugins\libgstvorbis.dll" File "/oname=libgstvorbis.dll" "gstreamer-plugins\libgstvorbis.dll"
File "/oname=libgstwasapi.dll" "gstreamer-plugins\libgstwasapi.dll" File "/oname=libgstwasapi.dll" "gstreamer-plugins\libgstwasapi.dll"
File "/oname=libgstwaveform.dll" "gstreamer-plugins\libgstwaveform.dll"
File "/oname=libgstwavenc.dll" "gstreamer-plugins\libgstwavenc.dll" File "/oname=libgstwavenc.dll" "gstreamer-plugins\libgstwavenc.dll"
File "/oname=libgstwavpack.dll" "gstreamer-plugins\libgstwavpack.dll" File "/oname=libgstwavpack.dll" "gstreamer-plugins\libgstwavpack.dll"
File "/oname=libgstwavparse.dll" "gstreamer-plugins\libgstwavparse.dll" File "/oname=libgstwavparse.dll" "gstreamer-plugins\libgstwavparse.dll"
@ -688,24 +700,24 @@ Section "Gstreamer plugins" gstreamer-plugins
!endif ; MinGW !endif ; MinGW
!ifdef msvc !ifdef msvc
File "/oname=gstadaptivedemux2.dll" "gstreamer-plugins\gstadaptivedemux2.dll"
File "/oname=gstaes.dll" "gstreamer-plugins\gstaes.dll" File "/oname=gstaes.dll" "gstreamer-plugins\gstaes.dll"
File "/oname=gstaiff.dll" "gstreamer-plugins\gstaiff.dll" File "/oname=gstaiff.dll" "gstreamer-plugins\gstaiff.dll"
File "/oname=gstapetag.dll" "gstreamer-plugins\gstapetag.dll" File "/oname=gstapetag.dll" "gstreamer-plugins\gstapetag.dll"
File "/oname=gstapp.dll" "gstreamer-plugins\gstapp.dll" File "/oname=gstapp.dll" "gstreamer-plugins\gstapp.dll"
File "/oname=gstasf.dll" "gstreamer-plugins\gstasf.dll" File "/oname=gstasf.dll" "gstreamer-plugins\gstasf.dll"
File "/oname=gstasfmux.dll" "gstreamer-plugins\gstasfmux.dll" File "/oname=gstasfmux.dll" "gstreamer-plugins\gstasfmux.dll"
File "/oname=gstasio.dll" "gstreamer-plugins\gstasio.dll"
File "/oname=gstaudioconvert.dll" "gstreamer-plugins\gstaudioconvert.dll" File "/oname=gstaudioconvert.dll" "gstreamer-plugins\gstaudioconvert.dll"
File "/oname=gstaudiofx.dll" "gstreamer-plugins\gstaudiofx.dll" File "/oname=gstaudiofx.dll" "gstreamer-plugins\gstaudiofx.dll"
File "/oname=gstaudiomixer.dll" "gstreamer-plugins\gstaudiomixer.dll"
File "/oname=gstaudioparsers.dll" "gstreamer-plugins\gstaudioparsers.dll" File "/oname=gstaudioparsers.dll" "gstreamer-plugins\gstaudioparsers.dll"
File "/oname=gstaudiorate.dll" "gstreamer-plugins\gstaudiorate.dll"
File "/oname=gstaudioresample.dll" "gstreamer-plugins\gstaudioresample.dll" File "/oname=gstaudioresample.dll" "gstreamer-plugins\gstaudioresample.dll"
File "/oname=gstaudiotestsrc.dll" "gstreamer-plugins\gstaudiotestsrc.dll"
File "/oname=gstautodetect.dll" "gstreamer-plugins\gstautodetect.dll" File "/oname=gstautodetect.dll" "gstreamer-plugins\gstautodetect.dll"
File "/oname=gstbs2b.dll" "gstreamer-plugins\gstbs2b.dll" File "/oname=gstbs2b.dll" "gstreamer-plugins\gstbs2b.dll"
File "/oname=gstcoreelements.dll" "gstreamer-plugins\gstcoreelements.dll" File "/oname=gstcoreelements.dll" "gstreamer-plugins\gstcoreelements.dll"
File "/oname=gstdash.dll" "gstreamer-plugins\gstdash.dll" File "/oname=gstdash.dll" "gstreamer-plugins\gstdash.dll"
File "/oname=gstdirectsound.dll" "gstreamer-plugins\gstdirectsound.dll" File "/oname=gstdirectsound.dll" "gstreamer-plugins\gstdirectsound.dll"
File "/oname=gstdsd.dll" "gstreamer-plugins\gstdsd.dll"
File "/oname=gstequalizer.dll" "gstreamer-plugins\gstequalizer.dll" File "/oname=gstequalizer.dll" "gstreamer-plugins\gstequalizer.dll"
File "/oname=gstfaac.dll" "gstreamer-plugins\gstfaac.dll" File "/oname=gstfaac.dll" "gstreamer-plugins\gstfaac.dll"
File "/oname=gstfaad.dll" "gstreamer-plugins\gstfaad.dll" File "/oname=gstfaad.dll" "gstreamer-plugins\gstfaad.dll"
@ -720,6 +732,10 @@ Section "Gstreamer plugins" gstreamer-plugins
File "/oname=gstisomp4.dll" "gstreamer-plugins\gstisomp4.dll" File "/oname=gstisomp4.dll" "gstreamer-plugins\gstisomp4.dll"
File "/oname=gstlame.dll" "gstreamer-plugins\gstlame.dll" File "/oname=gstlame.dll" "gstreamer-plugins\gstlame.dll"
File "/oname=gstlibav.dll" "gstreamer-plugins\gstlibav.dll" File "/oname=gstlibav.dll" "gstreamer-plugins\gstlibav.dll"
File "/oname=gstmpegpsdemux.dll" "gstreamer-plugins\gstmpegpsdemux.dll"
File "/oname=gstmpegpsmux.dll" "gstreamer-plugins\gstmpegpsmux.dll"
File "/oname=gstmpegtsdemux.dll" "gstreamer-plugins\gstmpegtsdemux.dll"
File "/oname=gstmpegtsmux.dll" "gstreamer-plugins\gstmpegtsmux.dll"
File "/oname=gstmpg123.dll" "gstreamer-plugins\gstmpg123.dll" File "/oname=gstmpg123.dll" "gstreamer-plugins\gstmpg123.dll"
File "/oname=gstmusepack.dll" "gstreamer-plugins\gstmusepack.dll" File "/oname=gstmusepack.dll" "gstreamer-plugins\gstmusepack.dll"
File "/oname=gstogg.dll" "gstreamer-plugins\gstogg.dll" File "/oname=gstogg.dll" "gstreamer-plugins\gstogg.dll"
@ -742,8 +758,8 @@ Section "Gstreamer plugins" gstreamer-plugins
File "/oname=gstvolume.dll" "gstreamer-plugins\gstvolume.dll" File "/oname=gstvolume.dll" "gstreamer-plugins\gstvolume.dll"
File "/oname=gstvorbis.dll" "gstreamer-plugins\gstvorbis.dll" File "/oname=gstvorbis.dll" "gstreamer-plugins\gstvorbis.dll"
File "/oname=gstwasapi.dll" "gstreamer-plugins\gstwasapi.dll" File "/oname=gstwasapi.dll" "gstreamer-plugins\gstwasapi.dll"
; Disable wasapi2 until issue (https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/2870) is fixed. File "/oname=gstwasapi2.dll" "gstreamer-plugins\gstwasapi2.dll"
;File "/oname=gstwasapi2.dll" "gstreamer-plugins\gstwasapi2.dll" File "/oname=gstwaveform.dll" "gstreamer-plugins\gstwaveform.dll"
File "/oname=gstwavenc.dll" "gstreamer-plugins\gstwavenc.dll" File "/oname=gstwavenc.dll" "gstreamer-plugins\gstwavenc.dll"
File "/oname=gstwavpack.dll" "gstreamer-plugins\gstwavpack.dll" File "/oname=gstwavpack.dll" "gstreamer-plugins\gstwavpack.dll"
File "/oname=gstwavparse.dll" "gstreamer-plugins\gstwavparse.dll" File "/oname=gstwavparse.dll" "gstreamer-plugins\gstwavparse.dll"
@ -839,8 +855,10 @@ Section "Uninstall"
Delete "$INSTDIR\libgstaudio-1.0-0.dll" Delete "$INSTDIR\libgstaudio-1.0-0.dll"
Delete "$INSTDIR\libgstbadaudio-1.0-0.dll" Delete "$INSTDIR\libgstbadaudio-1.0-0.dll"
Delete "$INSTDIR\libgstbase-1.0-0.dll" Delete "$INSTDIR\libgstbase-1.0-0.dll"
Delete "$INSTDIR\libgstcodecparsers-1.0-0.dll"
Delete "$INSTDIR\libgstfft-1.0-0.dll" Delete "$INSTDIR\libgstfft-1.0-0.dll"
Delete "$INSTDIR\libgstisoff-1.0-0.dll" Delete "$INSTDIR\libgstisoff-1.0-0.dll"
Delete "$INSTDIR\libgstmpegts-1.0-0.dll"
Delete "$INSTDIR\libgstnet-1.0-0.dll" Delete "$INSTDIR\libgstnet-1.0-0.dll"
Delete "$INSTDIR\libgstpbutils-1.0-0.dll" Delete "$INSTDIR\libgstpbutils-1.0-0.dll"
Delete "$INSTDIR\libgstreamer-1.0-0.dll" Delete "$INSTDIR\libgstreamer-1.0-0.dll"
@ -979,8 +997,10 @@ Section "Uninstall"
Delete "$INSTDIR\gstaudio-1.0-0.dll" Delete "$INSTDIR\gstaudio-1.0-0.dll"
Delete "$INSTDIR\gstbadaudio-1.0-0.dll" Delete "$INSTDIR\gstbadaudio-1.0-0.dll"
Delete "$INSTDIR\gstbase-1.0-0.dll" Delete "$INSTDIR\gstbase-1.0-0.dll"
Delete "$INSTDIR\gstcodecparsers-1.0-0.dll"
Delete "$INSTDIR\gstfft-1.0-0.dll" Delete "$INSTDIR\gstfft-1.0-0.dll"
Delete "$INSTDIR\gstisoff-1.0-0.dll" Delete "$INSTDIR\gstisoff-1.0-0.dll"
Delete "$INSTDIR\gstmpegts-1.0-0.dll"
Delete "$INSTDIR\gstnet-1.0-0.dll" Delete "$INSTDIR\gstnet-1.0-0.dll"
Delete "$INSTDIR\gstpbutils-1.0-0.dll" Delete "$INSTDIR\gstpbutils-1.0-0.dll"
Delete "$INSTDIR\gstreamer-1.0-0.dll" Delete "$INSTDIR\gstreamer-1.0-0.dll"
@ -1049,7 +1069,7 @@ Section "Uninstall"
; Common files ; Common files
Delete "$INSTDIR\icudt74.dll" Delete "$INSTDIR\icudt75.dll"
Delete "$INSTDIR\libfftw3-3.dll" Delete "$INSTDIR\libfftw3-3.dll"
!ifdef debug !ifdef debug
Delete "$INSTDIR\libprotobufd.dll" Delete "$INSTDIR\libprotobufd.dll"
@ -1057,8 +1077,8 @@ Section "Uninstall"
Delete "$INSTDIR\libprotobuf.dll" Delete "$INSTDIR\libprotobuf.dll"
!endif !endif
!ifdef msvc && debug !ifdef msvc && debug
Delete "$INSTDIR\icuin74d.dll" Delete "$INSTDIR\icuin75d.dll"
Delete "$INSTDIR\icuuc74d.dll" Delete "$INSTDIR\icuuc75d.dll"
Delete "$INSTDIR\Qt6Concurrentd.dll" Delete "$INSTDIR\Qt6Concurrentd.dll"
Delete "$INSTDIR\Qt6Cored.dll" Delete "$INSTDIR\Qt6Cored.dll"
Delete "$INSTDIR\Qt6Guid.dll" Delete "$INSTDIR\Qt6Guid.dll"
@ -1066,8 +1086,8 @@ Section "Uninstall"
Delete "$INSTDIR\Qt6Sqld.dll" Delete "$INSTDIR\Qt6Sqld.dll"
Delete "$INSTDIR\Qt6Widgetsd.dll" Delete "$INSTDIR\Qt6Widgetsd.dll"
!else !else
Delete "$INSTDIR\icuin74.dll" Delete "$INSTDIR\icuin75.dll"
Delete "$INSTDIR\icuuc74.dll" Delete "$INSTDIR\icuuc75.dll"
Delete "$INSTDIR\Qt6Concurrent.dll" Delete "$INSTDIR\Qt6Concurrent.dll"
Delete "$INSTDIR\Qt6Core.dll" Delete "$INSTDIR\Qt6Core.dll"
Delete "$INSTDIR\Qt6Gui.dll" Delete "$INSTDIR\Qt6Gui.dll"
@ -1095,7 +1115,7 @@ Section "Uninstall"
!ifdef msvc && debug !ifdef msvc && debug
Delete "$INSTDIR\platforms\qwindowsd.dll" Delete "$INSTDIR\platforms\qwindowsd.dll"
Delete "$INSTDIR\styles\qwindowsvistastyled.dll" Delete "$INSTDIR\styles\qmodernwindowsstyled.dll"
Delete "$INSTDIR\tls\qschannelbackendd.dll" Delete "$INSTDIR\tls\qschannelbackendd.dll"
Delete "$INSTDIR\tls\qopensslbackendd.dll" Delete "$INSTDIR\tls\qopensslbackendd.dll"
Delete "$INSTDIR\sqldrivers\qsqlited.dll" Delete "$INSTDIR\sqldrivers\qsqlited.dll"
@ -1104,7 +1124,7 @@ Section "Uninstall"
Delete "$INSTDIR\imageformats\qjpegd.dll" Delete "$INSTDIR\imageformats\qjpegd.dll"
!else !else
Delete "$INSTDIR\platforms\qwindows.dll" Delete "$INSTDIR\platforms\qwindows.dll"
Delete "$INSTDIR\styles\qwindowsvistastyle.dll" Delete "$INSTDIR\styles\qmodernwindowsstyle.dll"
Delete "$INSTDIR\tls\qschannelbackend.dll" Delete "$INSTDIR\tls\qschannelbackend.dll"
Delete "$INSTDIR\tls\qopensslbackend.dll" Delete "$INSTDIR\tls\qopensslbackend.dll"
Delete "$INSTDIR\sqldrivers\qsqlite.dll" Delete "$INSTDIR\sqldrivers\qsqlite.dll"
@ -1116,6 +1136,7 @@ Section "Uninstall"
; MinGW GStreamer plugins ; MinGW GStreamer plugins
!ifdef mingw !ifdef mingw
Delete "$INSTDIR\gstreamer-plugins\libgstadaptivedemux2.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaes.dll" Delete "$INSTDIR\gstreamer-plugins\libgstaes.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaiff.dll" Delete "$INSTDIR\gstreamer-plugins\libgstaiff.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstapetag.dll" Delete "$INSTDIR\gstreamer-plugins\libgstapetag.dll"
@ -1124,16 +1145,14 @@ Section "Uninstall"
Delete "$INSTDIR\gstreamer-plugins\libgstasfmux.dll" Delete "$INSTDIR\gstreamer-plugins\libgstasfmux.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudioconvert.dll" Delete "$INSTDIR\gstreamer-plugins\libgstaudioconvert.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudiofx.dll" Delete "$INSTDIR\gstreamer-plugins\libgstaudiofx.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudiomixer.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudioparsers.dll" Delete "$INSTDIR\gstreamer-plugins\libgstaudioparsers.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudiorate.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudioresample.dll" Delete "$INSTDIR\gstreamer-plugins\libgstaudioresample.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudiotestsrc.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstautodetect.dll" Delete "$INSTDIR\gstreamer-plugins\libgstautodetect.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstbs2b.dll" Delete "$INSTDIR\gstreamer-plugins\libgstbs2b.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstcoreelements.dll" Delete "$INSTDIR\gstreamer-plugins\libgstcoreelements.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstdash.dll" Delete "$INSTDIR\gstreamer-plugins\libgstdash.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstdirectsound.dll" Delete "$INSTDIR\gstreamer-plugins\libgstdirectsound.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstdsd.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstequalizer.dll" Delete "$INSTDIR\gstreamer-plugins\libgstequalizer.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstfaac.dll" Delete "$INSTDIR\gstreamer-plugins\libgstfaac.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstfaad.dll" Delete "$INSTDIR\gstreamer-plugins\libgstfaad.dll"
@ -1148,6 +1167,10 @@ Section "Uninstall"
Delete "$INSTDIR\gstreamer-plugins\libgstisomp4.dll" Delete "$INSTDIR\gstreamer-plugins\libgstisomp4.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstlame.dll" Delete "$INSTDIR\gstreamer-plugins\libgstlame.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstlibav.dll" Delete "$INSTDIR\gstreamer-plugins\libgstlibav.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstmpegpsdemux.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstmpegpsmux.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstmpegtsdemux.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstmpegtsmux.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstmpg123.dll" Delete "$INSTDIR\gstreamer-plugins\libgstmpg123.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstmusepack.dll" Delete "$INSTDIR\gstreamer-plugins\libgstmusepack.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstogg.dll" Delete "$INSTDIR\gstreamer-plugins\libgstogg.dll"
@ -1170,6 +1193,7 @@ Section "Uninstall"
Delete "$INSTDIR\gstreamer-plugins\libgstvolume.dll" Delete "$INSTDIR\gstreamer-plugins\libgstvolume.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstvorbis.dll" Delete "$INSTDIR\gstreamer-plugins\libgstvorbis.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstwasapi.dll" Delete "$INSTDIR\gstreamer-plugins\libgstwasapi.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstwaveform.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstwavenc.dll" Delete "$INSTDIR\gstreamer-plugins\libgstwavenc.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstwavpack.dll" Delete "$INSTDIR\gstreamer-plugins\libgstwavpack.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstwavparse.dll" Delete "$INSTDIR\gstreamer-plugins\libgstwavparse.dll"
@ -1179,24 +1203,24 @@ Section "Uninstall"
; MSVC GStreamer plugins ; MSVC GStreamer plugins
!ifdef msvc !ifdef msvc
Delete "$INSTDIR\gstreamer-plugins\gstadaptivedemux2.dll"
Delete "$INSTDIR\gstreamer-plugins\gstaes.dll" Delete "$INSTDIR\gstreamer-plugins\gstaes.dll"
Delete "$INSTDIR\gstreamer-plugins\gstaiff.dll" Delete "$INSTDIR\gstreamer-plugins\gstaiff.dll"
Delete "$INSTDIR\gstreamer-plugins\gstapetag.dll" Delete "$INSTDIR\gstreamer-plugins\gstapetag.dll"
Delete "$INSTDIR\gstreamer-plugins\gstapp.dll" Delete "$INSTDIR\gstreamer-plugins\gstapp.dll"
Delete "$INSTDIR\gstreamer-plugins\gstasf.dll" Delete "$INSTDIR\gstreamer-plugins\gstasf.dll"
Delete "$INSTDIR\gstreamer-plugins\gstasfmux.dll" Delete "$INSTDIR\gstreamer-plugins\gstasfmux.dll"
Delete "$INSTDIR\gstreamer-plugins\gstasio.dll"
Delete "$INSTDIR\gstreamer-plugins\gstaudioconvert.dll" Delete "$INSTDIR\gstreamer-plugins\gstaudioconvert.dll"
Delete "$INSTDIR\gstreamer-plugins\gstaudiofx.dll" Delete "$INSTDIR\gstreamer-plugins\gstaudiofx.dll"
Delete "$INSTDIR\gstreamer-plugins\gstaudiomixer.dll"
Delete "$INSTDIR\gstreamer-plugins\gstaudioparsers.dll" Delete "$INSTDIR\gstreamer-plugins\gstaudioparsers.dll"
Delete "$INSTDIR\gstreamer-plugins\gstaudiorate.dll"
Delete "$INSTDIR\gstreamer-plugins\gstaudioresample.dll" Delete "$INSTDIR\gstreamer-plugins\gstaudioresample.dll"
Delete "$INSTDIR\gstreamer-plugins\gstaudiotestsrc.dll"
Delete "$INSTDIR\gstreamer-plugins\gstautodetect.dll" Delete "$INSTDIR\gstreamer-plugins\gstautodetect.dll"
Delete "$INSTDIR\gstreamer-plugins\gstbs2b.dll" Delete "$INSTDIR\gstreamer-plugins\gstbs2b.dll"
Delete "$INSTDIR\gstreamer-plugins\gstcoreelements.dll" Delete "$INSTDIR\gstreamer-plugins\gstcoreelements.dll"
Delete "$INSTDIR\gstreamer-plugins\gstdash.dll" Delete "$INSTDIR\gstreamer-plugins\gstdash.dll"
Delete "$INSTDIR\gstreamer-plugins\gstdirectsound.dll" Delete "$INSTDIR\gstreamer-plugins\gstdirectsound.dll"
Delete "$INSTDIR\gstreamer-plugins\gstdsd.dll"
Delete "$INSTDIR\gstreamer-plugins\gstequalizer.dll" Delete "$INSTDIR\gstreamer-plugins\gstequalizer.dll"
Delete "$INSTDIR\gstreamer-plugins\gstfaac.dll" Delete "$INSTDIR\gstreamer-plugins\gstfaac.dll"
Delete "$INSTDIR\gstreamer-plugins\gstfaad.dll" Delete "$INSTDIR\gstreamer-plugins\gstfaad.dll"
@ -1211,6 +1235,10 @@ Section "Uninstall"
Delete "$INSTDIR\gstreamer-plugins\gstisomp4.dll" Delete "$INSTDIR\gstreamer-plugins\gstisomp4.dll"
Delete "$INSTDIR\gstreamer-plugins\gstlame.dll" Delete "$INSTDIR\gstreamer-plugins\gstlame.dll"
Delete "$INSTDIR\gstreamer-plugins\gstlibav.dll" Delete "$INSTDIR\gstreamer-plugins\gstlibav.dll"
Delete "$INSTDIR\gstreamer-plugins\gstmpegpsdemux.dll"
Delete "$INSTDIR\gstreamer-plugins\gstmpegpsmux.dll"
Delete "$INSTDIR\gstreamer-plugins\gstmpegtsdemux.dll"
Delete "$INSTDIR\gstreamer-plugins\gstmpegtsmux.dll"
Delete "$INSTDIR\gstreamer-plugins\gstmpg123.dll" Delete "$INSTDIR\gstreamer-plugins\gstmpg123.dll"
Delete "$INSTDIR\gstreamer-plugins\gstmusepack.dll" Delete "$INSTDIR\gstreamer-plugins\gstmusepack.dll"
Delete "$INSTDIR\gstreamer-plugins\gstogg.dll" Delete "$INSTDIR\gstreamer-plugins\gstogg.dll"
@ -1234,6 +1262,7 @@ Section "Uninstall"
Delete "$INSTDIR\gstreamer-plugins\gstvorbis.dll" Delete "$INSTDIR\gstreamer-plugins\gstvorbis.dll"
Delete "$INSTDIR\gstreamer-plugins\gstwasapi.dll" Delete "$INSTDIR\gstreamer-plugins\gstwasapi.dll"
Delete "$INSTDIR\gstreamer-plugins\gstwasapi2.dll" Delete "$INSTDIR\gstreamer-plugins\gstwasapi2.dll"
Delete "$INSTDIR\gstreamer-plugins\gstwaveform.dll"
Delete "$INSTDIR\gstreamer-plugins\gstwavenc.dll" Delete "$INSTDIR\gstreamer-plugins\gstwavenc.dll"
Delete "$INSTDIR\gstreamer-plugins\gstwavpack.dll" Delete "$INSTDIR\gstreamer-plugins\gstwavpack.dll"
Delete "$INSTDIR\gstreamer-plugins\gstwavparse.dll" Delete "$INSTDIR\gstreamer-plugins\gstwavparse.dll"

View File

@ -67,8 +67,8 @@ static QIODevice *sNullDevice = nullptr;
const char *kDefaultLogLevels = "*:3"; const char *kDefaultLogLevels = "*:3";
static const char *kMessageHandlerMagic = "__logging_message__"; static constexpr char kMessageHandlerMagic[] = "__logging_message__";
static const size_t kMessageHandlerMagicLength = strlen(kMessageHandlerMagic); static const size_t kMessageHandlerMagicLen = strlen(kMessageHandlerMagic);
static QtMessageHandler sOriginalMessageHandler = nullptr; static QtMessageHandler sOriginalMessageHandler = nullptr;
template<class T> template<class T>
@ -135,9 +135,9 @@ class LoggedDebug : public DebugBase<LoggedDebug> {
static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QString &message) { static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QString &message) {
if (message.startsWith(kMessageHandlerMagic)) { if (message.startsWith(QLatin1String(kMessageHandlerMagic))) {
QByteArray message_data = message.toUtf8(); QByteArray message_data = message.toUtf8();
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", message_data.constData() + kMessageHandlerMagicLength); fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", message_data.constData() + kMessageHandlerMagicLen);
fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout); fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout);
return; return;
} }
@ -157,8 +157,8 @@ static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QStr
break; break;
} }
for (const QString &line : message.split('\n')) { for (const QString &line : message.split(QLatin1Char('\n'))) {
BufferedDebug d = CreateLogger<BufferedDebug>(level, "unknown", -1, nullptr); BufferedDebug d = CreateLogger<BufferedDebug>(level, QStringLiteral("unknown"), -1, nullptr);
d << line.toLocal8Bit().constData(); d << line.toLocal8Bit().constData();
if (d.buf_) { if (d.buf_) {
d.buf_->close(); d.buf_->close();
@ -193,8 +193,8 @@ void SetLevels(const QString &levels) {
if (!sClassLevels) return; if (!sClassLevels) return;
for (const QString &item : levels.split(',')) { for (const QString &item : levels.split(QLatin1Char(','))) {
const QStringList class_level = item.split(':'); const QStringList class_level = item.split(QLatin1Char(':'));
QString class_name; QString class_name;
bool ok = false; bool ok = false;
@ -212,7 +212,7 @@ void SetLevels(const QString &levels) {
continue; continue;
} }
if (class_name.isEmpty() || class_name == "*") { if (class_name.isEmpty() || class_name == QStringLiteral("*")) {
sDefaultLevel = static_cast<Level>(level); sDefaultLevel = static_cast<Level>(level);
} }
else { else {
@ -225,10 +225,10 @@ void SetLevels(const QString &levels) {
static QString ParsePrettyFunction(const char *pretty_function) { static QString ParsePrettyFunction(const char *pretty_function) {
// Get the class name out of the function name. // Get the class name out of the function name.
QString class_name = pretty_function; QString class_name = QLatin1String(pretty_function);
const qint64 paren = class_name.indexOf('('); const qint64 paren = class_name.indexOf(QLatin1Char('('));
if (paren != -1) { if (paren != -1) {
const qint64 colons = class_name.lastIndexOf("::", paren); const qint64 colons = class_name.lastIndexOf(QLatin1String("::"), paren);
if (colons != -1) { if (colons != -1) {
class_name = class_name.left(colons); class_name = class_name.left(colons);
} }
@ -237,7 +237,7 @@ static QString ParsePrettyFunction(const char *pretty_function) {
} }
} }
const qint64 space = class_name.lastIndexOf(' '); const qint64 space = class_name.lastIndexOf(QLatin1Char(' '));
if (space != -1) { if (space != -1) {
class_name = class_name.mid(space + 1); class_name = class_name.mid(space + 1);
} }
@ -259,7 +259,7 @@ static T CreateLogger(Level level, const QString &class_name, int line, const ch
case Level_Fatal: level_name = " FATAL "; break; case Level_Fatal: level_name = " FATAL "; break;
} }
QString filter_category = (category != nullptr) ? category : class_name; QString filter_category = (category != nullptr) ? QLatin1String(category) : class_name;
// Check the settings to see if we're meant to show or hide this message. // Check the settings to see if we're meant to show or hide this message.
Level threshold_level = sDefaultLevel; Level threshold_level = sDefaultLevel;
if (sClassLevels && sClassLevels->contains(filter_category)) { if (sClassLevels && sClassLevels->contains(filter_category)) {
@ -272,10 +272,10 @@ static T CreateLogger(Level level, const QString &class_name, int line, const ch
QString function_line = class_name; QString function_line = class_name;
if (line != -1) { if (line != -1) {
function_line += ":" + QString::number(line); function_line += QStringLiteral(":") + QString::number(line);
} }
if (category) { if (category) {
function_line += "(" + QString(category) + ")"; function_line += QStringLiteral("(") + QLatin1String(category) + QStringLiteral(")");
} }
QtMsgType type = QtDebugMsg; QtMsgType type = QtDebugMsg;
@ -284,7 +284,7 @@ static T CreateLogger(Level level, const QString &class_name, int line, const ch
} }
T ret(type); T ret(type);
ret.nospace() << QDateTime::currentDateTime().toString("hh:mm:ss.zzz").toLatin1().constData() << level_name << function_line.leftJustified(32).toLatin1().constData(); ret.nospace() << QDateTime::currentDateTime().toString(QStringLiteral("hh:mm:ss.zzz")).toLatin1().constData() << level_name << function_line.leftJustified(32).toLatin1().constData();
return ret.space(); return ret.space();
@ -310,7 +310,7 @@ QString CXXDemangle(const QString &mangled_function) {
QString LinuxDemangle(const QString &symbol); QString LinuxDemangle(const QString &symbol);
QString LinuxDemangle(const QString &symbol) { QString LinuxDemangle(const QString &symbol) {
QRegularExpression regex("\\(([^+]+)"); QRegularExpression regex(QStringLiteral("\\(([^+]+)"));
QRegularExpressionMatch match = regex.match(symbol); QRegularExpressionMatch match = regex.match(symbol);
if (!match.hasMatch()) { if (!match.hasMatch()) {
return symbol; return symbol;
@ -326,9 +326,9 @@ QString DarwinDemangle(const QString &symbol);
QString DarwinDemangle(const QString &symbol) { QString DarwinDemangle(const QString &symbol) {
# if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) # if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QStringList split = symbol.split(' ', Qt::SkipEmptyParts); QStringList split = symbol.split(QLatin1Char(' '), Qt::SkipEmptyParts);
# else # else
QStringList split = symbol.split(' ', QString::SkipEmptyParts); QStringList split = symbol.split(QLatin1Char(' '), QString::SkipEmptyParts);
# endif # endif
QString mangled_function = split[3]; QString mangled_function = split[3];
return CXXDemangle(mangled_function); return CXXDemangle(mangled_function);
@ -392,7 +392,7 @@ namespace {
template<typename T> template<typename T>
QString print_duration(T duration, const std::string &unit) { QString print_duration(T duration, const std::string &unit) {
return QString("%1%2").arg(duration.count()).arg(unit.c_str()); return QStringLiteral("%1%2").arg(duration.count()).arg(QString::fromStdString(unit));
} }
} // namespace } // namespace

View File

@ -23,6 +23,7 @@
#include <cstdio> #include <cstdio>
#include <cstddef> #include <cstddef>
#include <utility>
#include <QtGlobal> #include <QtGlobal>
#include <QObject> #include <QObject>
@ -171,7 +172,7 @@ WorkerPool<HandlerType>::WorkerPool(QObject *parent)
local_server_name_ = qApp->applicationName().toLower(); local_server_name_ = qApp->applicationName().toLower();
if (local_server_name_.isEmpty()) { if (local_server_name_.isEmpty()) {
local_server_name_ = "workerpool"; local_server_name_ = QStringLiteral("workerpool");
} }
} }
@ -243,15 +244,15 @@ void WorkerPool<HandlerType>::DoStart() {
QStringList search_path; QStringList search_path;
search_path << QCoreApplication::applicationDirPath(); search_path << QCoreApplication::applicationDirPath();
#if defined(Q_OS_UNIX) #if defined(Q_OS_UNIX)
search_path << "/usr/libexec"; search_path << QStringLiteral("/usr/libexec");
search_path << "/usr/local/libexec"; search_path << QStringLiteral("/usr/local/libexec");
#endif #endif
#if defined(Q_OS_MACOS) #if defined(Q_OS_MACOS)
search_path << QDir::cleanPath(QCoreApplication::applicationDirPath() + "/../PlugIns"); search_path << QDir::cleanPath(QCoreApplication::applicationDirPath() + QStringLiteral("/../PlugIns"));
#endif #endif
for (const QString &path_prefix : search_path) { for (const QString &path_prefix : std::as_const(search_path)) {
const QString executable_path = path_prefix + "/" + executable_name_; const QString executable_path = path_prefix + QLatin1Char('/') + executable_name_;
if (QFile::exists(executable_path)) { if (QFile::exists(executable_path)) {
executable_path_ = executable_path; executable_path_ = executable_path;
qLog(Debug) << "Using worker" << executable_name_ << "from" << path_prefix; qLog(Debug) << "Using worker" << executable_name_ << "from" << path_prefix;
@ -294,7 +295,7 @@ void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
// Create a server, find an unused name and start listening // Create a server, find an unused name and start listening
forever { forever {
const quint32 unique_number = QRandomGenerator::global()->bounded(static_cast<quint32>(quint64(this) & 0xFFFFFFFF)); const quint32 unique_number = QRandomGenerator::global()->bounded(static_cast<quint32>(quint64(this) & 0xFFFFFFFF));
const QString name = QString("%1_%2").arg(local_server_name_).arg(unique_number); const QString name = QStringLiteral("%1_%2").arg(local_server_name_).arg(unique_number);
if (worker->local_server_->listen(name)) { if (worker->local_server_->listen(name)) {
break; break;

View File

@ -74,7 +74,7 @@ TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const spb::tagreader::S
} }
QString cover_mime_type; QString cover_mime_type;
if (request.has_cover_mime_type()) { if (request.has_cover_mime_type()) {
cover_mime_type = QByteArray(request.cover_mime_type().data(), static_cast<qint64>(request.cover_mime_type().size())); cover_mime_type = QString::fromStdString(request.cover_mime_type());
} }
return LoadCoverFromRequest(song_filename, cover_filename, cover_data, cover_mime_type); return LoadCoverFromRequest(song_filename, cover_filename, cover_data, cover_mime_type);
@ -94,7 +94,7 @@ TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const spb::tagreader::S
} }
QString cover_mime_type; QString cover_mime_type;
if (request.has_cover_mime_type()) { if (request.has_cover_mime_type()) {
cover_mime_type = QByteArray(request.cover_mime_type().data(), static_cast<qint64>(request.cover_mime_type().size())); cover_mime_type = QString::fromStdString(request.cover_mime_type());
} }
return LoadCoverFromRequest(song_filename, cover_filename, cover_data, cover_mime_type); return LoadCoverFromRequest(song_filename, cover_filename, cover_data, cover_mime_type);
@ -118,24 +118,28 @@ TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const QString &song_fil
if (cover_mime_type.isEmpty()) { if (cover_mime_type.isEmpty()) {
cover_mime_type = QMimeDatabase().mimeTypeForData(cover_data).name(); cover_mime_type = QMimeDatabase().mimeTypeForData(cover_data).name();
} }
if (cover_mime_type == "image/jpeg") { if (cover_mime_type == QStringLiteral("image/jpeg")) {
qLog(Debug) << "Using cover from JPEG data for" << song_filename; qLog(Debug) << "Using cover from JPEG data for" << song_filename;
return Cover(cover_data, cover_mime_type); return Cover(cover_data, cover_mime_type);
} }
if (cover_mime_type == "image/png") { if (cover_mime_type == QStringLiteral("image/png")) {
qLog(Debug) << "Using cover from PNG data for" << song_filename; qLog(Debug) << "Using cover from PNG data for" << song_filename;
return Cover(cover_data, cover_mime_type); return Cover(cover_data, cover_mime_type);
} }
// Convert image to JPEG. // Convert image to JPEG.
qLog(Debug) << "Converting cover to JPEG data for" << song_filename; qLog(Debug) << "Converting cover to JPEG data for" << song_filename;
QImage cover_image(cover_data); QImage cover_image;
if (!cover_image.loadFromData(cover_data)) {
qLog(Error) << "Failed to load image from cover data for" << song_filename;
return Cover();
}
cover_data.clear(); cover_data.clear();
QBuffer buffer(&cover_data); QBuffer buffer(&cover_data);
if (buffer.open(QIODevice::WriteOnly)) { if (buffer.open(QIODevice::WriteOnly)) {
cover_image.save(&buffer, "JPEG"); cover_image.save(&buffer, "JPEG");
buffer.close(); buffer.close();
} }
return Cover(cover_data, "image/jpeg"); return Cover(cover_data, QStringLiteral("image/jpeg"));
} }
return Cover(); return Cover();

View File

@ -36,16 +36,16 @@
#include "tagreadertaglib.h" #include "tagreadertaglib.h"
bool GME::IsSupportedFormat(const QFileInfo &file_info) { bool GME::IsSupportedFormat(const QFileInfo &file_info) {
return file_info.exists() && (file_info.completeSuffix().endsWith("spc", Qt::CaseInsensitive) || file_info.completeSuffix().endsWith("vgm"), Qt::CaseInsensitive); return file_info.exists() && (file_info.completeSuffix().endsWith(QLatin1String("spc"), Qt::CaseInsensitive) || file_info.completeSuffix().endsWith(QLatin1String("vgm")), Qt::CaseInsensitive);
} }
bool GME::ReadFile(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info) { bool GME::ReadFile(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info) {
if (file_info.completeSuffix().endsWith("spc"), Qt::CaseInsensitive) { if (file_info.completeSuffix().endsWith(QLatin1String("spc")), Qt::CaseInsensitive) {
SPC::Read(file_info, song_info); SPC::Read(file_info, song_info);
return true; return true;
} }
if (file_info.completeSuffix().endsWith("vgm", Qt::CaseInsensitive)) { if (file_info.completeSuffix().endsWith(QLatin1String("vgm"), Qt::CaseInsensitive)) {
VGM::Read(file_info, song_info); VGM::Read(file_info, song_info);
return true; return true;
} }
@ -75,7 +75,7 @@ void GME::SPC::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so
qLog(Debug) << "Reading tags from SPC file" << file_info.fileName(); qLog(Debug) << "Reading tags from SPC file" << file_info.fileName();
// Check for header -- more reliable than file name alone. // Check for header -- more reliable than file name alone.
if (!file.read(33).startsWith(QString("SNES-SPC700").toLatin1())) return; if (!file.read(33).startsWith(QStringLiteral("SNES-SPC700").toLatin1())) return;
// First order of business -- get any tag values that exist within the core file information. // First order of business -- get any tag values that exist within the core file information.
// These only allow for a certain number of bytes per field, // These only allow for a certain number of bytes per field,
@ -125,7 +125,7 @@ void GME::SPC::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so
// Check for XID6 data -- this is infrequently used, but being able to fill in data from this is ideal before trying to rely on APETAG values. // Check for XID6 data -- this is infrequently used, but being able to fill in data from this is ideal before trying to rely on APETAG values.
// XID6 format follows EA's binary file format standard named "IFF" // XID6 format follows EA's binary file format standard named "IFF"
file.seek(XID6_OFFSET); file.seek(XID6_OFFSET);
if (has_id6 && file.read(4) == QString("xid6").toLatin1()) { if (has_id6 && file.read(4) == QStringLiteral("xid6").toLatin1()) {
QByteArray xid6_head_data = file.read(4); QByteArray xid6_head_data = file.read(4);
if (xid6_head_data.size() >= 4) { if (xid6_head_data.size() >= 4) {
qint64 xid6_size = xid6_head_data[0] | (xid6_head_data[1] << 8) | (xid6_head_data[2] << 16) | xid6_head_data[3]; qint64 xid6_size = xid6_head_data[0] | (xid6_head_data[1] << 8) | (xid6_head_data[2] << 16) | xid6_head_data[3];
@ -195,7 +195,7 @@ void GME::VGM::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so
qLog(Debug) << "Reading tags from VGM file" << file_info.fileName(); qLog(Debug) << "Reading tags from VGM file" << file_info.fileName();
if (!file.read(4).startsWith(QString("Vgm ").toLatin1())) return; if (!file.read(4).startsWith(QStringLiteral("Vgm ").toLatin1())) return;
file.seek(GD3_TAG_PTR); file.seek(GD3_TAG_PTR);
QByteArray gd3_head = file.read(4); QByteArray gd3_head = file.read(4);
@ -226,7 +226,7 @@ void GME::VGM::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so
#else #else
fileTagStream.setCodec("UTF-16"); fileTagStream.setCodec("UTF-16");
#endif #endif
QStringList strings = fileTagStream.readLine(0).split(QChar('\0')); QStringList strings = fileTagStream.readLine(0).split(QLatin1Char('\0'));
if (strings.count() < 10) return; if (strings.count() < 10) return;
// VGM standard dictates string tag data exist in specific order. // VGM standard dictates string tag data exist in specific order.

View File

@ -248,7 +248,7 @@ bool TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
song->set_ctime(song->mtime()); song->set_ctime(song->mtime());
} }
song->set_lastseen(QDateTime::currentDateTime().toSecsSinceEpoch()); song->set_lastseen(QDateTime::currentSecsSinceEpoch());
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename)); std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
if (fileref->isNull()) { if (fileref->isNull()) {
@ -328,137 +328,8 @@ bool TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
} }
else if (TagLib::MPEG::File *file_mpeg = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) { else if (TagLib::MPEG::File *file_mpeg = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
if (file_mpeg->hasID3v2Tag()) {
if (file_mpeg->ID3v2Tag()) { ParseID3v2Tag(file_mpeg->ID3v2Tag(), &disc, &compilation, song);
const TagLib::ID3v2::FrameListMap &map = file_mpeg->ID3v2Tag()->frameListMap();
if (map.contains("TPOS")) disc = TStringToQString(map["TPOS"].front()->toString()).trimmed();
if (map.contains("TCOM")) TStringToStdString(map["TCOM"].front()->toString(), song->mutable_composer());
// content group
if (map.contains("TIT1")) TStringToStdString(map["TIT1"].front()->toString(), song->mutable_grouping());
// original artist/performer
if (map.contains("TOPE")) TStringToStdString(map["TOPE"].front()->toString(), song->mutable_performer());
// Skip TPE1 (which is the artist) here because we already fetched it
// non-standard: Apple, Microsoft
if (map.contains("TPE2")) TStringToStdString(map["TPE2"].front()->toString(), song->mutable_albumartist());
if (map.contains("TCMP")) compilation = TStringToQString(map["TCMP"].front()->toString()).trimmed();
if (map.contains("TDOR")) {
song->set_originalyear(map["TDOR"].front()->toString().substr(0, 4).toInt());
}
else if (map.contains("TORY")) {
song->set_originalyear(map["TORY"].front()->toString().substr(0, 4).toInt());
}
if (map.contains("USLT")) {
TStringToStdString(map["USLT"].front()->toString(), song->mutable_lyrics());
}
else if (map.contains("SYLT")) {
TStringToStdString(map["SYLT"].front()->toString(), song->mutable_lyrics());
}
if (map.contains("APIC")) song->set_art_embedded(true);
// Find a suitable comment tag. For now we ignore iTunNORM comments.
for (uint i = 0; i < map["COMM"].size(); ++i) {
const TagLib::ID3v2::CommentsFrame *frame = dynamic_cast<const TagLib::ID3v2::CommentsFrame*>(map["COMM"][i]);
if (frame && TStringToQString(frame->description()) != "iTunNORM") {
TStringToStdString(frame->text(), song->mutable_comment());
break;
}
}
if (TagLib::ID3v2::UserTextIdentificationFrame *frame_fmps_playcount = TagLib::ID3v2::UserTextIdentificationFrame::find(file_mpeg->ID3v2Tag(), "FMPS_Playcount")) {
TagLib::StringList frame_field_list = frame_fmps_playcount->fieldList();
if (frame_field_list.size() > 1) {
int playcount = TStringToQString(frame_field_list[1]).toInt();
if (song->playcount() <= 0 && playcount > 0) {
song->set_playcount(playcount);
}
}
}
if (TagLib::ID3v2::UserTextIdentificationFrame *frame_fmps_rating = TagLib::ID3v2::UserTextIdentificationFrame::find(file_mpeg->ID3v2Tag(), "FMPS_Rating")) {
TagLib::StringList frame_field_list = frame_fmps_rating->fieldList();
if (frame_field_list.size() > 1) {
float rating = TStringToQString(frame_field_list[1]).toFloat();
if (song->rating() <= 0 && rating > 0 && rating <= 1.0) {
song->set_rating(rating);
}
}
}
if (map.contains("POPM")) {
const TagLib::ID3v2::PopularimeterFrame *frame = dynamic_cast<const TagLib::ID3v2::PopularimeterFrame*>(map["POPM"].front());
if (frame) {
if (song->playcount() <= 0 && frame->counter() > 0) {
song->set_playcount(frame->counter());
}
if (song->rating() <= 0 && frame->rating() > 0) {
song->set_rating(ConvertPOPMRating(frame->rating()));
}
}
}
if (map.contains("UFID")) {
for (uint i = 0; i < map["UFID"].size(); ++i) {
if (TagLib::ID3v2::UniqueFileIdentifierFrame *frame = dynamic_cast<TagLib::ID3v2::UniqueFileIdentifierFrame*>(map["UFID"][i])) {
const TagLib::PropertyMap property_map = frame->asProperties();
if (property_map.contains(kID3v2_MusicBrainz_RecordingID)) {
TStringToStdString(property_map[kID3v2_MusicBrainz_RecordingID].toString(), song->mutable_musicbrainz_recording_id());
}
}
}
}
if (map.contains("TXXX")) {
for (uint i = 0; i < map["TXXX"].size(); ++i) {
if (TagLib::ID3v2::UserTextIdentificationFrame *frame = dynamic_cast<TagLib::ID3v2::UserTextIdentificationFrame*>(map["TXXX"][i])) {
const TagLib::StringList frame_field_list = frame->fieldList();
if (frame_field_list.size() != 2) continue;
if (frame->description() == kID3v2_AcoustID_ID) {
TStringToStdString(frame_field_list.back(), song->mutable_acoustid_id());
}
if (frame->description() == kID3v2_AcoustID_Fingerprint) {
TStringToStdString(frame_field_list.back(), song->mutable_acoustid_fingerprint());
}
if (frame->description() == kID3v2_MusicBrainz_AlbumArtistID) {
TStringToStdString(frame_field_list.back(), song->mutable_musicbrainz_album_artist_id());
}
if (frame->description() == kID3v2_MusicBrainz_ArtistID) {
TStringToStdString(frame_field_list.back(), song->mutable_musicbrainz_artist_id());
}
if (frame->description() == kID3v2_MusicBrainz_OriginalArtistID) {
TStringToStdString(frame_field_list.back(), song->mutable_musicbrainz_original_artist_id());
}
if (frame->description() == kID3v2_MusicBrainz_AlbumID) {
TStringToStdString(frame_field_list.back(), song->mutable_musicbrainz_album_id());
}
if (frame->description() == kID3v2_MusicBrainz_OriginalAlbumID) {
TStringToStdString(frame_field_list.back(), song->mutable_musicbrainz_original_album_id());
}
if (frame->description() == kID3v2_MusicBrainz_TrackID) {
TStringToStdString(frame_field_list.back(), song->mutable_musicbrainz_track_id());
}
if (frame->description() == kID3v2_MusicBrainz_DiscID) {
TStringToStdString(frame_field_list.back(), song->mutable_musicbrainz_disc_id());
}
if (frame->description() == kID3v2_MusicBrainz_ReleaseGroupID) {
TStringToStdString(frame_field_list.back(), song->mutable_musicbrainz_release_group_id());
}
if (frame->description() == kID3v2_MusicBrainz_WorkID) {
TStringToStdString(frame_field_list.back(), song->mutable_musicbrainz_work_id());
}
}
}
}
} }
} }
@ -658,12 +529,18 @@ bool TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
if (tag) TStringToStdString(tag->comment(), song->mutable_comment()); if (tag) TStringToStdString(tag->comment(), song->mutable_comment());
} }
else if (TagLib::RIFF::WAV::File *file_wav = dynamic_cast<TagLib::RIFF::WAV::File*>(fileref->file())) {
if (file_wav->hasID3v2Tag()) {
ParseID3v2Tag(file_wav->ID3v2Tag(), &disc, &compilation, song);
}
}
else if (tag) { else if (tag) {
TStringToStdString(tag->comment(), song->mutable_comment()); TStringToStdString(tag->comment(), song->mutable_comment());
} }
if (!disc.isEmpty()) { if (!disc.isEmpty()) {
const qint64 i = disc.indexOf('/'); const qint64 i = disc.indexOf(QLatin1Char('/'));
if (i != -1) { if (i != -1) {
// disc.right( i ).toInt() is total number of discs, we don't use this at the moment // disc.right( i ).toInt() is total number of discs, we don't use this at the moment
song->set_disc(disc.left(i).toInt()); song->set_disc(disc.left(i).toInt());
@ -677,7 +554,7 @@ bool TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
// well, it wasn't set, but if the artist is VA assume it's a compilation // well, it wasn't set, but if the artist is VA assume it's a compilation
const QString albumartist = QString::fromUtf8(song->albumartist().data(), static_cast<qint64>(song->albumartist().size())); const QString albumartist = QString::fromUtf8(song->albumartist().data(), static_cast<qint64>(song->albumartist().size()));
const QString artist = QString::fromUtf8(song->artist().data(), static_cast<qint64>(song->artist().size())); const QString artist = QString::fromUtf8(song->artist().data(), static_cast<qint64>(song->artist().size()));
if (artist.compare("various artists") == 0 || albumartist.compare("various artists") == 0) { if (artist.compare(QLatin1String("various artists")) == 0 || albumartist.compare(QLatin1String("various artists")) == 0) {
song->set_compilation(true); song->set_compilation(true);
} }
} }
@ -710,6 +587,139 @@ void TagReaderTagLib::TStringToStdString(const TagLib::String &tag, std::string
} }
void TagReaderTagLib::ParseID3v2Tag(TagLib::ID3v2::Tag *tag, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const {
TagLib::ID3v2::FrameListMap map = tag->frameListMap();
if (map.contains("TPOS")) *disc = TStringToQString(map["TPOS"].front()->toString()).trimmed();
if (map.contains("TCOM")) TStringToStdString(map["TCOM"].front()->toString(), song->mutable_composer());
// content group
if (map.contains("TIT1")) TStringToStdString(map["TIT1"].front()->toString(), song->mutable_grouping());
// original artist/performer
if (map.contains("TOPE")) TStringToStdString(map["TOPE"].front()->toString(), song->mutable_performer());
// Skip TPE1 (which is the artist) here because we already fetched it
// non-standard: Apple, Microsoft
if (map.contains("TPE2")) TStringToStdString(map["TPE2"].front()->toString(), song->mutable_albumartist());
if (map.contains("TCMP")) *compilation = TStringToQString(map["TCMP"].front()->toString()).trimmed();
if (map.contains("TDOR")) {
song->set_originalyear(map["TDOR"].front()->toString().substr(0, 4).toInt());
}
else if (map.contains("TORY")) {
song->set_originalyear(map["TORY"].front()->toString().substr(0, 4).toInt());
}
if (map.contains("USLT")) {
TStringToStdString(map["USLT"].front()->toString(), song->mutable_lyrics());
}
else if (map.contains("SYLT")) {
TStringToStdString(map["SYLT"].front()->toString(), song->mutable_lyrics());
}
if (map.contains("APIC")) song->set_art_embedded(true);
// Find a suitable comment tag. For now we ignore iTunNORM comments.
for (uint i = 0; i < map["COMM"].size(); ++i) {
const TagLib::ID3v2::CommentsFrame *frame = dynamic_cast<const TagLib::ID3v2::CommentsFrame*>(map["COMM"][i]);
if (frame && TStringToQString(frame->description()) != QStringLiteral("iTunNORM")) {
TStringToStdString(frame->text(), song->mutable_comment());
break;
}
}
if (TagLib::ID3v2::UserTextIdentificationFrame *frame_fmps_playcount = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, "FMPS_Playcount")) {
TagLib::StringList frame_field_list = frame_fmps_playcount->fieldList();
if (frame_field_list.size() > 1) {
int playcount = TStringToQString(frame_field_list[1]).toInt();
if (song->playcount() <= 0 && playcount > 0) {
song->set_playcount(playcount);
}
}
}
if (TagLib::ID3v2::UserTextIdentificationFrame *frame_fmps_rating = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, "FMPS_Rating")) {
TagLib::StringList frame_field_list = frame_fmps_rating->fieldList();
if (frame_field_list.size() > 1) {
float rating = TStringToQString(frame_field_list[1]).toFloat();
if (song->rating() <= 0 && rating > 0 && rating <= 1.0) {
song->set_rating(rating);
}
}
}
if (map.contains("POPM")) {
const TagLib::ID3v2::PopularimeterFrame *frame = dynamic_cast<const TagLib::ID3v2::PopularimeterFrame*>(map["POPM"].front());
if (frame) {
if (song->playcount() <= 0 && frame->counter() > 0) {
song->set_playcount(frame->counter());
}
if (song->rating() <= 0 && frame->rating() > 0) {
song->set_rating(ConvertPOPMRating(frame->rating()));
}
}
}
if (map.contains("UFID")) {
for (uint i = 0; i < map["UFID"].size(); ++i) {
if (TagLib::ID3v2::UniqueFileIdentifierFrame *frame = dynamic_cast<TagLib::ID3v2::UniqueFileIdentifierFrame*>(map["UFID"][i])) {
const TagLib::PropertyMap property_map = frame->asProperties();
if (property_map.contains(kID3v2_MusicBrainz_RecordingID)) {
TStringToStdString(property_map[kID3v2_MusicBrainz_RecordingID].toString(), song->mutable_musicbrainz_recording_id());
}
}
}
}
if (map.contains("TXXX")) {
for (uint i = 0; i < map["TXXX"].size(); ++i) {
if (TagLib::ID3v2::UserTextIdentificationFrame *frame = dynamic_cast<TagLib::ID3v2::UserTextIdentificationFrame*>(map["TXXX"][i])) {
const TagLib::StringList frame_field_list = frame->fieldList();
if (frame_field_list.size() != 2) continue;
if (frame->description() == kID3v2_AcoustID_ID) {
TStringToStdString(frame_field_list.back(), song->mutable_acoustid_id());
}
if (frame->description() == kID3v2_AcoustID_Fingerprint) {
TStringToStdString(frame_field_list.back(), song->mutable_acoustid_fingerprint());
}
if (frame->description() == kID3v2_MusicBrainz_AlbumArtistID) {
TStringToStdString(frame_field_list.back(), song->mutable_musicbrainz_album_artist_id());
}
if (frame->description() == kID3v2_MusicBrainz_ArtistID) {
TStringToStdString(frame_field_list.back(), song->mutable_musicbrainz_artist_id());
}
if (frame->description() == kID3v2_MusicBrainz_OriginalArtistID) {
TStringToStdString(frame_field_list.back(), song->mutable_musicbrainz_original_artist_id());
}
if (frame->description() == kID3v2_MusicBrainz_AlbumID) {
TStringToStdString(frame_field_list.back(), song->mutable_musicbrainz_album_id());
}
if (frame->description() == kID3v2_MusicBrainz_OriginalAlbumID) {
TStringToStdString(frame_field_list.back(), song->mutable_musicbrainz_original_album_id());
}
if (frame->description() == kID3v2_MusicBrainz_TrackID) {
TStringToStdString(frame_field_list.back(), song->mutable_musicbrainz_track_id());
}
if (frame->description() == kID3v2_MusicBrainz_DiscID) {
TStringToStdString(frame_field_list.back(), song->mutable_musicbrainz_disc_id());
}
if (frame->description() == kID3v2_MusicBrainz_ReleaseGroupID) {
TStringToStdString(frame_field_list.back(), song->mutable_musicbrainz_release_group_id());
}
if (frame->description() == kID3v2_MusicBrainz_WorkID) {
TStringToStdString(frame_field_list.back(), song->mutable_musicbrainz_work_id());
}
}
}
}
}
void TagReaderTagLib::ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const { void TagReaderTagLib::ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const {
if (map.contains("COMPOSER")) TStringToStdString(map["COMPOSER"].front(), song->mutable_composer()); if (map.contains("COMPOSER")) TStringToStdString(map["COMPOSER"].front(), song->mutable_composer());
@ -824,7 +834,7 @@ void TagReaderTagLib::SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comment
vorbis_comment->addField("PERFORMER", StdStringToTaglibString(song.performer()), true); vorbis_comment->addField("PERFORMER", StdStringToTaglibString(song.performer()), true);
vorbis_comment->addField("GROUPING", StdStringToTaglibString(song.grouping()), true); vorbis_comment->addField("GROUPING", StdStringToTaglibString(song.grouping()), true);
vorbis_comment->addField("DISCNUMBER", QStringToTaglibString(song.disc() <= 0 ? QString() : QString::number(song.disc())), true); vorbis_comment->addField("DISCNUMBER", QStringToTaglibString(song.disc() <= 0 ? QString() : QString::number(song.disc())), true);
vorbis_comment->addField("COMPILATION", QStringToTaglibString(song.compilation() ? "1" : QString()), true); vorbis_comment->addField("COMPILATION", QStringToTaglibString(song.compilation() ? QStringLiteral("1") : QString()), true);
// Try to be coherent, the two forms are used but the first one is preferred // Try to be coherent, the two forms are used but the first one is preferred
@ -849,19 +859,19 @@ bool TagReaderTagLib::SaveFile(const spb::tagreader::SaveFileRequest &request) c
QStringList save_tags_options; QStringList save_tags_options;
if (save_tags) { if (save_tags) {
save_tags_options << "tags"; save_tags_options << QStringLiteral("tags");
} }
if (save_playcount) { if (save_playcount) {
save_tags_options << "playcount"; save_tags_options << QStringLiteral("playcount");
} }
if (save_rating) { if (save_rating) {
save_tags_options << "rating"; save_tags_options << QStringLiteral("rating");
} }
if (save_cover) { if (save_cover) {
save_tags_options << "embedded cover"; save_tags_options << QStringLiteral("embedded cover");
} }
qLog(Debug) << "Saving" << save_tags_options.join(", ") << "to" << filename; qLog(Debug) << "Saving" << save_tags_options.join(QStringLiteral(", ")) << "to" << filename;
const Cover cover = LoadCoverFromRequest(request); const Cover cover = LoadCoverFromRequest(request);
@ -943,14 +953,7 @@ bool TagReaderTagLib::SaveFile(const spb::tagreader::SaveFileRequest &request) c
TagLib::ID3v2::Tag *tag = file_mpeg->ID3v2Tag(true); TagLib::ID3v2::Tag *tag = file_mpeg->ID3v2Tag(true);
if (!tag) return false; if (!tag) return false;
if (save_tags) { if (save_tags) {
SetTextFrame("TPOS", song.disc() <= 0 ? QString() : QString::number(song.disc()), tag); SaveID3v2Tag(tag, song);
SetTextFrame("TCOM", song.composer().empty() ? std::string() : song.composer(), tag);
SetTextFrame("TIT1", song.grouping().empty() ? std::string() : song.grouping(), tag);
SetTextFrame("TOPE", song.performer().empty() ? std::string() : song.performer(), tag);
// Skip TPE1 (which is the artist) here because we already set it
SetTextFrame("TPE2", song.albumartist().empty() ? std::string() : song.albumartist(), tag);
SetTextFrame("TCMP", song.compilation() ? QString::number(1) : QString(), tag);
SetUnsyncLyricsFrame(song.lyrics().empty() ? std::string() : song.lyrics(), tag);
} }
if (save_playcount) { if (save_playcount) {
SetPlaycount(tag, song); SetPlaycount(tag, song);
@ -959,7 +962,7 @@ bool TagReaderTagLib::SaveFile(const spb::tagreader::SaveFileRequest &request) c
SetRating(tag, song); SetRating(tag, song);
} }
if (save_cover) { if (save_cover) {
SetEmbeddedArt(file_mpeg, tag, cover.data, cover.mime_type); SetEmbeddedArt(tag, cover.data, cover.mime_type);
} }
} }
@ -985,6 +988,22 @@ bool TagReaderTagLib::SaveFile(const spb::tagreader::SaveFileRequest &request) c
} }
} }
else if (TagLib::RIFF::WAV::File *file_wav = dynamic_cast<TagLib::RIFF::WAV::File*>(fileref->file())) {
TagLib::ID3v2::Tag *tag = file_wav->ID3v2Tag();
if (save_tags) {
SaveID3v2Tag(tag, song);
}
if (save_playcount) {
SetPlaycount(tag, song);
}
if (save_rating) {
SetRating(tag, song);
}
if (save_cover) {
SetEmbeddedArt(tag, cover.data, cover.mime_type);
}
}
// Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same way; // Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same way;
// apart, so we keep specific behavior for some formats by adding another "else if" block above. // apart, so we keep specific behavior for some formats by adding another "else if" block above.
if (!is_flac) { if (!is_flac) {
@ -1016,6 +1035,19 @@ bool TagReaderTagLib::SaveFile(const spb::tagreader::SaveFileRequest &request) c
} }
void TagReaderTagLib::SaveID3v2Tag(TagLib::ID3v2::Tag *tag, const spb::tagreader::SongMetadata &song) const {
SetTextFrame("TPOS", song.disc() <= 0 ? QString() : QString::number(song.disc()), tag);
SetTextFrame("TCOM", song.composer().empty() ? std::string() : song.composer(), tag);
SetTextFrame("TIT1", song.grouping().empty() ? std::string() : song.grouping(), tag);
SetTextFrame("TOPE", song.performer().empty() ? std::string() : song.performer(), tag);
// Skip TPE1 (which is the artist) here because we already set it
SetTextFrame("TPE2", song.albumartist().empty() ? std::string() : song.albumartist(), tag);
SetTextFrame("TCMP", song.compilation() ? QString::number(1) : QString(), tag);
SetUnsyncLyricsFrame(song.lyrics().empty() ? std::string() : song.lyrics(), tag);
}
void TagReaderTagLib::SaveAPETag(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const { void TagReaderTagLib::SaveAPETag(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const {
tag->setItem("album artist", TagLib::APE::Item("album artist", TagLib::StringList(song.albumartist().c_str()))); tag->setItem("album artist", TagLib::APE::Item("album artist", TagLib::StringList(song.albumartist().c_str())));
@ -1278,9 +1310,7 @@ void TagReaderTagLib::SetEmbeddedArt(TagLib::Ogg::XiphComment *xiph_comment, con
} }
void TagReaderTagLib::SetEmbeddedArt(TagLib::MPEG::File *file_mp3, TagLib::ID3v2::Tag *tag, const QByteArray &data, const QString &mime_type) const { void TagReaderTagLib::SetEmbeddedArt(TagLib::ID3v2::Tag *tag, const QByteArray &data, const QString &mime_type) const {
(void)file_mp3;
// Remove existing covers // Remove existing covers
TagLib::ID3v2::FrameList apiclist = tag->frameListMap()["APIC"]; TagLib::ID3v2::FrameList apiclist = tag->frameListMap()["APIC"];
@ -1311,10 +1341,10 @@ void TagReaderTagLib::SetEmbeddedArt(TagLib::MP4::File *aac_file, TagLib::MP4::T
} }
else { else {
TagLib::MP4::CoverArt::Format cover_format = TagLib::MP4::CoverArt::Format::JPEG; TagLib::MP4::CoverArt::Format cover_format = TagLib::MP4::CoverArt::Format::JPEG;
if (mime_type == "image/jpeg") { if (mime_type == QStringLiteral("image/jpeg")) {
cover_format = TagLib::MP4::CoverArt::Format::JPEG; cover_format = TagLib::MP4::CoverArt::Format::JPEG;
} }
else if (mime_type == "image/png") { else if (mime_type == QStringLiteral("image/png")) {
cover_format = TagLib::MP4::CoverArt::Format::PNG; cover_format = TagLib::MP4::CoverArt::Format::PNG;
} }
else { else {
@ -1360,7 +1390,7 @@ bool TagReaderTagLib::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtReque
else if (TagLib::MPEG::File *file_mp3 = dynamic_cast<TagLib::MPEG::File*>(fileref.file())) { else if (TagLib::MPEG::File *file_mp3 = dynamic_cast<TagLib::MPEG::File*>(fileref.file())) {
TagLib::ID3v2::Tag *tag = file_mp3->ID3v2Tag(); TagLib::ID3v2::Tag *tag = file_mp3->ID3v2Tag();
if (!tag) return false; if (!tag) return false;
SetEmbeddedArt(file_mp3, tag, cover.data, cover.mime_type); SetEmbeddedArt(tag, cover.data, cover.mime_type);
} }
// MP4/AAC // MP4/AAC
@ -1427,7 +1457,7 @@ void TagReaderTagLib::SetPlaycount(TagLib::APE::Tag *tag, const spb::tagreader::
void TagReaderTagLib::SetPlaycount(TagLib::ID3v2::Tag *tag, const spb::tagreader::SongMetadata &song) const { void TagReaderTagLib::SetPlaycount(TagLib::ID3v2::Tag *tag, const spb::tagreader::SongMetadata &song) const {
SetUserTextFrame("FMPS_Playcount", QString::number(song.playcount()), tag); SetUserTextFrame(QStringLiteral("FMPS_Playcount"), QString::number(song.playcount()), tag);
TagLib::ID3v2::PopularimeterFrame *frame = GetPOPMFrameFromTag(tag); TagLib::ID3v2::PopularimeterFrame *frame = GetPOPMFrameFromTag(tag);
if (frame) { if (frame) {
frame->setCounter(song.playcount()); frame->setCounter(song.playcount());
@ -1546,7 +1576,7 @@ void TagReaderTagLib::SetRating(TagLib::APE::Tag *tag, const spb::tagreader::Son
void TagReaderTagLib::SetRating(TagLib::ID3v2::Tag *tag, const spb::tagreader::SongMetadata &song) const { void TagReaderTagLib::SetRating(TagLib::ID3v2::Tag *tag, const spb::tagreader::SongMetadata &song) const {
SetUserTextFrame("FMPS_Rating", QString::number(song.rating()), tag); SetUserTextFrame(QStringLiteral("FMPS_Rating"), QString::number(song.rating()), tag);
TagLib::ID3v2::PopularimeterFrame *frame = GetPOPMFrameFromTag(tag); TagLib::ID3v2::PopularimeterFrame *frame = GetPOPMFrameFromTag(tag);
if (frame) { if (frame) {
frame->setRating(ConvertToPOPMRating(song.rating())); frame->setRating(ConvertToPOPMRating(song.rating()));

View File

@ -68,10 +68,12 @@ class TagReaderTagLib : public TagReaderBase {
private: private:
spb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const; spb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const;
void ParseID3v2Tag(TagLib::ID3v2::Tag *tag, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
void ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const; void ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
void ParseAPETag(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const; void ParseAPETag(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
void SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comment, const spb::tagreader::SongMetadata &song) const; void SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comment, const spb::tagreader::SongMetadata &song) const;
void SaveID3v2Tag(TagLib::ID3v2::Tag *tag, const spb::tagreader::SongMetadata &song) const;
void SaveAPETag(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const; void SaveAPETag(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const;
void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const; void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const;
@ -98,7 +100,7 @@ class TagReaderTagLib : public TagReaderBase {
void SetEmbeddedArt(TagLib::FLAC::File *flac_file, TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data, const QString &mime_type) const; void SetEmbeddedArt(TagLib::FLAC::File *flac_file, TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data, const QString &mime_type) const;
void SetEmbeddedArt(TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data, const QString &mime_type) const; void SetEmbeddedArt(TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data, const QString &mime_type) const;
void SetEmbeddedArt(TagLib::MPEG::File *file_mp3, TagLib::ID3v2::Tag *tag, const QByteArray &data, const QString &mime_type) const; 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; void SetEmbeddedArt(TagLib::MP4::File *aac_file, TagLib::MP4::Tag *tag, const QByteArray &data, const QString &mime_type) const;
private: private:

View File

@ -62,7 +62,7 @@ bool TagReaderWorker::HandleMessage(const spb::tagreader::Message &message, spb:
return success; return success;
} }
else if (message.has_read_file_request()) { else if (message.has_read_file_request()) {
const QString filename = QString::fromUtf8(message.read_file_request().filename().data(), static_cast<qint64>(static_cast<qint64>(message.read_file_request().filename().size()))); const QString filename = QString::fromUtf8(message.read_file_request().filename().data(), static_cast<qint64>(message.read_file_request().filename().size()));
bool success = reader->ReadFile(filename, reply.mutable_read_file_response()->mutable_metadata()); bool success = reader->ReadFile(filename, reply.mutable_read_file_response()->mutable_metadata());
return success; return success;
} }
@ -72,7 +72,7 @@ bool TagReaderWorker::HandleMessage(const spb::tagreader::Message &message, spb:
return success; return success;
} }
else if (message.has_load_embedded_art_request()) { else if (message.has_load_embedded_art_request()) {
const QString filename = QString::fromUtf8(message.load_embedded_art_request().filename().data(), static_cast<qint64>(static_cast<qint64>(message.load_embedded_art_request().filename().size()))); const QString filename = QString::fromUtf8(message.load_embedded_art_request().filename().data(), static_cast<qint64>(message.load_embedded_art_request().filename().size()));
QByteArray data = reader->LoadEmbeddedArt(filename); QByteArray data = reader->LoadEmbeddedArt(filename);
reply.mutable_load_embedded_art_response()->set_data(data.constData(), data.size()); reply.mutable_load_embedded_art_response()->set_data(data.constData(), data.size());
return true; return true;
@ -83,7 +83,7 @@ bool TagReaderWorker::HandleMessage(const spb::tagreader::Message &message, spb:
return success; return success;
} }
else if (message.has_save_song_playcount_to_file_request()) { else if (message.has_save_song_playcount_to_file_request()) {
const QString filename = QString::fromUtf8(message.save_song_playcount_to_file_request().filename().data(), static_cast<qint64>(static_cast<qint64>(message.save_song_playcount_to_file_request().filename().size()))); const QString filename = QString::fromUtf8(message.save_song_playcount_to_file_request().filename().data(), static_cast<qint64>(message.save_song_playcount_to_file_request().filename().size()));
bool success = reader->SaveSongPlaycountToFile(filename, message.save_song_playcount_to_file_request().metadata()); bool success = reader->SaveSongPlaycountToFile(filename, message.save_song_playcount_to_file_request().metadata());
reply.mutable_save_song_playcount_to_file_response()->set_success(success); reply.mutable_save_song_playcount_to_file_response()->set_success(success);
return success; return success;

View File

@ -24,6 +24,7 @@ set(SOURCES
core/networktimeouts.cpp core/networktimeouts.cpp
core/networkproxyfactory.cpp core/networkproxyfactory.cpp
core/qtfslistener.cpp core/qtfslistener.cpp
core/settings.cpp
core/settingsprovider.cpp core/settingsprovider.cpp
core/signalchecker.cpp core/signalchecker.cpp
core/song.cpp core/song.cpp
@ -58,6 +59,7 @@ set(SOURCES
utilities/coverutils.cpp utilities/coverutils.cpp
utilities/screenutils.cpp utilities/screenutils.cpp
utilities/searchparserutils.cpp utilities/searchparserutils.cpp
utilities/textencodingutils.cpp
engine/enginebase.cpp engine/enginebase.cpp
engine/enginedevice.cpp engine/enginedevice.cpp
@ -72,6 +74,7 @@ set(SOURCES
analyzer/boomanalyzer.cpp analyzer/boomanalyzer.cpp
analyzer/rainbowanalyzer.cpp analyzer/rainbowanalyzer.cpp
analyzer/sonogram.cpp analyzer/sonogram.cpp
analyzer/waverubber.cpp
equalizer/equalizer.cpp equalizer/equalizer.cpp
equalizer/equalizerslider.cpp equalizer/equalizerslider.cpp
@ -171,6 +174,7 @@ set(SOURCES
covermanager/qobuzcoverprovider.cpp covermanager/qobuzcoverprovider.cpp
covermanager/musixmatchcoverprovider.cpp covermanager/musixmatchcoverprovider.cpp
covermanager/spotifycoverprovider.cpp covermanager/spotifycoverprovider.cpp
covermanager/opentidalcoverprovider.cpp
lyrics/lyricsproviders.cpp lyrics/lyricsproviders.cpp
lyrics/lyricsprovider.cpp lyrics/lyricsprovider.cpp
@ -188,7 +192,7 @@ set(SOURCES
lyrics/songlyricscomlyricsprovider.cpp lyrics/songlyricscomlyricsprovider.cpp
lyrics/azlyricscomlyricsprovider.cpp lyrics/azlyricscomlyricsprovider.cpp
lyrics/elyricsnetlyricsprovider.cpp lyrics/elyricsnetlyricsprovider.cpp
lyrics/lyricsmodecomlyricsprovider.cpp lyrics/letraslyricsprovider.cpp
providers/musixmatchprovider.cpp providers/musixmatchprovider.cpp
@ -196,6 +200,7 @@ set(SOURCES
settings/settingspage.cpp settings/settingspage.cpp
settings/behavioursettingspage.cpp settings/behavioursettingspage.cpp
settings/collectionsettingspage.cpp settings/collectionsettingspage.cpp
settings/collectionsettingsdirectorymodel.cpp
settings/backendsettingspage.cpp settings/backendsettingspage.cpp
settings/playlistsettingspage.cpp settings/playlistsettingspage.cpp
settings/scrobblersettingspage.cpp settings/scrobblersettingspage.cpp
@ -306,6 +311,7 @@ set(HEADERS
core/threadsafenetworkdiskcache.h core/threadsafenetworkdiskcache.h
core/networktimeouts.h core/networktimeouts.h
core/qtfslistener.h core/qtfslistener.h
core/settings.h
core/songloader.h core/songloader.h
core/tagreaderclient.h core/tagreaderclient.h
core/taskmanager.h core/taskmanager.h
@ -326,6 +332,7 @@ set(HEADERS
analyzer/boomanalyzer.h analyzer/boomanalyzer.h
analyzer/rainbowanalyzer.h analyzer/rainbowanalyzer.h
analyzer/sonogram.h analyzer/sonogram.h
analyzer/waverubber.h
equalizer/equalizer.h equalizer/equalizer.h
equalizer/equalizerslider.h equalizer/equalizerslider.h
@ -416,6 +423,7 @@ set(HEADERS
covermanager/qobuzcoverprovider.h covermanager/qobuzcoverprovider.h
covermanager/musixmatchcoverprovider.h covermanager/musixmatchcoverprovider.h
covermanager/spotifycoverprovider.h covermanager/spotifycoverprovider.h
covermanager/opentidalcoverprovider.h
lyrics/lyricsproviders.h lyrics/lyricsproviders.h
lyrics/lyricsprovider.h lyrics/lyricsprovider.h
@ -431,12 +439,13 @@ set(HEADERS
lyrics/songlyricscomlyricsprovider.h lyrics/songlyricscomlyricsprovider.h
lyrics/azlyricscomlyricsprovider.h lyrics/azlyricscomlyricsprovider.h
lyrics/elyricsnetlyricsprovider.h lyrics/elyricsnetlyricsprovider.h
lyrics/lyricsmodecomlyricsprovider.h lyrics/letraslyricsprovider.h
settings/settingsdialog.h settings/settingsdialog.h
settings/settingspage.h settings/settingspage.h
settings/behavioursettingspage.h settings/behavioursettingspage.h
settings/collectionsettingspage.h settings/collectionsettingspage.h
settings/collectionsettingsdirectorymodel.h
settings/backendsettingspage.h settings/backendsettingspage.h
settings/playlistsettingspage.h settings/playlistsettingspage.h
settings/scrobblersettingspage.h settings/scrobblersettingspage.h
@ -486,6 +495,7 @@ set(HEADERS
widgets/qsearchfield.h widgets/qsearchfield.h
widgets/ratingwidget.h widgets/ratingwidget.h
widgets/forcescrollperpixel.h widgets/forcescrollperpixel.h
widgets/resizabletextedit.h
osd/osdbase.h osd/osdbase.h
osd/osdpretty.h osd/osdpretty.h
@ -850,7 +860,7 @@ optional_source(WIN32
HEADERS HEADERS
core/windows7thumbbar.h core/windows7thumbbar.h
) )
optional_source(MSVC SOURCES engine/uwpdevicefinder.cpp) optional_source(MSVC SOURCES engine/uwpdevicefinder.cpp engine/asiodevicefinder.cpp)
optional_source(HAVE_SUBSONIC optional_source(HAVE_SUBSONIC
SOURCES SOURCES
@ -988,14 +998,9 @@ link_directories(
${SQLITE_LIBRARY_DIRS} ${SQLITE_LIBRARY_DIRS}
${PROTOBUF_LIBRARY_DIRS} ${PROTOBUF_LIBRARY_DIRS}
${SINGLEAPPLICATION_LIBRARY_DIRS} ${SINGLEAPPLICATION_LIBRARY_DIRS}
${ICU_LIBRARY_DIRS}
) )
if(HAVE_ICU)
link_directories(${ICU_LIBRARY_DIRS})
else()
link_directories(${Iconv_LIBRARY_DIRS})
endif()
if(HAVE_ALSA) if(HAVE_ALSA)
link_directories(${ALSA_LIBRARY_DIRS}) link_directories(${ALSA_LIBRARY_DIRS})
endif() endif()
@ -1079,6 +1084,7 @@ target_include_directories(strawberry_lib SYSTEM PUBLIC
${GOBJECT_INCLUDE_DIRS} ${GOBJECT_INCLUDE_DIRS}
${SQLITE_INCLUDE_DIRS} ${SQLITE_INCLUDE_DIRS}
${PROTOBUF_INCLUDE_DIRS} ${PROTOBUF_INCLUDE_DIRS}
${ICU_INCLUDE_DIRS}
) )
if(HAVE_QPA_QPLATFORMNATIVEINTERFACE_H) if(HAVE_QPA_QPLATFORMNATIVEINTERFACE_H)
@ -1101,6 +1107,7 @@ target_link_libraries(strawberry_lib PUBLIC
${GLIB_LIBRARIES} ${GLIB_LIBRARIES}
${GOBJECT_LIBRARIES} ${GOBJECT_LIBRARIES}
${SQLITE_LIBRARIES} ${SQLITE_LIBRARIES}
${ICU_LIBRARIES}
Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Concurrent Qt${QT_VERSION_MAJOR}::Concurrent
Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Gui
@ -1121,17 +1128,6 @@ if(HAVE_X11_GLOBALSHORTCUTS AND HAVE_X11EXTRAS)
target_link_libraries(strawberry_lib PUBLIC Qt${QT_VERSION_MAJOR}::X11Extras) target_link_libraries(strawberry_lib PUBLIC Qt${QT_VERSION_MAJOR}::X11Extras)
endif() endif()
if(HAVE_ICU)
target_include_directories(strawberry_lib SYSTEM PRIVATE ${ICU_INCLUDE_DIRS})
target_link_libraries(strawberry_lib PRIVATE ${ICU_LIBRARIES})
else()
if(FREEBSD AND NOT Iconv_LIBRARIES)
set(Iconv_LIBRARIES iconv)
endif()
target_include_directories(strawberry_lib SYSTEM PRIVATE ${Iconv_INCLUDE_DIRS})
target_link_libraries(strawberry_lib PRIVATE ${Iconv_LIBRARIES})
endif()
if(HAVE_ALSA) if(HAVE_ALSA)
target_include_directories(strawberry_lib SYSTEM PRIVATE ${ALSA_INCLUDE_DIRS}) target_include_directories(strawberry_lib SYSTEM PRIVATE ${ALSA_INCLUDE_DIRS})
target_link_libraries(strawberry_lib PRIVATE ${ALSA_LIBRARIES}) target_link_libraries(strawberry_lib PRIVATE ${ALSA_LIBRARIES})

View File

@ -108,7 +108,7 @@ void AnalyzerBase::paintEvent(QPaintEvent *e) {
p.fillRect(e->rect(), palette().color(QPalette::Window)); p.fillRect(e->rect(), palette().color(QPalette::Window));
switch (engine_->state()) { switch (engine_->state()) {
case EngineBase::State::Playing: { case EngineBase::State::Playing:{
const EngineBase::Scope &thescope = engine_->scope(timeout_); const EngineBase::Scope &thescope = engine_->scope(timeout_);
int i = 0; int i = 0;

View File

@ -41,9 +41,11 @@
#include "boomanalyzer.h" #include "boomanalyzer.h"
#include "rainbowanalyzer.h" #include "rainbowanalyzer.h"
#include "sonogram.h" #include "sonogram.h"
#include "waverubber.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/shared_ptr.h" #include "core/shared_ptr.h"
#include "core/settings.h"
#include "engine/enginebase.h" #include "engine/enginebase.h"
using namespace std::chrono_literals; using namespace std::chrono_literals;
@ -87,6 +89,7 @@ AnalyzerContainer::AnalyzerContainer(QWidget *parent)
AddAnalyzerType<NyanCatAnalyzer>(); AddAnalyzerType<NyanCatAnalyzer>();
AddAnalyzerType<RainbowDashAnalyzer>(); AddAnalyzerType<RainbowDashAnalyzer>();
AddAnalyzerType<Sonogram>(); AddAnalyzerType<Sonogram>();
AddAnalyzerType<WaveRubber>();
disable_action_ = context_menu_->addAction(tr("No analyzer"), this, &AnalyzerContainer::DisableAnalyzer); disable_action_ = context_menu_->addAction(tr("No analyzer"), this, &AnalyzerContainer::DisableAnalyzer);
disable_action_->setCheckable(true); disable_action_->setCheckable(true);
@ -178,9 +181,9 @@ void AnalyzerContainer::ChangeFramerate(int new_framerate) {
void AnalyzerContainer::Load() { void AnalyzerContainer::Load() {
QSettings s; Settings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup);
QString type = s.value("type", "BlockAnalyzer").toString(); QString type = s.value("type", QStringLiteral("BlockAnalyzer")).toString();
current_framerate_ = s.value(kSettingsFramerate, kMediumFramerate).toInt(); current_framerate_ = s.value(kSettingsFramerate, kMediumFramerate).toInt();
s.endGroup(); s.endGroup();
@ -191,7 +194,7 @@ void AnalyzerContainer::Load() {
} }
else { else {
for (int i = 0; i < analyzer_types_.count(); ++i) { for (int i = 0; i < analyzer_types_.count(); ++i) {
if (type == analyzer_types_[i]->className()) { if (type == QString::fromLatin1(analyzer_types_[i]->className())) {
ChangeAnalyzer(i); ChangeAnalyzer(i);
actions_[i]->setChecked(true); actions_[i]->setChecked(true);
break; break;
@ -215,7 +218,7 @@ void AnalyzerContainer::SaveFramerate(const int framerate) {
// For now, framerate is common for all analyzers. Maybe each analyzer should have its own framerate? // For now, framerate is common for all analyzers. Maybe each analyzer should have its own framerate?
current_framerate_ = framerate; current_framerate_ = framerate;
QSettings s; Settings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup);
s.setValue(kSettingsFramerate, current_framerate_); s.setValue(kSettingsFramerate, current_framerate_);
s.endGroup(); s.endGroup();
@ -224,9 +227,9 @@ void AnalyzerContainer::SaveFramerate(const int framerate) {
void AnalyzerContainer::Save() { void AnalyzerContainer::Save() {
QSettings s; Settings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup);
s.setValue("type", current_analyzer_ ? current_analyzer_->metaObject()->className() : QVariant()); s.setValue("type", current_analyzer_ ? QString::fromLatin1(current_analyzer_->metaObject()->className()) : QVariant());
s.endGroup(); s.endGroup();
} }

View File

@ -266,12 +266,12 @@ QColor ensureContrast(const QColor &bg, const QColor &fg, int amount) {
// value is the best measure of contrast // value is the best measure of contrast
// if there is enough difference in value already, return fg unchanged // if there is enough difference in value already, return fg unchanged
if (dv > static_cast<int>(amount)) return fg; if (dv > amount) return fg;
int ds = abs(bs - fs); int ds = abs(bs - fs);
// saturation is good enough too. But not as good. TODO adapt this a little // saturation is good enough too. But not as good. TODO adapt this a little
if (ds > static_cast<int>(amount)) return fg; if (ds > amount) return fg;
int dh = abs(bh - fh); int dh = abs(bh - fh);
@ -294,7 +294,7 @@ QColor ensureContrast(const QColor &bg, const QColor &fg, int amount) {
// low saturation on a low saturation is sad // low saturation on a low saturation is sad
const int tmp = 50 - fs; const int tmp = 50 - fs;
fs = 50; fs = 50;
if (static_cast<int>(amount) > tmp) { if (amount > tmp) {
amount -= tmp; amount -= tmp;
} }
else { else {
@ -310,25 +310,25 @@ QColor ensureContrast(const QColor &bg, const QColor &fg, int amount) {
if (amount > 0) adjustToLimits(bs, fs, amount); if (amount > 0) adjustToLimits(bs, fs, amount);
// see if we need to adjust the hue // see if we need to adjust the hue
if (static_cast<int>(amount) > 0) if (amount > 0)
fh += static_cast<int>(amount); // cycles around; fh += amount; // cycles around;
return QColor::fromHsv(fh, fs, fv); return QColor::fromHsv(fh, fs, fv);
} }
if (fv > bv && bv > static_cast<int>(amount)) { if (fv > bv && bv > amount) {
return QColor::fromHsv(fh, fs, bv - static_cast<int>(amount)); return QColor::fromHsv(fh, fs, bv - amount);
} }
if (fv < bv && fv > static_cast<int>(amount)) { if (fv < bv && fv > amount) {
return QColor::fromHsv(fh, fs, fv - amount); return QColor::fromHsv(fh, fs, fv - amount);
} }
if (fv > bv && (255 - fv > static_cast<int>(amount))) { if (fv > bv && (255 - fv > amount)) {
return QColor::fromHsv(fh, fs, fv + amount); return QColor::fromHsv(fh, fs, fv + amount);
} }
if (fv < bv && (255 - bv > static_cast<int>(amount))) { if (fv < bv && (255 - bv > amount)) {
return QColor::fromHsv(fh, fs, bv + amount); return QColor::fromHsv(fh, fs, bv + amount);
} }

View File

@ -78,7 +78,7 @@ void BoomAnalyzer::resizeEvent(QResizeEvent *e) {
bands_ = qMin(static_cast<int>(static_cast<double>(width() + 1) / (kColumnWidth + 1)) + 1, kMaxBandCount); bands_ = qMin(static_cast<int>(static_cast<double>(width() + 1) / (kColumnWidth + 1)) + 1, kMaxBandCount);
scope_.resize(bands_); scope_.resize(bands_);
F_ = static_cast<double>(HEIGHT) / (log10(256) * static_cast<double>(1.1) /*<- max. amplitude*/); F_ = static_cast<double>(HEIGHT) / (log10(256) * 1.1 /*<- max. amplitude*/);
barPixmap_ = QPixmap(kColumnWidth - 2, HEIGHT); barPixmap_ = QPixmap(kColumnWidth - 2, HEIGHT);
canvas_ = QPixmap(size()); canvas_ = QPixmap(size());

View File

@ -28,7 +28,7 @@
#include <QVector> #include <QVector>
#include <QtMath> #include <QtMath>
FHT::FHT(uint n) : num_((n < 3) ? 0 : 1 << n), exp2_((n < 3) ? static_cast<int>(-1) : static_cast<int>(n)) { FHT::FHT(uint n) : num_((n < 3) ? 0 : 1 << n), exp2_((n < 3) ? -1 : static_cast<int>(n)) {
if (n > 3) { if (n > 3) {
buf_vector_.resize(num_); buf_vector_.resize(num_);

View File

@ -65,8 +65,8 @@ RainbowAnalyzer::RainbowAnalyzer(const RainbowType rbtype, QWidget *parent)
background_brush_(QColor(0x0f, 0x43, 0x73)) { background_brush_(QColor(0x0f, 0x43, 0x73)) {
rainbowtype = rbtype; rainbowtype = rbtype;
cat_dash_[0] = QPixmap(":/pictures/nyancat.png"); cat_dash_[0] = QPixmap(QStringLiteral(":/pictures/nyancat.png"));
cat_dash_[1] = QPixmap(":/pictures/rainbowdash.png"); cat_dash_[1] = QPixmap(QStringLiteral(":/pictures/rainbowdash.png"));
memset(history_, 0, sizeof(history_)); memset(history_, 0, sizeof(history_));
for (int i = 0; i < kRainbowBands; ++i) { for (int i = 0; i < kRainbowBands; ++i) {

View File

@ -0,0 +1,93 @@
/*
Strawberry Music Player
Copyright 2024, Gustavo L Conte <suporte@gu.pro.br>
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QPainter>
#include <QResizeEvent>
#include "engine/enginebase.h"
#include "waverubber.h"
const char *WaveRubber::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "WaveRubber");
WaveRubber::WaveRubber(QWidget *parent)
: AnalyzerBase(parent, 9) {}
void WaveRubber::resizeEvent(QResizeEvent *e) {
Q_UNUSED(e)
canvas_ = QPixmap(size());
canvas_.fill(palette().color(QPalette::AlternateBase));
}
void WaveRubber::analyze(QPainter &p, const Scope &s, bool new_frame) {
if (!new_frame || engine_->state() == EngineBase::State::Paused) {
p.drawPixmap(0, 0, canvas_);
return;
}
// Clear the canvas
canvas_ = QPixmap(size());
canvas_.fill(palette().color(QPalette::Window));
QPainter canvas_painter(&canvas_);
// Set the pen color to the QT palette highlight color
canvas_painter.setPen(palette().color(QPalette::Highlight));
// Get pointer to amplitude data
const float *amplitude_data = s.data();
int mid_y = height() / 4;
int num_samples = s.size();
float x_scale = static_cast<float>(width()) / num_samples;
float prev_y = mid_y;
// Draw the waveform
for (int i = 0; i < num_samples; ++i) {
// Normalize amplitude to 0-1 range
float color_factor = amplitude_data[i] / 2.0f + 0.5f;
int rgb_value = static_cast<int>(255 - color_factor * 255);
QColor highlight_color = palette().color(QPalette::Highlight);
// Blend blue and green with highlight color from QT palette based on amplitude
QColor blended_color = QColor(rgb_value, highlight_color.green(), highlight_color.blue());
canvas_painter.setPen(blended_color);
int x = static_cast<int>(i * x_scale );
int y = static_cast<int>(mid_y - (s[i] * mid_y));
canvas_painter.drawLine(x, prev_y + mid_y, x + x_scale, y + mid_y); // Draw
prev_y = y;
}
canvas_painter.end();
p.drawPixmap(0, 0, canvas_);
}
void WaveRubber::transform(Scope &s) {
// No need transformation for waveform analyzer
Q_UNUSED(s);
}
void WaveRubber::demo(QPainter &p) {
analyze(p, Scope(fht_->size(), 0), new_frame_);
}

43
src/analyzer/waverubber.h Normal file
View File

@ -0,0 +1,43 @@
/*
Strawberry Music Player
Copyright 2024, Gustavo L Conte <suporte@gu.pro.br>
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QPixmap>
#include <QPainter>
#include "analyzerbase.h"
class WaveRubber : public AnalyzerBase {
Q_OBJECT
public:
Q_INVOKABLE explicit WaveRubber(QWidget *parent);
static const char *kName;
protected:
void resizeEvent(QResizeEvent *e) override;
void analyze(QPainter &p, const Scope &s, bool new_frame) override;
void transform(Scope &scope) override;
void demo(QPainter &p) override;
private:
QPixmap canvas_;
};

View File

@ -37,6 +37,7 @@
#include "core/thread.h" #include "core/thread.h"
#include "core/song.h" #include "core/song.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/settings.h"
#include "utilities/threadutils.h" #include "utilities/threadutils.h"
#include "collection.h" #include "collection.h"
#include "collectionwatcher.h" #include "collectionwatcher.h"
@ -69,7 +70,7 @@ SCollection::SCollection(Application *app, QObject *parent)
backend()->moveToThread(app->database()->thread()); backend()->moveToThread(app->database()->thread());
qLog(Debug) << &*backend_ << "moved to thread" << app->database()->thread(); qLog(Debug) << &*backend_ << "moved to thread" << app->database()->thread();
backend_->Init(app->database(), app->task_manager(), Song::Source::Collection, kSongsTable, kFtsTable, kDirsTable, kSubdirsTable); backend_->Init(app->database(), app->task_manager(), Song::Source::Collection, QLatin1String(kSongsTable), QLatin1String(kFtsTable), QLatin1String(kDirsTable), QLatin1String(kSubdirsTable));
model_ = new CollectionModel(backend_, app_, this); model_ = new CollectionModel(backend_, app_, this);
@ -107,7 +108,7 @@ void SCollection::Init() {
watcher_->set_task_manager(app_->task_manager()); watcher_->set_task_manager(app_->task_manager());
QObject::connect(&*backend_, &CollectionBackend::Error, this, &SCollection::Error); QObject::connect(&*backend_, &CollectionBackend::Error, this, &SCollection::Error);
QObject::connect(&*backend_, &CollectionBackend::DirectoryDiscovered, watcher_, &CollectionWatcher::AddDirectory); QObject::connect(&*backend_, &CollectionBackend::DirectoryAdded, watcher_, &CollectionWatcher::AddDirectory);
QObject::connect(&*backend_, &CollectionBackend::DirectoryDeleted, watcher_, &CollectionWatcher::RemoveDirectory); QObject::connect(&*backend_, &CollectionBackend::DirectoryDeleted, watcher_, &CollectionWatcher::RemoveDirectory);
QObject::connect(&*backend_, &CollectionBackend::SongsRatingChanged, this, &SCollection::SongsRatingChanged); QObject::connect(&*backend_, &CollectionBackend::SongsRatingChanged, this, &SCollection::SongsRatingChanged);
QObject::connect(&*backend_, &CollectionBackend::SongsStatisticsChanged, this, &SCollection::SongsPlaycountChanged); QObject::connect(&*backend_, &CollectionBackend::SongsStatisticsChanged, this, &SCollection::SongsPlaycountChanged);
@ -179,7 +180,7 @@ void SCollection::ReloadSettings() {
watcher_->ReloadSettingsAsync(); watcher_->ReloadSettingsAsync();
model_->ReloadSettings(); model_->ReloadSettings();
QSettings s; Settings s;
s.beginGroup(CollectionSettingsPage::kSettingsGroup); s.beginGroup(CollectionSettingsPage::kSettingsGroup);
save_playcounts_to_files_ = s.value("save_playcounts", false).toBool(); save_playcounts_to_files_ = s.value("save_playcounts", false).toBool();
save_ratings_to_files_ = s.value("save_ratings", false).toBool(); save_ratings_to_files_ = s.value("save_ratings", false).toBool();

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -130,9 +130,10 @@ class CollectionBackendInterface : public QObject {
// Returns a section of a song with the given filename and beginning. If the section is not present in collection, returns invalid song. // Returns a section of a song with the given filename and beginning. If the section is not present in collection, returns invalid song.
// Using default beginning value is suitable when searching for single-section songs. // Using default beginning value is suitable when searching for single-section songs.
virtual Song GetSongByUrl(const QUrl &url, const qint64 beginning = 0) = 0; virtual Song GetSongByUrl(const QUrl &url, const qint64 beginning = 0) = 0;
virtual Song GetSongByUrlAndTrack(const QUrl &url, const int track) = 0;
virtual void AddDirectory(const QString &path) = 0; virtual void AddDirectoryAsync(const QString &path) = 0;
virtual void RemoveDirectory(const CollectionDirectory &dir) = 0; virtual void RemoveDirectoryAsync(const CollectionDirectory &dir) = 0;
}; };
class CollectionBackend : public CollectionBackendInterface { class CollectionBackend : public CollectionBackendInterface {
@ -203,9 +204,10 @@ class CollectionBackend : public CollectionBackendInterface {
SongList GetSongsByUrl(const QUrl &url, const bool unavailable = false) override; SongList GetSongsByUrl(const QUrl &url, const bool unavailable = false) override;
Song GetSongByUrl(const QUrl &url, qint64 beginning = 0) override; Song GetSongByUrl(const QUrl &url, qint64 beginning = 0) override;
Song GetSongByUrlAndTrack(const QUrl &url, const int track) override;
void AddDirectory(const QString &path) override; void AddDirectoryAsync(const QString &path) override;
void RemoveDirectory(const CollectionDirectory &dir) override; void RemoveDirectoryAsync(const CollectionDirectory &dir) override;
bool ExecCollectionQuery(CollectionQuery *query, SongList &songs); bool ExecCollectionQuery(CollectionQuery *query, SongList &songs);
bool ExecCollectionQuery(CollectionQuery *query, SongMap &songs); bool ExecCollectionQuery(CollectionQuery *query, SongMap &songs);
@ -237,6 +239,8 @@ class CollectionBackend : public CollectionBackendInterface {
void UpdateTotalSongCount(); void UpdateTotalSongCount();
void UpdateTotalArtistCount(); void UpdateTotalArtistCount();
void UpdateTotalAlbumCount(); void UpdateTotalAlbumCount();
void AddDirectory(const QString &path);
void RemoveDirectory(const CollectionDirectory &dir);
void AddOrUpdateSongs(const SongList &songs); void AddOrUpdateSongs(const SongList &songs);
void UpdateSongsBySongID(const SongMap &new_songs); void UpdateSongsBySongID(const SongMap &new_songs);
void UpdateMTimesOnly(const SongList &songs); void UpdateMTimesOnly(const SongList &songs);
@ -248,7 +252,7 @@ class CollectionBackend : public CollectionBackendInterface {
void UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &art_manual); void UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &art_manual);
void UnsetAlbumArt(const QString &effective_albumartist, const QString &album); void UnsetAlbumArt(const QString &effective_albumartist, const QString &album);
void ClearAlbumArt(const QString &effective_albumartist, const QString &album, const bool art_unset); void ClearAlbumArt(const QString &effective_albumartist, const QString &album, const bool art_unset);
void ForceCompilation(const QString &album, const QList<QString> &artists, const bool on); void ForceCompilation(const QString &album, const QStringList &artists, const bool on);
void IncrementPlayCount(const int id); void IncrementPlayCount(const int id);
void IncrementSkipCount(const int id, const float progress); void IncrementSkipCount(const int id, const float progress);
void ResetPlayStatistics(const int id, const bool save_tags = false); void ResetPlayStatistics(const int id, const bool save_tags = false);
@ -268,7 +272,7 @@ class CollectionBackend : public CollectionBackendInterface {
void ExpireSongs(const int directory_id, const int expire_unavailable_songs_days); void ExpireSongs(const int directory_id, const int expire_unavailable_songs_days);
signals: signals:
void DirectoryDiscovered(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdir); void DirectoryAdded(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdir);
void DirectoryDeleted(const CollectionDirectory &dir); void DirectoryDeleted(const CollectionDirectory &dir);
void SongsDiscovered(const SongList &songs); void SongsDiscovered(const SongList &songs);

View File

@ -2,6 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -41,15 +42,18 @@ using std::make_shared;
CollectionDirectoryModel::CollectionDirectoryModel(SharedPtr<CollectionBackend> backend, QObject *parent) CollectionDirectoryModel::CollectionDirectoryModel(SharedPtr<CollectionBackend> backend, QObject *parent)
: QStandardItemModel(parent), : QStandardItemModel(parent),
dir_icon_(IconLoader::Load("document-open-folder")), dir_icon_(IconLoader::Load(QStringLiteral("document-open-folder"))),
backend_(backend) { backend_(backend) {
QObject::connect(&*backend_, &CollectionBackend::DirectoryDiscovered, this, &CollectionDirectoryModel::DirectoryDiscovered); QObject::connect(&*backend_, &CollectionBackend::DirectoryAdded, this, &CollectionDirectoryModel::AddDirectory);
QObject::connect(&*backend_, &CollectionBackend::DirectoryDeleted, this, &CollectionDirectoryModel::DirectoryDeleted); QObject::connect(&*backend_, &CollectionBackend::DirectoryDeleted, this, &CollectionDirectoryModel::RemoveDirectory);
} }
void CollectionDirectoryModel::DirectoryDiscovered(const CollectionDirectory &dir) { void CollectionDirectoryModel::AddDirectory(const CollectionDirectory &dir) {
directories_.insert(dir.id, dir);
paths_.append(dir.path);
QStandardItem *item = new QStandardItem(dir.path); QStandardItem *item = new QStandardItem(dir.path);
item->setData(dir.id, kIdRole); item->setData(dir.id, kIdRole);
@ -59,7 +63,10 @@ void CollectionDirectoryModel::DirectoryDiscovered(const CollectionDirectory &di
} }
void CollectionDirectoryModel::DirectoryDeleted(const CollectionDirectory &dir) { void CollectionDirectoryModel::RemoveDirectory(const CollectionDirectory &dir) {
directories_.remove(dir.id);
paths_.removeAll(dir.path);
for (int i = 0; i < rowCount(); ++i) { for (int i = 0; i < rowCount(); ++i) {
if (item(i, 0)->data(kIdRole).toInt() == dir.id) { if (item(i, 0)->data(kIdRole).toInt() == dir.id) {
@ -71,26 +78,6 @@ void CollectionDirectoryModel::DirectoryDeleted(const CollectionDirectory &dir)
} }
void CollectionDirectoryModel::AddDirectory(const QString &path) {
if (!backend_) return;
backend_->AddDirectory(path);
}
void CollectionDirectoryModel::RemoveDirectory(const QModelIndex &idx) {
if (!backend_ || !idx.isValid()) return;
CollectionDirectory dir;
dir.path = idx.data().toString();
dir.id = idx.data(kIdRole).toInt();
backend_->RemoveDirectory(dir);
}
QVariant CollectionDirectoryModel::data(const QModelIndex &idx, int role) const { QVariant CollectionDirectoryModel::data(const QModelIndex &idx, int role) const {
switch (role) { switch (role) {

View File

@ -2,6 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -26,15 +27,17 @@
#include <QObject> #include <QObject>
#include <QStandardItemModel> #include <QStandardItemModel>
#include <QList> #include <QList>
#include <QMap>
#include <QVariant> #include <QVariant>
#include <QString> #include <QString>
#include <QStringList>
#include <QIcon> #include <QIcon>
#include "core/shared_ptr.h" #include "core/shared_ptr.h"
#include "collectiondirectory.h"
class QModelIndex; class QModelIndex;
struct CollectionDirectory;
class CollectionBackend; class CollectionBackend;
class MusicStorage; class MusicStorage;
@ -44,22 +47,24 @@ class CollectionDirectoryModel : public QStandardItemModel {
public: public:
explicit CollectionDirectoryModel(SharedPtr<CollectionBackend> collection_backend, QObject *parent = nullptr); explicit CollectionDirectoryModel(SharedPtr<CollectionBackend> collection_backend, QObject *parent = nullptr);
// To be called by GUIs
void AddDirectory(const QString &path);
void RemoveDirectory(const QModelIndex &idx);
QVariant data(const QModelIndex &idx, int role) const override; QVariant data(const QModelIndex &idx, int role) const override;
SharedPtr<CollectionBackend> backend() const { return backend_; }
QMap<int, CollectionDirectory> directories() const { return directories_; }
QStringList paths() const { return paths_; }
private slots: private slots:
// To be called by the backend void AddDirectory(const CollectionDirectory &directory);
void DirectoryDiscovered(const CollectionDirectory &directories); void RemoveDirectory(const CollectionDirectory &directory);
void DirectoryDeleted(const CollectionDirectory &directories);
private: private:
static const int kIdRole = Qt::UserRole + 1; static const int kIdRole = Qt::UserRole + 1;
QIcon dir_icon_; QIcon dir_icon_;
SharedPtr<CollectionBackend> backend_; SharedPtr<CollectionBackend> backend_;
QMap<int, CollectionDirectory> directories_;
QStringList paths_;
QList<SharedPtr<MusicStorage>> storage_; QList<SharedPtr<MusicStorage>> storage_;
}; };

View File

@ -29,7 +29,7 @@ CollectionFilterOptions::CollectionFilterOptions() : filter_mode_(FilterMode::Al
bool CollectionFilterOptions::Matches(const Song &song) const { bool CollectionFilterOptions::Matches(const Song &song) const {
if (max_age_ != -1) { if (max_age_ != -1) {
const qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - max_age_; const qint64 cutoff = QDateTime::currentSecsSinceEpoch() - max_age_;
if (song.ctime() <= cutoff) return false; if (song.ctime() <= cutoff) return false;
} }

View File

@ -21,6 +21,7 @@
#include "config.h" #include "config.h"
#include <utility>
#include <memory> #include <memory>
#include <QApplication> #include <QApplication>
@ -46,6 +47,7 @@
#include "core/iconloader.h" #include "core/iconloader.h"
#include "core/song.h" #include "core/song.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/settings.h"
#include "collectionfilteroptions.h" #include "collectionfilteroptions.h"
#include "collectionmodel.h" #include "collectionmodel.h"
#include "savedgroupingmanager.h" #include "savedgroupingmanager.h"
@ -72,34 +74,34 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
ui_->setupUi(this); ui_->setupUi(this);
QString available_fields = Song::kFtsColumns.join(", ").replace(QRegularExpression("\\bfts"), ""); QString available_fields = Song::kFtsColumns.join(QStringLiteral(", ")).replace(QRegularExpression(QStringLiteral("\\bfts")), QLatin1String(""));
available_fields += QString(", ") + Song::kNumericalColumns.join(", "); available_fields += QStringLiteral(", ") + Song::kNumericalColumns.join(QStringLiteral(", "));
ui_->search_field->setToolTip( ui_->search_field->setToolTip(
QString("<html><head/><body><p>") + QStringLiteral("<html><head/><body><p>") +
tr("Prefix a word with a field name to limit the search to that field, e.g.:") + tr("Prefix a word with a field name to limit the search to that field, e.g.:") +
QString(" ") + QStringLiteral(" ") +
QString("<span style=\"font-weight:600;\">") + QStringLiteral("<span style=\"font-weight:600;\">") +
tr("artist") + tr("artist") +
QString(":</span><span style=\"font-style:italic;\">Strawbs</span> ") + QStringLiteral(":</span><span style=\"font-style:italic;\">Strawbs</span> ") +
tr("searches the collection for all artists that contain the word %1. ").arg("Strawbs") + tr("searches the collection for all artists that contain the word %1. ").arg(QStringLiteral("Strawbs")) +
QString("</p><p>") + QStringLiteral("</p><p>") +
tr("Search terms for numerical fields can be prefixed with %1 or %2 to refine the search, e.g.: ") tr("Search terms for numerical fields can be prefixed with %1 or %2 to refine the search, e.g.: ")
.arg(" =, !=, &lt;, &gt;, &lt;=", "&gt;=") + .arg(QStringLiteral(" =, !=, &lt;, &gt;, &lt;="), QStringLiteral("&gt;=")) +
QString("<span style=\"font-weight:600;\">") + QStringLiteral("<span style=\"font-weight:600;\">") +
tr("rating") + tr("rating") +
QString("</span>") + QStringLiteral("</span>") +
QString(":>=") + QStringLiteral(":>=") +
QString("<span style=\"font-weight:italic;\">4</span>") + QStringLiteral("<span style=\"font-weight:italic;\">4</span>") +
QString("</p><p><span style=\"font-weight:600;\">") + QStringLiteral("</p><p><span style=\"font-weight:600;\">") +
tr("Available fields") + tr("Available fields") +
QString(": ") + QStringLiteral(": ") +
QString("</span>") + QStringLiteral("</span>") +
QString("<span style=\"font-style:italic;\">") + QStringLiteral("<span style=\"font-style:italic;\">") +
available_fields + available_fields +
QString("</span>.") + QStringLiteral("</span>.") +
QString("</p></body></html>") QStringLiteral("</p></body></html>")
); );
QObject::connect(ui_->search_field, &QSearchField::returnPressed, this, &CollectionFilterWidget::ReturnPressed); QObject::connect(ui_->search_field, &QSearchField::returnPressed, this, &CollectionFilterWidget::ReturnPressed);
@ -109,7 +111,7 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
filter_delay_->setSingleShot(true); filter_delay_->setSingleShot(true);
// Icons // Icons
ui_->options->setIcon(IconLoader::Load("configure")); ui_->options->setIcon(IconLoader::Load(QStringLiteral("configure")));
// Filter by age // Filter by age
QActionGroup *filter_age_group = new QActionGroup(this); QActionGroup *filter_age_group = new QActionGroup(this);
@ -160,7 +162,7 @@ void CollectionFilterWidget::Init(CollectionModel *model) {
QObject::disconnect(model_, nullptr, this, nullptr); QObject::disconnect(model_, nullptr, this, nullptr);
QObject::disconnect(model_, nullptr, group_by_dialog_, nullptr); QObject::disconnect(model_, nullptr, group_by_dialog_, nullptr);
QObject::disconnect(group_by_dialog_, nullptr, model_, nullptr); QObject::disconnect(group_by_dialog_, nullptr, model_, nullptr);
QList<QAction*> filter_ages = filter_ages_.keys(); const QList<QAction*> filter_ages = filter_ages_.keys();
for (QAction *action : filter_ages) { for (QAction *action : filter_ages) {
QObject::disconnect(action, &QAction::triggered, model_, nullptr); QObject::disconnect(action, &QAction::triggered, model_, nullptr);
} }
@ -173,7 +175,7 @@ void CollectionFilterWidget::Init(CollectionModel *model) {
QObject::connect(model_, &CollectionModel::GroupingChanged, this, &CollectionFilterWidget::GroupingChanged); QObject::connect(model_, &CollectionModel::GroupingChanged, this, &CollectionFilterWidget::GroupingChanged);
QObject::connect(group_by_dialog_, &GroupByDialog::Accepted, model_, &CollectionModel::SetGroupBy); QObject::connect(group_by_dialog_, &GroupByDialog::Accepted, model_, &CollectionModel::SetGroupBy);
QList<QAction*> filter_ages = filter_ages_.keys(); const QList<QAction*> filter_ages = filter_ages_.keys();
for (QAction *action : filter_ages) { for (QAction *action : filter_ages) {
int age = filter_ages_[action]; int age = filter_ages_[action];
QObject::connect(action, &QAction::triggered, this, [this, age]() { model_->SetFilterAge(age); } ); QObject::connect(action, &QAction::triggered, this, [this, age]() { model_->SetFilterAge(age); } );
@ -181,7 +183,7 @@ void CollectionFilterWidget::Init(CollectionModel *model) {
// Load settings // Load settings
if (!settings_group_.isEmpty()) { if (!settings_group_.isEmpty()) {
QSettings s; Settings s;
s.beginGroup(settings_group_); s.beginGroup(settings_group_);
int version = 0; int version = 0;
if (s.contains(group_by_version())) version = s.value(group_by_version(), 0).toInt(); if (s.contains(group_by_version())) version = s.value(group_by_version(), 0).toInt();
@ -217,7 +219,7 @@ void CollectionFilterWidget::SetSettingsPrefix(const QString &prefix) {
void CollectionFilterWidget::ReloadSettings() { void CollectionFilterWidget::ReloadSettings() {
QSettings s; Settings s;
s.beginGroup(AppearanceSettingsPage::kSettingsGroup); s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
int iconsize = s.value(AppearanceSettingsPage::kIconSizeConfigureButtons, 20).toInt(); int iconsize = s.value(AppearanceSettingsPage::kIconSizeConfigureButtons, 20).toInt();
s.endGroup(); s.endGroup();
@ -229,10 +231,10 @@ void CollectionFilterWidget::ReloadSettings() {
QString CollectionFilterWidget::group_by_version() const { QString CollectionFilterWidget::group_by_version() const {
if (settings_prefix_.isEmpty()) { if (settings_prefix_.isEmpty()) {
return "group_by_version"; return QStringLiteral("group_by_version");
} }
else { else {
return QString("%1_group_by_version").arg(settings_prefix_); return QStringLiteral("%1_group_by_version").arg(settings_prefix_);
} }
} }
@ -240,10 +242,10 @@ QString CollectionFilterWidget::group_by_version() const {
QString CollectionFilterWidget::group_by_key() const { QString CollectionFilterWidget::group_by_key() const {
if (settings_prefix_.isEmpty()) { if (settings_prefix_.isEmpty()) {
return "group_by"; return QStringLiteral("group_by");
} }
else { else {
return QString("%1_group_by").arg(settings_prefix_); return QStringLiteral("%1_group_by").arg(settings_prefix_);
} }
} }
@ -253,10 +255,10 @@ QString CollectionFilterWidget::group_by_key(const int number) const { return gr
QString CollectionFilterWidget::separate_albums_by_grouping_key() const { QString CollectionFilterWidget::separate_albums_by_grouping_key() const {
if (settings_prefix_.isEmpty()) { if (settings_prefix_.isEmpty()) {
return "separate_albums_by_grouping"; return QStringLiteral("separate_albums_by_grouping");
} }
else { else {
return QString("%1_separate_albums_by_grouping").arg(settings_prefix_); return QStringLiteral("%1_separate_albums_by_grouping").arg(settings_prefix_);
} }
} }
@ -306,13 +308,13 @@ QActionGroup *CollectionFilterWidget::CreateGroupByActions(const QString &saved_
ret->addAction(sep1); ret->addAction(sep1);
// Read saved groupings // Read saved groupings
QSettings s; Settings s;
s.beginGroup(saved_groupings_settings_group); s.beginGroup(saved_groupings_settings_group);
int version = s.value("version").toInt(); int version = s.value("version").toInt();
if (version == 1) { if (version == 1) {
QStringList saved = s.childKeys(); QStringList saved = s.childKeys();
for (int i = 0; i < saved.size(); ++i) { for (int i = 0; i < saved.size(); ++i) {
if (saved.at(i) == "version") continue; if (saved.at(i) == QStringLiteral("version")) continue;
QByteArray bytes = s.value(saved.at(i)).toByteArray(); QByteArray bytes = s.value(saved.at(i)).toByteArray();
QDataStream ds(&bytes, QIODevice::ReadOnly); QDataStream ds(&bytes, QIODevice::ReadOnly);
CollectionModel::Grouping g; CollectionModel::Grouping g;
@ -323,7 +325,7 @@ QActionGroup *CollectionFilterWidget::CreateGroupByActions(const QString &saved_
else { else {
QStringList saved = s.childKeys(); QStringList saved = s.childKeys();
for (int i = 0; i < saved.size(); ++i) { for (int i = 0; i < saved.size(); ++i) {
if (saved.at(i) == "version") continue; if (saved.at(i) == QStringLiteral("version")) continue;
s.remove(saved.at(i)); s.remove(saved.at(i));
} }
} }
@ -361,17 +363,17 @@ void CollectionFilterWidget::SaveGroupBy() {
qLog(Debug) << "Saving current grouping to" << name; qLog(Debug) << "Saving current grouping to" << name;
QSettings s; Settings s;
if (settings_group_.isEmpty() || settings_group_ == CollectionSettingsPage::kSettingsGroup) { if (settings_group_.isEmpty() || settings_group_ == QLatin1String(CollectionSettingsPage::kSettingsGroup)) {
s.beginGroup(SavedGroupingManager::kSavedGroupingsSettingsGroup); s.beginGroup(SavedGroupingManager::kSavedGroupingsSettingsGroup);
} }
else { else {
s.beginGroup(QString(SavedGroupingManager::kSavedGroupingsSettingsGroup) + "_" + settings_group_); s.beginGroup(QLatin1String(SavedGroupingManager::kSavedGroupingsSettingsGroup) + QLatin1Char('_') + settings_group_);
} }
QByteArray buffer; QByteArray buffer;
QDataStream datastream(&buffer, QIODevice::WriteOnly); QDataStream datastream(&buffer, QIODevice::WriteOnly);
datastream << model_->GetGroupBy(); datastream << model_->GetGroupBy();
s.setValue("version", "1"); s.setValue("version", QStringLiteral("1"));
s.setValue(name, buffer); s.setValue(name, buffer);
s.endGroup(); s.endGroup();
@ -425,7 +427,7 @@ void CollectionFilterWidget::GroupByClicked(QAction *action) {
void CollectionFilterWidget::GroupingChanged(const CollectionModel::Grouping g, const bool separate_albums_by_grouping) { void CollectionFilterWidget::GroupingChanged(const CollectionModel::Grouping g, const bool separate_albums_by_grouping) {
if (!settings_group_.isEmpty()) { if (!settings_group_.isEmpty()) {
QSettings s; Settings s;
s.beginGroup(settings_group_); s.beginGroup(settings_group_);
s.setValue(group_by_version(), 1); s.setValue(group_by_version(), 1);
s.setValue(group_by_key(1), static_cast<int>(g[0])); s.setValue(group_by_key(1), static_cast<int>(g[0]));
@ -446,7 +448,8 @@ void CollectionFilterWidget::CheckCurrentGrouping(const CollectionModel::Groupin
UpdateGroupByActions(); UpdateGroupByActions();
} }
for (QAction *action : group_by_group_->actions()) { const QList<QAction*> actions = group_by_group_->actions();
for (QAction *action : actions) {
if (action->property("group_by").isNull()) continue; if (action->property("group_by").isNull()) continue;
if (g == action->property("group_by").value<CollectionModel::Grouping>()) { if (g == action->property("group_by").value<CollectionModel::Grouping>()) {
@ -456,7 +459,6 @@ void CollectionFilterWidget::CheckCurrentGrouping(const CollectionModel::Groupin
} }
// Check the advanced action // Check the advanced action
QList<QAction*> actions = group_by_group_->actions();
QAction *action = actions.last(); QAction *action = actions.last();
action->setChecked(true); action->setChecked(true);

View File

@ -38,6 +38,9 @@
</item> </item>
<item> <item>
<widget class="QToolButton" name="options"> <widget class="QToolButton" name="options">
<property name="accessibleName">
<string>MenuPopupToolButton</string>
</property>
<property name="iconSize"> <property name="iconSize">
<size> <size>
<width>16</width> <width>16</width>

View File

@ -130,7 +130,7 @@ bool CollectionItemDelegate::helpEvent(QHelpEvent *event, QAbstractItemView *vie
if (text.isEmpty()) return false; if (text.isEmpty()) return false;
switch (event->type()) { switch (event->type()) {
case QEvent::ToolTip: { case QEvent::ToolTip:{
QSize real_text = sizeHint(option, idx); QSize real_text = sizeHint(option, idx);
QRect displayed_text = view->visualRect(idx); QRect displayed_text = view->visualRect(idx);

View File

@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -61,6 +61,7 @@
#include "core/logging.h" #include "core/logging.h"
#include "core/taskmanager.h" #include "core/taskmanager.h"
#include "core/sqlrow.h" #include "core/sqlrow.h"
#include "core/settings.h"
#include "collectionfilteroptions.h" #include "collectionfilteroptions.h"
#include "collectionquery.h" #include "collectionquery.h"
#include "collectionqueryoptions.h" #include "collectionqueryoptions.h"
@ -86,12 +87,13 @@ CollectionModel::CollectionModel(SharedPtr<CollectionBackend> backend, Applicati
app_(app), app_(app),
dir_model_(new CollectionDirectoryModel(backend, this)), dir_model_(new CollectionDirectoryModel(backend, this)),
show_various_artists_(true), show_various_artists_(true),
sort_skips_articles_(true),
total_song_count_(0), total_song_count_(0),
total_artist_count_(0), total_artist_count_(0),
total_album_count_(0), total_album_count_(0),
separate_albums_by_grouping_(false), separate_albums_by_grouping_(false),
artist_icon_(IconLoader::Load("folder-sound")), artist_icon_(IconLoader::Load(QStringLiteral("folder-sound"))),
album_icon_(IconLoader::Load("cdcase")), album_icon_(IconLoader::Load(QStringLiteral("cdcase"))),
init_task_id_(-1), init_task_id_(-1),
use_pretty_covers_(true), use_pretty_covers_(true),
show_dividers_(true), show_dividers_(true),
@ -108,7 +110,7 @@ CollectionModel::CollectionModel(SharedPtr<CollectionBackend> backend, Applicati
QObject::connect(&*app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &CollectionModel::AlbumCoverLoaded); QObject::connect(&*app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &CollectionModel::AlbumCoverLoaded);
} }
QIcon nocover = IconLoader::Load("cdcase"); QIcon nocover = IconLoader::Load(QStringLiteral("cdcase"));
if (!nocover.isNull()) { if (!nocover.isNull()) {
QList<QSize> nocover_sizes = nocover.availableSizes(); QList<QSize> nocover_sizes = nocover.availableSizes();
no_cover_icon_ = nocover.pixmap(nocover_sizes.last()).scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); no_cover_icon_ = nocover.pixmap(nocover_sizes.last()).scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
@ -116,7 +118,7 @@ CollectionModel::CollectionModel(SharedPtr<CollectionBackend> backend, Applicati
if (app_ && !sIconCache) { if (app_ && !sIconCache) {
sIconCache = new QNetworkDiskCache(this); sIconCache = new QNetworkDiskCache(this);
sIconCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + kPixmapDiskCacheDir); sIconCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1Char('/') + QLatin1String(kPixmapDiskCacheDir));
QObject::connect(app_, &Application::ClearPixmapDiskCache, this, &CollectionModel::ClearDiskCache); QObject::connect(app_, &Application::ClearPixmapDiskCache, this, &CollectionModel::ClearDiskCache);
} }
@ -141,7 +143,9 @@ CollectionModel::~CollectionModel() {
qLog(Debug) << "Collection model" << this << "for" << Song::TextForSource(backend_->source()) << "deleted"; qLog(Debug) << "Collection model" << this << "for" << Song::TextForSource(backend_->source()) << "deleted";
delete root_; beginResetModel();
Clear();
endResetModel();
} }
@ -163,9 +167,18 @@ void CollectionModel::set_show_dividers(const bool show_dividers) {
} }
void CollectionModel::set_sort_skips_articles(const bool sort_skips_articles) {
if (sort_skips_articles != sort_skips_articles_) {
sort_skips_articles_ = sort_skips_articles;
Reset();
}
}
void CollectionModel::ReloadSettings() { void CollectionModel::ReloadSettings() {
QSettings s; Settings s;
s.beginGroup(CollectionSettingsPage::kSettingsGroup); s.beginGroup(CollectionSettingsPage::kSettingsGroup);
@ -189,6 +202,8 @@ void CollectionModel::ReloadSettings() {
void CollectionModel::Init(const bool async) { void CollectionModel::Init(const bool async) {
if (!root_) return;
if (async) { if (async) {
// Show a loading indicator in the model. // Show a loading indicator in the model.
CollectionItem *loading = new CollectionItem(CollectionItem::Type_LoadingIndicator, root_); CollectionItem *loading = new CollectionItem(CollectionItem::Type_LoadingIndicator, root_);
@ -212,6 +227,8 @@ void CollectionModel::Init(const bool async) {
void CollectionModel::SongsDiscovered(const SongList &songs) { void CollectionModel::SongsDiscovered(const SongList &songs) {
if (!root_) return;
for (const Song &song : songs) { for (const Song &song : songs) {
// Sanity check to make sure we don't add songs that are outside the user's filter // Sanity check to make sure we don't add songs that are outside the user's filter
@ -231,7 +248,7 @@ void CollectionModel::SongsDiscovered(const SongList &songs) {
GroupBy group_by = group_by_[i]; GroupBy group_by = group_by_[i];
if (group_by == GroupBy::None) break; if (group_by == GroupBy::None) break;
if (!key.isEmpty()) key.append("-"); if (!key.isEmpty()) key.append(QLatin1Char('-'));
// Special case: if the song is a compilation and the current GroupBy level is Artists, then we want the Various Artists node :( // Special case: if the song is a compilation and the current GroupBy level is Artists, then we want the Various Artists node :(
if (IsArtistGroupBy(group_by) && song.is_compilation()) { if (IsArtistGroupBy(group_by) && song.is_compilation()) {
@ -291,7 +308,7 @@ CollectionItem *CollectionModel::CreateCompilationArtistNode(const bool signal,
if (parent != root_ && !parent->key.isEmpty()) parent->compilation_artist_node_->key.append(parent->key); if (parent != root_ && !parent->key.isEmpty()) parent->compilation_artist_node_->key.append(parent->key);
parent->compilation_artist_node_->key.append(tr("Various artists")); parent->compilation_artist_node_->key.append(tr("Various artists"));
parent->compilation_artist_node_->display_text = tr("Various artists"); parent->compilation_artist_node_->display_text = tr("Various artists");
parent->compilation_artist_node_->sort_text = " various"; parent->compilation_artist_node_->sort_text = QStringLiteral(" various");
parent->compilation_artist_node_->container_level = parent->container_level + 1; parent->compilation_artist_node_->container_level = parent->container_level + 1;
if (signal) endInsertRows(); if (signal) endInsertRows();
@ -313,33 +330,33 @@ QString CollectionModel::ContainerKey(const GroupBy group_by, const bool separat
break; break;
case GroupBy::Album: case GroupBy::Album:
key = TextOrUnknown(song.album()); key = TextOrUnknown(song.album());
if (!song.album_id().isEmpty()) key.append("-" + song.album_id()); if (!song.album_id().isEmpty()) key.append(QLatin1Char('-') + song.album_id());
if (separate_albums_by_grouping && !song.grouping().isEmpty()) key.append("-" + song.grouping()); if (separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
break; break;
case GroupBy::AlbumDisc: case GroupBy::AlbumDisc:
key = PrettyAlbumDisc(song.album(), song.disc()); key = PrettyAlbumDisc(song.album(), song.disc());
if (!song.album_id().isEmpty()) key.append("-" + song.album_id()); if (!song.album_id().isEmpty()) key.append(QLatin1Char('-') + song.album_id());
if (separate_albums_by_grouping && !song.grouping().isEmpty()) key.append("-" + song.grouping()); if (separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
break; break;
case GroupBy::YearAlbum: case GroupBy::YearAlbum:
key = PrettyYearAlbum(song.year(), song.album()); key = PrettyYearAlbum(song.year(), song.album());
if (!song.album_id().isEmpty()) key.append("-" + song.album_id()); if (!song.album_id().isEmpty()) key.append(QLatin1Char('-') + song.album_id());
if (separate_albums_by_grouping && !song.grouping().isEmpty()) key.append("-" + song.grouping()); if (separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
break; break;
case GroupBy::YearAlbumDisc: case GroupBy::YearAlbumDisc:
key = PrettyYearAlbumDisc(song.year(), song.album(), song.disc()); key = PrettyYearAlbumDisc(song.year(), song.album(), song.disc());
if (!song.album_id().isEmpty()) key.append("-" + song.album_id()); if (!song.album_id().isEmpty()) key.append(QLatin1Char('-') + song.album_id());
if (separate_albums_by_grouping && !song.grouping().isEmpty()) key.append("-" + song.grouping()); if (separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
break; break;
case GroupBy::OriginalYearAlbum: case GroupBy::OriginalYearAlbum:
key = PrettyYearAlbum(song.effective_originalyear(), song.album()); key = PrettyYearAlbum(song.effective_originalyear(), song.album());
if (!song.album_id().isEmpty()) key.append("-" + song.album_id()); if (!song.album_id().isEmpty()) key.append(QLatin1Char('-') + song.album_id());
if (separate_albums_by_grouping && !song.grouping().isEmpty()) key.append("-" + song.grouping()); if (separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
break; break;
case GroupBy::OriginalYearAlbumDisc: case GroupBy::OriginalYearAlbumDisc:
key = PrettyYearAlbumDisc(song.effective_originalyear(), song.album(), song.disc()); key = PrettyYearAlbumDisc(song.effective_originalyear(), song.album(), song.disc());
if (!song.album_id().isEmpty()) key.append("-" + song.album_id()); if (!song.album_id().isEmpty()) key.append(QLatin1Char('-') + song.album_id());
if (separate_albums_by_grouping && !song.grouping().isEmpty()) key.append("-" + song.grouping()); if (separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
break; break;
case GroupBy::Disc: case GroupBy::Disc:
key = PrettyDisc(song.disc()); key = PrettyDisc(song.disc());
@ -380,10 +397,10 @@ QString CollectionModel::ContainerKey(const GroupBy group_by, const bool separat
} }
else { else {
if (song.bitdepth() <= 0) { if (song.bitdepth() <= 0) {
key = QString("%1 (%2)").arg(song.TextForFiletype(), QString::number(song.samplerate() / 1000.0, 'G', 5)); key = QStringLiteral("%1 (%2)").arg(song.TextForFiletype(), QString::number(song.samplerate() / 1000.0, 'G', 5));
} }
else { else {
key = QString("%1 (%2/%3)").arg(song.TextForFiletype(), QString::number(song.samplerate() / 1000.0, 'G', 5)).arg(song.bitdepth()); key = QStringLiteral("%1 (%2/%3)").arg(song.TextForFiletype(), QString::number(song.samplerate() / 1000.0, 'G', 5)).arg(song.bitdepth());
} }
} }
break; break;
@ -414,10 +431,10 @@ QString CollectionModel::DividerKey(const GroupBy group_by, CollectionItem *item
case GroupBy::Disc: case GroupBy::Disc:
case GroupBy::Genre: case GroupBy::Genre:
case GroupBy::Format: case GroupBy::Format:
case GroupBy::FileType: { case GroupBy::FileType:{
QChar c = item->sort_text[0]; QChar c = item->sort_text[0];
if (c.isDigit()) return "0"; if (c.isDigit()) return QStringLiteral("0");
if (c == ' ') return QString(); if (c == QLatin1Char(' ')) return QString();
if (c.decompositionTag() != QChar::NoDecomposition) { if (c.decompositionTag() != QChar::NoDecomposition) {
QString decomposition = c.decomposition(); QString decomposition = c.decomposition();
return QChar(decomposition[0]); return QChar(decomposition[0]);
@ -471,31 +488,31 @@ QString CollectionModel::DividerDisplayText(const GroupBy group_by, const QStrin
case GroupBy::Genre: case GroupBy::Genre:
case GroupBy::FileType: case GroupBy::FileType:
case GroupBy::Format: case GroupBy::Format:
if (key == "0") return "0-9"; if (key == QLatin1String("0")) return QStringLiteral("0-9");
return key.toUpper(); return key.toUpper();
case GroupBy::YearAlbum: case GroupBy::YearAlbum:
case GroupBy::YearAlbumDisc: case GroupBy::YearAlbumDisc:
case GroupBy::OriginalYearAlbum: case GroupBy::OriginalYearAlbum:
case GroupBy::OriginalYearAlbumDisc: case GroupBy::OriginalYearAlbumDisc:
if (key == "0000") return tr("Unknown"); if (key == QStringLiteral("0000")) return tr("Unknown");
return key.toUpper(); return key.toUpper();
case GroupBy::Year: case GroupBy::Year:
case GroupBy::OriginalYear: case GroupBy::OriginalYear:
if (key == "0000") return tr("Unknown"); if (key == QStringLiteral("0000")) return tr("Unknown");
return QString::number(key.toInt()); // To remove leading 0s return QString::number(key.toInt()); // To remove leading 0s
case GroupBy::Samplerate: case GroupBy::Samplerate:
if (key == "000") return tr("Unknown"); if (key == QStringLiteral("000")) return tr("Unknown");
return QString::number(key.toInt()); // To remove leading 0s return QString::number(key.toInt()); // To remove leading 0s
case GroupBy::Bitdepth: case GroupBy::Bitdepth:
if (key == "000") return tr("Unknown"); if (key == QStringLiteral("000")) return tr("Unknown");
return QString::number(key.toInt()); // To remove leading 0s return QString::number(key.toInt()); // To remove leading 0s
case GroupBy::Bitrate: case GroupBy::Bitrate:
if (key == "000") return tr("Unknown"); if (key == QStringLiteral("000")) return tr("Unknown");
return QString::number(key.toInt()); // To remove leading 0s return QString::number(key.toInt()); // To remove leading 0s
case GroupBy::None: case GroupBy::None:
@ -509,6 +526,8 @@ QString CollectionModel::DividerDisplayText(const GroupBy group_by, const QStrin
void CollectionModel::SongsDeleted(const SongList &songs) { void CollectionModel::SongsDeleted(const SongList &songs) {
if (!root_) return;
// Delete the actual song nodes first, keeping track of each parent so we might check to see if they're empty later. // Delete the actual song nodes first, keeping track of each parent so we might check to see if they're empty later.
QSet<CollectionItem*> parents; QSet<CollectionItem*> parents;
for (const Song &song : songs) { for (const Song &song : songs) {
@ -613,13 +632,13 @@ QString CollectionModel::AlbumIconPixmapCacheKey(const QModelIndex &idx) const {
idx_copy = idx_copy.parent(); idx_copy = idx_copy.parent();
} }
return Song::TextForSource(backend_->source()) + "/" + path.join("/"); return Song::TextForSource(backend_->source()) + QLatin1Char('/') + path.join(QLatin1Char('/'));
} }
QUrl CollectionModel::AlbumIconPixmapDiskCacheKey(const QString &cache_key) const { QUrl CollectionModel::AlbumIconPixmapDiskCacheKey(const QString &cache_key) const {
return QUrl(QUrl::toPercentEncoding(cache_key)); return QUrl(QString::fromLatin1(QUrl::toPercentEncoding(cache_key)));
} }
@ -700,7 +719,7 @@ void CollectionModel::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderR
disk_cache_metadata.setSaveToDisk(true); disk_cache_metadata.setSaveToDisk(true);
disk_cache_metadata.setUrl(disk_cache_key); disk_cache_metadata.setUrl(disk_cache_key);
// Qt 6 now ignores any entry without headers, so add a fake header. // Qt 6 now ignores any entry without headers, so add a fake header.
disk_cache_metadata.setRawHeaders(QNetworkCacheMetaData::RawHeaderList() << qMakePair(QByteArray(), QByteArray())); disk_cache_metadata.setRawHeaders(QNetworkCacheMetaData::RawHeaderList() << qMakePair(QByteArray("collection-thumbnail"), cache_key.toUtf8()));
QIODevice *device_iconcache = sIconCache->prepare(disk_cache_metadata); QIODevice *device_iconcache = sIconCache->prepare(disk_cache_metadata);
if (device_iconcache) { if (device_iconcache) {
result.image_scaled.save(device_iconcache, "XPM"); result.image_scaled.save(device_iconcache, "XPM");
@ -913,6 +932,8 @@ CollectionModel::QueryResult CollectionModel::RunQuery(const CollectionFilterOpt
void CollectionModel::PostQuery(CollectionItem *parent, const CollectionModel::QueryResult &result, const bool signal) { void CollectionModel::PostQuery(CollectionItem *parent, const CollectionModel::QueryResult &result, const bool signal) {
if (!root_) return;
// Information about what we want the children to be // Information about what we want the children to be
int child_level = parent == root_ ? 0 : parent->container_level + 1; int child_level = parent == root_ ? 0 : parent->container_level + 1;
GroupBy child_group_by = child_level >= 3 ? GroupBy::None : group_by_[child_level]; GroupBy child_group_by = child_level >= 3 ? GroupBy::None : group_by_[child_level];
@ -939,6 +960,8 @@ void CollectionModel::PostQuery(CollectionItem *parent, const CollectionModel::Q
void CollectionModel::LazyPopulate(CollectionItem *parent, const bool signal) { void CollectionModel::LazyPopulate(CollectionItem *parent, const bool signal) {
if (!root_) return;
if (parent->lazy_loaded) return; if (parent->lazy_loaded) return;
parent->lazy_loaded = true; parent->lazy_loaded = true;
@ -950,6 +973,8 @@ void CollectionModel::LazyPopulate(CollectionItem *parent, const bool signal) {
void CollectionModel::ResetAsync() { void CollectionModel::ResetAsync() {
if (!root_) return;
CollectionQueryOptions query_options = PrepareQuery(root_); CollectionQueryOptions query_options = PrepareQuery(root_);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
@ -965,6 +990,8 @@ void CollectionModel::ResetAsync() {
void CollectionModel::ResetAsyncQueryFinished() { void CollectionModel::ResetAsyncQueryFinished() {
if (!root_) return;
QFutureWatcher<CollectionModel::QueryResult> *watcher = static_cast<QFutureWatcher<CollectionModel::QueryResult>*>(sender()); QFutureWatcher<CollectionModel::QueryResult> *watcher = static_cast<QFutureWatcher<CollectionModel::QueryResult>*>(sender());
const struct QueryResult result = watcher->result(); const struct QueryResult result = watcher->result();
watcher->deleteLater(); watcher->deleteLater();
@ -985,10 +1012,12 @@ void CollectionModel::ResetAsyncQueryFinished() {
} }
void CollectionModel::BeginReset() { void CollectionModel::Clear() {
beginResetModel(); if (root_) {
delete root_; delete root_;
root_ = nullptr;
}
song_nodes_.clear(); song_nodes_.clear();
container_nodes_[0].clear(); container_nodes_[0].clear();
container_nodes_[1].clear(); container_nodes_[1].clear();
@ -997,6 +1026,13 @@ void CollectionModel::BeginReset() {
pending_art_.clear(); pending_art_.clear();
pending_cache_keys_.clear(); pending_cache_keys_.clear();
}
void CollectionModel::BeginReset() {
beginResetModel();
Clear();
root_ = new CollectionItem(this); root_ = new CollectionItem(this);
root_->compilation_artist_node_ = nullptr; root_->compilation_artist_node_ = nullptr;
root_->lazy_loaded = false; root_->lazy_loaded = false;
@ -1019,86 +1055,86 @@ void CollectionModel::SetQueryColumnSpec(const GroupBy group_by, const bool sepa
// Say what group_by of thing we want to get back from the database. // Say what group_by of thing we want to get back from the database.
switch (group_by) { switch (group_by) {
case GroupBy::AlbumArtist: case GroupBy::AlbumArtist:
query_options->set_column_spec("DISTINCT effective_albumartist"); query_options->set_column_spec(QStringLiteral("DISTINCT effective_albumartist"));
break; break;
case GroupBy::Artist: case GroupBy::Artist:
query_options->set_column_spec("DISTINCT artist"); query_options->set_column_spec(QStringLiteral("DISTINCT artist"));
break; break;
case GroupBy::Album:{ case GroupBy::Album:{
QString query("DISTINCT album, album_id"); QString query(QStringLiteral("DISTINCT album, album_id"));
if (separate_albums_by_grouping) query.append(", grouping"); if (separate_albums_by_grouping) query.append(QStringLiteral(", grouping"));
query_options->set_column_spec(query); query_options->set_column_spec(query);
break; break;
} }
case GroupBy::AlbumDisc:{ case GroupBy::AlbumDisc:{
QString query("DISTINCT album, album_id, disc"); QString query(QStringLiteral("DISTINCT album, album_id, disc"));
if (separate_albums_by_grouping) query.append(", grouping"); if (separate_albums_by_grouping) query.append(QStringLiteral(", grouping"));
query_options->set_column_spec(query); query_options->set_column_spec(query);
break; break;
} }
case GroupBy::YearAlbum:{ case GroupBy::YearAlbum:{
QString query("DISTINCT year, album, album_id"); QString query(QStringLiteral("DISTINCT year, album, album_id"));
if (separate_albums_by_grouping) query.append(", grouping"); if (separate_albums_by_grouping) query.append(QStringLiteral(", grouping"));
query_options->set_column_spec(query); query_options->set_column_spec(query);
break; break;
} }
case GroupBy::YearAlbumDisc:{ case GroupBy::YearAlbumDisc:{
QString query("DISTINCT year, album, album_id, disc"); QString query(QStringLiteral("DISTINCT year, album, album_id, disc"));
if (separate_albums_by_grouping) query.append(", grouping"); if (separate_albums_by_grouping) query.append(QStringLiteral(", grouping"));
query_options->set_column_spec(query); query_options->set_column_spec(query);
break; break;
} }
case GroupBy::OriginalYearAlbum:{ case GroupBy::OriginalYearAlbum:{
QString query("DISTINCT year, originalyear, album, album_id"); QString query(QStringLiteral("DISTINCT year, originalyear, album, album_id"));
if (separate_albums_by_grouping) query.append(", grouping"); if (separate_albums_by_grouping) query.append(QStringLiteral(", grouping"));
query_options->set_column_spec(query); query_options->set_column_spec(query);
break; break;
} }
case GroupBy::OriginalYearAlbumDisc:{ case GroupBy::OriginalYearAlbumDisc:{
QString query("DISTINCT year, originalyear, album, album_id, disc"); QString query(QStringLiteral("DISTINCT year, originalyear, album, album_id, disc"));
if (separate_albums_by_grouping) query.append(", grouping"); if (separate_albums_by_grouping) query.append(QStringLiteral(", grouping"));
query_options->set_column_spec(query); query_options->set_column_spec(query);
break; break;
} }
case GroupBy::Disc: case GroupBy::Disc:
query_options->set_column_spec("DISTINCT disc"); query_options->set_column_spec(QStringLiteral("DISTINCT disc"));
break; break;
case GroupBy::Year: case GroupBy::Year:
query_options->set_column_spec("DISTINCT year"); query_options->set_column_spec(QStringLiteral("DISTINCT year"));
break; break;
case GroupBy::OriginalYear: case GroupBy::OriginalYear:
query_options->set_column_spec("DISTINCT effective_originalyear"); query_options->set_column_spec(QStringLiteral("DISTINCT effective_originalyear"));
break; break;
case GroupBy::Genre: case GroupBy::Genre:
query_options->set_column_spec("DISTINCT genre"); query_options->set_column_spec(QStringLiteral("DISTINCT genre"));
break; break;
case GroupBy::Composer: case GroupBy::Composer:
query_options->set_column_spec("DISTINCT composer"); query_options->set_column_spec(QStringLiteral("DISTINCT composer"));
break; break;
case GroupBy::Performer: case GroupBy::Performer:
query_options->set_column_spec("DISTINCT performer"); query_options->set_column_spec(QStringLiteral("DISTINCT performer"));
break; break;
case GroupBy::Grouping: case GroupBy::Grouping:
query_options->set_column_spec("DISTINCT grouping"); query_options->set_column_spec(QStringLiteral("DISTINCT grouping"));
break; break;
case GroupBy::FileType: case GroupBy::FileType:
query_options->set_column_spec("DISTINCT filetype"); query_options->set_column_spec(QStringLiteral("DISTINCT filetype"));
break; break;
case GroupBy::Format: case GroupBy::Format:
query_options->set_column_spec("DISTINCT filetype, samplerate, bitdepth"); query_options->set_column_spec(QStringLiteral("DISTINCT filetype, samplerate, bitdepth"));
break; break;
case GroupBy::Samplerate: case GroupBy::Samplerate:
query_options->set_column_spec("DISTINCT samplerate"); query_options->set_column_spec(QStringLiteral("DISTINCT samplerate"));
break; break;
case GroupBy::Bitdepth: case GroupBy::Bitdepth:
query_options->set_column_spec("DISTINCT bitdepth"); query_options->set_column_spec(QStringLiteral("DISTINCT bitdepth"));
break; break;
case GroupBy::Bitrate: case GroupBy::Bitrate:
query_options->set_column_spec("DISTINCT bitrate"); query_options->set_column_spec(QStringLiteral("DISTINCT bitrate"));
break; break;
case GroupBy::None: case GroupBy::None:
case GroupBy::GroupByCount: case GroupBy::GroupByCount:
query_options->set_column_spec("%songs_table.ROWID, " + Song::kColumnSpec); query_options->set_column_spec(QStringLiteral("%songs_table.ROWID, ") + Song::kColumnSpec);
break; break;
} }
@ -1116,7 +1152,7 @@ void CollectionModel::AddQueryWhere(const GroupBy group_by, const bool separate_
else { else {
// Don't duplicate compilations outside the Various artists node // Don't duplicate compilations outside the Various artists node
query_options->set_compilation_requirement(CollectionQueryOptions::CompilationRequirement::Off); query_options->set_compilation_requirement(CollectionQueryOptions::CompilationRequirement::Off);
query_options->AddWhere("effective_albumartist", item->metadata.effective_albumartist()); query_options->AddWhere(QStringLiteral("effective_albumartist"), item->metadata.effective_albumartist());
} }
break; break;
case GroupBy::Artist: case GroupBy::Artist:
@ -1126,85 +1162,85 @@ void CollectionModel::AddQueryWhere(const GroupBy group_by, const bool separate_
else { else {
// Don't duplicate compilations outside the Various artists node // Don't duplicate compilations outside the Various artists node
query_options->set_compilation_requirement(CollectionQueryOptions::CompilationRequirement::Off); query_options->set_compilation_requirement(CollectionQueryOptions::CompilationRequirement::Off);
query_options->AddWhere("artist", item->metadata.artist()); query_options->AddWhere(QStringLiteral("artist"), item->metadata.artist());
} }
break; break;
case GroupBy::Album: case GroupBy::Album:
query_options->AddWhere("album", item->metadata.album()); query_options->AddWhere(QStringLiteral("album"), item->metadata.album());
query_options->AddWhere("album_id", item->metadata.album_id()); query_options->AddWhere(QStringLiteral("album_id"), item->metadata.album_id());
if (separate_albums_by_grouping) query_options->AddWhere("grouping", item->metadata.grouping()); if (separate_albums_by_grouping) query_options->AddWhere(QStringLiteral("grouping"), item->metadata.grouping());
break; break;
case GroupBy::AlbumDisc: case GroupBy::AlbumDisc:
query_options->AddWhere("album", item->metadata.album()); query_options->AddWhere(QStringLiteral("album"), item->metadata.album());
query_options->AddWhere("album_id", item->metadata.album_id()); query_options->AddWhere(QStringLiteral("album_id"), item->metadata.album_id());
query_options->AddWhere("disc", item->metadata.disc()); query_options->AddWhere(QStringLiteral("disc"), item->metadata.disc());
if (separate_albums_by_grouping) query_options->AddWhere("grouping", item->metadata.grouping()); if (separate_albums_by_grouping) query_options->AddWhere(QStringLiteral("grouping"), item->metadata.grouping());
break; break;
case GroupBy::YearAlbum: case GroupBy::YearAlbum:
query_options->AddWhere("year", item->metadata.year()); query_options->AddWhere(QStringLiteral("year"), item->metadata.year());
query_options->AddWhere("album", item->metadata.album()); query_options->AddWhere(QStringLiteral("album"), item->metadata.album());
query_options->AddWhere("album_id", item->metadata.album_id()); query_options->AddWhere(QStringLiteral("album_id"), item->metadata.album_id());
if (separate_albums_by_grouping) query_options->AddWhere("grouping", item->metadata.grouping()); if (separate_albums_by_grouping) query_options->AddWhere(QStringLiteral("grouping"), item->metadata.grouping());
break; break;
case GroupBy::YearAlbumDisc: case GroupBy::YearAlbumDisc:
query_options->AddWhere("year", item->metadata.year()); query_options->AddWhere(QStringLiteral("year"), item->metadata.year());
query_options->AddWhere("album", item->metadata.album()); query_options->AddWhere(QStringLiteral("album"), item->metadata.album());
query_options->AddWhere("album_id", item->metadata.album_id()); query_options->AddWhere(QStringLiteral("album_id"), item->metadata.album_id());
query_options->AddWhere("disc", item->metadata.disc()); query_options->AddWhere(QStringLiteral("disc"), item->metadata.disc());
if (separate_albums_by_grouping) query_options->AddWhere("grouping", item->metadata.grouping()); if (separate_albums_by_grouping) query_options->AddWhere(QStringLiteral("grouping"), item->metadata.grouping());
break; break;
case GroupBy::OriginalYearAlbum: case GroupBy::OriginalYearAlbum:
query_options->AddWhere("year", item->metadata.year()); query_options->AddWhere(QStringLiteral("year"), item->metadata.year());
query_options->AddWhere("originalyear", item->metadata.originalyear()); query_options->AddWhere(QStringLiteral("originalyear"), item->metadata.originalyear());
query_options->AddWhere("album", item->metadata.album()); query_options->AddWhere(QStringLiteral("album"), item->metadata.album());
query_options->AddWhere("album_id", item->metadata.album_id()); query_options->AddWhere(QStringLiteral("album_id"), item->metadata.album_id());
if (separate_albums_by_grouping) query_options->AddWhere("grouping", item->metadata.grouping()); if (separate_albums_by_grouping) query_options->AddWhere(QStringLiteral("grouping"), item->metadata.grouping());
break; break;
case GroupBy::OriginalYearAlbumDisc: case GroupBy::OriginalYearAlbumDisc:
query_options->AddWhere("year", item->metadata.year()); query_options->AddWhere(QStringLiteral("year"), item->metadata.year());
query_options->AddWhere("originalyear", item->metadata.originalyear()); query_options->AddWhere(QStringLiteral("originalyear"), item->metadata.originalyear());
query_options->AddWhere("album", item->metadata.album()); query_options->AddWhere(QStringLiteral("album"), item->metadata.album());
query_options->AddWhere("album_id", item->metadata.album_id()); query_options->AddWhere(QStringLiteral("album_id"), item->metadata.album_id());
query_options->AddWhere("disc", item->metadata.disc()); query_options->AddWhere(QStringLiteral("disc"), item->metadata.disc());
if (separate_albums_by_grouping) query_options->AddWhere("grouping", item->metadata.grouping()); if (separate_albums_by_grouping) query_options->AddWhere(QStringLiteral("grouping"), item->metadata.grouping());
break; break;
case GroupBy::Disc: case GroupBy::Disc:
query_options->AddWhere("disc", item->metadata.disc()); query_options->AddWhere(QStringLiteral("disc"), item->metadata.disc());
break; break;
case GroupBy::Year: case GroupBy::Year:
query_options->AddWhere("year", item->metadata.year()); query_options->AddWhere(QStringLiteral("year"), item->metadata.year());
break; break;
case GroupBy::OriginalYear: case GroupBy::OriginalYear:
query_options->AddWhere("effective_originalyear", item->metadata.effective_originalyear()); query_options->AddWhere(QStringLiteral("effective_originalyear"), item->metadata.effective_originalyear());
break; break;
case GroupBy::Genre: case GroupBy::Genre:
query_options->AddWhere("genre", item->metadata.genre()); query_options->AddWhere(QStringLiteral("genre"), item->metadata.genre());
break; break;
case GroupBy::Composer: case GroupBy::Composer:
query_options->AddWhere("composer", item->metadata.composer()); query_options->AddWhere(QStringLiteral("composer"), item->metadata.composer());
break; break;
case GroupBy::Performer: case GroupBy::Performer:
query_options->AddWhere("performer", item->metadata.performer()); query_options->AddWhere(QStringLiteral("performer"), item->metadata.performer());
break; break;
case GroupBy::Grouping: case GroupBy::Grouping:
query_options->AddWhere("grouping", item->metadata.grouping()); query_options->AddWhere(QStringLiteral("grouping"), item->metadata.grouping());
break; break;
case GroupBy::FileType: case GroupBy::FileType:
query_options->AddWhere("filetype", static_cast<int>(item->metadata.filetype())); query_options->AddWhere(QStringLiteral("filetype"), static_cast<int>(item->metadata.filetype()));
break; break;
case GroupBy::Format: case GroupBy::Format:
query_options->AddWhere("filetype", static_cast<int>(item->metadata.filetype())); query_options->AddWhere(QStringLiteral("filetype"), static_cast<int>(item->metadata.filetype()));
query_options->AddWhere("samplerate", item->metadata.samplerate()); query_options->AddWhere(QStringLiteral("samplerate"), item->metadata.samplerate());
query_options->AddWhere("bitdepth", item->metadata.bitdepth()); query_options->AddWhere(QStringLiteral("bitdepth"), item->metadata.bitdepth());
break; break;
case GroupBy::Samplerate: case GroupBy::Samplerate:
query_options->AddWhere("samplerate", item->metadata.samplerate()); query_options->AddWhere(QStringLiteral("samplerate"), item->metadata.samplerate());
break; break;
case GroupBy::Bitdepth: case GroupBy::Bitdepth:
query_options->AddWhere("bitdepth", item->metadata.bitdepth()); query_options->AddWhere(QStringLiteral("bitdepth"), item->metadata.bitdepth());
break; break;
case GroupBy::Bitrate: case GroupBy::Bitrate:
query_options->AddWhere("bitrate", item->metadata.bitrate()); query_options->AddWhere(QStringLiteral("bitrate"), item->metadata.bitrate());
break; break;
case GroupBy::None: case GroupBy::None:
case GroupBy::GroupByCount: case GroupBy::GroupByCount:
@ -1234,7 +1270,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
CollectionItem *item = InitItem(group_by, signal, parent, container_level); CollectionItem *item = InitItem(group_by, signal, parent, container_level);
if (parent != root_ && !parent->key.isEmpty()) { if (parent != root_ && !parent->key.isEmpty()) {
item->key = parent->key + "-"; item->key = parent->key + QLatin1Char('-');
} }
switch (group_by) { switch (group_by) {
@ -1242,30 +1278,30 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
item->metadata.set_albumartist(row.value(0).toString()); item->metadata.set_albumartist(row.value(0).toString());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.albumartist()); item->display_text = TextOrUnknown(item->metadata.albumartist());
item->sort_text = SortTextForArtist(item->metadata.albumartist()); item->sort_text = SortTextForArtist(item->metadata.albumartist(), sort_skips_articles_);
break; break;
} }
case GroupBy::Artist:{ case GroupBy::Artist:{
item->metadata.set_artist(row.value(0).toString()); item->metadata.set_artist(row.value(0).toString());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.artist()); item->display_text = TextOrUnknown(item->metadata.artist());
item->sort_text = SortTextForArtist(item->metadata.artist()); item->sort_text = SortTextForArtist(item->metadata.artist(), sort_skips_articles_);
break; break;
} }
case GroupBy::Album:{ case GroupBy::Album:{
item->metadata.set_album(row.value(0).toString()); item->metadata.set_album(row.value(0).toString());
item->metadata.set_album_id(row.value(1).toString()); item->metadata.set_album_id(row.value(1).toString());
item->metadata.set_grouping(row.value(2).toString()); if (separate_albums_by_grouping) item->metadata.set_grouping(row.value(2).toString());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.album()); item->display_text = TextOrUnknown(item->metadata.album());
item->sort_text = SortTextForArtist(item->metadata.album()); item->sort_text = SortTextForArtist(item->metadata.album(), sort_skips_articles_);
break; break;
} }
case GroupBy::AlbumDisc:{ case GroupBy::AlbumDisc:{
item->metadata.set_album(row.value(0).toString()); item->metadata.set_album(row.value(0).toString());
item->metadata.set_album_id(row.value(1).toString()); item->metadata.set_album_id(row.value(1).toString());
item->metadata.set_disc(row.value(2).toInt()); item->metadata.set_disc(row.value(2).toInt());
item->metadata.set_grouping(row.value(3).toString()); if (separate_albums_by_grouping) item->metadata.set_grouping(row.value(3).toString());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = PrettyAlbumDisc(item->metadata.album(), item->metadata.disc()); item->display_text = PrettyAlbumDisc(item->metadata.album(), item->metadata.disc());
item->sort_text = item->metadata.album() + SortTextForNumber(std::max(0, item->metadata.disc())); item->sort_text = item->metadata.album() + SortTextForNumber(std::max(0, item->metadata.disc()));
@ -1275,7 +1311,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
item->metadata.set_year(row.value(0).toInt()); item->metadata.set_year(row.value(0).toInt());
item->metadata.set_album(row.value(1).toString()); item->metadata.set_album(row.value(1).toString());
item->metadata.set_album_id(row.value(2).toString()); item->metadata.set_album_id(row.value(2).toString());
item->metadata.set_grouping(row.value(3).toString()); if (separate_albums_by_grouping) item->metadata.set_grouping(row.value(3).toString());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = PrettyYearAlbum(item->metadata.year(), item->metadata.album()); item->display_text = PrettyYearAlbum(item->metadata.year(), item->metadata.album());
item->sort_text = SortTextForNumber(std::max(0, item->metadata.year())) + item->metadata.grouping() + item->metadata.album(); item->sort_text = SortTextForNumber(std::max(0, item->metadata.year())) + item->metadata.grouping() + item->metadata.album();
@ -1286,7 +1322,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
item->metadata.set_album(row.value(1).toString()); item->metadata.set_album(row.value(1).toString());
item->metadata.set_album_id(row.value(2).toString()); item->metadata.set_album_id(row.value(2).toString());
item->metadata.set_disc(row.value(3).toInt()); item->metadata.set_disc(row.value(3).toInt());
item->metadata.set_grouping(row.value(4).toString()); if (separate_albums_by_grouping) item->metadata.set_grouping(row.value(4).toString());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = PrettyYearAlbumDisc(item->metadata.year(), item->metadata.album(), item->metadata.disc()); item->display_text = PrettyYearAlbumDisc(item->metadata.year(), item->metadata.album(), item->metadata.disc());
item->sort_text = SortTextForNumber(std::max(0, item->metadata.year())) + item->metadata.album() + SortTextForNumber(std::max(0, item->metadata.disc())); item->sort_text = SortTextForNumber(std::max(0, item->metadata.year())) + item->metadata.album() + SortTextForNumber(std::max(0, item->metadata.disc()));
@ -1297,7 +1333,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
item->metadata.set_originalyear(row.value(1).toInt()); item->metadata.set_originalyear(row.value(1).toInt());
item->metadata.set_album(row.value(2).toString()); item->metadata.set_album(row.value(2).toString());
item->metadata.set_album_id(row.value(3).toString()); item->metadata.set_album_id(row.value(3).toString());
item->metadata.set_grouping(row.value(4).toString()); if (separate_albums_by_grouping) item->metadata.set_grouping(row.value(4).toString());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = PrettyYearAlbum(item->metadata.effective_originalyear(), item->metadata.album()); item->display_text = PrettyYearAlbum(item->metadata.effective_originalyear(), item->metadata.album());
item->sort_text = SortTextForNumber(std::max(0, item->metadata.effective_originalyear())) + item->metadata.grouping() + item->metadata.album(); item->sort_text = SortTextForNumber(std::max(0, item->metadata.effective_originalyear())) + item->metadata.grouping() + item->metadata.album();
@ -1309,7 +1345,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
item->metadata.set_album(row.value(2).toString()); item->metadata.set_album(row.value(2).toString());
item->metadata.set_album_id(row.value(3).toString()); item->metadata.set_album_id(row.value(3).toString());
item->metadata.set_disc(row.value(4).toInt()); item->metadata.set_disc(row.value(4).toInt());
item->metadata.set_grouping(row.value(5).toString()); if (separate_albums_by_grouping) item->metadata.set_grouping(row.value(5).toString());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = PrettyYearAlbumDisc(item->metadata.effective_originalyear(), item->metadata.album(), item->metadata.disc()); item->display_text = PrettyYearAlbumDisc(item->metadata.effective_originalyear(), item->metadata.album(), item->metadata.disc());
item->sort_text = SortTextForNumber(std::max(0, item->metadata.effective_originalyear())) + item->metadata.album() + SortTextForNumber(std::max(0, item->metadata.disc())); item->sort_text = SortTextForNumber(std::max(0, item->metadata.effective_originalyear())) + item->metadata.album() + SortTextForNumber(std::max(0, item->metadata.disc()));
@ -1328,7 +1364,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
const int year = std::max(0, item->metadata.year()); const int year = std::max(0, item->metadata.year());
item->display_text = QString::number(year); item->display_text = QString::number(year);
item->sort_text = SortTextForNumber(year) + " "; item->sort_text = SortTextForNumber(year) + QLatin1Char(' ');
break; break;
} }
case GroupBy::OriginalYear:{ case GroupBy::OriginalYear:{
@ -1336,35 +1372,35 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
const int year = std::max(0, item->metadata.originalyear()); const int year = std::max(0, item->metadata.originalyear());
item->display_text = QString::number(year); item->display_text = QString::number(year);
item->sort_text = SortTextForNumber(year) + " "; item->sort_text = SortTextForNumber(year) + QLatin1Char(' ');
break; break;
} }
case GroupBy::Genre:{ case GroupBy::Genre:{
item->metadata.set_genre(row.value(0).toString()); item->metadata.set_genre(row.value(0).toString());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.genre()); item->display_text = TextOrUnknown(item->metadata.genre());
item->sort_text = SortTextForArtist(item->metadata.genre()); item->sort_text = SortTextForArtist(item->metadata.genre(), sort_skips_articles_);
break; break;
} }
case GroupBy::Composer:{ case GroupBy::Composer:{
item->metadata.set_composer(row.value(0).toString()); item->metadata.set_composer(row.value(0).toString());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.composer()); item->display_text = TextOrUnknown(item->metadata.composer());
item->sort_text = SortTextForArtist(item->metadata.composer()); item->sort_text = SortTextForArtist(item->metadata.composer(), sort_skips_articles_);
break; break;
} }
case GroupBy::Performer:{ case GroupBy::Performer:{
item->metadata.set_performer(row.value(0).toString()); item->metadata.set_performer(row.value(0).toString());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.performer()); item->display_text = TextOrUnknown(item->metadata.performer());
item->sort_text = SortTextForArtist(item->metadata.performer()); item->sort_text = SortTextForArtist(item->metadata.performer(), sort_skips_articles_);
break; break;
} }
case GroupBy::Grouping:{ case GroupBy::Grouping:{
item->metadata.set_grouping(row.value(0).toString()); item->metadata.set_grouping(row.value(0).toString());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.grouping()); item->display_text = TextOrUnknown(item->metadata.grouping());
item->sort_text = SortTextForArtist(item->metadata.grouping()); item->sort_text = SortTextForArtist(item->metadata.grouping(), sort_skips_articles_);
break; break;
} }
case GroupBy::FileType:{ case GroupBy::FileType:{
@ -1389,7 +1425,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
const int samplerate = std::max(0, item->metadata.samplerate()); const int samplerate = std::max(0, item->metadata.samplerate());
item->display_text = QString::number(samplerate); item->display_text = QString::number(samplerate);
item->sort_text = SortTextForNumber(samplerate) + " "; item->sort_text = SortTextForNumber(samplerate) + QLatin1Char(' ');
break; break;
} }
case GroupBy::Bitdepth:{ case GroupBy::Bitdepth:{
@ -1397,7 +1433,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
const int bitdepth = std::max(0, item->metadata.bitdepth()); const int bitdepth = std::max(0, item->metadata.bitdepth());
item->display_text = QString::number(bitdepth); item->display_text = QString::number(bitdepth);
item->sort_text = SortTextForNumber(bitdepth) + " "; item->sort_text = SortTextForNumber(bitdepth) + QLatin1Char(' ');
break; break;
} }
case GroupBy::Bitrate:{ case GroupBy::Bitrate:{
@ -1405,7 +1441,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
const int bitrate = std::max(0, item->metadata.bitrate()); const int bitrate = std::max(0, item->metadata.bitrate());
item->display_text = QString::number(bitrate); item->display_text = QString::number(bitrate);
item->sort_text = SortTextForNumber(bitrate) + " "; item->sort_text = SortTextForNumber(bitrate) + QLatin1Char(' ');
break; break;
} }
case GroupBy::None: case GroupBy::None:
@ -1433,7 +1469,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
CollectionItem *item = InitItem(group_by, signal, parent, container_level); CollectionItem *item = InitItem(group_by, signal, parent, container_level);
if (parent != root_ && !parent->key.isEmpty()) { if (parent != root_ && !parent->key.isEmpty()) {
item->key = parent->key + "-"; item->key = parent->key + QLatin1Char('-');
} }
switch (group_by) { switch (group_by) {
@ -1441,14 +1477,14 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
item->metadata.set_albumartist(s.effective_albumartist()); item->metadata.set_albumartist(s.effective_albumartist());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.effective_albumartist()); item->display_text = TextOrUnknown(s.effective_albumartist());
item->sort_text = SortTextForArtist(s.effective_albumartist()); item->sort_text = SortTextForArtist(s.effective_albumartist(), sort_skips_articles_);
break; break;
} }
case GroupBy::Artist:{ case GroupBy::Artist:{
item->metadata.set_artist(s.artist()); item->metadata.set_artist(s.artist());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.artist()); item->display_text = TextOrUnknown(s.artist());
item->sort_text = SortTextForArtist(s.artist()); item->sort_text = SortTextForArtist(s.artist(), sort_skips_articles_);
break; break;
} }
case GroupBy::Album:{ case GroupBy::Album:{
@ -1457,7 +1493,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
item->metadata.set_grouping(s.grouping()); item->metadata.set_grouping(s.grouping());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.album()); item->display_text = TextOrUnknown(s.album());
item->sort_text = SortTextForArtist(s.album()); item->sort_text = SortTextForArtist(s.album(), sort_skips_articles_);
break; break;
} }
case GroupBy::AlbumDisc:{ case GroupBy::AlbumDisc:{
@ -1527,7 +1563,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
const int year = std::max(0, s.year()); const int year = std::max(0, s.year());
item->display_text = QString::number(year); item->display_text = QString::number(year);
item->sort_text = SortTextForNumber(year) + " "; item->sort_text = SortTextForNumber(year) + QLatin1Char(' ');
break; break;
} }
case GroupBy::OriginalYear:{ case GroupBy::OriginalYear:{
@ -1535,35 +1571,35 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
const int year = std::max(0, s.effective_originalyear()); const int year = std::max(0, s.effective_originalyear());
item->display_text = QString::number(year); item->display_text = QString::number(year);
item->sort_text = SortTextForNumber(year) + " "; item->sort_text = SortTextForNumber(year) + QLatin1Char(' ');
break; break;
} }
case GroupBy::Genre:{ case GroupBy::Genre:{
item->metadata.set_genre(s.genre()); item->metadata.set_genre(s.genre());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.genre()); item->display_text = TextOrUnknown(s.genre());
item->sort_text = SortTextForArtist(s.genre()); item->sort_text = SortTextForArtist(s.genre(), sort_skips_articles_);
break; break;
} }
case GroupBy::Composer:{ case GroupBy::Composer:{
item->metadata.set_composer(s.composer()); item->metadata.set_composer(s.composer());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.composer()); item->display_text = TextOrUnknown(s.composer());
item->sort_text = SortTextForArtist(s.composer()); item->sort_text = SortTextForArtist(s.composer(), sort_skips_articles_);
break; break;
} }
case GroupBy::Performer:{ case GroupBy::Performer:{
item->metadata.set_performer(s.performer()); item->metadata.set_performer(s.performer());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.performer()); item->display_text = TextOrUnknown(s.performer());
item->sort_text = SortTextForArtist(s.performer()); item->sort_text = SortTextForArtist(s.performer(), sort_skips_articles_);
break; break;
} }
case GroupBy::Grouping:{ case GroupBy::Grouping:{
item->metadata.set_grouping(s.grouping()); item->metadata.set_grouping(s.grouping());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.grouping()); item->display_text = TextOrUnknown(s.grouping());
item->sort_text = SortTextForArtist(s.grouping()); item->sort_text = SortTextForArtist(s.grouping(), sort_skips_articles_);
break; break;
} }
case GroupBy::FileType:{ case GroupBy::FileType:{
@ -1588,7 +1624,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
const int samplerate = std::max(0, s.samplerate()); const int samplerate = std::max(0, s.samplerate());
item->display_text = QString::number(samplerate); item->display_text = QString::number(samplerate);
item->sort_text = SortTextForNumber(samplerate) + " "; item->sort_text = SortTextForNumber(samplerate) + QLatin1Char(' ');
break; break;
} }
case GroupBy::Bitdepth:{ case GroupBy::Bitdepth:{
@ -1596,7 +1632,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
const int bitdepth = std::max(0, s.bitdepth()); const int bitdepth = std::max(0, s.bitdepth());
item->display_text = QString::number(bitdepth); item->display_text = QString::number(bitdepth);
item->sort_text = SortTextForNumber(bitdepth) + " "; item->sort_text = SortTextForNumber(bitdepth) + QLatin1Char(' ');
break; break;
} }
case GroupBy::Bitrate:{ case GroupBy::Bitrate:{
@ -1604,7 +1640,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s)); item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
const int bitrate = std::max(0, s.bitrate()); const int bitrate = std::max(0, s.bitrate());
item->display_text = QString::number(bitrate); item->display_text = QString::number(bitrate);
item->sort_text = SortTextForNumber(bitrate) + " "; item->sort_text = SortTextForNumber(bitrate) + QLatin1Char(' ');
break; break;
} }
case GroupBy::None: case GroupBy::None:
@ -1623,7 +1659,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
} }
FinishItem(group_by, signal, create_divider, parent, item); FinishItem(group_by, signal, create_divider, parent, item);
if (s.url().scheme() == "cdda") item->lazy_loaded = true; if (s.url().scheme() == QStringLiteral("cdda")) item->lazy_loaded = true;
return item; return item;
@ -1631,6 +1667,8 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
void CollectionModel::FinishItem(const GroupBy group_by, const bool signal, const bool create_divider, CollectionItem *parent, CollectionItem *item) { void CollectionModel::FinishItem(const GroupBy group_by, const bool signal, const bool create_divider, CollectionItem *parent, CollectionItem *item) {
if (!root_) return;
if (group_by == GroupBy::None) item->lazy_loaded = true; if (group_by == GroupBy::None) item->lazy_loaded = true;
if (signal) { if (signal) {
@ -1641,7 +1679,7 @@ void CollectionModel::FinishItem(const GroupBy group_by, const bool signal, cons
if (create_divider && show_dividers_) { if (create_divider && show_dividers_) {
QString divider_key = DividerKey(group_by, item); QString divider_key = DividerKey(group_by, item);
if (!divider_key.isEmpty()) { if (!divider_key.isEmpty()) {
item->sort_text.prepend(divider_key + " "); item->sort_text.prepend(divider_key + QLatin1Char(' '));
} }
if (!divider_key.isEmpty() && !divider_nodes_.contains(divider_key)) { if (!divider_key.isEmpty() && !divider_nodes_.contains(divider_key)) {
@ -1652,7 +1690,7 @@ void CollectionModel::FinishItem(const GroupBy group_by, const bool signal, cons
CollectionItem *divider = new CollectionItem(CollectionItem::Type_Divider, root_); CollectionItem *divider = new CollectionItem(CollectionItem::Type_Divider, root_);
divider->key = divider_key; divider->key = divider_key;
divider->display_text = DividerDisplayText(group_by, divider_key); divider->display_text = DividerDisplayText(group_by, divider_key);
divider->sort_text = divider_key + " "; divider->sort_text = divider_key + QStringLiteral(" ");
divider->lazy_loaded = true; divider->lazy_loaded = true;
divider_nodes_[divider_key] = divider; divider_nodes_[divider_key] = divider;
@ -1675,14 +1713,14 @@ QString CollectionModel::TextOrUnknown(const QString &text) {
QString CollectionModel::PrettyYearAlbum(const int year, const QString &album) { QString CollectionModel::PrettyYearAlbum(const int year, const QString &album) {
if (year <= 0) return TextOrUnknown(album); if (year <= 0) return TextOrUnknown(album);
return QString::number(year) + " - " + TextOrUnknown(album); return QString::number(year) + QStringLiteral(" - ") + TextOrUnknown(album);
} }
QString CollectionModel::PrettyAlbumDisc(const QString &album, const int disc) { QString CollectionModel::PrettyAlbumDisc(const QString &album, const int disc) {
if (disc <= 0 || album.contains(Song::kAlbumRemoveDisc)) return TextOrUnknown(album); if (disc <= 0 || Song::AlbumContainsDisc(album)) return TextOrUnknown(album);
else return TextOrUnknown(album) + " - (Disc " + QString::number(disc) + ")"; else return TextOrUnknown(album) + QStringLiteral(" - (Disc ") + QString::number(disc) + QStringLiteral(")");
} }
@ -1691,9 +1729,9 @@ QString CollectionModel::PrettyYearAlbumDisc(const int year, const QString &albu
QString str; QString str;
if (year <= 0) str = TextOrUnknown(album); if (year <= 0) str = TextOrUnknown(album);
else str = QString::number(year) + " - " + TextOrUnknown(album); else str = QString::number(year) + QStringLiteral(" - ") + TextOrUnknown(album);
if (!album.contains(Song::kAlbumRemoveDisc) && disc > 0) str += " - (Disc " + QString::number(disc) + ")"; if (!Song::AlbumContainsDisc(album) && disc > 0) str += QStringLiteral(" - (Disc ") + QString::number(disc) + QStringLiteral(")");
return str; return str;
@ -1701,33 +1739,35 @@ QString CollectionModel::PrettyYearAlbumDisc(const int year, const QString &albu
QString CollectionModel::PrettyDisc(const int disc) { QString CollectionModel::PrettyDisc(const int disc) {
return "Disc " + QString::number(std::max(1, disc)); return QStringLiteral("Disc ") + QString::number(std::max(1, disc));
} }
QString CollectionModel::SortText(QString text) { QString CollectionModel::SortText(QString text) {
if (text.isEmpty()) { if (text.isEmpty()) {
text = " unknown"; text = QStringLiteral(" unknown");
} }
else { else {
text = text.toLower(); text = text.toLower();
} }
text = text.remove(QRegularExpression("[^\\w ]", QRegularExpression::UseUnicodePropertiesOption)); text = text.remove(QRegularExpression(QStringLiteral("[^\\w ]"), QRegularExpression::UseUnicodePropertiesOption));
return text; return text;
} }
QString CollectionModel::SortTextForArtist(QString artist) { QString CollectionModel::SortTextForArtist(QString artist, const bool skip_articles) {
artist = SortText(artist); artist = SortText(artist);
for (const auto &i : Song::kArticles) { if (skip_articles) {
if (artist.startsWith(i)) { for (const auto &i : Song::kArticles) {
qint64 ilen = i.length(); if (artist.startsWith(i)) {
artist = artist.right(artist.length() - ilen) + ", " + i.left(ilen - 1); qint64 ilen = i.length();
break; artist = artist.right(artist.length() - ilen) + QStringLiteral(", ") + i.left(ilen - 1);
break;
}
} }
} }
@ -1737,27 +1777,27 @@ QString CollectionModel::SortTextForArtist(QString artist) {
QString CollectionModel::SortTextForNumber(const int number) { QString CollectionModel::SortTextForNumber(const int number) {
return QString("%1").arg(number, 4, 10, QChar('0')); return QStringLiteral("%1").arg(number, 4, 10, QLatin1Char('0'));
} }
QString CollectionModel::SortTextForYear(const int year) { QString CollectionModel::SortTextForYear(const int year) {
QString str = QString::number(year); QString str = QString::number(year);
return QString("0").repeated(qMax(0, 4 - str.length())) + str; return QStringLiteral("0").repeated(qMax(0, 4 - str.length())) + str;
} }
QString CollectionModel::SortTextForBitrate(const int bitrate) { QString CollectionModel::SortTextForBitrate(const int bitrate) {
QString str = QString::number(bitrate); QString str = QString::number(bitrate);
return QString("0").repeated(qMax(0, 3 - str.length())) + str; return QStringLiteral("0").repeated(qMax(0, 3 - str.length())) + str;
} }
QString CollectionModel::SortTextForSong(const Song &song) { QString CollectionModel::SortTextForSong(const Song &song) {
QString ret = QString::number(std::max(0, song.disc()) * 1000 + std::max(0, song.track())); QString ret = QString::number(std::max(0, song.disc()) * 1000 + std::max(0, song.track()));
ret.prepend(QString("0").repeated(6 - ret.length())); ret.prepend(QStringLiteral("0").repeated(6 - ret.length()));
ret.append(song.url().toString()); ret.append(song.url().toString());
return ret; return ret;
@ -1779,7 +1819,7 @@ Qt::ItemFlags CollectionModel::flags(const QModelIndex &idx) const {
} }
QStringList CollectionModel::mimeTypes() const { QStringList CollectionModel::mimeTypes() const {
return QStringList() << "text/uri-list"; return QStringList() << QStringLiteral("text/uri-list");
} }
QMimeData *CollectionModel::mimeData(const QModelIndexList &indexes) const { QMimeData *CollectionModel::mimeData(const QModelIndexList &indexes) const {
@ -1819,10 +1859,10 @@ bool CollectionModel::CompareItems(const CollectionItem *a, const CollectionItem
} }
qint64 CollectionModel::MaximumCacheSize(QSettings *s, const char *size_id, const char *size_unit_id, const qint64 cache_size_default) { qint64 CollectionModel::MaximumCacheSize(Settings *s, const char *size_id, const char *size_unit_id, const qint64 cache_size_default) {
qint64 size = s->value(size_id, cache_size_default).toInt(); qint64 size = s->value(QLatin1String(size_id), cache_size_default).toInt();
int unit = s->value(size_unit_id, static_cast<int>(CollectionSettingsPage::CacheSizeUnit::MB)).toInt() + 1; int unit = s->value(QLatin1String(size_unit_id), static_cast<int>(CollectionSettingsPage::CacheSizeUnit::MB)).toInt() + 1;
do { do {
size *= 1024; size *= 1024;
@ -1836,7 +1876,7 @@ qint64 CollectionModel::MaximumCacheSize(QSettings *s, const char *size_id, cons
void CollectionModel::GetChildSongs(CollectionItem *item, QList<QUrl> *urls, SongList *songs, QSet<int> *song_ids) const { void CollectionModel::GetChildSongs(CollectionItem *item, QList<QUrl> *urls, SongList *songs, QSet<int> *song_ids) const {
switch (item->type) { switch (item->type) {
case CollectionItem::Type_Container: { case CollectionItem::Type_Container:{
const_cast<CollectionModel*>(this)->LazyPopulate(item); const_cast<CollectionModel*>(this)->LazyPopulate(item);
QList<CollectionItem*> children = item->children; QList<CollectionItem*> children = item->children;
@ -1970,8 +2010,10 @@ void CollectionModel::ClearDiskCache() {
void CollectionModel::ExpandAll(CollectionItem *item) const { void CollectionModel::ExpandAll(CollectionItem *item) const {
if (!root_) return;
if (!item) item = root_; if (!item) item = root_;
const_cast<CollectionModel*>(this)->LazyPopulate(const_cast<CollectionItem*>(item), false); const_cast<CollectionModel*>(this)->LazyPopulate(item, false);
for (CollectionItem *child : item->children) { for (CollectionItem *child : item->children) {
ExpandAll(child); ExpandAll(child);
} }

View File

@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -55,7 +55,7 @@
#include "collectionqueryoptions.h" #include "collectionqueryoptions.h"
#include "collectionitem.h" #include "collectionitem.h"
class QSettings; class Settings;
class Application; class Application;
class CollectionBackend; class CollectionBackend;
@ -82,7 +82,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
LastRole LastRole
}; };
// These values get saved in QSettings - don't change them // These values get saved in Settings - don't change them
enum class GroupBy { enum class GroupBy {
None = 0, None = 0,
AlbumArtist = 1, AlbumArtist = 1,
@ -162,6 +162,9 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
// Whether or not to show letters heading in the collection view // Whether or not to show letters heading in the collection view
void set_show_dividers(const bool show_dividers); void set_show_dividers(const bool show_dividers);
// Whether to skip articles such as “The” when sorting artist names
void set_sort_skips_articles(const bool sort_skips_articles);
// Reload settings. // Reload settings.
void ReloadSettings(); void ReloadSettings();
@ -173,7 +176,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
static QString PrettyDisc(const int disc); static QString PrettyDisc(const int disc);
static QString SortText(QString text); static QString SortText(QString text);
static QString SortTextForNumber(const int number); static QString SortTextForNumber(const int number);
static QString SortTextForArtist(QString artist); static QString SortTextForArtist(QString artist, const bool skip_articles);
static QString SortTextForSong(const Song &song); static QString SortTextForSong(const Song &song);
static QString SortTextForYear(const int year); static QString SortTextForYear(const int year);
static QString SortTextForBitrate(const int bitrate); static QString SortTextForBitrate(const int bitrate);
@ -242,6 +245,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
bool HasCompilations(const QSqlDatabase &db, const CollectionFilterOptions &filter_options, const CollectionQueryOptions &query_options); bool HasCompilations(const QSqlDatabase &db, const CollectionFilterOptions &filter_options, const CollectionQueryOptions &query_options);
void Clear();
void BeginReset(); void BeginReset();
// Functions for working with queries and creating items. // Functions for working with queries and creating items.
@ -271,13 +275,14 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
QVariant AlbumIcon(const QModelIndex &idx); QVariant AlbumIcon(const QModelIndex &idx);
QVariant data(const CollectionItem *item, const int role) const; QVariant data(const CollectionItem *item, const int role) const;
bool CompareItems(const CollectionItem *a, const CollectionItem *b) const; bool CompareItems(const CollectionItem *a, const CollectionItem *b) const;
static qint64 MaximumCacheSize(QSettings *s, const char *size_id, const char *size_unit_id, const qint64 cache_size_default); static qint64 MaximumCacheSize(Settings *s, const char *size_id, const char *size_unit_id, const qint64 cache_size_default);
private: private:
SharedPtr<CollectionBackend> backend_; SharedPtr<CollectionBackend> backend_;
Application *app_; Application *app_;
CollectionDirectoryModel *dir_model_; CollectionDirectoryModel *dir_model_;
bool show_various_artists_; bool show_various_artists_;
bool sort_skips_articles_;
int total_song_count_; int total_song_count_;
int total_artist_count_; int total_artist_count_;

View File

@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -30,8 +30,8 @@
#include <QStringBuilder> #include <QStringBuilder>
#include <QRegularExpression> #include <QRegularExpression>
#include <QSqlDatabase> #include <QSqlDatabase>
#include <QSqlQuery>
#include "core/sqlquery.h"
#include "core/song.h" #include "core/song.h"
#include "collectionquery.h" #include "collectionquery.h"
@ -39,7 +39,7 @@
#include "utilities/searchparserutils.h" #include "utilities/searchparserutils.h"
CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const CollectionFilterOptions &filter_options) CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const CollectionFilterOptions &filter_options)
: QSqlQuery(db), : SqlQuery(db),
songs_table_(songs_table), songs_table_(songs_table),
fts_table_(fts_table), fts_table_(fts_table),
include_unavailable_(false), include_unavailable_(false),
@ -54,35 +54,35 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta
// 3) Remove colons which don't correspond to column names. // 3) Remove colons which don't correspond to column names.
// Split on whitespace // Split on whitespace
QString filter_text = filter_options.filter_text().replace(QRegularExpression(":\\s+"), ":"); QString filter_text = filter_options.filter_text().replace(QRegularExpression(QStringLiteral(":\\s+")), QStringLiteral(":"));
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QStringList tokens(filter_text.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts)); QStringList tokens(filter_text.split(QRegularExpression(QStringLiteral("\\s+")), Qt::SkipEmptyParts));
#else #else
QStringList tokens(filter_text.split(QRegularExpression("\\s+"), QString::SkipEmptyParts)); QStringList tokens(filter_text.split(QRegularExpression(QStringLiteral("\\s+")), QString::SkipEmptyParts));
#endif #endif
QString query; QString query;
for (QString token : tokens) { for (QString token : tokens) {
token.remove('(') token.remove(QLatin1Char('('))
.remove(')') .remove(QLatin1Char(')'))
.remove('"') .remove(QLatin1Char('"'))
.replace('-', ' '); .replace(QLatin1Char('-'), QLatin1Char(' '));
if (token.contains(':')) { if (token.contains(QLatin1Char(':'))) {
const QString columntoken = token.section(':', 0, 0); const QString columntoken = token.section(QLatin1Char(':'), 0, 0);
QString subtoken = token.section(':', 1, -1).replace(":", " ").trimmed(); QString subtoken = token.section(QLatin1Char(':'), 1, -1).replace(QLatin1String(":"), QLatin1String(" ")).trimmed();
if (subtoken.isEmpty()) continue; if (subtoken.isEmpty()) continue;
if (Song::kFtsColumns.contains("fts" + columntoken, Qt::CaseInsensitive)) { if (Song::kFtsColumns.contains(QLatin1String("fts") + columntoken, Qt::CaseInsensitive)) {
if (!query.isEmpty()) query.append(" "); if (!query.isEmpty()) query.append(QLatin1String(" "));
query += "fts" + columntoken + ":\"" + subtoken + "\"*"; query += QStringLiteral("fts") + columntoken + QStringLiteral(":\"") + subtoken + QStringLiteral("\"*");
} }
else if (Song::kNumericalColumns.contains(columntoken, Qt::CaseInsensitive)) { else if (Song::kNumericalColumns.contains(columntoken, Qt::CaseInsensitive)) {
QString comparator = RemoveSqlOperator(subtoken); QString comparator = RemoveSqlOperator(subtoken);
if (columntoken.compare("rating", Qt::CaseInsensitive) == 0) { if (columntoken.compare(QLatin1String("rating"), Qt::CaseInsensitive) == 0) {
AddWhereRating(subtoken, comparator); AddWhereRating(subtoken, comparator);
} }
else if (columntoken.compare("length", Qt::CaseInsensitive) == 0) { else if (columntoken.compare(QLatin1String("length"), Qt::CaseInsensitive) == 0) {
// Time is saved in nanoseconds, so add 9 0's // Time is saved in nanoseconds, so add 9 0's
QString parsedTime = QString::number(Utilities::ParseSearchTime(subtoken)) + "000000000"; QString parsedTime = QString::number(Utilities::ParseSearchTime(subtoken)) + QStringLiteral("000000000");
AddWhere(columntoken, parsedTime, comparator); AddWhere(columntoken, parsedTime, comparator);
} }
else { else {
@ -91,20 +91,20 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta
} }
// Not a valid filter, remove // Not a valid filter, remove
else { else {
token = token.replace(":", " ").trimmed(); token = token.replace(QLatin1String(":"), QLatin1String(" ")).trimmed();
if (!token.isEmpty()) { if (!token.isEmpty()) {
if (!query.isEmpty()) query.append(" "); if (!query.isEmpty()) query.append(QLatin1Char(' '));
query += "\"" + token + "\"*"; query += QLatin1Char('\"') + token + QStringLiteral("\"*");
} }
} }
} }
else { else {
if (!query.isEmpty()) query.append(" "); if (!query.isEmpty()) query.append(QLatin1Char(' '));
query += "\"" + token + "\"*"; query += QLatin1Char('\"') + token + QStringLiteral("\"*");
} }
} }
if (!query.isEmpty()) { if (!query.isEmpty()) {
where_clauses_ << "fts.%fts_table_noprefix MATCH ?"; where_clauses_ << QStringLiteral("fts.%fts_table_noprefix MATCH ?");
bound_values_ << query; bound_values_ << query;
join_with_fts_ = true; join_with_fts_ = true;
} }
@ -113,7 +113,7 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta
if (filter_options.max_age() != -1) { if (filter_options.max_age() != -1) {
qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - filter_options.max_age(); qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - filter_options.max_age();
where_clauses_ << "ctime > ?"; where_clauses_ << QStringLiteral("ctime > ?");
bound_values_ << cutoff; bound_values_ << cutoff;
} }
@ -126,23 +126,23 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta
duplicates_only_ = filter_options.filter_mode() == CollectionFilterOptions::FilterMode::Duplicates; duplicates_only_ = filter_options.filter_mode() == CollectionFilterOptions::FilterMode::Duplicates;
if (filter_options.filter_mode() == CollectionFilterOptions::FilterMode::Untagged) { if (filter_options.filter_mode() == CollectionFilterOptions::FilterMode::Untagged) {
where_clauses_ << "(artist = '' OR album = '' OR title ='')"; where_clauses_ << QStringLiteral("(artist = '' OR album = '' OR title ='')");
} }
} }
QString CollectionQuery::RemoveSqlOperator(QString &token) { QString CollectionQuery::RemoveSqlOperator(QString &token) {
QString op = "="; QString op = QStringLiteral("=");
static QRegularExpression rxOp("^(=|<[>=]?|>=?|!=)"); static QRegularExpression rxOp(QStringLiteral("^(=|<[>=]?|>=?|!=)"));
QRegularExpressionMatch match = rxOp.match(token); QRegularExpressionMatch match = rxOp.match(token);
if (match.hasMatch()) { if (match.hasMatch()) {
op = match.captured(0); op = match.captured(0);
} }
token.remove(rxOp); token.remove(rxOp);
if (op == "!=") { if (op == QStringLiteral("!=")) {
op = "<>"; op = QStringLiteral("<>");
} }
return op; return op;
@ -152,16 +152,16 @@ QString CollectionQuery::RemoveSqlOperator(QString &token) {
void CollectionQuery::AddWhere(const QString &column, const QVariant &value, const QString &op) { void CollectionQuery::AddWhere(const QString &column, const QVariant &value, const QString &op) {
// Ignore 'literal' for IN // Ignore 'literal' for IN
if (op.compare("IN", Qt::CaseInsensitive) == 0) { if (op.compare(QLatin1String("IN"), Qt::CaseInsensitive) == 0) {
QStringList values = value.toStringList(); QStringList values = value.toStringList();
QStringList final_values; QStringList final_values;
final_values.reserve(values.count()); final_values.reserve(values.count());
for (const QString &single_value : values) { for (const QString &single_value : values) {
final_values.append("?"); final_values.append(QStringLiteral("?"));
bound_values_ << single_value; bound_values_ << single_value;
} }
where_clauses_ << QString("%1 IN (" + final_values.join(",") + ")").arg(column); where_clauses_ << QStringLiteral("%1 IN (%2)").arg(column, final_values.join(QStringLiteral(",")));
} }
else { else {
// Do integers inline - sqlite seems to get confused when you pass integers to bound parameters // Do integers inline - sqlite seems to get confused when you pass integers to bound parameters
@ -170,7 +170,7 @@ void CollectionQuery::AddWhere(const QString &column, const QVariant &value, con
#else #else
if (value.type() == QVariant::Int) { if (value.type() == QVariant::Int) {
#endif #endif
where_clauses_ << QString("%1 %2 %3").arg(column, op, value.toString()); where_clauses_ << QStringLiteral("%1 %2 %3").arg(column, op, value.toString());
} }
else if ( else if (
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
@ -179,11 +179,11 @@ void CollectionQuery::AddWhere(const QString &column, const QVariant &value, con
value.type() == QVariant::String value.type() == QVariant::String
#endif #endif
&& value.toString().isNull()) { && value.toString().isNull()) {
where_clauses_ << QString("%1 %2 ?").arg(column, op); where_clauses_ << QStringLiteral("%1 %2 ?").arg(column, op);
bound_values_ << QString(""); bound_values_ << QLatin1String("");
} }
else { else {
where_clauses_ << QString("%1 %2 ?").arg(column, op); where_clauses_ << QStringLiteral("%1 %2 ?").arg(column, op);
bound_values_ << value; bound_values_ << value;
} }
} }
@ -192,7 +192,7 @@ void CollectionQuery::AddWhere(const QString &column, const QVariant &value, con
void CollectionQuery::AddWhereArtist(const QVariant &value) { void CollectionQuery::AddWhereArtist(const QVariant &value) {
where_clauses_ << QString("((artist = ? AND albumartist = '') OR albumartist = ?)"); where_clauses_ << QStringLiteral("((artist = ? AND albumartist = '') OR albumartist = ?)");
bound_values_ << value; bound_values_ << value;
bound_values_ << value; bound_values_ << value;
@ -205,26 +205,26 @@ void CollectionQuery::AddWhereRating(const QVariant &value, const QString &op) {
// You can't query the database for a float, due to float precision errors, // You can't query the database for a float, due to float precision errors,
// So we have to use a certain tolerance, so that the searched value is definetly included. // So we have to use a certain tolerance, so that the searched value is definetly included.
const float tolerance = 0.001F; const float tolerance = 0.001F;
if (op == "<") { if (op == QStringLiteral("<")) {
AddWhere("rating", parsed_rating-tolerance, "<"); AddWhere(QStringLiteral("rating"), parsed_rating-tolerance, QStringLiteral("<"));
} }
else if (op == ">") { else if (op == QStringLiteral(">")) {
AddWhere("rating", parsed_rating+tolerance, ">"); AddWhere(QStringLiteral("rating"), parsed_rating+tolerance, QStringLiteral(">"));
} }
else if (op == "<=") { else if (op == QStringLiteral("<=")) {
AddWhere("rating", parsed_rating+tolerance, "<="); AddWhere(QStringLiteral("rating"), parsed_rating+tolerance, QStringLiteral("<="));
} }
else if (op == ">=") { else if (op == QStringLiteral(">=")) {
AddWhere("rating", parsed_rating-tolerance, ">="); AddWhere(QStringLiteral("rating"), parsed_rating-tolerance, QStringLiteral(">="));
} }
else if (op == "<>") { else if (op == QStringLiteral("<>")) {
where_clauses_ << QString("(rating<? OR rating>?)"); where_clauses_ << QStringLiteral("(rating<? OR rating>?)");
bound_values_ << parsed_rating - tolerance; bound_values_ << parsed_rating - tolerance;
bound_values_ << parsed_rating + tolerance; bound_values_ << parsed_rating + tolerance;
} }
else /* (op == "=") */ { else /* (op == "=") */ {
AddWhere("rating", parsed_rating+tolerance, "<"); AddWhere(QStringLiteral("rating"), parsed_rating+tolerance, QStringLiteral("<"));
AddWhere("rating", parsed_rating-tolerance, ">"); AddWhere(QStringLiteral("rating"), parsed_rating-tolerance, QStringLiteral(">"));
} }
} }
@ -233,13 +233,13 @@ void CollectionQuery::AddCompilationRequirement(const bool compilation) {
// The unary + is added to prevent sqlite from using the index idx_comp_artist. // The unary + is added to prevent sqlite from using the index idx_comp_artist.
// When joining with fts, sqlite 3.8 has a tendency to use this index and thereby nesting the tables in an order which gives very poor performance // When joining with fts, sqlite 3.8 has a tendency to use this index and thereby nesting the tables in an order which gives very poor performance
where_clauses_ << QString("+compilation_effective = %1").arg(compilation ? 1 : 0); where_clauses_ << QStringLiteral("+compilation_effective = %1").arg(compilation ? 1 : 0);
} }
QString CollectionQuery::GetInnerQuery() const { QString CollectionQuery::GetInnerQuery() const {
return duplicates_only_ return duplicates_only_
? QString(" INNER JOIN (select * from duplicated_songs) dsongs " ? QStringLiteral(" INNER JOIN (select * from duplicated_songs) dsongs "
"ON (%songs_table.artist = dsongs.dup_artist " "ON (%songs_table.artist = dsongs.dup_artist "
"AND %songs_table.album = dsongs.dup_album " "AND %songs_table.album = dsongs.dup_album "
"AND %songs_table.title = dsongs.dup_title) ") "AND %songs_table.title = dsongs.dup_title) ")
@ -251,26 +251,26 @@ bool CollectionQuery::Exec() {
QString sql; QString sql;
if (join_with_fts_) { if (join_with_fts_) {
sql = QString("SELECT %1 FROM %2 INNER JOIN %3 AS fts ON %2.ROWID = fts.ROWID").arg(column_spec_, songs_table_, fts_table_); sql = QStringLiteral("SELECT %1 FROM %2 INNER JOIN %3 AS fts ON %2.ROWID = fts.ROWID").arg(column_spec_, songs_table_, fts_table_);
} }
else { else {
sql = QString("SELECT %1 FROM %2 %3").arg(column_spec_, songs_table_, GetInnerQuery()); sql = QStringLiteral("SELECT %1 FROM %2 %3").arg(column_spec_, songs_table_, GetInnerQuery());
} }
QStringList where_clauses(where_clauses_); QStringList where_clauses(where_clauses_);
if (!include_unavailable_) { if (!include_unavailable_) {
where_clauses << "unavailable = 0"; where_clauses << QStringLiteral("unavailable = 0");
} }
if (!where_clauses.isEmpty()) sql += " WHERE " + where_clauses.join(" AND "); if (!where_clauses.isEmpty()) sql += QStringLiteral(" WHERE ") + where_clauses.join(QStringLiteral(" AND "));
if (!order_by_.isEmpty()) sql += " ORDER BY " + order_by_; if (!order_by_.isEmpty()) sql += QStringLiteral(" ORDER BY ") + order_by_;
if (limit_ != -1) sql += " LIMIT " + QString::number(limit_); if (limit_ != -1) sql += QStringLiteral(" LIMIT ") + QString::number(limit_);
sql.replace("%songs_table", songs_table_); sql.replace(QLatin1String("%songs_table"), songs_table_);
sql.replace("%fts_table_noprefix", fts_table_.section('.', -1, -1)); sql.replace(QLatin1String("%fts_table_noprefix"), fts_table_.section(QLatin1Char('.'), -1, -1));
sql.replace("%fts_table", fts_table_); sql.replace(QLatin1String("%fts_table"), fts_table_);
if (!QSqlQuery::prepare(sql)) return false; if (!QSqlQuery::prepare(sql)) return false;

View File

@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -29,11 +29,12 @@
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
#include <QSqlDatabase> #include <QSqlDatabase>
#include <QSqlQuery>
#include "core/sqlquery.h"
#include "collectionfilteroptions.h" #include "collectionfilteroptions.h"
class CollectionQuery : public QSqlQuery { class CollectionQuery : public SqlQuery {
public: public:
explicit CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const CollectionFilterOptions &filter_options = CollectionFilterOptions()); explicit CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const CollectionFilterOptions &filter_options = CollectionFilterOptions());
@ -41,7 +42,7 @@ class CollectionQuery : public QSqlQuery {
QVariant value(const int column) const { return Value(column); } QVariant value(const int column) const { return Value(column); }
bool Exec(); bool Exec();
bool exec() { return QSqlQuery::exec(); } bool exec() { return SqlQuery::exec(); }
bool Next(); bool Next();
@ -67,9 +68,9 @@ class CollectionQuery : public QSqlQuery {
QString RemoveSqlOperator(QString &token); QString RemoveSqlOperator(QString &token);
// Adds a fragment of WHERE clause. When executed, this Query will connect all the fragments with AND operator. // Adds a fragment of WHERE clause. When executed, this Query will connect all the fragments with AND operator.
// Please note that IN operator expects a QStringList as value. // Please note that IN operator expects a QStringList as value.
void AddWhere(const QString &column, const QVariant &value, const QString &op = "="); void AddWhere(const QString &column, const QVariant &value, const QString &op = QStringLiteral("="));
void AddWhereArtist(const QVariant &value); void AddWhereArtist(const QVariant &value);
void AddWhereRating(const QVariant &value, const QString &op = "="); void AddWhereRating(const QVariant &value, const QString &op = QStringLiteral("="));
void SetBoundValues(const QVariantList &bound_values) { bound_values_ = bound_values; } void SetBoundValues(const QVariantList &bound_values) { bound_values_ = bound_values; }
void SetDuplicatesOnly(const bool duplicates_only) { duplicates_only_ = duplicates_only; } void SetDuplicatesOnly(const bool duplicates_only) { duplicates_only_ = duplicates_only; }

View File

@ -51,7 +51,7 @@ class CollectionQueryOptions {
void set_query_have_compilations(const bool query_have_compilations) { query_have_compilations_ = query_have_compilations; } void set_query_have_compilations(const bool query_have_compilations) { query_have_compilations_ = query_have_compilations; }
QList<Where> where_clauses() const { return where_clauses_; } QList<Where> where_clauses() const { return where_clauses_; }
void AddWhere(const QString &column, const QVariant &value, const QString &op = "="); void AddWhere(const QString &column, const QVariant &value, const QString &op = QStringLiteral("="));
private: private:
QString column_spec_; QString column_spec_;

View File

@ -21,6 +21,7 @@
#include "config.h" #include "config.h"
#include <utility>
#include <memory> #include <memory>
#include <QtGlobal> #include <QtGlobal>
@ -52,6 +53,7 @@
#include "core/mimedata.h" #include "core/mimedata.h"
#include "core/musicstorage.h" #include "core/musicstorage.h"
#include "core/deletefiles.h" #include "core/deletefiles.h"
#include "core/settings.h"
#include "utilities/filemanagerutils.h" #include "utilities/filemanagerutils.h"
#include "collection.h" #include "collection.h"
#include "collectionbackend.h" #include "collectionbackend.h"
@ -80,7 +82,7 @@ CollectionView::CollectionView(QWidget *parent)
total_song_count_(-1), total_song_count_(-1),
total_artist_count_(-1), total_artist_count_(-1),
total_album_count_(-1), total_album_count_(-1),
nomusic_(":/pictures/nomusic.png"), nomusic_(QStringLiteral(":/pictures/nomusic.png")),
context_menu_(nullptr), context_menu_(nullptr),
action_load_(nullptr), action_load_(nullptr),
action_add_to_playlist_(nullptr), action_add_to_playlist_(nullptr),
@ -88,6 +90,7 @@ CollectionView::CollectionView(QWidget *parent)
action_add_to_playlist_enqueue_next_(nullptr), action_add_to_playlist_enqueue_next_(nullptr),
action_open_in_new_playlist_(nullptr), action_open_in_new_playlist_(nullptr),
action_organize_(nullptr), action_organize_(nullptr),
action_search_for_this_(nullptr),
#ifndef Q_OS_WIN #ifndef Q_OS_WIN
action_copy_to_device_(nullptr), action_copy_to_device_(nullptr),
#endif #endif
@ -109,7 +112,7 @@ CollectionView::CollectionView(QWidget *parent)
setDragDropMode(QAbstractItemView::DragOnly); setDragDropMode(QAbstractItemView::DragOnly);
setSelectionMode(QAbstractItemView::ExtendedSelection); setSelectionMode(QAbstractItemView::ExtendedSelection);
setStyleSheet("QTreeView::item{padding-top:1px;}"); setStyleSheet(QStringLiteral("QTreeView::item{padding-top:1px;}"));
} }
@ -128,7 +131,7 @@ void CollectionView::SaveFocus() {
last_selected_container_ = QString(); last_selected_container_ = QString();
switch (type.toInt()) { switch (type.toInt()) {
case CollectionItem::Type_Song: { case CollectionItem::Type_Song:{
QModelIndex index = qobject_cast<QSortFilterProxyModel*>(model())->mapToSource(current); QModelIndex index = qobject_cast<QSortFilterProxyModel*>(model())->mapToSource(current);
SongList songs = app_->collection_model()->GetChildSongs(index); SongList songs = app_->collection_model()->GetChildSongs(index);
if (!songs.isEmpty()) { if (!songs.isEmpty()) {
@ -138,7 +141,7 @@ void CollectionView::SaveFocus() {
} }
case CollectionItem::Type_Container: case CollectionItem::Type_Container:
case CollectionItem::Type_Divider: { case CollectionItem::Type_Divider:{
QString text = model()->data(current, CollectionModel::Role_SortText).toString(); QString text = model()->data(current, CollectionModel::Role_SortText).toString();
last_selected_container_ = text; last_selected_container_ = text;
break; break;
@ -197,7 +200,7 @@ bool CollectionView::RestoreLevelFocus(const QModelIndex &parent) {
break; break;
case CollectionItem::Type_Container: case CollectionItem::Type_Container:
case CollectionItem::Type_Divider: { case CollectionItem::Type_Divider:{
QString text = model()->data(current, CollectionModel::Role_SortText).toString(); QString text = model()->data(current, CollectionModel::Role_SortText).toString();
if (!last_selected_container_.isEmpty() && last_selected_container_ == text) { if (!last_selected_container_.isEmpty() && last_selected_container_ == text) {
expand(current); expand(current);
@ -224,7 +227,7 @@ bool CollectionView::RestoreLevelFocus(const QModelIndex &parent) {
void CollectionView::ReloadSettings() { void CollectionView::ReloadSettings() {
QSettings settings; Settings settings;
settings.beginGroup(CollectionSettingsPage::kSettingsGroup); settings.beginGroup(CollectionSettingsPage::kSettingsGroup);
SetAutoOpen(settings.value("auto_open", false).toBool()); SetAutoOpen(settings.value("auto_open", false).toBool());
@ -232,6 +235,7 @@ void CollectionView::ReloadSettings() {
if (app_) { if (app_) {
app_->collection_model()->set_pretty_covers(settings.value("pretty_covers", true).toBool()); app_->collection_model()->set_pretty_covers(settings.value("pretty_covers", true).toBool());
app_->collection_model()->set_show_dividers(settings.value("show_dividers", true).toBool()); app_->collection_model()->set_show_dividers(settings.value("show_dividers", true).toBool());
app_->collection_model()->set_sort_skips_articles(settings.value("sort_skips_articles", true).toBool());
} }
delete_files_ = settings.value("delete_files", false).toBool(); delete_files_ = settings.value("delete_files", false).toBool();
@ -343,29 +347,49 @@ void CollectionView::mouseReleaseEvent(QMouseEvent *e) {
} }
void CollectionView::keyPressEvent(QKeyEvent *e) {
switch (e->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
if (currentIndex().isValid()) {
AddToPlaylist();
}
e->accept();
break;
}
AutoExpandingTreeView::keyPressEvent(e);
}
void CollectionView::contextMenuEvent(QContextMenuEvent *e) { void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
if (!context_menu_) { if (!context_menu_) {
context_menu_ = new QMenu(this); context_menu_ = new QMenu(this);
action_add_to_playlist_ = context_menu_->addAction(IconLoader::Load("media-playback-start"), tr("Append to current playlist"), this, &CollectionView::AddToPlaylist); action_add_to_playlist_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("media-playback-start")), tr("Append to current playlist"), this, &CollectionView::AddToPlaylist);
action_load_ = context_menu_->addAction(IconLoader::Load("media-playback-start"), tr("Replace current playlist"), this, &CollectionView::Load); action_load_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("media-playback-start")), tr("Replace current playlist"), this, &CollectionView::Load);
action_open_in_new_playlist_ = context_menu_->addAction(IconLoader::Load("document-new"), tr("Open in new playlist"), this, &CollectionView::OpenInNewPlaylist); action_open_in_new_playlist_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("document-new")), tr("Open in new playlist"), this, &CollectionView::OpenInNewPlaylist);
context_menu_->addSeparator(); context_menu_->addSeparator();
action_add_to_playlist_enqueue_ = context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue track"), this, &CollectionView::AddToPlaylistEnqueue); action_add_to_playlist_enqueue_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("go-next")), tr("Queue track"), this, &CollectionView::AddToPlaylistEnqueue);
action_add_to_playlist_enqueue_next_ = context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue to play next"), this, &CollectionView::AddToPlaylistEnqueueNext); action_add_to_playlist_enqueue_next_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("go-next")), tr("Queue to play next"), this, &CollectionView::AddToPlaylistEnqueueNext);
context_menu_->addSeparator(); context_menu_->addSeparator();
action_organize_ = context_menu_->addAction(IconLoader::Load("edit-copy"), tr("Organize files..."), this, &CollectionView::Organize);
action_search_for_this_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("edit-find")), tr("Search for this"), this, &CollectionView::SearchForThis);
context_menu_->addSeparator();
action_organize_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("edit-copy")), tr("Organize files..."), this, &CollectionView::Organize);
#ifndef Q_OS_WIN #ifndef Q_OS_WIN
action_copy_to_device_ = context_menu_->addAction(IconLoader::Load("device"), tr("Copy to device..."), this, &CollectionView::CopyToDevice); action_copy_to_device_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("device")), tr("Copy to device..."), this, &CollectionView::CopyToDevice);
#endif #endif
action_delete_files_ = context_menu_->addAction(IconLoader::Load("edit-delete"), tr("Delete from disk..."), this, &CollectionView::Delete); action_delete_files_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("edit-delete")), tr("Delete from disk..."), this, &CollectionView::Delete);
context_menu_->addSeparator(); context_menu_->addSeparator();
action_edit_track_ = context_menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit track information..."), this, &CollectionView::EditTracks); action_edit_track_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("edit-rename")), tr("Edit track information..."), this, &CollectionView::EditTracks);
action_edit_tracks_ = context_menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit tracks information..."), this, &CollectionView::EditTracks); action_edit_tracks_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("edit-rename")), tr("Edit tracks information..."), this, &CollectionView::EditTracks);
action_show_in_browser_ = context_menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, &CollectionView::ShowInBrowser); action_show_in_browser_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("document-open-folder")), tr("Show in file browser..."), this, &CollectionView::ShowInBrowser);
context_menu_->addSeparator(); context_menu_->addSeparator();
@ -391,7 +415,7 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
context_menu_index_ = qobject_cast<QSortFilterProxyModel*>(model())->mapToSource(context_menu_index_); context_menu_index_ = qobject_cast<QSortFilterProxyModel*>(model())->mapToSource(context_menu_index_);
QModelIndexList selected_indexes = qobject_cast<QSortFilterProxyModel*>(model())->mapSelectionToSource(selectionModel()->selection()).indexes(); const QModelIndexList selected_indexes = qobject_cast<QSortFilterProxyModel*>(model())->mapSelectionToSource(selectionModel()->selection()).indexes();
int regular_elements = 0; int regular_elements = 0;
int regular_editable = 0; int regular_editable = 0;
@ -462,7 +486,8 @@ void CollectionView::SetShowInVarious(const bool on) {
// We put through "Various Artists" changes one album at a time, // We put through "Various Artists" changes one album at a time,
// to make sure the old album node gets removed (due to all children removed), before the new one gets added // to make sure the old album node gets removed (due to all children removed), before the new one gets added
QMultiMap<QString, QString> albums; QMultiMap<QString, QString> albums;
for (const Song &song : GetSelectedSongs()) { const SongList songs = GetSelectedSongs();
for (const Song &song : songs) {
if (albums.find(song.album(), song.artist()) == albums.end()) if (albums.find(song.album(), song.artist()) == albums.end())
albums.insert(song.album(), song.artist()); albums.insert(song.album(), song.artist());
} }
@ -472,7 +497,7 @@ void CollectionView::SetShowInVarious(const bool on) {
if (on && albums.keys().count() == 1) { if (on && albums.keys().count() == 1) {
const QStringList albums_list = albums.keys(); const QStringList albums_list = albums.keys();
const QString album = albums_list.first(); const QString album = albums_list.first();
SongList all_of_album = app_->collection_backend()->GetSongsByAlbum(album); const SongList all_of_album = app_->collection_backend()->GetSongsByAlbum(album);
QSet<QString> other_artists; QSet<QString> other_artists;
for (const Song &s : all_of_album) { for (const Song &s : all_of_album) {
if (!albums.contains(album, s.artist()) && !other_artists.contains(s.artist())) { if (!albums.contains(album, s.artist()) && !other_artists.contains(s.artist())) {
@ -489,9 +514,9 @@ void CollectionView::SetShowInVarious(const bool on) {
} }
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
QSet<QString> albums_set = QSet<QString>(albums.keyBegin(), albums.keyEnd()); const QSet<QString> albums_set = QSet<QString>(albums.keyBegin(), albums.keyEnd());
#else #else
QSet<QString> albums_set = QSet<QString>::fromList(albums.keys()); const QSet<QString> albums_set = QSet<QString>::fromList(albums.keys());
#endif #endif
for (const QString &album : albums_set) { for (const QString &album : albums_set) {
app_->collection_backend()->ForceCompilation(album, albums.values(album), on); app_->collection_backend()->ForceCompilation(album, albums.values(album), on);
@ -545,6 +570,100 @@ void CollectionView::OpenInNewPlaylist() {
} }
void CollectionView::SearchForThis() {
QModelIndex current = currentIndex();
QVariant type = model()->data(current, CollectionModel::Role_Type);
if (!type.isValid() || (type.toInt() != CollectionItem::Type_Song && type.toInt() != CollectionItem::Type_Container && type.toInt() != CollectionItem::Type_Divider)) {
return;
}
QString search;
QModelIndex index = qobject_cast<QSortFilterProxyModel*>(model())->mapToSource(current);
switch (type.toInt()) {
case CollectionItem::Type_Song:{
SongList songs = app_->collection_model()->GetChildSongs(index);
if (!songs.isEmpty()) {
last_selected_song_ = songs.last();
}
search = QStringLiteral("title:%1").arg(last_selected_song_.title());
break;
}
case CollectionItem::Type_Divider:{
break;
}
case CollectionItem::Type_Container:{
CollectionItem *item = app_->collection_model()->IndexToItem(index);
int container_level = item->container_level;
CollectionModel::GroupBy container_group_by = app_->collection_model()->GetGroupBy()[container_level];
switch (container_group_by) {
case CollectionModel::GroupBy::AlbumArtist:
search = QStringLiteral("albumartist:%1").arg(item->metadata.effective_albumartist());
break;
case CollectionModel::GroupBy::Artist:
search = QStringLiteral("artist:%1").arg(item->metadata.artist());
break;
case CollectionModel::GroupBy::Album:
search = QStringLiteral("album:%1").arg(item->metadata.album());
break;
case CollectionModel::GroupBy::AlbumDisc:
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());
break;
}
case CollectionModel::GroupBy::OriginalYearAlbum:
case CollectionModel::GroupBy::OriginalYearAlbumDisc:{
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());
break;
case CollectionModel::GroupBy::OriginalYear:
search = QStringLiteral("year:%1").arg(item->metadata.effective_originalyear());
break;
case CollectionModel::GroupBy::Genre:
search = QStringLiteral("genre:%1").arg(item->metadata.genre());
break;
case CollectionModel::GroupBy::Composer:
search = QStringLiteral("composer:%1").arg(item->metadata.composer());
break;
case CollectionModel::GroupBy::Performer:
search = QStringLiteral("performer:%1").arg(item->metadata.performer());
break;
case CollectionModel::GroupBy::Grouping:
search = QStringLiteral("grouping:%1").arg(item->metadata.grouping());
break;
case CollectionModel::GroupBy::Samplerate:
search = QStringLiteral("samplerate:%1").arg(item->metadata.samplerate());
break;
case CollectionModel::GroupBy::Bitdepth:
search = QStringLiteral("bitdepth:%1").arg(item->metadata.bitdepth());
break;
case CollectionModel::GroupBy::Bitrate:
search = QStringLiteral("bitrate:%1").arg(item->metadata.bitrate());
break;
default:
search = model()->data(current, Qt::DisplayRole).toString();
}
break;
}
default:
return;
}
filter_->ShowInCollection(search);
}
void CollectionView::keyboardSearch(const QString &search) { void CollectionView::keyboardSearch(const QString &search) {
is_in_keyboard_search_ = true; is_in_keyboard_search_ = true;
@ -646,7 +765,7 @@ void CollectionView::FilterReturnPressed() {
void CollectionView::ShowInBrowser() const { void CollectionView::ShowInBrowser() const {
SongList songs = GetSelectedSongs(); const SongList songs = GetSelectedSongs();
QList<QUrl> urls; QList<QUrl> urls;
urls.reserve(songs.count()); urls.reserve(songs.count());
for (const Song &song : songs) { for (const Song &song : songs) {
@ -671,7 +790,7 @@ void CollectionView::Delete() {
if (!delete_files_) return; if (!delete_files_) return;
SongList selected_songs = GetSelectedSongs(); const SongList selected_songs = GetSelectedSongs();
SongList songs; SongList songs;
QStringList files; QStringList files;

View File

@ -93,6 +93,7 @@ class CollectionView : public AutoExpandingTreeView {
protected: protected:
// QWidget // QWidget
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
void keyPressEvent(QKeyEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override;
@ -102,6 +103,7 @@ class CollectionView : public AutoExpandingTreeView {
void AddToPlaylistEnqueue(); void AddToPlaylistEnqueue();
void AddToPlaylistEnqueueNext(); void AddToPlaylistEnqueueNext();
void OpenInNewPlaylist(); void OpenInNewPlaylist();
void SearchForThis();
void Organize(); void Organize();
void CopyToDevice(); void CopyToDevice();
void EditTracks(); void EditTracks();
@ -136,6 +138,8 @@ class CollectionView : public AutoExpandingTreeView {
QAction *action_add_to_playlist_enqueue_next_; QAction *action_add_to_playlist_enqueue_next_;
QAction *action_open_in_new_playlist_; QAction *action_open_in_new_playlist_;
QAction *action_organize_; QAction *action_organize_;
QAction *action_search_for_this_;
#ifndef Q_OS_WIN #ifndef Q_OS_WIN
QAction *action_copy_to_device_; QAction *action_copy_to_device_;
#endif #endif

View File

@ -48,6 +48,7 @@
#include "core/logging.h" #include "core/logging.h"
#include "core/tagreaderclient.h" #include "core/tagreaderclient.h"
#include "core/taskmanager.h" #include "core/taskmanager.h"
#include "core/settings.h"
#include "utilities/imageutils.h" #include "utilities/imageutils.h"
#include "utilities/timeconstants.h" #include "utilities/timeconstants.h"
#include "collectiondirectory.h" #include "collectiondirectory.h"
@ -70,8 +71,8 @@
using namespace std::chrono_literals; using namespace std::chrono_literals;
QStringList CollectionWatcher::sValidImages = QStringList() << "jpg" << "png" << "gif" << "jpeg"; QStringList CollectionWatcher::sValidImages = QStringList() << QStringLiteral("jpg") << QStringLiteral("png") << QStringLiteral("gif") << QStringLiteral("jpeg");
QStringList CollectionWatcher::kIgnoredExtensions = QStringList() << "tmp" << "tar" << "gz" << "bz2" << "xz" << "tbz" << "tgz" << "z" << "zip" << "rar"; 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) CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
: QObject(parent), : QObject(parent),
@ -105,7 +106,7 @@ CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
periodic_scan_timer_->setInterval(86400 * kMsecPerSec); periodic_scan_timer_->setInterval(86400 * kMsecPerSec);
periodic_scan_timer_->setSingleShot(false); periodic_scan_timer_->setSingleShot(false);
QStringList image_formats = ImageUtils::SupportedImageFormats(); const QStringList image_formats = ImageUtils::SupportedImageFormats();
for (const QString &format : image_formats) { for (const QString &format : image_formats) {
if (!sValidImages.contains(format)) { if (!sValidImages.contains(format)) {
sValidImages.append(format); sValidImages.append(format);
@ -150,11 +151,11 @@ void CollectionWatcher::ReloadSettingsAsync() {
void CollectionWatcher::ReloadSettings() { void CollectionWatcher::ReloadSettings() {
const bool was_monitoring_before = monitor_; const bool was_monitoring_before = monitor_;
QSettings s; Settings s;
s.beginGroup(CollectionSettingsPage::kSettingsGroup); s.beginGroup(CollectionSettingsPage::kSettingsGroup);
scan_on_startup_ = s.value("startup_scan", true).toBool(); scan_on_startup_ = s.value("startup_scan", true).toBool();
monitor_ = s.value("monitor", true).toBool(); monitor_ = s.value("monitor", true).toBool();
QStringList filters = s.value("cover_art_patterns", QStringList() << "front" << "cover").toStringList(); const QStringList filters = s.value("cover_art_patterns", QStringList() << QStringLiteral("front") << QStringLiteral("cover")).toStringList();
if (source_ == Song::Source::Collection) { if (source_ == Song::Source::Collection) {
song_tracking_ = s.value("song_tracking", false).toBool(); song_tracking_ = s.value("song_tracking", false).toBool();
song_ebur128_loudness_analysis_ = s.value("song_ebur128_loudness_analysis", false).toBool(); song_ebur128_loudness_analysis_ = s.value("song_ebur128_loudness_analysis", false).toBool();
@ -182,7 +183,7 @@ void CollectionWatcher::ReloadSettings() {
else if (monitor_ && !was_monitoring_before) { else if (monitor_ && !was_monitoring_before) {
// Add all directories to all QFileSystemWatchers again // Add all directories to all QFileSystemWatchers again
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) { for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
CollectionSubdirectoryList subdirs = backend_->SubdirsInDirectory(dir.id); const CollectionSubdirectoryList subdirs = backend_->SubdirsInDirectory(dir.id);
for (const CollectionSubdirectory &subdir : subdirs) { for (const CollectionSubdirectory &subdir : subdirs) {
AddWatch(dir, subdir.path); AddWatch(dir, subdir.path);
} }
@ -287,7 +288,7 @@ void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
touched_subdirs.clear(); touched_subdirs.clear();
} }
for (const CollectionSubdirectory &subdir : deleted_subdirs) { for (const CollectionSubdirectory &subdir : std::as_const(deleted_subdirs)) {
if (watcher_->watched_dirs_.contains(dir_)) { if (watcher_->watched_dirs_.contains(dir_)) {
watcher_->RemoveWatch(watcher_->watched_dirs_[dir_], subdir); watcher_->RemoveWatch(watcher_->watched_dirs_[dir_], subdir);
} }
@ -296,7 +297,7 @@ void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
if (watcher_->monitor_) { if (watcher_->monitor_) {
// Watch the new subdirectories // Watch the new subdirectories
for (const CollectionSubdirectory &subdir : new_subdirs) { for (const CollectionSubdirectory &subdir : std::as_const(new_subdirs)) {
if (watcher_->watched_dirs_.contains(dir_)) { if (watcher_->watched_dirs_.contains(dir_)) {
watcher_->AddWatch(watcher_->watched_dirs_[dir_], subdir.path); watcher_->AddWatch(watcher_->watched_dirs_[dir_], subdir.path);
} }
@ -316,7 +317,7 @@ SongList CollectionWatcher::ScanTransaction::FindSongsInSubdirectory(const QStri
if (cached_songs_dirty_) { if (cached_songs_dirty_) {
const SongList songs = watcher_->backend_->FindSongsInDirectory(dir_); const SongList songs = watcher_->backend_->FindSongsInDirectory(dir_);
for (const Song &song : songs) { for (const Song &song : songs) {
const QString p = song.url().toLocalFile().section('/', 0, -2); const QString p = song.url().toLocalFile().section(QLatin1Char('/'), 0, -2);
cached_songs_.insert(p, song); cached_songs_.insert(p, song);
} }
cached_songs_dirty_ = false; cached_songs_dirty_ = false;
@ -334,7 +335,7 @@ bool CollectionWatcher::ScanTransaction::HasSongsWithMissingFingerprint(const QS
if (cached_songs_missing_fingerprint_dirty_) { if (cached_songs_missing_fingerprint_dirty_) {
const SongList songs = watcher_->backend_->SongsWithMissingFingerprint(dir_); const SongList songs = watcher_->backend_->SongsWithMissingFingerprint(dir_);
for (const Song &song : songs) { for (const Song &song : songs) {
const QString p = song.url().toLocalFile().section('/', 0, -2); const QString p = song.url().toLocalFile().section(QLatin1Char('/'), 0, -2);
cached_songs_missing_fingerprint_.insert(p, song); cached_songs_missing_fingerprint_.insert(p, song);
} }
cached_songs_missing_fingerprint_dirty_ = false; cached_songs_missing_fingerprint_dirty_ = false;
@ -349,7 +350,7 @@ bool CollectionWatcher::ScanTransaction::HasSongsWithMissingLoudnessCharacterist
if (cached_songs_missing_loudness_characteristics_dirty_) { if (cached_songs_missing_loudness_characteristics_dirty_) {
const SongList songs = watcher_->backend_->SongsWithMissingLoudnessCharacteristics(dir_); const SongList songs = watcher_->backend_->SongsWithMissingLoudnessCharacteristics(dir_);
for (const Song &song : songs) { for (const Song &song : songs) {
const QString p = song.url().toLocalFile().section('/', 0, -2); const QString p = song.url().toLocalFile().section(QLatin1Char('/'), 0, -2);
cached_songs_missing_loudness_characteristics_.insert(p, song); cached_songs_missing_loudness_characteristics_.insert(p, song);
} }
cached_songs_missing_loudness_characteristics_dirty_ = false; cached_songs_missing_loudness_characteristics_dirty_ = false;
@ -383,7 +384,7 @@ CollectionSubdirectoryList CollectionWatcher::ScanTransaction::GetImmediateSubdi
} }
CollectionSubdirectoryList ret; CollectionSubdirectoryList ret;
for (const CollectionSubdirectory &subdir : known_subdirs_) { for (const CollectionSubdirectory &subdir : std::as_const(known_subdirs_)) {
if (subdir.path.left(subdir.path.lastIndexOf(QDir::separator())) == path && subdir.mtime != 0) { if (subdir.path.left(subdir.path.lastIndexOf(QDir::separator())) == path && subdir.mtime != 0) {
ret << subdir; ret << subdir;
} }
@ -416,7 +417,7 @@ void CollectionWatcher::AddDirectory(const CollectionDirectory &dir, const Colle
transaction.SetKnownSubdirs(subdirs); transaction.SetKnownSubdirs(subdirs);
transaction.AddToProgressMax(files_count); transaction.AddToProgressMax(files_count);
ScanSubdirectory(dir.path, CollectionSubdirectory(), files_count, &transaction); ScanSubdirectory(dir.path, CollectionSubdirectory(), files_count, &transaction);
last_scan_time_ = QDateTime::currentDateTime().toSecsSinceEpoch(); last_scan_time_ = QDateTime::currentSecsSinceEpoch();
} }
else { else {
// We can do an incremental scan - looking at the mtimes of each subdirectory and only rescan if the directory has changed. // We can do an incremental scan - looking at the mtimes of each subdirectory and only rescan if the directory has changed.
@ -433,7 +434,7 @@ void CollectionWatcher::AddDirectory(const CollectionDirectory &dir, const Colle
if (monitor_) AddWatch(dir, subdir.path); if (monitor_) AddWatch(dir, subdir.path);
} }
last_scan_time_ = QDateTime::currentDateTime().toSecsSinceEpoch(); last_scan_time_ = QDateTime::currentSecsSinceEpoch();
} }
@ -480,7 +481,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
// If a directory is moved then only its parent gets a changed notification, so we need to look and see if any of our children don't exist anymore. // If a directory is moved then only its parent gets a changed notification, so we need to look and see if any of our children don't exist anymore.
// If one has been removed, "rescan" it to get the deleted songs // If one has been removed, "rescan" it to get the deleted songs
CollectionSubdirectoryList previous_subdirs = t->GetImmediateSubdirs(path); const CollectionSubdirectoryList previous_subdirs = t->GetImmediateSubdirs(path);
for (const CollectionSubdirectory &prev_subdir : previous_subdirs) { for (const CollectionSubdirectory &prev_subdir : previous_subdirs) {
if (!QFile::exists(prev_subdir.path) && prev_subdir.path != path) { if (!QFile::exists(prev_subdir.path) && prev_subdir.path != path) {
ScanSubdirectory(prev_subdir.path, prev_subdir, 0, t, true); ScanSubdirectory(prev_subdir.path, prev_subdir, 0, t, true);
@ -510,7 +511,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
else { else {
QString ext_part(ExtensionPart(child)); QString ext_part(ExtensionPart(child));
QString dir_part(DirectoryPart(child)); QString dir_part(DirectoryPart(child));
if (kIgnoredExtensions.contains(child_info.suffix(), Qt::CaseInsensitive) || child_info.baseName() == "qt_temp") { if (kIgnoredExtensions.contains(child_info.suffix(), Qt::CaseInsensitive) || child_info.baseName() == QStringLiteral("qt_temp")) {
t->AddToProgress(1); t->AddToProgress(1);
} }
else if (sValidImages.contains(ext_part)) { else if (sValidImages.contains(ext_part)) {
@ -612,7 +613,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
Chromaprinter chromaprinter(file); Chromaprinter chromaprinter(file);
fingerprint = chromaprinter.CreateFingerprint(); fingerprint = chromaprinter.CreateFingerprint();
if (fingerprint.isEmpty()) { if (fingerprint.isEmpty()) {
fingerprint = "NONE"; fingerprint = QStringLiteral("NONE");
} }
} }
#endif #endif
@ -639,11 +640,11 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
Chromaprinter chromaprinter(file); Chromaprinter chromaprinter(file);
fingerprint = chromaprinter.CreateFingerprint(); fingerprint = chromaprinter.CreateFingerprint();
if (fingerprint.isEmpty()) { if (fingerprint.isEmpty()) {
fingerprint = "NONE"; fingerprint = QStringLiteral("NONE");
} }
} }
#endif #endif
if (song_tracking_ && !fingerprint.isEmpty() && fingerprint != "NONE" && FindSongsByFingerprint(file, fingerprint, &matching_songs)) { if (song_tracking_ && !fingerprint.isEmpty() && fingerprint != QStringLiteral("NONE") && FindSongsByFingerprint(file, fingerprint, &matching_songs)) {
// The song is in the database and still on disk. // The song is in the database and still on disk.
// Check the mtime to see if it's been changed since it was added. // Check the mtime to see if it's been changed since it was added.
@ -657,7 +658,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
// Make sure the songs aren't deleted, as they still exist elsewhere with a different file path. // Make sure the songs aren't deleted, as they still exist elsewhere with a different file path.
bool matching_songs_has_cue = false; bool matching_songs_has_cue = false;
for (const Song &matching_song : matching_songs) { for (const Song &matching_song : std::as_const(matching_songs)) {
QString matching_filename = matching_song.url().toLocalFile(); QString matching_filename = matching_song.url().toLocalFile();
if (!t->files_changed_path_.contains(matching_filename)) { if (!t->files_changed_path_.contains(matching_filename)) {
t->files_changed_path_ << matching_filename; t->files_changed_path_ << matching_filename;
@ -690,7 +691,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
} }
else { // The song is on disk but not in the DB else { // The song is on disk but not in the DB
SongList songs = ScanNewFile(file, path, fingerprint, new_cue, &cues_processed); const SongList songs = ScanNewFile(file, path, fingerprint, new_cue, &cues_processed);
if (songs.isEmpty()) { if (songs.isEmpty()) {
t->AddToProgress(1); t->AddToProgress(1);
continue; continue;
@ -712,7 +713,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
} }
// Look for deleted songs // Look for deleted songs
for (const Song &song : songs_in_db) { for (const Song &song : std::as_const(songs_in_db)) {
QString file = song.url().toLocalFile(); QString file = song.url().toLocalFile();
if (!song.unavailable() && !files_on_disk.contains(file) && !t->files_changed_path_.contains(file)) { if (!song.unavailable() && !files_on_disk.contains(file) && !t->files_changed_path_.contains(file)) {
qLog(Debug) << "Song deleted from disk:" << file; qLog(Debug) << "Song deleted from disk:" << file;
@ -738,7 +739,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
} }
// Recurse into the new subdirs that we found // Recurse into the new subdirs that we found
for (const CollectionSubdirectory &my_new_subdir : my_new_subdirs) { for (const CollectionSubdirectory &my_new_subdir : std::as_const(my_new_subdirs)) {
if (stop_requested_ || abort_requested_) return; if (stop_requested_ || abort_requested_) return;
ScanSubdirectory(my_new_subdir.path, my_new_subdir, 0, t, true); ScanSubdirectory(my_new_subdir.path, my_new_subdir, 0, t, true);
} }
@ -892,50 +893,50 @@ void CollectionWatcher::AddChangedSong(const QString &file, const Song &matching
} }
else { else {
if (matching_song.url() != new_song.url()) { if (matching_song.url() != new_song.url()) {
changes << "file path"; changes << QStringLiteral("file path");
notify_new = true; notify_new = true;
} }
if (matching_song.fingerprint() != new_song.fingerprint()) { if (matching_song.fingerprint() != new_song.fingerprint()) {
changes << "fingerprint"; changes << QStringLiteral("fingerprint");
notify_new = true; notify_new = true;
} }
if (!matching_song.IsMetadataEqual(new_song)) { if (!matching_song.IsMetadataEqual(new_song)) {
changes << "metadata"; changes << QStringLiteral("metadata");
notify_new = true; notify_new = true;
} }
if (!matching_song.IsPlayStatisticsEqual(new_song)) { if (!matching_song.IsPlayStatisticsEqual(new_song)) {
changes << "play statistics"; changes << QStringLiteral("play statistics");
notify_new = true; notify_new = true;
} }
if (!matching_song.IsRatingEqual(new_song)) { if (!matching_song.IsRatingEqual(new_song)) {
changes << "rating"; changes << QStringLiteral("rating");
notify_new = true; notify_new = true;
} }
if (!matching_song.IsArtEqual(new_song)) { if (!matching_song.IsArtEqual(new_song)) {
changes << "album art"; changes << QStringLiteral("album art");
notify_new = true; notify_new = true;
} }
if (!matching_song.IsAcoustIdEqual(new_song)) { if (!matching_song.IsAcoustIdEqual(new_song)) {
changes << "acoustid"; changes << QStringLiteral("acoustid");
notify_new = true; notify_new = true;
} }
if (!matching_song.IsMusicBrainzEqual(new_song)) { if (!matching_song.IsMusicBrainzEqual(new_song)) {
changes << "musicbrainz"; changes << QStringLiteral("musicbrainz");
notify_new = true; notify_new = true;
} }
if (!matching_song.IsEBUR128Equal(new_song)) { if (!matching_song.IsEBUR128Equal(new_song)) {
changes << "ebur128 loudness characteristics"; changes << QStringLiteral("ebur128 loudness characteristics");
notify_new = true; notify_new = true;
} }
if (matching_song.mtime() != new_song.mtime()) { if (matching_song.mtime() != new_song.mtime()) {
changes << "mtime"; changes << QStringLiteral("mtime");
} }
if (changes.isEmpty()) { if (changes.isEmpty()) {
qLog(Debug) << "Song" << file << "unchanged."; qLog(Debug) << "Song" << file << "unchanged.";
} }
else { else {
qLog(Debug) << "Song" << file << changes.join(", ") << "changed."; qLog(Debug) << "Song" << file << changes.join(QStringLiteral(", ")) << "changed.";
} }
} }
@ -990,7 +991,7 @@ void CollectionWatcher::AddWatch(const CollectionDirectory &dir, const QString &
void CollectionWatcher::RemoveWatch(const CollectionDirectory &dir, const CollectionSubdirectory &subdir) { void CollectionWatcher::RemoveWatch(const CollectionDirectory &dir, const CollectionSubdirectory &subdir) {
QStringList subdir_paths = subdir_mapping_.keys(dir); const QStringList subdir_paths = subdir_mapping_.keys(dir);
for (const QString &subdir_path : subdir_paths) { for (const QString &subdir_path : subdir_paths) {
if (subdir_path != subdir.path) continue; if (subdir_path != subdir.path) continue;
fs_watcher_->RemovePath(subdir_path); fs_watcher_->RemovePath(subdir_path);
@ -1007,7 +1008,7 @@ void CollectionWatcher::RemoveDirectory(const CollectionDirectory &dir) {
// Stop watching the directory's subdirectories // Stop watching the directory's subdirectories
QStringList subdir_paths = subdir_mapping_.keys(dir); QStringList subdir_paths = subdir_mapping_.keys(dir);
for (const QString &subdir_path : subdir_paths) { for (const QString &subdir_path : std::as_const(subdir_paths)) {
fs_watcher_->RemovePath(subdir_path); fs_watcher_->RemovePath(subdir_path);
subdir_mapping_.remove(subdir_path); subdir_mapping_.remove(subdir_path);
} }
@ -1029,7 +1030,7 @@ bool CollectionWatcher::FindSongsByPath(const SongList &songs, const QString &pa
bool CollectionWatcher::FindSongsByFingerprint(const QString &file, const QString &fingerprint, SongList *out) { bool CollectionWatcher::FindSongsByFingerprint(const QString &file, const QString &fingerprint, SongList *out) {
SongList songs = backend_->GetSongsByFingerprint(fingerprint); SongList songs = backend_->GetSongsByFingerprint(fingerprint);
for (const Song &song : songs) { for (const Song &song : std::as_const(songs)) {
QString filename = song.url().toLocalFile(); QString filename = song.url().toLocalFile();
QFileInfo info(filename); QFileInfo info(filename);
// Allow mulitiple songs in different directories with the same fingerprint. // Allow mulitiple songs in different directories with the same fingerprint.
@ -1077,19 +1078,21 @@ void CollectionWatcher::DirectoryChanged(const QString &subdir) {
void CollectionWatcher::RescanPathsNow() { void CollectionWatcher::RescanPathsNow() {
QList<int> dirs = rescan_queue_.keys(); const QList<int> dirs = rescan_queue_.keys();
for (const int dir : dirs) { for (const int dir : dirs) {
if (stop_requested_ || abort_requested_) break; if (stop_requested_ || abort_requested_) break;
ScanTransaction transaction(this, dir, false, false, mark_songs_unavailable_); ScanTransaction transaction(this, dir, false, false, mark_songs_unavailable_);
const QStringList paths = rescan_queue_[dir];
QMap<QString, quint64> subdir_files_count; QMap<QString, quint64> subdir_files_count;
for (const QString &path : rescan_queue_[dir]) { for (const QString &path : paths) {
quint64 files_count = FilesCountForPath(&transaction, path); quint64 files_count = FilesCountForPath(&transaction, path);
subdir_files_count[path] = files_count; subdir_files_count[path] = files_count;
transaction.AddToProgressMax(files_count); transaction.AddToProgressMax(files_count);
} }
for (const QString &path : rescan_queue_[dir]) { for (const QString &path : paths) {
if (stop_requested_ || abort_requested_) break; if (stop_requested_ || abort_requested_) break;
CollectionSubdirectory subdir; CollectionSubdirectory subdir;
subdir.directory_id = dir; subdir.directory_id = dir;
@ -1112,7 +1115,7 @@ QString CollectionWatcher::PickBestArt(const QStringList &art_automatic_list) {
QStringList filtered; QStringList filtered;
for (const QString &filter_text : best_art_filters_) { for (const QString &filter_text : std::as_const(best_art_filters_)) {
// The images in the images list are represented by a full path, so we need to isolate just the filename // The images in the images list are represented by a full path, so we need to isolate just the filename
for (const QString &art_automatic : art_automatic_list) { for (const QString &art_automatic : art_automatic_list) {
QFileInfo fileinfo(art_automatic); QFileInfo fileinfo(art_automatic);
@ -1134,7 +1137,7 @@ QString CollectionWatcher::PickBestArt(const QStringList &art_automatic_list) {
int biggest_size = 0; int biggest_size = 0;
QString biggest_path; QString biggest_path;
for (const QString &path : filtered) { for (const QString &path : std::as_const(filtered)) {
if (stop_requested_ || abort_requested_) break; if (stop_requested_ || abort_requested_) break;
QImage image(path); QImage image(path);
@ -1197,7 +1200,7 @@ void CollectionWatcher::FullScanAsync() {
void CollectionWatcher::IncrementalScanCheck() { void CollectionWatcher::IncrementalScanCheck() {
qint64 duration = QDateTime::currentDateTime().toSecsSinceEpoch() - last_scan_time_; qint64 duration = QDateTime::currentSecsSinceEpoch() - last_scan_time_;
if (duration >= 86400) { if (duration >= 86400) {
qLog(Debug) << "Performing periodic incremental scan."; qLog(Debug) << "Performing periodic incremental scan.";
IncrementalScanNow(); IncrementalScanNow();
@ -1232,14 +1235,14 @@ void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mt
quint64 files_count = FilesCountForSubdirs(&transaction, subdirs, subdir_files_count); quint64 files_count = FilesCountForSubdirs(&transaction, subdirs, subdir_files_count);
transaction.AddToProgressMax(files_count); transaction.AddToProgressMax(files_count);
for (const CollectionSubdirectory &subdir : subdirs) { for (const CollectionSubdirectory &subdir : std::as_const(subdirs)) {
if (stop_requested_ || abort_requested_) break; if (stop_requested_ || abort_requested_) break;
ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction); ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
} }
} }
last_scan_time_ = QDateTime::currentDateTime().toSecsSinceEpoch(); last_scan_time_ = QDateTime::currentSecsSinceEpoch();
emit CompilationsNeedUpdating(); emit CompilationsNeedUpdating();
@ -1308,10 +1311,10 @@ void CollectionWatcher::RescanSongs(const SongList &songs) {
QStringList scanned_paths; QStringList scanned_paths;
for (const Song &song : songs) { for (const Song &song : songs) {
if (stop_requested_ || abort_requested_) break; if (stop_requested_ || abort_requested_) break;
const QString song_path = song.url().toLocalFile().section('/', 0, -2); const QString song_path = song.url().toLocalFile().section(QLatin1Char('/'), 0, -2);
if (scanned_paths.contains(song_path)) continue; if (scanned_paths.contains(song_path)) continue;
ScanTransaction transaction(this, song.directory_id(), false, true, mark_songs_unavailable_); ScanTransaction transaction(this, song.directory_id(), false, true, mark_songs_unavailable_);
CollectionSubdirectoryList subdirs(transaction.GetAllSubdirs()); const CollectionSubdirectoryList subdirs = transaction.GetAllSubdirs();
for (const CollectionSubdirectory &subdir : subdirs) { for (const CollectionSubdirectory &subdir : subdirs) {
if (stop_requested_ || abort_requested_) break; if (stop_requested_ || abort_requested_) break;
if (subdir.path != song_path) continue; if (subdir.path != song_path) continue;

View File

@ -252,14 +252,14 @@ class CollectionWatcher : public QObject {
}; };
inline QString CollectionWatcher::NoExtensionPart(const QString &fileName) { inline QString CollectionWatcher::NoExtensionPart(const QString &fileName) {
return fileName.contains('.') ? fileName.section('.', 0, -2) : ""; return fileName.contains(QLatin1Char('.')) ? fileName.section(QLatin1Char('.'), 0, -2) : QLatin1String("");
} }
// Thanks Amarok // Thanks Amarok
inline QString CollectionWatcher::ExtensionPart(const QString &fileName) { inline QString CollectionWatcher::ExtensionPart(const QString &fileName) {
return fileName.contains( '.' ) ? fileName.mid( fileName.lastIndexOf('.') + 1 ).toLower() : ""; return fileName.contains(QLatin1Char('.')) ? fileName.mid(fileName.lastIndexOf(QLatin1Char('.')) + 1).toLower() : QLatin1String("");
} }
inline QString CollectionWatcher::DirectoryPart(const QString &fileName) { inline QString CollectionWatcher::DirectoryPart(const QString &fileName) {
return fileName.section('/', 0, -2); return fileName.section(QLatin1Char('/'), 0, -2);
} }
#endif // COLLECTIONWATCHER_H #endif // COLLECTIONWATCHER_H

View File

@ -21,6 +21,8 @@
#include "config.h" #include "config.h"
#include <utility>
#include <QDialog> #include <QDialog>
#include <QStandardItemModel> #include <QStandardItemModel>
#include <QItemSelectionModel> #include <QItemSelectionModel>
@ -36,6 +38,7 @@
#include "core/logging.h" #include "core/logging.h"
#include "core/iconloader.h" #include "core/iconloader.h"
#include "core/settings.h"
#include "settings/collectionsettingspage.h" #include "settings/collectionsettingspage.h"
#include "collectionmodel.h" #include "collectionmodel.h"
#include "savedgroupingmanager.h" #include "savedgroupingmanager.h"
@ -56,7 +59,7 @@ SavedGroupingManager::SavedGroupingManager(const QString &saved_groupings_settin
model_->setHorizontalHeaderItem(2, new QStandardItem(tr("Second Level"))); model_->setHorizontalHeaderItem(2, new QStandardItem(tr("Second Level")));
model_->setHorizontalHeaderItem(3, new QStandardItem(tr("Third Level"))); model_->setHorizontalHeaderItem(3, new QStandardItem(tr("Third Level")));
ui_->list->setModel(model_); ui_->list->setModel(model_);
ui_->remove->setIcon(IconLoader::Load("edit-delete")); ui_->remove->setIcon(IconLoader::Load(QStringLiteral("edit-delete")));
ui_->remove->setEnabled(false); ui_->remove->setEnabled(false);
ui_->remove->setShortcut(QKeySequence::Delete); ui_->remove->setShortcut(QKeySequence::Delete);
@ -72,11 +75,11 @@ SavedGroupingManager::~SavedGroupingManager() {
QString SavedGroupingManager::GetSavedGroupingsSettingsGroup(const QString &settings_group) { QString SavedGroupingManager::GetSavedGroupingsSettingsGroup(const QString &settings_group) {
if (settings_group.isEmpty() || settings_group == CollectionSettingsPage::kSettingsGroup) { if (settings_group.isEmpty() || settings_group == QLatin1String(CollectionSettingsPage::kSettingsGroup)) {
return kSavedGroupingsSettingsGroup; return QLatin1String(kSavedGroupingsSettingsGroup);
} }
else { else {
return QString(kSavedGroupingsSettingsGroup) + "_" + settings_group; return QLatin1String(kSavedGroupingsSettingsGroup) + QLatin1Char('_') + settings_group;
} }
} }
@ -85,67 +88,67 @@ QString SavedGroupingManager::GroupByToString(const CollectionModel::GroupBy g)
switch (g) { switch (g) {
case CollectionModel::GroupBy::None: case CollectionModel::GroupBy::None:
case CollectionModel::GroupBy::GroupByCount: { case CollectionModel::GroupBy::GroupByCount:{
return tr("None"); return tr("None");
} }
case CollectionModel::GroupBy::AlbumArtist: { case CollectionModel::GroupBy::AlbumArtist:{
return tr("Album artist"); return tr("Album artist");
} }
case CollectionModel::GroupBy::Artist: { case CollectionModel::GroupBy::Artist:{
return tr("Artist"); return tr("Artist");
} }
case CollectionModel::GroupBy::Album: { case CollectionModel::GroupBy::Album:{
return tr("Album"); return tr("Album");
} }
case CollectionModel::GroupBy::AlbumDisc: { case CollectionModel::GroupBy::AlbumDisc:{
return tr("Album - Disc"); return tr("Album - Disc");
} }
case CollectionModel::GroupBy::YearAlbum: { case CollectionModel::GroupBy::YearAlbum:{
return tr("Year - Album"); return tr("Year - Album");
} }
case CollectionModel::GroupBy::YearAlbumDisc: { case CollectionModel::GroupBy::YearAlbumDisc:{
return tr("Year - Album - Disc"); return tr("Year - Album - Disc");
} }
case CollectionModel::GroupBy::OriginalYearAlbum: { case CollectionModel::GroupBy::OriginalYearAlbum:{
return tr("Original year - Album"); return tr("Original year - Album");
} }
case CollectionModel::GroupBy::OriginalYearAlbumDisc: { case CollectionModel::GroupBy::OriginalYearAlbumDisc:{
return tr("Original year - Album - Disc"); return tr("Original year - Album - Disc");
} }
case CollectionModel::GroupBy::Disc: { case CollectionModel::GroupBy::Disc:{
return tr("Disc"); return tr("Disc");
} }
case CollectionModel::GroupBy::Year: { case CollectionModel::GroupBy::Year:{
return tr("Year"); return tr("Year");
} }
case CollectionModel::GroupBy::OriginalYear: { case CollectionModel::GroupBy::OriginalYear:{
return tr("Original year"); return tr("Original year");
} }
case CollectionModel::GroupBy::Genre: { case CollectionModel::GroupBy::Genre:{
return tr("Genre"); return tr("Genre");
} }
case CollectionModel::GroupBy::Composer: { case CollectionModel::GroupBy::Composer:{
return tr("Composer"); return tr("Composer");
} }
case CollectionModel::GroupBy::Performer: { case CollectionModel::GroupBy::Performer:{
return tr("Performer"); return tr("Performer");
} }
case CollectionModel::GroupBy::Grouping: { case CollectionModel::GroupBy::Grouping:{
return tr("Grouping"); return tr("Grouping");
} }
case CollectionModel::GroupBy::FileType: { case CollectionModel::GroupBy::FileType:{
return tr("File type"); return tr("File type");
} }
case CollectionModel::GroupBy::Format: { case CollectionModel::GroupBy::Format:{
return tr("Format"); return tr("Format");
} }
case CollectionModel::GroupBy::Samplerate: { case CollectionModel::GroupBy::Samplerate:{
return tr("Sample rate"); return tr("Sample rate");
} }
case CollectionModel::GroupBy::Bitdepth: { case CollectionModel::GroupBy::Bitdepth:{
return tr("Bit depth"); return tr("Bit depth");
} }
case CollectionModel::GroupBy::Bitrate: { case CollectionModel::GroupBy::Bitrate:{
return tr("Bitrate"); return tr("Bitrate");
} }
} }
@ -157,13 +160,13 @@ QString SavedGroupingManager::GroupByToString(const CollectionModel::GroupBy g)
void SavedGroupingManager::UpdateModel() { void SavedGroupingManager::UpdateModel() {
model_->setRowCount(0); // don't use clear, it deletes headers model_->setRowCount(0); // don't use clear, it deletes headers
QSettings s; Settings s;
s.beginGroup(saved_groupings_settings_group_); s.beginGroup(saved_groupings_settings_group_);
int version = s.value("version").toInt(); int version = s.value("version").toInt();
if (version == 1) { if (version == 1) {
QStringList saved = s.childKeys(); QStringList saved = s.childKeys();
for (int i = 0; i < saved.size(); ++i) { for (int i = 0; i < saved.size(); ++i) {
if (saved.at(i) == "version") continue; if (saved.at(i) == QStringLiteral("version")) continue;
QByteArray bytes = s.value(saved.at(i)).toByteArray(); QByteArray bytes = s.value(saved.at(i)).toByteArray();
QDataStream ds(&bytes, QIODevice::ReadOnly); QDataStream ds(&bytes, QIODevice::ReadOnly);
CollectionModel::Grouping g; CollectionModel::Grouping g;
@ -181,7 +184,7 @@ void SavedGroupingManager::UpdateModel() {
else { else {
QStringList saved = s.childKeys(); QStringList saved = s.childKeys();
for (int i = 0; i < saved.size(); ++i) { for (int i = 0; i < saved.size(); ++i) {
if (saved.at(i) == "version") continue; if (saved.at(i) == QStringLiteral("version")) continue;
s.remove(saved.at(i)); s.remove(saved.at(i));
} }
} }
@ -192,9 +195,10 @@ void SavedGroupingManager::UpdateModel() {
void SavedGroupingManager::Remove() { void SavedGroupingManager::Remove() {
if (ui_->list->selectionModel()->hasSelection()) { if (ui_->list->selectionModel()->hasSelection()) {
QSettings s; Settings s;
s.beginGroup(saved_groupings_settings_group_); s.beginGroup(saved_groupings_settings_group_);
for (const QModelIndex &idx : ui_->list->selectionModel()->selectedRows()) { const QModelIndexList indexes = ui_->list->selectionModel()->selectedRows();
for (const QModelIndex &idx : indexes) {
if (idx.isValid()) { if (idx.isValid()) {
qLog(Debug) << "Remove saved grouping: " << model_->item(idx.row(), 0)->text(); qLog(Debug) << "Remove saved grouping: " << model_->item(idx.row(), 0)->text();
s.remove(model_->item(idx.row(), 0)->text()); s.remove(model_->item(idx.row(), 0)->text());

View File

@ -21,7 +21,6 @@
#cmakedefine HAVE_MUSICBRAINZ #cmakedefine HAVE_MUSICBRAINZ
#cmakedefine HAVE_GLOBALSHORTCUTS #cmakedefine HAVE_GLOBALSHORTCUTS
#cmakedefine HAVE_X11_GLOBALSHORTCUTS #cmakedefine HAVE_X11_GLOBALSHORTCUTS
#cmakedefine HAVE_ICU
#cmakedefine USE_INSTALL_PREFIX #cmakedefine USE_INSTALL_PREFIX

View File

@ -56,12 +56,12 @@ ContextAlbum::ContextAlbum(QWidget *parent)
album_cover_choice_controller_(nullptr), album_cover_choice_controller_(nullptr),
downloading_covers_(false), downloading_covers_(false),
timeline_fade_(new QTimeLine(kFadeTimeLineMs, this)), timeline_fade_(new QTimeLine(kFadeTimeLineMs, this)),
image_strawberry_(":/pictures/strawberry.png"), image_strawberry_(QStringLiteral(":/pictures/strawberry.png")),
image_original_(image_strawberry_), image_original_(image_strawberry_),
pixmap_current_opacity_(1.0), pixmap_current_opacity_(1.0),
desired_height_(width()) { desired_height_(width()) {
setObjectName("context-widget-album"); setObjectName(QStringLiteral("context-widget-album"));
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
@ -266,7 +266,7 @@ void ContextAlbum::SearchCoverInProgress() {
downloading_covers_ = true; downloading_covers_ = true;
// Show a spinner animation // Show a spinner animation
spinner_animation_ = make_unique<QMovie>(":/pictures/spinner.gif", QByteArray(), this); spinner_animation_ = make_unique<QMovie>(QStringLiteral(":/pictures/spinner.gif"), QByteArray(), this);
QObject::connect(&*spinner_animation_, &QMovie::updated, this, &ContextAlbum::Update); QObject::connect(&*spinner_animation_, &QMovie::updated, this, &ContextAlbum::Update);
spinner_animation_->start(); spinner_animation_->start();
update(); update();

View File

@ -51,6 +51,7 @@
#include "core/application.h" #include "core/application.h"
#include "core/player.h" #include "core/player.h"
#include "core/song.h" #include "core/song.h"
#include "core/settings.h"
#include "utilities/strutils.h" #include "utilities/strutils.h"
#include "utilities/timeutils.h" #include "utilities/timeutils.h"
#include "widgets/resizabletextedit.h" #include "widgets/resizabletextedit.h"
@ -112,25 +113,25 @@ ContextView::ContextView(QWidget *parent)
setLayout(layout_container_); setLayout(layout_container_);
layout_container_->setObjectName("context-layout-container"); layout_container_->setObjectName(QStringLiteral("context-layout-container"));
layout_container_->setContentsMargins(0, 0, 0, 0); layout_container_->setContentsMargins(0, 0, 0, 0);
layout_container_->addWidget(scrollarea_); layout_container_->addWidget(scrollarea_);
scrollarea_->setObjectName("context-scrollarea"); scrollarea_->setObjectName(QStringLiteral("context-scrollarea"));
scrollarea_->setWidgetResizable(true); scrollarea_->setWidgetResizable(true);
scrollarea_->setWidget(widget_scrollarea_); scrollarea_->setWidget(widget_scrollarea_);
scrollarea_->setContentsMargins(0, 0, 0, 0); scrollarea_->setContentsMargins(0, 0, 0, 0);
scrollarea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scrollarea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollarea_->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); scrollarea_->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
widget_scrollarea_->setObjectName("context-widget-scrollarea"); widget_scrollarea_->setObjectName(QStringLiteral("context-widget-scrollarea"));
widget_scrollarea_->setLayout(layout_scrollarea_); widget_scrollarea_->setLayout(layout_scrollarea_);
widget_scrollarea_->setContentsMargins(0, 0, 0, 0); widget_scrollarea_->setContentsMargins(0, 0, 0, 0);
textedit_top_->setReadOnly(true); textedit_top_->setReadOnly(true);
textedit_top_->setFrameShape(QFrame::NoFrame); textedit_top_->setFrameShape(QFrame::NoFrame);
layout_scrollarea_->setObjectName("context-layout-scrollarea"); layout_scrollarea_->setObjectName(QStringLiteral("context-layout-scrollarea"));
layout_scrollarea_->setContentsMargins(15, 15, 15, 15); layout_scrollarea_->setContentsMargins(15, 15, 15, 15);
layout_scrollarea_->addWidget(textedit_top_); layout_scrollarea_->addWidget(textedit_top_);
layout_scrollarea_->addWidget(widget_album_); layout_scrollarea_->addWidget(widget_album_);
@ -291,20 +292,20 @@ void ContextView::ReloadSettings() {
QString default_font; QString default_font;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
if (QFontDatabase::families().contains(ContextSettingsPage::kDefaultFontFamily)) { if (QFontDatabase::families().contains(QLatin1String(ContextSettingsPage::kDefaultFontFamily))) {
#else #else
if (QFontDatabase().families().contains(ContextSettingsPage::kDefaultFontFamily)) { if (QFontDatabase().families().contains(QLatin1String(ContextSettingsPage::kDefaultFontFamily))) {
#endif #endif
default_font = ContextSettingsPage::kDefaultFontFamily; default_font = QLatin1String(ContextSettingsPage::kDefaultFontFamily);
} }
else { else {
default_font = font().family(); default_font = font().family();
} }
QSettings s; Settings s;
s.beginGroup(ContextSettingsPage::kSettingsGroup); s.beginGroup(ContextSettingsPage::kSettingsGroup);
title_fmt_ = s.value(ContextSettingsPage::kSettingsTitleFmt, "%title% - %artist%").toString(); title_fmt_ = s.value(ContextSettingsPage::kSettingsTitleFmt, QStringLiteral("%title% - %artist%")).toString();
summary_fmt_ = s.value(ContextSettingsPage::kSettingsSummaryFmt, "%album%").toString(); summary_fmt_ = s.value(ContextSettingsPage::kSettingsSummaryFmt, QStringLiteral("%album%")).toString();
action_show_album_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ALBUM)], true).toBool()); action_show_album_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ALBUM)], true).toBool());
action_show_data_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA)], false).toBool()); action_show_data_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA)], false).toBool());
action_show_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SONG_LYRICS)], true).toBool()); action_show_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SONG_LYRICS)], true).toBool());
@ -390,7 +391,7 @@ void ContextView::FadeStopFinished() {
} }
void ContextView::SetLabelText(QLabel *label, int value, const QString &suffix, const QString &def) { void ContextView::SetLabelText(QLabel *label, int value, const QString &suffix, const QString &def) {
label->setText(value <= 0 ? def : (QString::number(value) + " " + suffix)); label->setText(value <= 0 ? def : (QString::number(value) + QLatin1Char(' ') + suffix));
} }
void ContextView::UpdateNoSong() { void ContextView::UpdateNoSong() {
@ -409,15 +410,15 @@ void ContextView::NoSong() {
QString html; QString html;
if (collectionview_->TotalSongs() == 1) html += tr("%1 song").arg(collectionview_->TotalSongs()); if (collectionview_->TotalSongs() == 1) html += tr("%1 song").arg(collectionview_->TotalSongs());
else html += tr("%1 songs").arg(collectionview_->TotalSongs()); else html += tr("%1 songs").arg(collectionview_->TotalSongs());
html += "<br />"; html += QLatin1String("<br />");
if (collectionview_->TotalArtists() == 1) html += tr("%1 artist").arg(collectionview_->TotalArtists()); if (collectionview_->TotalArtists() == 1) html += tr("%1 artist").arg(collectionview_->TotalArtists());
else html += tr("%1 artists").arg(collectionview_->TotalArtists()); else html += tr("%1 artists").arg(collectionview_->TotalArtists());
html += "<br />"; html += QLatin1String("<br />");
if (collectionview_->TotalAlbums() == 1) html += tr("%1 album").arg(collectionview_->TotalAlbums()); if (collectionview_->TotalAlbums() == 1) html += tr("%1 album").arg(collectionview_->TotalAlbums());
else html += tr("%1 albums").arg(collectionview_->TotalAlbums()); else html += tr("%1 albums").arg(collectionview_->TotalAlbums());
html += "<br />"; html += QLatin1String("<br />");
label_stop_summary_->setFont(font_normal_); label_stop_summary_->setFont(font_normal_);
label_stop_summary_->setText(html); label_stop_summary_->setText(html);
@ -438,7 +439,7 @@ void ContextView::UpdateFonts() {
void ContextView::SetSong() { void ContextView::SetSong() {
textedit_top_->setFont(font_headline_); textedit_top_->setFont(font_headline_);
textedit_top_->SetText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song_playing_, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song_playing_, "<br />", true))); textedit_top_->SetText(QStringLiteral("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song_playing_, QStringLiteral("<br />"), true), Utilities::ReplaceMessage(summary_fmt_, song_playing_, QStringLiteral("<br />"), true)));
label_stop_summary_->clear(); label_stop_summary_->clear();
@ -474,7 +475,7 @@ void ContextView::SetSong() {
else { else {
label_samplerate_title_->show(); label_samplerate_title_->show();
label_samplerate_->show(); label_samplerate_->show();
SetLabelText(label_samplerate_, song_playing_.samplerate(), "Hz"); SetLabelText(label_samplerate_, song_playing_.samplerate(), QStringLiteral("Hz"));
} }
if (song_playing_.bitdepth() <= 0) { if (song_playing_.bitdepth() <= 0) {
label_bitdepth_title_->hide(); label_bitdepth_title_->hide();
@ -484,7 +485,7 @@ void ContextView::SetSong() {
else { else {
label_bitdepth_title_->show(); label_bitdepth_title_->show();
label_bitdepth_->show(); label_bitdepth_->show();
SetLabelText(label_bitdepth_, song_playing_.bitdepth(), "Bit"); SetLabelText(label_bitdepth_, song_playing_.bitdepth(), QStringLiteral("Bit"));
} }
if (song_playing_.bitrate() <= 0) { if (song_playing_.bitrate() <= 0) {
label_bitrate_title_->hide(); label_bitrate_title_->hide();
@ -546,7 +547,7 @@ void ContextView::SetSong() {
void ContextView::UpdateSong(const Song &song) { void ContextView::UpdateSong(const Song &song) {
textedit_top_->SetText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song, "<br />", true))); 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)));
if (action_show_data_->isChecked()) { if (action_show_data_->isChecked()) {
if (song.filetype() != song_playing_.filetype()) label_filetype_->setText(song.TextForFiletype()); if (song.filetype() != song_playing_.filetype()) label_filetype_->setText(song.TextForFiletype());
@ -571,7 +572,7 @@ void ContextView::UpdateSong(const Song &song) {
else { else {
label_samplerate_title_->show(); label_samplerate_title_->show();
label_samplerate_->show(); label_samplerate_->show();
SetLabelText(label_samplerate_, song.samplerate(), "Hz"); SetLabelText(label_samplerate_, song.samplerate(), QStringLiteral("Hz"));
} }
} }
if (song.bitdepth() != song_playing_.bitdepth()) { if (song.bitdepth() != song_playing_.bitdepth()) {
@ -583,7 +584,7 @@ void ContextView::UpdateSong(const Song &song) {
else { else {
label_bitdepth_title_->show(); label_bitdepth_title_->show();
label_bitdepth_->show(); label_bitdepth_->show();
SetLabelText(label_bitdepth_, song.bitdepth(), "Bit"); SetLabelText(label_bitdepth_, song.bitdepth(), QStringLiteral("Bit"));
} }
} }
if (song.bitrate() != song_playing_.bitrate()) { if (song.bitrate() != song_playing_.bitrate()) {
@ -631,7 +632,12 @@ void ContextView::UpdateLyrics(const quint64 id, const QString &provider, const
if (static_cast<qint64>(id) != lyrics_id_) return; if (static_cast<qint64>(id) != lyrics_id_) return;
lyrics_ = lyrics + "\n\n(Lyrics from " + provider + ")\n"; if (lyrics.isEmpty()) {
lyrics_ = QStringLiteral("No lyrics found.\n");
}
else {
lyrics_ = lyrics + QStringLiteral("\n\n(Lyrics from ") + provider + QStringLiteral(")\n");
}
lyrics_id_ = -1; lyrics_id_ = -1;
if (action_show_lyrics_->isChecked() && !lyrics_.isEmpty()) { if (action_show_lyrics_->isChecked() && !lyrics_.isEmpty()) {
@ -687,7 +693,7 @@ void ContextView::AlbumCoverLoaded(const Song &song, const QImage &image) {
void ContextView::ActionShowAlbum() { void ContextView::ActionShowAlbum() {
QSettings s; Settings s;
s.beginGroup(ContextSettingsPage::kSettingsGroup); s.beginGroup(ContextSettingsPage::kSettingsGroup);
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ALBUM)], action_show_album_->isChecked()); s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ALBUM)], action_show_album_->isChecked());
s.endGroup(); s.endGroup();
@ -697,7 +703,7 @@ void ContextView::ActionShowAlbum() {
void ContextView::ActionShowData() { void ContextView::ActionShowData() {
QSettings s; Settings s;
s.beginGroup(ContextSettingsPage::kSettingsGroup); s.beginGroup(ContextSettingsPage::kSettingsGroup);
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA)], action_show_data_->isChecked()); s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA)], action_show_data_->isChecked());
s.endGroup(); s.endGroup();
@ -707,7 +713,7 @@ void ContextView::ActionShowData() {
void ContextView::ActionShowLyrics() { void ContextView::ActionShowLyrics() {
QSettings s; Settings s;
s.beginGroup(ContextSettingsPage::kSettingsGroup); s.beginGroup(ContextSettingsPage::kSettingsGroup);
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SONG_LYRICS)], action_show_lyrics_->isChecked()); s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SONG_LYRICS)], action_show_lyrics_->isChecked());
s.endGroup(); s.endGroup();
@ -720,7 +726,7 @@ void ContextView::ActionShowLyrics() {
void ContextView::ActionSearchLyrics() { void ContextView::ActionSearchLyrics() {
QSettings s; Settings s;
s.beginGroup(ContextSettingsPage::kSettingsGroup); s.beginGroup(ContextSettingsPage::kSettingsGroup);
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SEARCH_LYRICS)], action_search_lyrics_->isChecked()); s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SEARCH_LYRICS)], action_search_lyrics_->isChecked());
s.endGroup(); s.endGroup();

View File

@ -57,6 +57,7 @@
#include "covermanager/deezercoverprovider.h" #include "covermanager/deezercoverprovider.h"
#include "covermanager/musixmatchcoverprovider.h" #include "covermanager/musixmatchcoverprovider.h"
#include "covermanager/spotifycoverprovider.h" #include "covermanager/spotifycoverprovider.h"
#include "covermanager/opentidalcoverprovider.h"
#include "lyrics/lyricsproviders.h" #include "lyrics/lyricsproviders.h"
#include "lyrics/geniuslyricsprovider.h" #include "lyrics/geniuslyricsprovider.h"
@ -67,7 +68,7 @@
#include "lyrics/songlyricscomlyricsprovider.h" #include "lyrics/songlyricscomlyricsprovider.h"
#include "lyrics/azlyricscomlyricsprovider.h" #include "lyrics/azlyricscomlyricsprovider.h"
#include "lyrics/elyricsnetlyricsprovider.h" #include "lyrics/elyricsnetlyricsprovider.h"
#include "lyrics/lyricsmodecomlyricsprovider.h" #include "lyrics/letraslyricsprovider.h"
#include "scrobbler/audioscrobbler.h" #include "scrobbler/audioscrobbler.h"
#include "scrobbler/lastfmscrobbler.h" #include "scrobbler/lastfmscrobbler.h"
@ -143,6 +144,7 @@ class ApplicationImpl {
cover_providers->AddProvider(new DeezerCoverProvider(app, app->network())); cover_providers->AddProvider(new DeezerCoverProvider(app, app->network()));
cover_providers->AddProvider(new MusixmatchCoverProvider(app, app->network())); cover_providers->AddProvider(new MusixmatchCoverProvider(app, app->network()));
cover_providers->AddProvider(new SpotifyCoverProvider(app, app->network())); cover_providers->AddProvider(new SpotifyCoverProvider(app, app->network()));
cover_providers->AddProvider(new OpenTidalCoverProvider(app, app->network()));
#ifdef HAVE_TIDAL #ifdef HAVE_TIDAL
cover_providers->AddProvider(new TidalCoverProvider(app, app->network())); cover_providers->AddProvider(new TidalCoverProvider(app, app->network()));
#endif #endif
@ -169,7 +171,7 @@ class ApplicationImpl {
lyrics_providers->AddProvider(new SongLyricsComLyricsProvider(app->network())); lyrics_providers->AddProvider(new SongLyricsComLyricsProvider(app->network()));
lyrics_providers->AddProvider(new AzLyricsComLyricsProvider(app->network())); lyrics_providers->AddProvider(new AzLyricsComLyricsProvider(app->network()));
lyrics_providers->AddProvider(new ElyricsNetLyricsProvider(app->network())); lyrics_providers->AddProvider(new ElyricsNetLyricsProvider(app->network()));
lyrics_providers->AddProvider(new LyricsModeComLyricsProvider(app->network())); lyrics_providers->AddProvider(new LetrasLyricsProvider(app->network()));
lyrics_providers->ReloadSettings(); lyrics_providers->ReloadSettings();
return lyrics_providers; return lyrics_providers;
}), }),

View File

@ -34,6 +34,7 @@
#include <QIODevice> #include <QIODevice>
#include <QDataStream> #include <QDataStream>
#include <QBuffer> #include <QBuffer>
#include <QDir>
#include <QFile> #include <QFile>
#include <QFileInfo> #include <QFileInfo>
#include <QByteArray> #include <QByteArray>
@ -100,7 +101,7 @@ CommandlineOptions::CommandlineOptions(int argc, char **argv)
play_track_at_(-1), play_track_at_(-1),
show_osd_(false), show_osd_(false),
toggle_pretty_osd_(false), toggle_pretty_osd_(false),
log_levels_(logging::kDefaultLogLevels) { log_levels_(QLatin1String(logging::kDefaultLogLevels)) {
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
Q_UNUSED(argv); Q_UNUSED(argv);
@ -108,11 +109,11 @@ CommandlineOptions::CommandlineOptions(int argc, char **argv)
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
// Remove -psn_xxx option that Mac passes when opened from Finder. // Remove -psn_xxx option that Mac passes when opened from Finder.
RemoveArg("-psn", 1); RemoveArg(QStringLiteral("-psn"), 1);
#endif #endif
// Remove the -session option that KDE passes // Remove the -session option that KDE passes
RemoveArg("-session", 2); RemoveArg(QStringLiteral("-session"), 2);
} }
@ -212,9 +213,9 @@ bool CommandlineOptions::Parse() {
if (c == -1) break; if (c == -1) break;
switch (c) { switch (c) {
case 'h': { case 'h':{
QString translated_help_text = QString translated_help_text =
QString(kHelpText) QString::fromUtf8(kHelpText)
.arg(QObject::tr("Usage"), QObject::tr("options"), QObject::tr("URL(s)"), .arg(QObject::tr("Usage"), QObject::tr("options"), QObject::tr("URL(s)"),
QObject::tr("Player options"), QObject::tr("Player options"),
QObject::tr("Start the playlist currently playing"), QObject::tr("Start the playlist currently playing"),
@ -301,16 +302,16 @@ bool CommandlineOptions::Parse() {
volume_modifier_ = -4; volume_modifier_ = -4;
break; break;
case LongOptions::Quiet: case LongOptions::Quiet:
log_levels_ = "1"; log_levels_ = QStringLiteral("1");
break; break;
case LongOptions::Verbose: case LongOptions::Verbose:
log_levels_ = "3"; log_levels_ = QStringLiteral("3");
break; break;
case LongOptions::LogLevels: case LongOptions::LogLevels:
log_levels_ = OptArgToString(optarg); log_levels_ = OptArgToString(optarg);
break; break;
case LongOptions::Version: { case LongOptions::Version:{
QString version_text = QString(kVersionText).arg(STRAWBERRY_VERSION_DISPLAY); QString version_text = QString::fromUtf8(kVersionText).arg(QStringLiteral(STRAWBERRY_VERSION_DISPLAY));
std::cout << version_text.toLocal8Bit().constData() << std::endl; std::cout << version_text.toLocal8Bit().constData() << std::endl;
std::exit(0); std::exit(0);
} }
@ -364,7 +365,7 @@ bool CommandlineOptions::Parse() {
const QString value = DecodeName(argv_[i]); const QString value = DecodeName(argv_[i]);
QFileInfo fileinfo(value); QFileInfo fileinfo(value);
if (fileinfo.exists()) { if (fileinfo.exists()) {
urls_ << QUrl::fromLocalFile(fileinfo.canonicalFilePath()); urls_ << QUrl::fromLocalFile(QDir::cleanPath(fileinfo.filePath()));
} }
else { else {
urls_ << QUrl::fromUserInput(value); urls_ << QUrl::fromUserInput(value);
@ -416,7 +417,7 @@ void CommandlineOptions::Load(const QByteArray &serialized) {
} }
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
QString CommandlineOptions::OptArgToString(wchar_t *opt) { QString CommandlineOptions::OptArgToString(const wchar_t *opt) {
return QString::fromWCharArray(opt); return QString::fromWCharArray(opt);
@ -427,9 +428,9 @@ QString CommandlineOptions::DecodeName(wchar_t *opt) {
return QString::fromWCharArray(opt); return QString::fromWCharArray(opt);
} }
#else #else
QString CommandlineOptions::OptArgToString(char *opt) { QString CommandlineOptions::OptArgToString(const char *opt) {
return QString(opt); return QString::fromUtf8(opt);
} }
QString CommandlineOptions::DecodeName(char *opt) { QString CommandlineOptions::DecodeName(char *opt) {

View File

@ -108,10 +108,10 @@ class CommandlineOptions {
void RemoveArg(const QString &starts_with, int count); void RemoveArg(const QString &starts_with, int count);
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
static QString OptArgToString(wchar_t *opt); static QString OptArgToString(const wchar_t *opt);
static QString DecodeName(wchar_t *opt); static QString DecodeName(wchar_t *opt);
#else #else
static QString OptArgToString(char *opt); static QString OptArgToString(const char *opt);
static QString DecodeName(char *opt); static QString DecodeName(char *opt);
#endif #endif

View File

@ -21,6 +21,8 @@
#include "config.h" #include "config.h"
#include <utility>
#include <sqlite3.h> #include <sqlite3.h>
#include <QObject> #include <QObject>
@ -47,10 +49,13 @@
#include "sqlquery.h" #include "sqlquery.h"
#include "scopedtransaction.h" #include "scopedtransaction.h"
const char *Database::kDatabaseFilename = "strawberry.db";
const int Database::kSchemaVersion = 18; const int Database::kSchemaVersion = 18;
const int Database::kMinSupportedSchemaVersion = 10;
const char *Database::kMagicAllSongsTables = "%allsongstables"; namespace {
constexpr char kDatabaseFilename[] = "strawberry.db";
constexpr int kMinSupportedSchemaVersion = 10;
constexpr char kMagicAllSongsTables[] = "%allsongstables";
} // namespace
int Database::sNextConnectionId = 1; int Database::sNextConnectionId = 1;
QMutex Database::sNextConnectionIdMutex; QMutex Database::sNextConnectionIdMutex;
@ -84,7 +89,8 @@ Database::~Database() {
QMutexLocker l(&connect_mutex_); QMutexLocker l(&connect_mutex_);
for (const QString &connection_id : QSqlDatabase::connectionNames()) { const QStringList connection_names = QSqlDatabase::connectionNames();
for (const QString &connection_id : connection_names) {
qLog(Error) << "Connection" << connection_id << "is still open!"; qLog(Error) << "Connection" << connection_id << "is still open!";
} }
@ -114,7 +120,7 @@ QSqlDatabase Database::Connect() {
} }
} }
const QString connection_id = QString("%1_thread_%2").arg(connection_id_).arg(reinterpret_cast<quint64>(QThread::currentThread())); const QString connection_id = QStringLiteral("%1_thread_%2").arg(connection_id_).arg(reinterpret_cast<quint64>(QThread::currentThread()));
// Try to find an existing connection for this thread // Try to find an existing connection for this thread
QSqlDatabase db; QSqlDatabase db;
@ -122,23 +128,23 @@ QSqlDatabase Database::Connect() {
db = QSqlDatabase::database(connection_id); db = QSqlDatabase::database(connection_id);
} }
else { else {
db = QSqlDatabase::addDatabase("QSQLITE", connection_id); db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), connection_id);
} }
if (db.isOpen()) { if (db.isOpen()) {
return db; return db;
} }
db.setConnectOptions("QSQLITE_BUSY_TIMEOUT=30000"); db.setConnectOptions(QStringLiteral("QSQLITE_BUSY_TIMEOUT=30000"));
//qLog(Debug) << "Opened database with connection id" << connection_id; //qLog(Debug) << "Opened database with connection id" << connection_id;
if (injected_database_name_.isNull()) { if (injected_database_name_.isNull()) {
db.setDatabaseName(directory_ + "/" + kDatabaseFilename); db.setDatabaseName(directory_ + QLatin1Char('/') + QLatin1String(kDatabaseFilename));
} }
else { else {
db.setDatabaseName(injected_database_name_); db.setDatabaseName(injected_database_name_);
} }
if (!db.open()) { if (!db.open()) {
app_->AddError("Database: " + db.lastError().text()); app_->AddError(QStringLiteral("Database: ") + db.lastError().text());
return db; return db;
} }
@ -154,16 +160,16 @@ QSqlDatabase Database::Connect() {
// Attach external databases // Attach external databases
QStringList keys = attached_databases_.keys(); QStringList keys = attached_databases_.keys();
for (const QString &key : keys) { for (const QString &key : std::as_const(keys)) {
QString filename = attached_databases_[key].filename_; QString filename = attached_databases_[key].filename_;
if (!injected_database_name_.isNull()) filename = injected_database_name_; if (!injected_database_name_.isNull()) filename = injected_database_name_;
// Attach the db // Attach the db
SqlQuery q(db); SqlQuery q(db);
q.prepare("ATTACH DATABASE :filename AS :alias"); q.prepare(QStringLiteral("ATTACH DATABASE :filename AS :alias"));
q.BindValue(":filename", filename); q.BindValue(QStringLiteral(":filename"), filename);
q.BindValue(":alias", key); q.BindValue(QStringLiteral(":alias"), key);
if (!q.Exec()) { if (!q.Exec()) {
qFatal("Couldn't attach external database '%s'", key.toLatin1().constData()); qFatal("Couldn't attach external database '%s'", key.toLatin1().constData());
} }
@ -175,13 +181,13 @@ 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. // 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(); keys = attached_databases_.keys();
for (const QString &key : keys) { for (const QString &key : std::as_const(keys)) {
if (attached_databases_[key].is_temporary_ && attached_databases_[key].schema_.isEmpty()) { if (attached_databases_[key].is_temporary_ && attached_databases_[key].schema_.isEmpty()) {
continue; continue;
} }
// Find out if there are any tables in this database // Find out if there are any tables in this database
SqlQuery q(db); SqlQuery q(db);
q.prepare(QString("SELECT ROWID FROM %1.sqlite_master WHERE type='table'").arg(key)); q.prepare(QStringLiteral("SELECT ROWID FROM %1.sqlite_master WHERE type='table'").arg(key));
if (!q.Exec() || !q.next()) { if (!q.Exec() || !q.next()) {
q.finish(); q.finish();
ExecSchemaCommandsFromFile(db, attached_databases_[key].schema_, 0); ExecSchemaCommandsFromFile(db, attached_databases_[key].schema_, 0);
@ -196,7 +202,7 @@ void Database::Close() {
QMutexLocker l(&connect_mutex_); QMutexLocker l(&connect_mutex_);
const QString connection_id = QString("%1_thread_%2").arg(connection_id_).arg(reinterpret_cast<quint64>(QThread::currentThread())); const QString connection_id = QStringLiteral("%1_thread_%2").arg(connection_id_).arg(reinterpret_cast<quint64>(QThread::currentThread()));
// Try to find an existing connection for this thread // Try to find an existing connection for this thread
if (QSqlDatabase::connectionNames().contains(connection_id)) { if (QSqlDatabase::connectionNames().contains(connection_id)) {
@ -218,7 +224,7 @@ int Database::SchemaVersion(QSqlDatabase *db) {
int schema_version = 0; int schema_version = 0;
{ {
SqlQuery q(*db); SqlQuery q(*db);
q.prepare("SELECT version FROM schema_version"); q.prepare(QStringLiteral("SELECT version FROM schema_version"));
if (q.Exec() && q.next()) { if (q.Exec() && q.next()) {
schema_version = q.value(0).toInt(); schema_version = q.value(0).toInt();
} }
@ -259,8 +265,8 @@ void Database::RecreateAttachedDb(const QString &database_name) {
QSqlDatabase db(Connect()); QSqlDatabase db(Connect());
SqlQuery q(db); SqlQuery q(db);
q.prepare("DETACH DATABASE :alias"); q.prepare(QStringLiteral("DETACH DATABASE :alias"));
q.BindValue(":alias", database_name); q.BindValue(QStringLiteral(":alias"), database_name);
if (!q.Exec()) { if (!q.Exec()) {
qLog(Warning) << "Failed to detach database" << database_name; qLog(Warning) << "Failed to detach database" << database_name;
return; return;
@ -273,7 +279,8 @@ void Database::RecreateAttachedDb(const QString &database_name) {
// We can't just re-attach the database now because it needs to be done for each thread. // We can't just re-attach the database now because it needs to be done for each thread.
// Close all the database connections, so each thread will re-attach it when they next connect. // Close all the database connections, so each thread will re-attach it when they next connect.
for (const QString &name : QSqlDatabase::connectionNames()) { const QStringList connection_names = QSqlDatabase::connectionNames();
for (const QString &name : connection_names) {
QSqlDatabase::removeDatabase(name); QSqlDatabase::removeDatabase(name);
} }
@ -289,9 +296,9 @@ void Database::AttachDatabaseOnDbConnection(const QString &database_name, const
// Attach the db // Attach the db
SqlQuery q(db); SqlQuery q(db);
q.prepare("ATTACH DATABASE :filename AS :alias"); q.prepare(QStringLiteral("ATTACH DATABASE :filename AS :alias"));
q.BindValue(":filename", database.filename_); q.BindValue(QStringLiteral(":filename"), database.filename_);
q.BindValue(":alias", database_name); q.BindValue(QStringLiteral(":alias"), database_name);
if (!q.Exec()) { if (!q.Exec()) {
qFatal("Couldn't attach external database '%s'", database_name.toLatin1().constData()); qFatal("Couldn't attach external database '%s'", database_name.toLatin1().constData());
} }
@ -305,8 +312,8 @@ void Database::DetachDatabase(const QString &database_name) {
QSqlDatabase db(Connect()); QSqlDatabase db(Connect());
SqlQuery q(db); SqlQuery q(db);
q.prepare("DETACH DATABASE :alias"); q.prepare(QStringLiteral("DETACH DATABASE :alias"));
q.BindValue(":alias", database_name); q.BindValue(QStringLiteral(":alias"), database_name);
if (!q.Exec()) { if (!q.Exec()) {
qLog(Warning) << "Failed to detach database" << database_name; qLog(Warning) << "Failed to detach database" << database_name;
return; return;
@ -321,10 +328,10 @@ void Database::UpdateDatabaseSchema(int version, QSqlDatabase &db) {
QString filename; QString filename;
if (version == 0) { if (version == 0) {
filename = ":/schema/schema.sql"; filename = QStringLiteral(":/schema/schema.sql");
} }
else { else {
filename = QString(":/schema/schema-%1.sql").arg(version); filename = QStringLiteral(":/schema/schema-%1.sql").arg(version);
qLog(Debug) << "Applying database schema update" << version << "from" << filename; qLog(Debug) << "Applying database schema update" << version << "from" << filename;
} }
@ -335,9 +342,9 @@ void Database::UpdateDatabaseSchema(int version, QSqlDatabase &db) {
void Database::UrlEncodeFilenameColumn(const QString &table, QSqlDatabase &db) { void Database::UrlEncodeFilenameColumn(const QString &table, QSqlDatabase &db) {
SqlQuery select(db); SqlQuery select(db);
select.prepare(QString("SELECT ROWID, filename FROM %1").arg(table)); select.prepare(QStringLiteral("SELECT ROWID, filename FROM %1").arg(table));
SqlQuery update(db); SqlQuery update(db);
update.prepare(QString("UPDATE %1 SET filename=:filename WHERE ROWID=:id").arg(table)); update.prepare(QStringLiteral("UPDATE %1 SET filename=:filename WHERE ROWID=:id").arg(table));
if (!select.Exec()) { if (!select.Exec()) {
ReportErrors(select); ReportErrors(select);
} }
@ -346,14 +353,14 @@ void Database::UrlEncodeFilenameColumn(const QString &table, QSqlDatabase &db) {
const int rowid = select.value(0).toInt(); const int rowid = select.value(0).toInt();
const QString filename = select.value(1).toString(); const QString filename = select.value(1).toString();
if (filename.isEmpty() || filename.contains("://")) { if (filename.isEmpty() || filename.contains(QLatin1String("://"))) {
continue; continue;
} }
const QUrl url = QUrl::fromLocalFile(filename); const QUrl url = QUrl::fromLocalFile(filename);
update.BindValue(":filename", url.toEncoded()); update.BindValue(QStringLiteral(":filename"), url.toEncoded());
update.BindValue(":id", rowid); update.BindValue(QStringLiteral(":id"), rowid);
if (!update.Exec()) { if (!update.Exec()) {
ReportErrors(update); ReportErrors(update);
} }
@ -370,8 +377,8 @@ void Database::ExecSchemaCommandsFromFile(QSqlDatabase &db, const QString &filen
} }
QByteArray data = schema_file.readAll(); QByteArray data = schema_file.readAll();
QString schema = QString::fromUtf8(data); QString schema = QString::fromUtf8(data);
if (schema.contains("\r\n")) { if (schema.contains(QLatin1String("\r\n"))) {
schema = schema.replace("\r\n", "\n"); schema = schema.replace(QLatin1String("\r\n"), QLatin1String("\n"));
} }
schema_file.close(); schema_file.close();
ExecSchemaCommands(db, schema, schema_version, in_transaction); ExecSchemaCommands(db, schema, schema_version, in_transaction);
@ -381,8 +388,7 @@ void Database::ExecSchemaCommandsFromFile(QSqlDatabase &db, const QString &filen
void Database::ExecSchemaCommands(QSqlDatabase &db, const QString &schema, int schema_version, bool in_transaction) { void Database::ExecSchemaCommands(QSqlDatabase &db, const QString &schema, int schema_version, bool in_transaction) {
// Run each command // Run each command
QStringList commands; QStringList commands = schema.split(QRegularExpression(QStringLiteral("; *\n\n")));
commands = schema.split(QRegularExpression("; *\n\n"));
// We don't want this list to reflect possible DB schema changes, so we initialize it before executing any statements. // 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! // If no outer transaction is provided the song tables need to be queried before beginning an inner transaction!
@ -405,16 +411,16 @@ void Database::ExecSongTablesCommands(QSqlDatabase &db, const QStringList &song_
for (const QString &command : commands) { for (const QString &command : commands) {
// There are now lots of "songs" tables that need to have the same schema: songs and device_*_songs. // There are now lots of "songs" tables that need to have the same schema: songs and device_*_songs.
// We allow a magic value in the schema files to update all songs tables at once. // We allow a magic value in the schema files to update all songs tables at once.
if (command.contains(kMagicAllSongsTables)) { if (command.contains(QLatin1String(kMagicAllSongsTables))) {
for (const QString &table : song_tables) { for (const QString &table : song_tables) {
// Another horrible hack: device songs tables don't have matching _fts tables, so if this command tries to touch one, ignore it. // Another horrible hack: device songs tables don't have matching _fts tables, so if this command tries to touch one, ignore it.
if (table.startsWith("device_") && command.contains(QString(kMagicAllSongsTables) + "_fts")) { if (table.startsWith(QLatin1String("device_")) && command.contains(QLatin1String(kMagicAllSongsTables) + QStringLiteral("_fts"))) {
continue; continue;
} }
qLog(Info) << "Updating" << table << "for" << kMagicAllSongsTables; qLog(Info) << "Updating" << table << "for" << kMagicAllSongsTables;
QString new_command(command); QString new_command(command);
new_command.replace(kMagicAllSongsTables, table); new_command.replace(QLatin1String(kMagicAllSongsTables), table);
SqlQuery query(db); SqlQuery query(db);
query.prepare(new_command); query.prepare(new_command);
if (!query.Exec()) { if (!query.Exec()) {
@ -442,18 +448,19 @@ QStringList Database::SongsTables(QSqlDatabase &db, const int schema_version) {
QStringList ret; QStringList ret;
// look for the tables in the main db // look for the tables in the main db
for (const QString &table : db.tables()) { const QStringList &tables = db.tables();
if (table == "songs" || table.endsWith("_songs")) ret << table; for (const QString &table : tables) {
if (table == QStringLiteral("songs") || table.endsWith(QLatin1String("_songs"))) ret << table;
} }
// look for the tables in attached dbs // look for the tables in attached dbs
QStringList keys = attached_databases_.keys(); const QStringList keys = attached_databases_.keys();
for (const QString &key : keys) { for (const QString &key : keys) {
SqlQuery q(db); SqlQuery q(db);
q.prepare(QString("SELECT NAME FROM %1.sqlite_master WHERE type='table' AND name='songs' OR name LIKE '%songs'").arg(key)); q.prepare(QStringLiteral("SELECT NAME FROM %1.sqlite_master WHERE type='table' AND name='songs' OR name LIKE '%songs'").arg(key));
if (q.Exec()) { if (q.Exec()) {
while (q.next()) { while (q.next()) {
QString tab_name = key + "." + q.value(0).toString(); QString tab_name = key + QStringLiteral(".") + q.value(0).toString();
ret << tab_name; ret << tab_name;
} }
} }
@ -462,7 +469,7 @@ QStringList Database::SongsTables(QSqlDatabase &db, const int schema_version) {
} }
} }
ret << "playlist_items"; ret << QStringLiteral("playlist_items");
return ret; return ret;
@ -488,20 +495,20 @@ bool Database::IntegrityCheck(const QSqlDatabase &db) {
bool ok = false; bool ok = false;
// Ask for 10 error messages at most. // Ask for 10 error messages at most.
SqlQuery q(db); SqlQuery q(db);
q.prepare("PRAGMA integrity_check(10)"); q.prepare(QStringLiteral("PRAGMA integrity_check(10)"));
if (q.Exec()) { if (q.Exec()) {
bool error_reported = false; bool error_reported = false;
while (q.next()) { while (q.next()) {
QString message = q.value(0).toString(); QString message = q.value(0).toString();
// If no errors are found, a single row with the value "ok" is returned // If no errors are found, a single row with the value "ok" is returned
if (message == "ok") { if (message == QStringLiteral("ok")) {
ok = true; ok = true;
break; break;
} }
else { else {
if (!error_reported) { app_->AddError(tr("Database corruption detected.")); } if (!error_reported) { app_->AddError(tr("Database corruption detected.")); }
app_->AddError("Database: " + message); app_->AddError(QStringLiteral("Database: ") + message);
error_reported = true; error_reported = true;
} }
} }
@ -553,7 +560,7 @@ bool Database::OpenDatabase(const QString &filename, sqlite3 **connection) {
void Database::BackupFile(const QString &filename) { void Database::BackupFile(const QString &filename) {
qLog(Debug) << "Starting database backup"; qLog(Debug) << "Starting database backup";
QString dest_filename = QString("%1.bak").arg(filename); QString dest_filename = QStringLiteral("%1.bak").arg(filename);
const int task_id = app_->task_manager()->StartTask(tr("Backing up database")); const int task_id = app_->task_manager()->StartTask(tr("Backing up database"));
sqlite3 *source_connection = nullptr; sqlite3 *source_connection = nullptr;

View File

@ -50,6 +50,8 @@ class Database : public QObject {
explicit Database(Application *app, QObject *parent = nullptr, const QString &database_name = QString()); explicit Database(Application *app, QObject *parent = nullptr, const QString &database_name = QString());
~Database() override; ~Database() override;
static const int kSchemaVersion;
struct AttachedDatabase { struct AttachedDatabase {
AttachedDatabase() {} AttachedDatabase() {}
AttachedDatabase(const QString &filename, const QString &schema, bool is_temporary) AttachedDatabase(const QString &filename, const QString &schema, bool is_temporary)
@ -60,11 +62,6 @@ class Database : public QObject {
bool is_temporary_; bool is_temporary_;
}; };
static const int kSchemaVersion;
static const int kMinSupportedSchemaVersion;
static const char *kDatabaseFilename;
static const char *kMagicAllSongsTables;
void ExitAsync(); void ExitAsync();
QSqlDatabase Connect(); QSqlDatabase Connect();
void Close(); void Close();
@ -148,7 +145,7 @@ class MemoryDatabase : public Database {
public: public:
explicit MemoryDatabase(Application *app, QObject *parent = nullptr) explicit MemoryDatabase(Application *app, QObject *parent = nullptr)
: Database(app, parent, ":memory:") {} : Database(app, parent, QStringLiteral(":memory:")) {}
~MemoryDatabase() override { ~MemoryDatabase() override {
// Make sure Qt doesn't reuse the same database // Make sure Qt doesn't reuse the same database
QSqlDatabase::removeDatabase(Connect().connectionName()); QSqlDatabase::removeDatabase(Connect().connectionName());

View File

@ -92,7 +92,8 @@ void DeleteFiles::ProcessSomeFiles() {
if (progress_ >= songs_.count()) { if (progress_ >= songs_.count()) {
task_manager_->SetTaskProgress(task_id_, progress_, songs_.count()); task_manager_->SetTaskProgress(task_id_, progress_, songs_.count());
storage_->FinishCopy(songs_with_errors_.isEmpty()); QString error_text;
storage_->FinishCopy(songs_with_errors_.isEmpty(), error_text);
task_manager_->SetTaskFinished(task_id_); task_manager_->SetTaskFinished(task_id_);

View File

@ -36,16 +36,16 @@
FilesystemMusicStorage::FilesystemMusicStorage(const Song::Source source, const QString &root, const std::optional<int> collection_directory_id) : source_(source), root_(root), collection_directory_id_(collection_directory_id) {} FilesystemMusicStorage::FilesystemMusicStorage(const Song::Source source, const QString &root, const std::optional<int> collection_directory_id) : source_(source), root_(root), collection_directory_id_(collection_directory_id) {}
bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job) { bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job, QString &error_text) {
const QFileInfo src = QFileInfo(job.source_); const QFileInfo src = QFileInfo(job.source_);
const QFileInfo dest = QFileInfo(root_ + "/" + job.destination_); const QFileInfo dest = QFileInfo(root_ + QLatin1Char('/') + job.destination_);
QFileInfo cover_src; QFileInfo cover_src;
QFileInfo cover_dest; QFileInfo cover_dest;
if (job.albumcover_ && !job.cover_source_.isEmpty() && !job.cover_dest_.isEmpty()) { if (job.albumcover_ && !job.cover_source_.isEmpty() && !job.cover_dest_.isEmpty()) {
cover_src = QFileInfo(job.cover_source_); cover_src = QFileInfo(job.cover_source_);
cover_dest = QFileInfo(root_ + "/" + job.cover_dest_); cover_dest = QFileInfo(root_ + QLatin1Char('/') + job.cover_dest_);
} }
// Don't do anything if the destination is the same as the source // Don't do anything if the destination is the same as the source
@ -54,7 +54,8 @@ bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job) {
// Create directories as required // Create directories as required
QDir dir; QDir dir;
if (!dir.mkpath(dest.absolutePath())) { if (!dir.mkpath(dest.absolutePath())) {
qLog(Warning) << "Failed to create directory" << dest.dir().absolutePath(); error_text = QObject::tr("Failed to create directory %1.").arg(dest.dir().absolutePath());
qLog(Error) << error_text;
return false; return false;
} }
@ -65,10 +66,12 @@ bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job) {
} }
// Copy or move // Copy or move
bool result(true); bool result = true;
if (job.remove_original_) { if (job.remove_original_) {
if (dest.exists() && !job.overwrite_) { if (dest.exists() && !job.overwrite_) {
result = false; result = false;
error_text = QObject::tr("Destination file %1 exists, but not allowed to overwrite.").arg(dest.absoluteFilePath());
qLog(Error) << error_text;
} }
else { else {
result = QFile::rename(src.absoluteFilePath(), dest.absoluteFilePath()); result = QFile::rename(src.absoluteFilePath(), dest.absoluteFilePath());
@ -86,9 +89,15 @@ bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job) {
else { else {
if (dest.exists() && !job.overwrite_) { if (dest.exists() && !job.overwrite_) {
result = false; result = false;
error_text = QObject::tr("Destination file %1 exists, but not allowed to overwrite").arg(dest.absoluteFilePath());
qLog(Error) << error_text;
} }
else { else {
result = QFile::copy(src.absoluteFilePath(), dest.absoluteFilePath()); result = QFile::copy(src.absoluteFilePath(), dest.absoluteFilePath());
if (!result) {
error_text = QObject::tr("Could not copy file %1 to %2.").arg(src.absoluteFilePath(), dest.absoluteFilePath());
qLog(Error) << error_text;
}
} }
if ((!cover_dest.exists() || job.overwrite_) && !cover_src.filePath().isEmpty() && !cover_dest.filePath().isEmpty()) { if ((!cover_dest.exists() || job.overwrite_) && !cover_src.filePath().isEmpty() && !cover_dest.filePath().isEmpty()) {
QFile::copy(cover_src.absoluteFilePath(), cover_dest.absoluteFilePath()); QFile::copy(cover_src.absoluteFilePath(), cover_dest.absoluteFilePath());

View File

@ -39,7 +39,7 @@ class FilesystemMusicStorage : public virtual MusicStorage {
QString LocalPath() const override { return root_; } QString LocalPath() const override { return root_; }
std::optional<int> collection_directory_id() const override { return collection_directory_id_; } std::optional<int> collection_directory_id() const override { return collection_directory_id_; }
bool CopyToStorage(const CopyJob &job) override; bool CopyToStorage(const CopyJob &job, QString &error_text) override;
bool DeleteFromStorage(const DeleteJob &job) override; bool DeleteFromStorage(const DeleteJob &job) override;
private: private:

View File

@ -19,6 +19,8 @@
#include "config.h" #include "config.h"
#include <utility>
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
#include <QList> #include <QList>
@ -29,6 +31,7 @@
#include <QSettings> #include <QSettings>
#include "core/logging.h" #include "core/logging.h"
#include "settings.h"
#include "iconmapper.h" #include "iconmapper.h"
#include "settings/appearancesettingspage.h" #include "settings/appearancesettingspage.h"
#include "iconloader.h" #include "iconloader.h"
@ -39,14 +42,14 @@ bool IconLoader::custom_icons_ = false;
void IconLoader::Init() { void IconLoader::Init() {
#if !defined(Q_OS_MACOS) && !defined(Q_OS_WIN) #if !defined(Q_OS_MACOS) && !defined(Q_OS_WIN)
QSettings s; Settings s;
s.beginGroup(AppearanceSettingsPage::kSettingsGroup); s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
system_icons_ = s.value("system_icons", false).toBool(); system_icons_ = s.value("system_icons", false).toBool();
s.endGroup(); s.endGroup();
#endif #endif
QDir dir; QDir dir;
if (dir.exists(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/icons")) { if (dir.exists(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QStringLiteral("/icons"))) {
custom_icons_ = true; custom_icons_ = true;
} }
@ -79,7 +82,8 @@ QIcon IconLoader::Load(const QString &name, const bool system_icon, const int fi
if (icon_prop.allow_system_icon) { if (icon_prop.allow_system_icon) {
ret = QIcon::fromTheme(name); ret = QIcon::fromTheme(name);
if (ret.isNull()) { if (ret.isNull()) {
for (const QString &alt_name : icon_prop.names) { const QStringList alt_names = icon_prop.names;
for (const QString &alt_name : alt_names) {
ret = QIcon::fromTheme(alt_name); ret = QIcon::fromTheme(alt_name);
if (!ret.isNull()) break; if (!ret.isNull()) break;
} }
@ -95,7 +99,8 @@ QIcon IconLoader::Load(const QString &name, const bool system_icon, const int fi
else { else {
int size_smallest = 0; int size_smallest = 0;
int size_largest = 0; int size_largest = 0;
for (const QSize &s : ret.availableSizes()) { const QList<QSize> available_sizes = ret.availableSizes();
for (const QSize &s : available_sizes) {
if (s.width() != s.height()) { if (s.width() != s.height()) {
qLog(Warning) << "Can't use system icon for" << name << "icon is not proportional."; qLog(Warning) << "Can't use system icon for" << name << "icon is not proportional.";
ret = QIcon(); ret = QIcon();
@ -118,8 +123,8 @@ QIcon IconLoader::Load(const QString &name, const bool system_icon, const int fi
} }
if (custom_icons_) { if (custom_icons_) {
QString custom_icon_path = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/icons/%1x%2/%3.png"; QString custom_icon_path = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QStringLiteral("/icons/%1x%2/%3.png");
for (int s : sizes) { for (int s : std::as_const(sizes)) {
QString filename(custom_icon_path.arg(s).arg(s).arg(name)); QString filename(custom_icon_path.arg(s).arg(s).arg(name));
if (QFile::exists(filename)) ret.addFile(filename, QSize(s, s)); if (QFile::exists(filename)) ret.addFile(filename, QSize(s, s));
} }
@ -127,8 +132,8 @@ QIcon IconLoader::Load(const QString &name, const bool system_icon, const int fi
qLog(Warning) << "Couldn't load icon" << name << "from custom icons."; qLog(Warning) << "Couldn't load icon" << name << "from custom icons.";
} }
const QString path(":/icons/%1x%2/%3.png"); const QString path(QStringLiteral(":/icons/%1x%2/%3.png"));
for (int s : sizes) { for (int s : std::as_const(sizes)) {
QString filename(path.arg(s).arg(s).arg(name)); QString filename(path.arg(s).arg(s).arg(name));
if (QFile::exists(filename)) ret.addFile(filename, QSize(s, s)); if (QFile::exists(filename)) ret.addFile(filename, QSize(s, s));
} }

View File

@ -38,100 +38,100 @@ struct IconProperties {
static const QMap<QString, IconProperties> iconmapper_ = { // clazy:exclude=non-pod-global-static static const QMap<QString, IconProperties> iconmapper_ = { // clazy:exclude=non-pod-global-static
{ "albums", { {"media-optical"}} }, { QStringLiteral("albums"), { {QStringLiteral("media-optical")}} },
{ "alsa", { {}} }, { QStringLiteral("alsa"), { {}} },
{ "application-exit", { {}} }, { QStringLiteral("application-exit"), { {}} },
{ "applications-internet", { {}} }, { QStringLiteral("applications-internet"), { {}} },
{ "bluetooth", { {"preferences-system-bluetooth", "bluetooth-active"}} }, { QStringLiteral("bluetooth"), { {QStringLiteral("preferences-system-bluetooth"), QStringLiteral("bluetooth-active")}} },
{ "cdcase", { {"cdcover", "media-optical"}} }, { QStringLiteral("cdcase"), { {QStringLiteral("cdcover"), QStringLiteral("media-optical")}} },
{ "media-optical", { {"cd"}} }, { QStringLiteral("media-optical"), { {QStringLiteral("cd")}} },
{ "configure", { {}} }, { QStringLiteral("configure"), { {}} },
{ "device-ipod-nano", { {}} }, { QStringLiteral("device-ipod-nano"), { {}} },
{ "device-ipod", { {}} }, { QStringLiteral("device-ipod"), { {}} },
{ "device-phone", { {}} }, { QStringLiteral("device-phone"), { {}} },
{ "device", { {"drive-removable-media-usb-pendrive"}} }, { QStringLiteral("device"), { {QStringLiteral("drive-removable-media-usb-pendrive")}} },
{ "device-usb-drive", { {}} }, { QStringLiteral("device-usb-drive"), { {}} },
{ "device-usb-flash", { {}} }, { QStringLiteral("device-usb-flash"), { {}} },
{ "dialog-error", { {}} }, { QStringLiteral("dialog-error"), { {}} },
{ "dialog-information", { {}} }, { QStringLiteral("dialog-information"), { {}} },
{ "dialog-ok-apply", { {}} }, { QStringLiteral("dialog-ok-apply"), { {}} },
{ "dialog-password", { {}} }, { QStringLiteral("dialog-password"), { {}} },
{ "dialog-warning", { {}} }, { QStringLiteral("dialog-warning"), { {}} },
{ "document-download", { {"download"}} }, { QStringLiteral("document-download"), { {QStringLiteral("download")}} },
{ "document-new", { {}} }, { QStringLiteral("document-new"), { {}} },
{ "document-open-folder", { {}} }, { QStringLiteral("document-open-folder"), { {}} },
{ "document-open", { {}} }, { QStringLiteral("document-open"), { {}} },
{ "document-save", { {}} }, { QStringLiteral("document-save"), { {}} },
{ "document-search", { {}} }, { QStringLiteral("document-search"), { {}} },
{ "document-open-remote", { {}} }, { QStringLiteral("document-open-remote"), { {}} },
{ "download", { {"applications-internet", "network-workgroup"}} }, { QStringLiteral("download"), { {QStringLiteral("applications-internet"), QStringLiteral("network-workgroup")}} },
{ "edit-clear-list", { {"edit-clear-list","edit-clear-all"}} }, { QStringLiteral("edit-clear-list"), { {QStringLiteral("edit-clear-list"), QStringLiteral("edit-clear-all")}} },
{ "edit-clear-locationbar-ltr", { {"edit-clear-locationbar-ltr"}} }, { QStringLiteral("edit-clear-locationbar-ltr"), { {QStringLiteral("edit-clear-locationbar-ltr")}} },
{ "edit-copy", { {}} }, { QStringLiteral("edit-copy"), { {}} },
{ "edit-delete", { {}} }, { QStringLiteral("edit-delete"), { {}} },
{ "edit-find", { {}} }, { QStringLiteral("edit-find"), { {}} },
{ "edit-redo", { {}} }, { QStringLiteral("edit-redo"), { {}} },
{ "edit-rename", { {}} }, { QStringLiteral("edit-rename"), { {}} },
{ "edit-undo", { {}} }, { QStringLiteral("edit-undo"), { {}} },
{ "electrocompaniet", { {}} }, { QStringLiteral("electrocompaniet"), { {}} },
{ "equalizer", { {"view-media-equalizer"}} }, { QStringLiteral("equalizer"), { {QStringLiteral("view-media-equalizer")}} },
{ "folder-new", { {}} }, { QStringLiteral("folder-new"), { {}} },
{ "folder", { {}} }, { QStringLiteral("folder"), { {}} },
{ "folder-sound", { {"folder-music"}} }, { QStringLiteral("folder-sound"), { {QStringLiteral("folder-music")}} },
{ "footsteps", { {"go-jump"}} }, { QStringLiteral("footsteps"), { {QStringLiteral("go-jump")}} },
{ "go-down", { {}} }, { QStringLiteral("go-down"), { {}} },
{ "go-home", { {}} }, { QStringLiteral("go-home"), { {}} },
{ "go-jump", { {}} }, { QStringLiteral("go-jump"), { {}} },
{ "go-next", { {}} }, { QStringLiteral("go-next"), { {}} },
{ "go-previous", { {}} }, { QStringLiteral("go-previous"), { {}} },
{ "go-up", { {}} }, { QStringLiteral("go-up"), { {}} },
{ "gstreamer", { {"phonon-gstreamer"}} }, { QStringLiteral("gstreamer"), { {QStringLiteral("phonon-gstreamer")}} },
{ "headset", { {"audio-headset"}} }, { QStringLiteral("headset"), { {QStringLiteral("audio-headset")}} },
{ "help-hint", { {}} }, { QStringLiteral("help-hint"), { {}} },
{ "intel", { {}} }, { QStringLiteral("intel"), { {}} },
{ "jack", { {"audio-input-line"}} }, { QStringLiteral("jack"), { {QStringLiteral("audio-input-line")}} },
{ "keyboard", { {"input-keyboard"}} }, { QStringLiteral("keyboard"), { {QStringLiteral("input-keyboard")}} },
{ "list-add", { {}} }, { QStringLiteral("list-add"), { {}} },
{ "list-remove", { {}} }, { QStringLiteral("list-remove"), { {}} },
{ "love", { {"heart", "emblem-favorite"}} }, { QStringLiteral("love"), { {QStringLiteral("heart"), QStringLiteral("emblem-favorite")}} },
{ "mcintosh-player", { {}} }, { QStringLiteral("mcintosh-player"), { {}} },
{ "mcintosh", { {}} }, { QStringLiteral("mcintosh"), { {}} },
{ "mcintosh-text", { {}} }, { QStringLiteral("mcintosh-text"), { {}} },
{ "media-eject", { {}} }, { QStringLiteral("media-eject"), { {}} },
{ "media-playback-pause", { {"media-pause"}} }, { QStringLiteral("media-playback-pause"), { {QStringLiteral("media-pause")}} },
{ "media-playlist-repeat", { {}} }, { QStringLiteral("media-playlist-repeat"), { {}} },
{ "media-playlist-shuffle", { {""}} }, { QStringLiteral("media-playlist-shuffle"), { {QLatin1String("")}} },
{ "media-playback-start", { {"media-play", "media-playback-playing"}} }, { QStringLiteral("media-playback-start"), { {QStringLiteral("media-play"), QStringLiteral("media-playback-playing")}} },
{ "media-seek-backward", { {}} }, { QStringLiteral("media-seek-backward"), { {}} },
{ "media-seek-forward", { {}} }, { QStringLiteral("media-seek-forward"), { {}} },
{ "media-skip-backward", { {}} }, { QStringLiteral("media-skip-backward"), { {}} },
{ "media-skip-forward", { {}} }, { QStringLiteral("media-skip-forward"), { {}} },
{ "media-playback-stop", { {"media-stop"}} }, { QStringLiteral("media-playback-stop"), { {QStringLiteral("media-stop")}} },
{ "moodbar", { {"preferences-desktop-icons"}} }, { QStringLiteral("moodbar"), { {QStringLiteral("preferences-desktop-icons")}} },
{ "nvidia", { {}} }, { QStringLiteral("nvidia"), { {}} },
{ "pulseaudio", { {}} }, { QStringLiteral("pulseaudio"), { {}} },
{ "realtek", { {}} }, { QStringLiteral("realtek"), { {}} },
{ "scrobble-disabled", { {}} }, { QStringLiteral("scrobble-disabled"), { {}} },
{ "scrobble", { {"love"}} }, { QStringLiteral("scrobble"), { {QStringLiteral("love")}} },
{ "search", { {}} }, { QStringLiteral("search"), { {}} },
{ "soundcard", { {"audiocard", "audio-card"}} }, { QStringLiteral("soundcard"), { {QStringLiteral("audiocard"), QStringLiteral("audio-card")}} },
{ "speaker", { {}} }, { QStringLiteral("speaker"), { {}} },
{ "star-grey", { {}} }, { QStringLiteral("star-grey"), { {}} },
{ "star", { {}} }, { QStringLiteral("star"), { {}} },
{ "strawberry", { {}} }, { QStringLiteral("strawberry"), { {}} },
{ "subsonic", { {}} }, { QStringLiteral("subsonic"), { {}} },
{ "tidal", { {}} }, { QStringLiteral("tidal"), { {}} },
{ "tools-wizard", { {}} }, { QStringLiteral("tools-wizard"), { {}} },
{ "view-choose", { {}} }, { QStringLiteral("view-choose"), { {}} },
{ "view-fullscreen", { {}} }, { QStringLiteral("view-fullscreen"), { {}} },
{ "view-media-lyrics", { {}} }, { QStringLiteral("view-media-lyrics"), { {}} },
{ "view-media-playlist", { {}} }, { QStringLiteral("view-media-playlist"), { {}} },
{ "view-media-visualization", { {"preferences-desktop-theme"}} }, { QStringLiteral("view-media-visualization"), { {QStringLiteral("preferences-desktop-theme")}} },
{ "view-refresh", { {}} }, { QStringLiteral("view-refresh"), { {}} },
{ "library-music", { {"vinyl"}} }, { QStringLiteral("library-music"), { {QStringLiteral("vinyl")}} },
{ "vlc", { {}} }, { QStringLiteral("vlc"), { {}} },
{ "zoom-in", { {}} }, { QStringLiteral("zoom-in"), { {}} },
{ "zoom-out", { {}, 0, 0 } } { QStringLiteral("zoom-out"), { {}, 0, 0 } }
}; };

View File

@ -60,13 +60,12 @@
#include <QDir> #include <QDir>
#include <QEvent> #include <QEvent>
#include <QFile> #include <QFile>
#include <QSettings>
#include <QtDebug> #include <QtDebug>
QDebug operator<<(QDebug dbg, NSObject *object) { QDebug operator<<(QDebug dbg, NSObject *object) {
QString ns_format = [[NSString stringWithFormat:@"%@", object] UTF8String]; const QString ns_format = QString::fromUtf8([[NSString stringWithFormat:@"%@", object] UTF8String]);
dbg.nospace() << ns_format; dbg.nospace() << ns_format;
return dbg.space(); return dbg.space();

View File

@ -57,7 +57,7 @@ void MacFSListener::EventStreamCallback(ConstFSEventStreamRef stream, void *user
for (size_t i = 0; i < num_events; ++i) { for (size_t i = 0; i < num_events; ++i) {
QString path = QString::fromUtf8(paths[i]); QString path = QString::fromUtf8(paths[i]);
qLog(Debug) << "Something changed at:" << path; qLog(Debug) << "Something changed at:" << path;
while (path.endsWith('/')) { while (path.endsWith(QLatin1Char('/'))) {
path.chop(1); path.chop(1);
} }
emit me->PathChanged(path); emit me->PathChanged(path);

View File

@ -70,7 +70,7 @@ class MacSystemTrayIconPrivate {
MacSystemTrayIconPrivate() { MacSystemTrayIconPrivate() {
dock_menu_ = [[NSMenu alloc] initWithTitle:@"DockMenu"]; dock_menu_ = [[NSMenu alloc] initWithTitle:@"DockMenu"];
QString title = QT_TR_NOOP("Now Playing"); QString title = QT_TR_NOOP(QStringLiteral("Now Playing"));
NSString *t = [[NSString alloc] initWithUTF8String:title.toUtf8().constData()]; NSString *t = [[NSString alloc] initWithUTF8String:title.toUtf8().constData()];
now_playing_ = [[NSMenuItem alloc] initWithTitle:t action:nullptr keyEquivalent:@""]; now_playing_ = [[NSMenuItem alloc] initWithTitle:t action:nullptr keyEquivalent:@""];
now_playing_artist_ = [[NSMenuItem alloc] initWithTitle:@"Nothing to see here" action:nullptr keyEquivalent:@""]; now_playing_artist_ = [[NSMenuItem alloc] initWithTitle:@"Nothing to see here" action:nullptr keyEquivalent:@""];
@ -89,7 +89,7 @@ class MacSystemTrayIconPrivate {
void AddMenuItem(QAction *action) { void AddMenuItem(QAction *action) {
// Strip accelarators from name. // Strip accelarators from name.
QString text = action->text().remove("&"); QString text = action->text().remove(QLatin1Char('&'));
NSString *title = [[NSString alloc] initWithUTF8String: text.toUtf8().constData()]; NSString *title = [[NSString alloc] initWithUTF8String: text.toUtf8().constData()];
// Create an object that can receive user clicks and pass them on to the QAction. // Create an object that can receive user clicks and pass them on to the QAction.
Target *target = [[Target alloc] initWithQAction:action]; Target *target = [[Target alloc] initWithQAction:action];
@ -152,10 +152,10 @@ class MacSystemTrayIconPrivate {
SystemTrayIcon::SystemTrayIcon(QObject *parent) SystemTrayIcon::SystemTrayIcon(QObject *parent)
: QObject(parent), : QObject(parent),
normal_icon_(QPixmap(":/pictures/strawberry.png").scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation)), normal_icon_(QPixmap(QStringLiteral(":/pictures/strawberry.png")).scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation)),
grey_icon_(QPixmap(":/pictures/strawberry-grey.png").scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation)), grey_icon_(QPixmap(QStringLiteral(":/pictures/strawberry-grey.png")).scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation)),
playing_icon_(":/pictures/tiny-play.png"), playing_icon_(QStringLiteral(":/pictures/tiny-play.png")),
paused_icon_(":/pictures/tiny-pause.png"), paused_icon_(QStringLiteral(":/pictures/tiny-pause.png")),
trayicon_progress_(false), trayicon_progress_(false),
song_progress_(0) { song_progress_(0) {

View File

@ -73,6 +73,10 @@
#include <QToolButton> #include <QToolButton>
#include <QCheckBox> #include <QCheckBox>
#include <QClipboard> #include <QClipboard>
#ifdef HAVE_DBUS
# include <QDBusConnection>
# include <QDBusMessage>
#endif
#include "core/logging.h" #include "core/logging.h"
@ -100,6 +104,7 @@
# include "qtsystemtrayicon.h" # include "qtsystemtrayicon.h"
#endif #endif
#include "networkaccessmanager.h" #include "networkaccessmanager.h"
#include "settings.h"
#include "utilities/envutils.h" #include "utilities/envutils.h"
#include "utilities/filemanagerutils.h" #include "utilities/filemanagerutils.h"
#include "utilities/timeconstants.h" #include "utilities/timeconstants.h"
@ -200,6 +205,8 @@
#include "smartplaylists/smartplaylistsviewcontainer.h" #include "smartplaylists/smartplaylistsviewcontainer.h"
#include "organize/organizeerrordialog.h"
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
# include "windows7thumbbar.h" # include "windows7thumbbar.h"
#endif #endif
@ -295,13 +302,13 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
}), }),
smartplaylists_view_(new SmartPlaylistsViewContainer(app, this)), smartplaylists_view_(new SmartPlaylistsViewContainer(app, this)),
#ifdef HAVE_SUBSONIC #ifdef HAVE_SUBSONIC
subsonic_view_(new InternetSongsView(app_, app->internet_services()->ServiceBySource(Song::Source::Subsonic), SubsonicSettingsPage::kSettingsGroup, SettingsDialog::Page::Subsonic, this)), subsonic_view_(new InternetSongsView(app_, app->internet_services()->ServiceBySource(Song::Source::Subsonic), QLatin1String(SubsonicSettingsPage::kSettingsGroup), SettingsDialog::Page::Subsonic, this)),
#endif #endif
#ifdef HAVE_TIDAL #ifdef HAVE_TIDAL
tidal_view_(new InternetTabsView(app_, app->internet_services()->ServiceBySource(Song::Source::Tidal), TidalSettingsPage::kSettingsGroup, SettingsDialog::Page::Tidal, this)), tidal_view_(new InternetTabsView(app_, app->internet_services()->ServiceBySource(Song::Source::Tidal), QLatin1String(TidalSettingsPage::kSettingsGroup), SettingsDialog::Page::Tidal, this)),
#endif #endif
#ifdef HAVE_QOBUZ #ifdef HAVE_QOBUZ
qobuz_view_(new InternetTabsView(app_, app->internet_services()->ServiceBySource(Song::Source::Qobuz), QobuzSettingsPage::kSettingsGroup, SettingsDialog::Page::Qobuz, this)), qobuz_view_(new InternetTabsView(app_, app->internet_services()->ServiceBySource(Song::Source::Qobuz), QLatin1String(QobuzSettingsPage::kSettingsGroup), SettingsDialog::Page::Qobuz, this)),
#endif #endif
radio_view_(new RadioViewContainer(this)), radio_view_(new RadioViewContainer(this)),
lastfm_import_dialog_(new LastFMImportDialog(app_->lastfm_import(), this)), lastfm_import_dialog_(new LastFMImportDialog(app_->lastfm_import(), this)),
@ -333,6 +340,9 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
track_slider_timer_(new QTimer(this)), track_slider_timer_(new QTimer(this)),
keep_running_(false), keep_running_(false),
playing_widget_(true), playing_widget_(true),
#ifdef HAVE_DBUS
taskbar_progress_(false),
#endif
doubleclick_addmode_(BehaviourSettingsPage::AddBehaviour::Append), doubleclick_addmode_(BehaviourSettingsPage::AddBehaviour::Append),
doubleclick_playmode_(BehaviourSettingsPage::PlayBehaviour::Never), doubleclick_playmode_(BehaviourSettingsPage::PlayBehaviour::Never),
doubleclick_playlist_addmode_(BehaviourSettingsPage::PlaylistAddBehaviour::Play), doubleclick_playlist_addmode_(BehaviourSettingsPage::PlaylistAddBehaviour::Play),
@ -354,7 +364,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
// Initialize the UI // Initialize the UI
ui_->setupUi(this); ui_->setupUi(this);
setWindowIcon(IconLoader::Load("strawberry")); setWindowIcon(IconLoader::Load(QStringLiteral("strawberry")));
album_cover_choice_controller_->Init(app); album_cover_choice_controller_->Init(app);
@ -366,30 +376,30 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
StyleHelper::setBaseColor(palette().color(QPalette::Highlight).darker()); StyleHelper::setBaseColor(palette().color(QPalette::Highlight).darker());
// Add tabs to the fancy tab widget // Add tabs to the fancy tab widget
ui_->tabs->AddTab(context_view_, "context", IconLoader::Load("strawberry", true, 0, 32), tr("Context")); ui_->tabs->AddTab(context_view_, QStringLiteral("context"), IconLoader::Load(QStringLiteral("strawberry"), true, 0, 32), tr("Context"));
ui_->tabs->AddTab(collection_view_, "collection", IconLoader::Load("library-music", true, 0, 32), tr("Collection")); ui_->tabs->AddTab(collection_view_, QStringLiteral("collection"), IconLoader::Load(QStringLiteral("library-music"), true, 0, 32), tr("Collection"));
ui_->tabs->AddTab(queue_view_, "queue", IconLoader::Load("footsteps", true, 0, 32), tr("Queue")); ui_->tabs->AddTab(queue_view_, QStringLiteral("queue"), IconLoader::Load(QStringLiteral("footsteps"), true, 0, 32), tr("Queue"));
ui_->tabs->AddTab(playlist_list_, "playlists", IconLoader::Load("view-media-playlist", true, 0, 32), tr("Playlists")); ui_->tabs->AddTab(playlist_list_, QStringLiteral("playlists"), IconLoader::Load(QStringLiteral("view-media-playlist"), true, 0, 32), tr("Playlists"));
ui_->tabs->AddTab(smartplaylists_view_, "smartplaylists", IconLoader::Load("view-media-playlist", true, 0, 32), tr("Smart playlists")); ui_->tabs->AddTab(smartplaylists_view_, QStringLiteral("smartplaylists"), IconLoader::Load(QStringLiteral("view-media-playlist"), true, 0, 32), tr("Smart playlists"));
ui_->tabs->AddTab(file_view_, "files", IconLoader::Load("document-open", true, 0, 32), tr("Files")); ui_->tabs->AddTab(file_view_, QStringLiteral("files"), IconLoader::Load(QStringLiteral("document-open"), true, 0, 32), tr("Files"));
ui_->tabs->AddTab(radio_view_, "radios", IconLoader::Load("radio", true, 0, 32), tr("Radios")); ui_->tabs->AddTab(radio_view_, QStringLiteral("radios"), IconLoader::Load(QStringLiteral("radio"), true, 0, 32), tr("Radios"));
#ifndef Q_OS_WIN #ifndef Q_OS_WIN
ui_->tabs->AddTab(device_view_, "devices", IconLoader::Load("device", true, 0, 32), tr("Devices")); ui_->tabs->AddTab(device_view_, QStringLiteral("devices"), IconLoader::Load(QStringLiteral("device"), true, 0, 32), tr("Devices"));
#endif #endif
#ifdef HAVE_SUBSONIC #ifdef HAVE_SUBSONIC
ui_->tabs->AddTab(subsonic_view_, "subsonic", IconLoader::Load("subsonic", true, 0, 32), tr("Subsonic")); ui_->tabs->AddTab(subsonic_view_, QStringLiteral("subsonic"), IconLoader::Load(QStringLiteral("subsonic"), true, 0, 32), tr("Subsonic"));
#endif #endif
#ifdef HAVE_TIDAL #ifdef HAVE_TIDAL
ui_->tabs->AddTab(tidal_view_, "tidal", IconLoader::Load("tidal", true, 0, 32), tr("Tidal")); ui_->tabs->AddTab(tidal_view_, QStringLiteral("tidal"), IconLoader::Load(QStringLiteral("tidal"), true, 0, 32), tr("Tidal"));
#endif #endif
#ifdef HAVE_QOBUZ #ifdef HAVE_QOBUZ
ui_->tabs->AddTab(qobuz_view_, "qobuz", IconLoader::Load("qobuz", true, 0, 32), tr("Qobuz")); ui_->tabs->AddTab(qobuz_view_, QStringLiteral("qobuz"), IconLoader::Load(QStringLiteral("qobuz"), true, 0, 32), tr("Qobuz"));
#endif #endif
// Add the playing widget to the fancy tab widget // Add the playing widget to the fancy tab widget
ui_->tabs->addBottomWidget(ui_->widget_playing); ui_->tabs->addBottomWidget(ui_->widget_playing);
//ui_->tabs->SetBackgroundPixmap(QPixmap(":/pictures/strawberry-background.png")); ui_->tabs->setBackgroundPixmap(QPixmap(QStringLiteral(":/pictures/sidebar-background.png")));
ui_->tabs->Load(kSettingsGroup); ui_->tabs->Load(QLatin1String(kSettingsGroup));
track_position_timer_->setInterval(kTrackPositionUpdateTimeMs); track_position_timer_->setInterval(kTrackPositionUpdateTimeMs);
QObject::connect(track_position_timer_, &QTimer::timeout, this, &MainWindow::UpdateTrackPosition); QObject::connect(track_position_timer_, &QTimer::timeout, this, &MainWindow::UpdateTrackPosition);
@ -438,59 +448,59 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
// Help menu // Help menu
ui_->action_about_strawberry->setIcon(IconLoader::Load("strawberry")); ui_->action_about_strawberry->setIcon(IconLoader::Load(QStringLiteral("strawberry")));
ui_->action_about_qt->setIcon(QIcon(":/qt-project.org/qmessagebox/images/qtlogo-64.png")); ui_->action_about_qt->setIcon(QIcon(QStringLiteral(":/qt-project.org/qmessagebox/images/qtlogo-64.png")));
// Music menu // Music menu
ui_->action_open_file->setIcon(IconLoader::Load("document-open")); ui_->action_open_file->setIcon(IconLoader::Load(QStringLiteral("document-open")));
ui_->action_open_cd->setIcon(IconLoader::Load("media-optical")); ui_->action_open_cd->setIcon(IconLoader::Load(QStringLiteral("media-optical")));
ui_->action_previous_track->setIcon(IconLoader::Load("media-skip-backward")); ui_->action_previous_track->setIcon(IconLoader::Load(QStringLiteral("media-skip-backward")));
ui_->action_play_pause->setIcon(IconLoader::Load("media-playback-start")); ui_->action_play_pause->setIcon(IconLoader::Load(QStringLiteral("media-playback-start")));
ui_->action_stop->setIcon(IconLoader::Load("media-playback-stop")); ui_->action_stop->setIcon(IconLoader::Load(QStringLiteral("media-playback-stop")));
ui_->action_stop_after_this_track->setIcon(IconLoader::Load("media-playback-stop")); ui_->action_stop_after_this_track->setIcon(IconLoader::Load(QStringLiteral("media-playback-stop")));
ui_->action_next_track->setIcon(IconLoader::Load("media-skip-forward")); ui_->action_next_track->setIcon(IconLoader::Load(QStringLiteral("media-skip-forward")));
ui_->action_quit->setIcon(IconLoader::Load("application-exit")); ui_->action_quit->setIcon(IconLoader::Load(QStringLiteral("application-exit")));
// Playlist // Playlist
ui_->action_add_file->setIcon(IconLoader::Load("document-open")); ui_->action_add_file->setIcon(IconLoader::Load(QStringLiteral("document-open")));
ui_->action_add_folder->setIcon(IconLoader::Load("document-open-folder")); ui_->action_add_folder->setIcon(IconLoader::Load(QStringLiteral("document-open-folder")));
ui_->action_add_stream->setIcon(IconLoader::Load("document-open-remote")); ui_->action_add_stream->setIcon(IconLoader::Load(QStringLiteral("document-open-remote")));
ui_->action_shuffle_mode->setIcon(IconLoader::Load("media-playlist-shuffle")); ui_->action_shuffle_mode->setIcon(IconLoader::Load(QStringLiteral("media-playlist-shuffle")));
ui_->action_repeat_mode->setIcon(IconLoader::Load("media-playlist-repeat")); ui_->action_repeat_mode->setIcon(IconLoader::Load(QStringLiteral("media-playlist-repeat")));
ui_->action_new_playlist->setIcon(IconLoader::Load("document-new")); ui_->action_new_playlist->setIcon(IconLoader::Load(QStringLiteral("document-new")));
ui_->action_save_playlist->setIcon(IconLoader::Load("document-save")); ui_->action_save_playlist->setIcon(IconLoader::Load(QStringLiteral("document-save")));
ui_->action_load_playlist->setIcon(IconLoader::Load("document-open")); ui_->action_load_playlist->setIcon(IconLoader::Load(QStringLiteral("document-open")));
ui_->action_jump->setIcon(IconLoader::Load("go-jump")); ui_->action_jump->setIcon(IconLoader::Load(QStringLiteral("go-jump")));
ui_->action_clear_playlist->setIcon(IconLoader::Load("edit-clear-list")); ui_->action_clear_playlist->setIcon(IconLoader::Load(QStringLiteral("edit-clear-list")));
ui_->action_shuffle->setIcon(IconLoader::Load("media-playlist-shuffle")); ui_->action_shuffle->setIcon(IconLoader::Load(QStringLiteral("media-playlist-shuffle")));
ui_->action_remove_duplicates->setIcon(IconLoader::Load("list-remove")); ui_->action_remove_duplicates->setIcon(IconLoader::Load(QStringLiteral("list-remove")));
ui_->action_remove_unavailable->setIcon(IconLoader::Load("list-remove")); ui_->action_remove_unavailable->setIcon(IconLoader::Load(QStringLiteral("list-remove")));
ui_->action_remove_from_playlist->setIcon(IconLoader::Load("list-remove")); ui_->action_remove_from_playlist->setIcon(IconLoader::Load(QStringLiteral("list-remove")));
ui_->action_save_all_playlists->setIcon(IconLoader::Load("document-save-all")); ui_->action_save_all_playlists->setIcon(IconLoader::Load(QStringLiteral("document-save-all")));
// Configure // Configure
ui_->action_cover_manager->setIcon(IconLoader::Load("document-download")); ui_->action_cover_manager->setIcon(IconLoader::Load(QStringLiteral("document-download")));
ui_->action_edit_track->setIcon(IconLoader::Load("edit-rename")); ui_->action_edit_track->setIcon(IconLoader::Load(QStringLiteral("edit-rename")));
ui_->action_edit_value->setIcon(IconLoader::Load("edit-rename")); ui_->action_edit_value->setIcon(IconLoader::Load(QStringLiteral("edit-rename")));
ui_->action_selection_set_value->setIcon(IconLoader::Load("edit-rename")); ui_->action_selection_set_value->setIcon(IconLoader::Load(QStringLiteral("edit-rename")));
ui_->action_equalizer->setIcon(IconLoader::Load("equalizer")); ui_->action_equalizer->setIcon(IconLoader::Load(QStringLiteral("equalizer")));
ui_->action_transcoder->setIcon(IconLoader::Load("tools-wizard")); ui_->action_transcoder->setIcon(IconLoader::Load(QStringLiteral("tools-wizard")));
ui_->action_update_collection->setIcon(IconLoader::Load("view-refresh")); ui_->action_update_collection->setIcon(IconLoader::Load(QStringLiteral("view-refresh")));
ui_->action_full_collection_scan->setIcon(IconLoader::Load("view-refresh")); ui_->action_full_collection_scan->setIcon(IconLoader::Load(QStringLiteral("view-refresh")));
ui_->action_abort_collection_scan->setIcon(IconLoader::Load("dialog-error")); ui_->action_abort_collection_scan->setIcon(IconLoader::Load(QStringLiteral("dialog-error")));
ui_->action_settings->setIcon(IconLoader::Load("configure")); ui_->action_settings->setIcon(IconLoader::Load(QStringLiteral("configure")));
ui_->action_import_data_from_last_fm->setIcon(IconLoader::Load("scrobble")); ui_->action_import_data_from_last_fm->setIcon(IconLoader::Load(QStringLiteral("scrobble")));
ui_->action_console->setIcon(IconLoader::Load("keyboard")); ui_->action_console->setIcon(IconLoader::Load(QStringLiteral("keyboard")));
ui_->action_toggle_show_sidebar->setIcon(IconLoader::Load("view-choose")); ui_->action_toggle_show_sidebar->setIcon(IconLoader::Load(QStringLiteral("view-choose")));
ui_->action_auto_complete_tags->setIcon(IconLoader::Load("musicbrainz")); ui_->action_auto_complete_tags->setIcon(IconLoader::Load(QStringLiteral("musicbrainz")));
// Scrobble // Scrobble
ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble-disabled")); ui_->action_toggle_scrobbling->setIcon(IconLoader::Load(QStringLiteral("scrobble-disabled")));
ui_->action_love->setIcon(IconLoader::Load("love")); ui_->action_love->setIcon(IconLoader::Load(QStringLiteral("love")));
// File view connections // File view connections
QObject::connect(file_view_, &FileView::AddToPlaylist, this, &MainWindow::AddToPlaylist); QObject::connect(file_view_, &FileView::AddToPlaylist, this, &MainWindow::AddToPlaylist);
@ -549,7 +559,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
QObject::connect(ui_->action_abort_collection_scan, &QAction::triggered, &*app_->collection(), &SCollection::AbortScan); QObject::connect(ui_->action_abort_collection_scan, &QAction::triggered, &*app_->collection(), &SCollection::AbortScan);
#if defined(HAVE_GSTREAMER) #if defined(HAVE_GSTREAMER)
QObject::connect(ui_->action_add_files_to_transcoder, &QAction::triggered, this, &MainWindow::AddFilesToTranscoder); QObject::connect(ui_->action_add_files_to_transcoder, &QAction::triggered, this, &MainWindow::AddFilesToTranscoder);
ui_->action_add_files_to_transcoder->setIcon(IconLoader::Load("tools-wizard")); ui_->action_add_files_to_transcoder->setIcon(IconLoader::Load(QStringLiteral("tools-wizard")));
#else #else
ui_->action_add_files_to_transcoder->setDisabled(true); ui_->action_add_files_to_transcoder->setDisabled(true);
#endif #endif
@ -559,8 +569,8 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
QObject::connect(&*app_->scrobbler(), &AudioScrobbler::ErrorMessage, this, &MainWindow::ShowErrorDialog); QObject::connect(&*app_->scrobbler(), &AudioScrobbler::ErrorMessage, this, &MainWindow::ShowErrorDialog);
// Playlist view actions // Playlist view actions
ui_->action_next_playlist->setShortcuts(QList<QKeySequence>() << QKeySequence::fromString("Ctrl+Tab") << QKeySequence::fromString("Ctrl+PgDown")); ui_->action_next_playlist->setShortcuts(QList<QKeySequence>() << QKeySequence::fromString(QStringLiteral("Ctrl+Tab")) << QKeySequence::fromString(QStringLiteral("Ctrl+PgDown")));
ui_->action_previous_playlist->setShortcuts(QList<QKeySequence>() << QKeySequence::fromString("Ctrl+Shift+Tab") << QKeySequence::fromString("Ctrl+PgUp")); ui_->action_previous_playlist->setShortcuts(QList<QKeySequence>() << QKeySequence::fromString(QStringLiteral("Ctrl+Shift+Tab")) << QKeySequence::fromString(QStringLiteral("Ctrl+PgUp")));
// Actions for switching tabs will be global to the entire window, so adding them here // Actions for switching tabs will be global to the entire window, so adding them here
addAction(ui_->action_next_playlist); addAction(ui_->action_next_playlist);
@ -679,9 +689,9 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
QObject::connect(collection_view_group, &QActionGroup::triggered, this, &MainWindow::ChangeCollectionFilterMode); QObject::connect(collection_view_group, &QActionGroup::triggered, this, &MainWindow::ChangeCollectionFilterMode);
QAction *collection_config_action = new QAction(IconLoader::Load("configure"), tr("Configure collection..."), this); QAction *collection_config_action = new QAction(IconLoader::Load(QStringLiteral("configure")), tr("Configure collection..."), this);
QObject::connect(collection_config_action, &QAction::triggered, this, &MainWindow::ShowCollectionConfig); QObject::connect(collection_config_action, &QAction::triggered, this, &MainWindow::ShowCollectionConfig);
collection_view_->filter_widget()->SetSettingsGroup(CollectionSettingsPage::kSettingsGroup); collection_view_->filter_widget()->SetSettingsGroup(QLatin1String(CollectionSettingsPage::kSettingsGroup));
collection_view_->filter_widget()->Init(app_->collection()->model()); collection_view_->filter_widget()->Init(app_->collection()->model());
QAction *separator = new QAction(this); QAction *separator = new QAction(this);
@ -722,14 +732,14 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
QObject::connect(playlist_menu_, &QMenu::aboutToHide, this, &MainWindow::PlaylistMenuHidden); QObject::connect(playlist_menu_, &QMenu::aboutToHide, this, &MainWindow::PlaylistMenuHidden);
playlist_play_pause_ = playlist_menu_->addAction(tr("Play"), this, &MainWindow::PlaylistPlay); playlist_play_pause_ = playlist_menu_->addAction(tr("Play"), this, &MainWindow::PlaylistPlay);
playlist_menu_->addAction(ui_->action_stop); playlist_menu_->addAction(ui_->action_stop);
playlist_stop_after_ = playlist_menu_->addAction(IconLoader::Load("media-playback-stop"), tr("Stop after this track"), this, &MainWindow::PlaylistStopAfter); playlist_stop_after_ = playlist_menu_->addAction(IconLoader::Load(QStringLiteral("media-playback-stop")), tr("Stop after this track"), this, &MainWindow::PlaylistStopAfter);
playlist_queue_ = playlist_menu_->addAction(IconLoader::Load("go-next"), tr("Toggle queue status"), this, &MainWindow::PlaylistQueue); playlist_queue_ = playlist_menu_->addAction(IconLoader::Load(QStringLiteral("go-next")), tr("Toggle queue status"), this, &MainWindow::PlaylistQueue);
playlist_queue_->setShortcut(QKeySequence("Ctrl+D")); playlist_queue_->setShortcut(QKeySequence(QStringLiteral("Ctrl+D")));
ui_->playlist->addAction(playlist_queue_); ui_->playlist->addAction(playlist_queue_);
playlist_queue_play_next_ = playlist_menu_->addAction(IconLoader::Load("go-next"), tr("Queue selected tracks to play next"), this, &MainWindow::PlaylistQueuePlayNext); playlist_queue_play_next_ = playlist_menu_->addAction(IconLoader::Load(QStringLiteral("go-next")), tr("Queue selected tracks to play next"), this, &MainWindow::PlaylistQueuePlayNext);
playlist_queue_play_next_->setShortcut(QKeySequence("Ctrl+Shift+D")); playlist_queue_play_next_->setShortcut(QKeySequence(QStringLiteral("Ctrl+Shift+D")));
ui_->playlist->addAction(playlist_queue_play_next_); ui_->playlist->addAction(playlist_queue_play_next_);
playlist_skip_ = playlist_menu_->addAction(IconLoader::Load("media-skip-forward"), tr("Toggle skip status"), this, &MainWindow::PlaylistSkip); playlist_skip_ = playlist_menu_->addAction(IconLoader::Load(QStringLiteral("media-skip-forward")), tr("Toggle skip status"), this, &MainWindow::PlaylistSkip);
ui_->playlist->addAction(playlist_skip_); ui_->playlist->addAction(playlist_skip_);
playlist_menu_->addSeparator(); playlist_menu_->addSeparator();
@ -742,22 +752,22 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
#ifdef HAVE_MUSICBRAINZ #ifdef HAVE_MUSICBRAINZ
playlist_menu_->addAction(ui_->action_auto_complete_tags); playlist_menu_->addAction(ui_->action_auto_complete_tags);
#endif #endif
playlist_rescan_songs_ = playlist_menu_->addAction(IconLoader::Load("view-refresh"), tr("Rescan song(s)..."), this, &MainWindow::RescanSongs); playlist_rescan_songs_ = playlist_menu_->addAction(IconLoader::Load(QStringLiteral("view-refresh")), tr("Rescan song(s)..."), this, &MainWindow::RescanSongs);
playlist_menu_->addAction(playlist_rescan_songs_); playlist_menu_->addAction(playlist_rescan_songs_);
#ifdef HAVE_GSTREAMER #ifdef HAVE_GSTREAMER
playlist_menu_->addAction(ui_->action_add_files_to_transcoder); playlist_menu_->addAction(ui_->action_add_files_to_transcoder);
#endif #endif
playlist_menu_->addSeparator(); playlist_menu_->addSeparator();
playlist_copy_url_ = playlist_menu_->addAction(IconLoader::Load("edit-copy"), tr("Copy URL(s)..."), this, &MainWindow::PlaylistCopyUrl); playlist_copy_url_ = playlist_menu_->addAction(IconLoader::Load(QStringLiteral("edit-copy")), tr("Copy URL(s)..."), this, &MainWindow::PlaylistCopyUrl);
playlist_show_in_collection_ = playlist_menu_->addAction(IconLoader::Load("edit-find"), tr("Show in collection..."), this, &MainWindow::ShowInCollection); playlist_show_in_collection_ = playlist_menu_->addAction(IconLoader::Load(QStringLiteral("edit-find")), tr("Show in collection..."), this, &MainWindow::ShowInCollection);
playlist_open_in_browser_ = playlist_menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, &MainWindow::PlaylistOpenInBrowser); playlist_open_in_browser_ = playlist_menu_->addAction(IconLoader::Load(QStringLiteral("document-open-folder")), tr("Show in file browser..."), this, &MainWindow::PlaylistOpenInBrowser);
playlist_organize_ = playlist_menu_->addAction(IconLoader::Load("edit-copy"), tr("Organize files..."), this, &MainWindow::PlaylistMoveToCollection); playlist_organize_ = playlist_menu_->addAction(IconLoader::Load(QStringLiteral("edit-copy")), tr("Organize files..."), this, &MainWindow::PlaylistMoveToCollection);
playlist_copy_to_collection_ = playlist_menu_->addAction(IconLoader::Load("edit-copy"), tr("Copy to collection..."), this, &MainWindow::PlaylistCopyToCollection); playlist_copy_to_collection_ = playlist_menu_->addAction(IconLoader::Load(QStringLiteral("edit-copy")), tr("Copy to collection..."), this, &MainWindow::PlaylistCopyToCollection);
playlist_move_to_collection_ = playlist_menu_->addAction(IconLoader::Load("go-jump"), tr("Move to collection..."), this, &MainWindow::PlaylistMoveToCollection); playlist_move_to_collection_ = playlist_menu_->addAction(IconLoader::Load(QStringLiteral("go-jump")), tr("Move to collection..."), this, &MainWindow::PlaylistMoveToCollection);
#if defined(HAVE_GSTREAMER) && !defined(Q_OS_WIN) #if defined(HAVE_GSTREAMER) && !defined(Q_OS_WIN)
playlist_copy_to_device_ = playlist_menu_->addAction(IconLoader::Load("device"), tr("Copy to device..."), this, &MainWindow::PlaylistCopyToDevice); playlist_copy_to_device_ = playlist_menu_->addAction(IconLoader::Load(QStringLiteral("device")), tr("Copy to device..."), this, &MainWindow::PlaylistCopyToDevice);
#endif #endif
playlist_delete_ = playlist_menu_->addAction(IconLoader::Load("edit-delete"), tr("Delete from disk..."), this, &MainWindow::PlaylistDelete); playlist_delete_ = playlist_menu_->addAction(IconLoader::Load(QStringLiteral("edit-delete")), tr("Delete from disk..."), this, &MainWindow::PlaylistDelete);
playlist_menu_->addSeparator(); playlist_menu_->addSeparator();
playlistitem_actions_separator_ = playlist_menu_->addSeparator(); playlistitem_actions_separator_ = playlist_menu_->addSeparator();
playlist_menu_->addAction(ui_->action_clear_playlist); playlist_menu_->addAction(ui_->action_clear_playlist);
@ -845,7 +855,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
QObject::connect(ui_->analyzer, &AnalyzerContainer::WheelEvent, this, &MainWindow::VolumeWheelEvent); QObject::connect(ui_->analyzer, &AnalyzerContainer::WheelEvent, this, &MainWindow::VolumeWheelEvent);
// Statusbar widgets // Statusbar widgets
ui_->playlist_summary->setMinimumWidth(QFontMetrics(font()).horizontalAdvance("WW selected of WW tracks - [ WW:WW ]")); ui_->playlist_summary->setMinimumWidth(QFontMetrics(font()).horizontalAdvance(QStringLiteral("WW selected of WW tracks - [ WW:WW ]")));
ui_->status_bar_stack->setCurrentWidget(ui_->playlist_summary_page); ui_->status_bar_stack->setCurrentWidget(ui_->playlist_summary_page);
QObject::connect(ui_->multi_loading_indicator, &MultiLoadingIndicator::TaskCountChange, this, &MainWindow::TaskCountChanged); QObject::connect(ui_->multi_loading_indicator, &MultiLoadingIndicator::TaskCountChange, this, &MainWindow::TaskCountChanged);
@ -872,7 +882,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
PlayingWidgetPositionChanged(ui_->widget_playing->show_above_status_bar()); PlayingWidgetPositionChanged(ui_->widget_playing->show_above_status_bar());
StyleSheetLoader *css_loader = new StyleSheetLoader(this); StyleSheetLoader *css_loader = new StyleSheetLoader(this);
css_loader->SetStyleSheet(this, ":/style/strawberry.css"); css_loader->SetStyleSheet(this, QStringLiteral(":/style/strawberry.css"));
// Load playlists // Load playlists
app_->playlist_manager()->Init(app_->collection_backend(), app_->playlist_backend(), ui_->playlist_sequence, ui_->playlist); app_->playlist_manager()->Init(app_->collection_backend(), app_->playlist_backend(), ui_->playlist_sequence, ui_->playlist);
@ -943,7 +953,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
#else #else
BehaviourSettingsPage::StartupBehaviour startupbehaviour = BehaviourSettingsPage::StartupBehaviour::Remember; BehaviourSettingsPage::StartupBehaviour startupbehaviour = BehaviourSettingsPage::StartupBehaviour::Remember;
{ {
QSettings s; Settings s;
s.beginGroup(BehaviourSettingsPage::kSettingsGroup); s.beginGroup(BehaviourSettingsPage::kSettingsGroup);
startupbehaviour = static_cast<BehaviourSettingsPage::StartupBehaviour>(s.value("startupbehaviour", static_cast<int>(BehaviourSettingsPage::StartupBehaviour::Remember)).toInt()); startupbehaviour = static_cast<BehaviourSettingsPage::StartupBehaviour>(s.value("startupbehaviour", static_cast<int>(BehaviourSettingsPage::StartupBehaviour::Remember)).toInt());
s.endGroup(); s.endGroup();
@ -966,7 +976,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
} }
[[fallthrough]]; [[fallthrough]];
case BehaviourSettingsPage::StartupBehaviour::Remember: case BehaviourSettingsPage::StartupBehaviour::Remember:
default: { default:{
was_maximized_ = settings_.value("maximized", true).toBool(); was_maximized_ = settings_.value("maximized", true).toBool();
if (was_maximized_) setWindowState(windowState() | Qt::WindowMaximized); if (was_maximized_) setWindowState(windowState() | Qt::WindowMaximized);
@ -999,7 +1009,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
QObject::connect(close_window_shortcut, &QShortcut::activated, this, &MainWindow::ToggleHide); QObject::connect(close_window_shortcut, &QShortcut::activated, this, &MainWindow::ToggleHide);
QAction *action_focus_search = new QAction(this); QAction *action_focus_search = new QAction(this);
action_focus_search->setShortcuts(QList<QKeySequence>() << QKeySequence("Ctrl+F")); action_focus_search->setShortcuts(QList<QKeySequence>() << QKeySequence(QStringLiteral("Ctrl+F")));
addAction(action_focus_search); addAction(action_focus_search);
QObject::connect(action_focus_search, &QAction::triggered, this, &MainWindow::FocusSearchField); QObject::connect(action_focus_search, &QAction::triggered, this, &MainWindow::FocusSearchField);
@ -1015,18 +1025,18 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
} }
#ifdef HAVE_QTSPARKLE #ifdef HAVE_QTSPARKLE
QUrl sparkle_url(QTSPARKLE_URL); QUrl sparkle_url(QString::fromLatin1(QTSPARKLE_URL));
if (!sparkle_url.isEmpty()) { if (!sparkle_url.isEmpty()) {
qLog(Debug) << "Creating Qt Sparkle updater"; qLog(Debug) << "Creating Qt Sparkle updater";
qtsparkle::Updater *updater = new qtsparkle::Updater(sparkle_url, this); qtsparkle::Updater *updater = new qtsparkle::Updater(sparkle_url, this);
updater->SetVersion(STRAWBERRY_VERSION_PACKAGE); updater->SetVersion(QStringLiteral(STRAWBERRY_VERSION_PACKAGE));
QObject::connect(check_updates, &QAction::triggered, updater, &qtsparkle::Updater::CheckNow); QObject::connect(check_updates, &QAction::triggered, updater, &qtsparkle::Updater::CheckNow);
} }
#endif #endif
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
if (!Utilities::GetEnv("SNAP").isEmpty() && !Utilities::GetEnv("SNAP_NAME").isEmpty()) { if (!Utilities::GetEnv(QStringLiteral("SNAP")).isEmpty() && !Utilities::GetEnv(QStringLiteral("SNAP_NAME")).isEmpty()) {
QSettings s; Settings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup);
const bool ignore_snap = s.value("ignore_snap", false).toBool(); const bool ignore_snap = s.value("ignore_snap", false).toBool();
s.endGroup(); s.endGroup();
@ -1040,32 +1050,32 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
#if defined(Q_OS_MACOS) #if defined(Q_OS_MACOS)
if (Utilities::ProcessTranslated()) { if (Utilities::ProcessTranslated()) {
QSettings s; Settings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup);
const bool ignore_rosetta = s.value("ignore_rosetta", false).toBool(); const bool ignore_rosetta = s.value("ignore_rosetta", false).toBool();
s.endGroup(); s.endGroup();
if (!ignore_rosetta) { if (!ignore_rosetta) {
MessageDialog *rosetta_message = new MessageDialog(this); MessageDialog *rosetta_message = new MessageDialog(this);
rosetta_message->set_settings_group(kSettingsGroup); rosetta_message->set_settings_group(QLatin1String(kSettingsGroup));
rosetta_message->set_do_not_show_message_again("ignore_rosetta"); rosetta_message->set_do_not_show_message_again(QStringLiteral("ignore_rosetta"));
rosetta_message->setAttribute(Qt::WA_DeleteOnClose); rosetta_message->setAttribute(Qt::WA_DeleteOnClose);
rosetta_message->ShowMessage(tr("Strawberry running under Rosetta"), tr("You are running Strawberry under Rosetta. Running Strawberry under Rosetta is unsupported and known to have issues. You should download Strawberry for the correct CPU architecture from %1").arg("<a href=\"https://downloads.strawberrymusicplayer.org/\">downloads.strawberrymusicplayer.org</a>"), IconLoader::Load("dialog-warning")); rosetta_message->ShowMessage(tr("Strawberry running under Rosetta"), tr("You are running Strawberry under Rosetta. Running Strawberry under Rosetta is unsupported and known to have issues. You should download Strawberry for the correct CPU architecture from %1").arg(QStringLiteral("<a href=\"https://downloads.strawberrymusicplayer.org/\">downloads.strawberrymusicplayer.org</a>")), IconLoader::Load(QStringLiteral("dialog-warning")));
} }
} }
#endif #endif
{ {
QSettings s; Settings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup);
const QString do_not_show_sponsor_message_key = QString("do_not_show_sponsor_message"); 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(); const bool do_not_show_sponsor_message = s.value(do_not_show_sponsor_message_key, false).toBool();
s.endGroup(); s.endGroup();
if (!do_not_show_sponsor_message) { if (!do_not_show_sponsor_message) {
MessageDialog *sponsor_message = new MessageDialog(this); MessageDialog *sponsor_message = new MessageDialog(this);
sponsor_message->set_settings_group(kSettingsGroup); sponsor_message->set_settings_group(QLatin1String(kSettingsGroup));
sponsor_message->set_do_not_show_message_again(do_not_show_sponsor_message_key); sponsor_message->set_do_not_show_message_again(QLatin1String(do_not_show_sponsor_message_key));
sponsor_message->setAttribute(Qt::WA_DeleteOnClose); 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("<a href= \"https://www.strawberrymusicplayer.org/\">www.strawberrymusicplayer.org</a>"), IconLoader::Load("dialog-information")); 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")));
} }
} }
@ -1080,7 +1090,7 @@ MainWindow::~MainWindow() {
void MainWindow::ReloadSettings() { void MainWindow::ReloadSettings() {
QSettings s; Settings s;
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
constexpr bool keeprunning_available = true; constexpr bool keeprunning_available = true;
@ -1102,6 +1112,9 @@ void MainWindow::ReloadSettings() {
keep_running_ = keeprunning_available && s.value("keeprunning", false).toBool(); keep_running_ = keeprunning_available && s.value("keeprunning", false).toBool();
playing_widget_ = s.value("playing_widget", true).toBool(); playing_widget_ = s.value("playing_widget", true).toBool();
bool trayicon_progress = s.value("trayicon_progress", false).toBool(); bool trayicon_progress = s.value("trayicon_progress", false).toBool();
#ifdef HAVE_DBUS
const bool taskbar_progress = s.value("taskbar_progress", true).toBool();
#endif
if (playing_widget_ != ui_->widget_playing->IsEnabled()) TabSwitched(); if (playing_widget_ != ui_->widget_playing->IsEnabled()) TabSwitched();
doubleclick_addmode_ = static_cast<BehaviourSettingsPage::AddBehaviour>(s.value("doubleclick_addmode", static_cast<int>(BehaviourSettingsPage::AddBehaviour::Append)).toInt()); doubleclick_addmode_ = static_cast<BehaviourSettingsPage::AddBehaviour>(s.value("doubleclick_addmode", static_cast<int>(BehaviourSettingsPage::AddBehaviour::Append)).toInt());
doubleclick_playmode_ = static_cast<BehaviourSettingsPage::PlayBehaviour>(s.value("doubleclick_playmode", static_cast<int>(BehaviourSettingsPage::PlayBehaviour::Never)).toInt()); doubleclick_playmode_ = static_cast<BehaviourSettingsPage::PlayBehaviour>(s.value("doubleclick_playmode", static_cast<int>(BehaviourSettingsPage::PlayBehaviour::Never)).toInt());
@ -1115,6 +1128,13 @@ void MainWindow::ReloadSettings() {
tray_icon_->SetTrayiconProgress(trayicon_progress); tray_icon_->SetTrayiconProgress(trayicon_progress);
#ifdef HAVE_DBUS
if (taskbar_progress_ && !taskbar_progress) {
UpdateTaskbarProgress(false);
}
taskbar_progress_ = taskbar_progress;
#endif
ui_->back_button->setIconSize(QSize(iconsize, iconsize)); ui_->back_button->setIconSize(QSize(iconsize, iconsize));
ui_->pause_play_button->setIconSize(QSize(iconsize, iconsize)); ui_->pause_play_button->setIconSize(QSize(iconsize, iconsize));
ui_->stop_button->setIconSize(QSize(iconsize, iconsize)); ui_->stop_button->setIconSize(QSize(iconsize, iconsize));
@ -1224,7 +1244,7 @@ void MainWindow::ReloadAllSettings() {
void MainWindow::RefreshStyleSheet() { void MainWindow::RefreshStyleSheet() {
QString contents(styleSheet()); QString contents(styleSheet());
setStyleSheet(""); setStyleSheet(QLatin1String(""));
setStyleSheet(contents); setStyleSheet(contents);
} }
@ -1233,7 +1253,7 @@ void MainWindow::SaveSettings() {
SaveGeometry(); SaveGeometry();
SavePlaybackStatus(); SavePlaybackStatus();
app_->player()->SaveVolume(); app_->player()->SaveVolume();
ui_->tabs->SaveSettings(kSettingsGroup); ui_->tabs->SaveSettings(QLatin1String(kSettingsGroup));
ui_->playlist->view()->SaveSettings(); ui_->playlist->view()->SaveSettings();
app_->scrobbler()->WriteCache(); app_->scrobbler()->WriteCache();
@ -1269,6 +1289,13 @@ void MainWindow::Exit() {
return; // Don't quit the application now: wait for the fadeout finished signal return; // Don't quit the application now: wait for the fadeout finished signal
} }
} }
#ifdef HAVE_DBUS
if (taskbar_progress_) {
UpdateTaskbarProgress(false);
}
#endif
DoExit(); DoExit();
} }
@ -1302,11 +1329,11 @@ void MainWindow::EngineChanged(const EngineBase::Type enginetype) {
void MainWindow::MediaStopped() { void MainWindow::MediaStopped() {
setWindowTitle("Strawberry Music Player"); setWindowTitle(QStringLiteral("Strawberry Music Player"));
ui_->action_stop->setEnabled(false); ui_->action_stop->setEnabled(false);
ui_->action_stop_after_this_track->setEnabled(false); ui_->action_stop_after_this_track->setEnabled(false);
ui_->action_play_pause->setIcon(IconLoader::Load("media-playback-start")); ui_->action_play_pause->setIcon(IconLoader::Load(QStringLiteral("media-playback-start")));
ui_->action_play_pause->setText(tr("Play")); ui_->action_play_pause->setText(tr("Play"));
ui_->action_play_pause->setEnabled(true); ui_->action_play_pause->setEnabled(true);
@ -1321,6 +1348,12 @@ void MainWindow::MediaStopped() {
tray_icon_->SetProgress(0); tray_icon_->SetProgress(0);
tray_icon_->SetStopped(); tray_icon_->SetStopped();
#ifdef HAVE_DBUS
if (taskbar_progress_) {
UpdateTaskbarProgress(false);
}
#endif
song_playing_ = Song(); song_playing_ = Song();
song_ = Song(); song_ = Song();
album_cover_ = AlbumCoverImageResult(); album_cover_ = AlbumCoverImageResult();
@ -1333,7 +1366,7 @@ void MainWindow::MediaPaused() {
ui_->action_stop->setEnabled(true); ui_->action_stop->setEnabled(true);
ui_->action_stop_after_this_track->setEnabled(true); ui_->action_stop_after_this_track->setEnabled(true);
ui_->action_play_pause->setIcon(IconLoader::Load("media-playback-start")); ui_->action_play_pause->setIcon(IconLoader::Load(QStringLiteral("media-playback-start")));
ui_->action_play_pause->setText(tr("Play")); ui_->action_play_pause->setText(tr("Play"));
ui_->action_play_pause->setEnabled(true); ui_->action_play_pause->setEnabled(true);
@ -1349,7 +1382,7 @@ void MainWindow::MediaPlaying() {
ui_->action_stop->setEnabled(true); ui_->action_stop->setEnabled(true);
ui_->action_stop_after_this_track->setEnabled(true); ui_->action_stop_after_this_track->setEnabled(true);
ui_->action_play_pause->setIcon(IconLoader::Load("media-playback-pause")); ui_->action_play_pause->setIcon(IconLoader::Load(QStringLiteral("media-playback-pause")));
ui_->action_play_pause->setText(tr("Pause")); ui_->action_play_pause->setText(tr("Pause"));
bool enable_play_pause(false); bool enable_play_pause(false);
@ -1397,6 +1430,12 @@ void MainWindow::SongChanged(const Song &song) {
setWindowTitle(song.PrettyTitleWithArtist()); setWindowTitle(song.PrettyTitleWithArtist());
tray_icon_->SetProgress(0); tray_icon_->SetProgress(0);
#ifdef HAVE_DBUS
if (taskbar_progress_) {
UpdateTaskbarProgress(false);
}
#endif
SendNowPlaying(); SendNowPlaying();
const bool enable_change_art = song.is_collection_song() && !song.effective_albumartist().isEmpty() && !song.album().isEmpty(); const bool enable_change_art = song.is_collection_song() && !song.effective_albumartist().isEmpty() && !song.album().isEmpty();
@ -1469,7 +1508,7 @@ void MainWindow::SaveGeometry() {
void MainWindow::SavePlaybackStatus() { void MainWindow::SavePlaybackStatus() {
QSettings s; Settings s;
s.beginGroup(Player::kSettingsGroup); s.beginGroup(Player::kSettingsGroup);
s.setValue("playback_state", static_cast<int>(app_->player()->GetState())); s.setValue("playback_state", static_cast<int>(app_->player()->GetState()));
@ -1488,7 +1527,7 @@ void MainWindow::SavePlaybackStatus() {
void MainWindow::LoadPlaybackStatus() { void MainWindow::LoadPlaybackStatus() {
QSettings s; Settings s;
s.beginGroup(BehaviourSettingsPage::kSettingsGroup); s.beginGroup(BehaviourSettingsPage::kSettingsGroup);
const bool resume_playback = s.value("resumeplayback", false).toBool(); const bool resume_playback = s.value("resumeplayback", false).toBool();
@ -1512,7 +1551,7 @@ void MainWindow::ResumePlayback() {
qLog(Debug) << "Resuming playback"; qLog(Debug) << "Resuming playback";
QSettings s; Settings s;
s.beginGroup(Player::kSettingsGroup); s.beginGroup(Player::kSettingsGroup);
const EngineBase::State playback_state = static_cast<EngineBase::State>(s.value("playback_state", static_cast<int>(EngineBase::State::Empty)).toInt()); 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_playlist = s.value("playback_playlist", -1).toInt();
@ -1686,6 +1725,12 @@ void MainWindow::Seeked(const qint64 microseconds) {
const qint64 length = app_->player()->GetCurrentItem()->Metadata().length_nanosec() / kNsecPerSec; const qint64 length = app_->player()->GetCurrentItem()->Metadata().length_nanosec() / kNsecPerSec;
tray_icon_->SetProgress(static_cast<int>(static_cast<double>(position) / static_cast<double>(length) * 100.0)); tray_icon_->SetProgress(static_cast<int>(static_cast<double>(position) / static_cast<double>(length) * 100.0));
#ifdef HAVE_DBUS
if (taskbar_progress_) {
UpdateTaskbarProgress(true, static_cast<double>(position) / static_cast<double>(length));
}
#endif
} }
void MainWindow::UpdateTrackPosition() { void MainWindow::UpdateTrackPosition() {
@ -1700,6 +1745,12 @@ void MainWindow::UpdateTrackPosition() {
// Update the tray icon every 10 seconds // Update the tray icon every 10 seconds
if (position % 10 == 0) tray_icon_->SetProgress(static_cast<int>(static_cast<double>(position) / static_cast<double>(length) * 100.0)); if (position % 10 == 0) tray_icon_->SetProgress(static_cast<int>(static_cast<double>(position) / static_cast<double>(length) * 100.0));
#ifdef HAVE_DBUS
if (taskbar_progress_) {
UpdateTaskbarProgress(true, static_cast<double>(position) / static_cast<double>(length));
}
#endif
// Send Scrobble // Send Scrobble
if (app_->scrobbler()->enabled() && item->Metadata().is_metadata_good()) { if (app_->scrobbler()->enabled() && item->Metadata().is_metadata_good()) {
Playlist *playlist = app_->playlist_manager()->active(); Playlist *playlist = app_->playlist_manager()->active();
@ -1726,6 +1777,21 @@ void MainWindow::UpdateTrackSliderPosition() {
} }
#ifdef HAVE_DBUS
void MainWindow::UpdateTaskbarProgress(const bool visible, const double progress) {
QVariantMap map;
QDBusMessage msg = QDBusMessage::createSignal(QStringLiteral("/org/strawberrymusicplayer/strawberry"), QStringLiteral("com.canonical.Unity.LauncherEntry"), QStringLiteral("Update"));
map.insert(QStringLiteral("progress-visible"), visible);
map.insert(QStringLiteral("progress"), progress);
msg << QStringLiteral("application://org.strawberrymusicplayer.strawberry.desktop") << map;
QDBusConnection::sessionBus().send(msg);
}
#endif
void MainWindow::ApplyAddBehaviour(const BehaviourSettingsPage::AddBehaviour b, MimeData *mimedata) { void MainWindow::ApplyAddBehaviour(const BehaviourSettingsPage::AddBehaviour b, MimeData *mimedata) {
switch (b) { switch (b) {
@ -1850,11 +1916,11 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
// Is this song currently playing? // Is this song currently playing?
if (app_->playlist_manager()->current()->current_row() == source_index.row() && app_->player()->GetState() == EngineBase::State::Playing) { if (app_->playlist_manager()->current()->current_row() == source_index.row() && app_->player()->GetState() == EngineBase::State::Playing) {
playlist_play_pause_->setText(tr("Pause")); playlist_play_pause_->setText(tr("Pause"));
playlist_play_pause_->setIcon(IconLoader::Load("media-playback-pause")); playlist_play_pause_->setIcon(IconLoader::Load(QStringLiteral("media-playback-pause")));
} }
else { else {
playlist_play_pause_->setText(tr("Play")); playlist_play_pause_->setText(tr("Play"));
playlist_play_pause_->setIcon(IconLoader::Load("media-playback-start")); playlist_play_pause_->setIcon(IconLoader::Load(QStringLiteral("media-playback-start")));
} }
// Are we allowed to pause? // Are we allowed to pause?
@ -1968,11 +2034,11 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
else playlist_skip_->setText(tr("Toggle skip status")); else playlist_skip_->setText(tr("Toggle skip status"));
} }
if (not_in_queue == 0) playlist_queue_->setIcon(IconLoader::Load("go-previous")); if (not_in_queue == 0) playlist_queue_->setIcon(IconLoader::Load(QStringLiteral("go-previous")));
else playlist_queue_->setIcon(IconLoader::Load("go-next")); else playlist_queue_->setIcon(IconLoader::Load(QStringLiteral("go-next")));
if (in_skipped < selected) playlist_skip_->setIcon(IconLoader::Load("media-skip-forward")); if (in_skipped < selected) playlist_skip_->setIcon(IconLoader::Load(QStringLiteral("media-skip-forward")));
else playlist_skip_->setIcon(IconLoader::Load("media-playback-start")); else playlist_skip_->setIcon(IconLoader::Load(QStringLiteral("media-playback-start")));
if (!index.isValid()) { if (!index.isValid()) {
@ -1989,7 +2055,7 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
QString column_name = Playlist::column_name(column); QString column_name = Playlist::column_name(column);
QString column_value = app_->playlist_manager()->current()->data(source_index).toString(); QString column_value = app_->playlist_manager()->current()->data(source_index).toString();
if (column_value.length() > 25) column_value = column_value.left(25) + "..."; if (column_value.length() > 25) column_value = column_value.left(25) + QStringLiteral("...");
ui_->action_selection_set_value->setText(tr("Set %1 to \"%2\"...").arg(column_name.toLower(), column_value)); ui_->action_selection_set_value->setText(tr("Set %1 to \"%2\"...").arg(column_name.toLower(), column_value));
ui_->action_edit_value->setText(tr("Edit tag \"%1\"...").arg(column_name)); ui_->action_edit_value->setText(tr("Edit tag \"%1\"...").arg(column_name));
@ -2035,7 +2101,7 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
// Create the playlist submenu if songs are selected. // Create the playlist submenu if songs are selected.
if (selected > 0) { if (selected > 0) {
QMenu *add_to_another_menu = new QMenu(tr("Add to another playlist"), this); QMenu *add_to_another_menu = new QMenu(tr("Add to another playlist"), this);
add_to_another_menu->setIcon(IconLoader::Load("list-add")); add_to_another_menu->setIcon(IconLoader::Load(QStringLiteral("list-add")));
for (const PlaylistBackend::Playlist &playlist : app_->playlist_backend()->GetAllOpenPlaylists()) { for (const PlaylistBackend::Playlist &playlist : app_->playlist_backend()->GetAllOpenPlaylists()) {
// don't add the current playlist // don't add the current playlist
@ -2232,18 +2298,18 @@ void MainWindow::AddFile() {
PlaylistParser parser(app_->collection_backend()); PlaylistParser parser(app_->collection_backend());
// Show dialog // Show dialog
QStringList file_names = QFileDialog::getOpenFileNames(this, tr("Add file"), directory, QString("%1 (%2);;%3;;%4").arg(tr("Music"), FileView::kFileFilter, parser.filters(PlaylistParser::Type::Load), tr(kAllFilesFilterSpec))); 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 (file_names.isEmpty()) return; if (filenames.isEmpty()) return;
// Save last used directory // Save last used directory
settings_.setValue("add_media_path", file_names[0]); settings_.setValue("add_media_path", filenames[0]);
// Convert to URLs // Convert to URLs
QList<QUrl> urls; QList<QUrl> urls;
urls.reserve(file_names.count()); urls.reserve(filenames.count());
for (const QString &path : file_names) { for (const QString &path : filenames) {
urls << QUrl::fromLocalFile(QFileInfo(path).canonicalFilePath()); urls << QUrl::fromLocalFile(QDir::cleanPath(path));
} }
MimeData *mimedata = new MimeData; MimeData *mimedata = new MimeData;
@ -2266,7 +2332,7 @@ void MainWindow::AddFolder() {
// Add media // Add media
MimeData *mimedata = new MimeData; MimeData *mimedata = new MimeData;
mimedata->setUrls(QList<QUrl>() << QUrl::fromLocalFile(QFileInfo(directory).canonicalFilePath())); mimedata->setUrls(QList<QUrl>() << QUrl::fromLocalFile(QDir::cleanPath(directory)));
AddToPlaylist(mimedata); AddToPlaylist(mimedata);
} }
@ -2276,7 +2342,7 @@ void MainWindow::AddCDTracks() {
MimeData *mimedata = new MimeData; MimeData *mimedata = new MimeData;
// We are putting empty data, but we specify cdda mimetype to indicate that we want to load audio cd tracks // We are putting empty data, but we specify cdda mimetype to indicate that we want to load audio cd tracks
mimedata->open_in_new_playlist_ = true; mimedata->open_in_new_playlist_ = true;
mimedata->setData(Playlist::kCddaMimeType, QByteArray()); mimedata->setData(QLatin1String(Playlist::kCddaMimeType), QByteArray());
AddToPlaylist(mimedata); AddToPlaylist(mimedata);
} }
@ -2310,7 +2376,7 @@ void MainWindow::ShowInCollection() {
} }
QString search; QString search;
if (!songs.isEmpty()) { if (!songs.isEmpty()) {
search = "artist:" + songs.first().artist() + " album:" + songs.first().album(); search = QStringLiteral("artist:") + songs.first().artist() + QStringLiteral(" album:") + songs.first().album();
} }
collection_view_->filter_widget()->ShowInCollection(search); collection_view_->filter_widget()->ShowInCollection(search);
@ -2403,9 +2469,9 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
break; break;
case CommandlineOptions::PlayerAction::ResizeWindow:{ case CommandlineOptions::PlayerAction::ResizeWindow:{
if (options.window_size().contains('x') && options.window_size().length() >= 4) { if (options.window_size().contains(QLatin1Char('x')) && options.window_size().length() >= 4) {
QString str_w = options.window_size().left(options.window_size().indexOf('x')); QString str_w = options.window_size().left(options.window_size().indexOf(QLatin1Char('x')));
QString str_h = options.window_size().right(options.window_size().length() - options.window_size().indexOf('x') - 1); QString str_h = options.window_size().right(options.window_size().length() - options.window_size().indexOf(QLatin1Char('x')) - 1);
bool w_ok = false; bool w_ok = false;
bool h_ok = false; bool h_ok = false;
int w = str_w.toInt(&w_ok); int w = str_w.toInt(&w_ok);
@ -2444,7 +2510,7 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
#ifdef HAVE_TIDAL #ifdef HAVE_TIDAL
for (const QUrl &url : options.urls()) { for (const QUrl &url : options.urls()) {
if (url.scheme() == "tidal" && url.host() == "login") { if (url.scheme() == QStringLiteral("tidal") && url.host() == QStringLiteral("login")) {
emit AuthorizationUrlReceived(url); emit AuthorizationUrlReceived(url);
return; return;
} }
@ -2522,7 +2588,7 @@ bool MainWindow::LoadUrl(const QString &url) {
return true; return true;
} }
#ifdef HAVE_TIDAL #ifdef HAVE_TIDAL
else if (url.startsWith("tidal://login")) { else if (url.startsWith(QLatin1String("tidal://login"))) {
emit AuthorizationUrlReceived(QUrl(url)); emit AuthorizationUrlReceived(QUrl(url));
return true; return true;
} }
@ -2886,11 +2952,11 @@ void MainWindow::CheckFullRescanRevisions() {
// if we have any... // if we have any...
if (!reasons.isEmpty()) { 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:") + "<ul>"; 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) { for (const QString &reason : reasons) {
message += ("<li>" + reason + "</li>"); message += QStringLiteral("<li>") + reason + QStringLiteral("</li>");
} }
message += "</ul>" + tr("Would you like to run a full rescan right now?"); message += QStringLiteral("</ul>") + tr("Would you like to run a full rescan right now?");
if (QMessageBox::question(this, tr("Collection rescan notice"), message, QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { if (QMessageBox::question(this, tr("Collection rescan notice"), message, QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) {
app_->collection()->FullScan(); app_->collection()->FullScan();
} }
@ -3001,10 +3067,10 @@ void MainWindow::HandleNotificationPreview(const OSDBase::Behaviour type, const
qLog(Debug) << "The current playlist is empty, showing a fake song"; qLog(Debug) << "The current playlist is empty, showing a fake song";
// Create a fake song // Create a fake song
Song fake(Song::Source::LocalFile); Song fake(Song::Source::LocalFile);
fake.Init("Title", "Artist", "Album", 123); fake.Init(QStringLiteral("Title"), QStringLiteral("Artist"), QStringLiteral("Album"), 123);
fake.set_genre("Classical"); fake.set_genre(QStringLiteral("Classical"));
fake.set_composer("Anonymous"); fake.set_composer(QStringLiteral("Anonymous"));
fake.set_performer("Anonymous"); fake.set_performer(QStringLiteral("Anonymous"));
fake.set_track(1); fake.set_track(1);
fake.set_disc(1); fake.set_disc(1);
fake.set_year(2011); fake.set_year(2011);
@ -3147,12 +3213,12 @@ void MainWindow::SetToggleScrobblingIcon(const bool value) {
if (value) { if (value) {
if (app_->playlist_manager()->active() && app_->playlist_manager()->active()->scrobbled()) if (app_->playlist_manager()->active() && app_->playlist_manager()->active()->scrobbled())
ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble", true, 22)); ui_->action_toggle_scrobbling->setIcon(IconLoader::Load(QStringLiteral("scrobble"), true, 22));
else else
ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble", true, 22)); // TODO: Create a faint version of the icon ui_->action_toggle_scrobbling->setIcon(IconLoader::Load(QStringLiteral("scrobble"), true, 22)); // TODO: Create a faint version of the icon
} }
else { else {
ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble-disabled", true, 22)); ui_->action_toggle_scrobbling->setIcon(IconLoader::Load(QStringLiteral("scrobble-disabled"), true, 22));
} }
} }
@ -3197,13 +3263,23 @@ void MainWindow::PlaylistDelete() {
app_->player()->Next(); app_->player()->Next();
} }
SharedPtr<MusicStorage> storage = make_shared<FilesystemMusicStorage>(Song::Source::LocalFile, "/"); SharedPtr<MusicStorage> storage = make_shared<FilesystemMusicStorage>(Song::Source::LocalFile, QStringLiteral("/"));
DeleteFiles *delete_files = new DeleteFiles(app_->task_manager(), storage, true); DeleteFiles *delete_files = new DeleteFiles(app_->task_manager(), storage, true);
//QObject::connect(delete_files, &DeleteFiles::Finished, this, &MainWindow::DeleteFinished); QObject::connect(delete_files, &DeleteFiles::Finished, this, &MainWindow::DeleteFilesFinished);
delete_files->Start(selected_songs); delete_files->Start(selected_songs);
} }
void MainWindow::DeleteFilesFinished(const SongList &songs_with_errors) {
if (songs_with_errors.isEmpty()) return;
OrganizeErrorDialog *dialog = new OrganizeErrorDialog(this);
dialog->Show(OrganizeErrorDialog::OperationType::Delete, songs_with_errors);
// It deletes itself when the user closes it
}
void MainWindow::FocusSearchField() { void MainWindow::FocusSearchField() {
if (ui_->tabs->currentIndex() == ui_->tabs->IndexOfTab(collection_view_) && !collection_view_->filter_widget()->SearchFieldHasFocus()) { if (ui_->tabs->currentIndex() == ui_->tabs->IndexOfTab(collection_view_) && !collection_view_->filter_widget()->SearchFieldHasFocus()) {

View File

@ -52,6 +52,7 @@
#include "platforminterface.h" #include "platforminterface.h"
#include "song.h" #include "song.h"
#include "tagreaderclient.h" #include "tagreaderclient.h"
#include "settings.h"
#include "engine/enginebase.h" #include "engine/enginebase.h"
#include "osd/osdbase.h" #include "osd/osdbase.h"
#include "playlist/playlist.h" #include "playlist/playlist.h"
@ -270,6 +271,8 @@ class MainWindow : public QMainWindow, public PlatformInterface {
void FocusSearchField(); void FocusSearchField();
void DeleteFilesFinished(const SongList &songs_with_errors);
public slots: public slots:
void CommandlineOptionsReceived(const QByteArray &string_options); void CommandlineOptionsReceived(const QByteArray &string_options);
void Raise(); void Raise();
@ -290,6 +293,10 @@ class MainWindow : public QMainWindow, public PlatformInterface {
void SetToggleScrobblingIcon(const bool value); void SetToggleScrobblingIcon(const bool value);
#ifdef HAVE_DBUS
void UpdateTaskbarProgress(const bool visible, const double progress = 0);
#endif
private: private:
Ui_MainWindow *ui_; Ui_MainWindow *ui_;
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
@ -373,10 +380,13 @@ class MainWindow : public QMainWindow, public PlatformInterface {
QTimer *track_position_timer_; QTimer *track_position_timer_;
QTimer *track_slider_timer_; QTimer *track_slider_timer_;
QSettings settings_; Settings settings_;
bool keep_running_; bool keep_running_;
bool playing_widget_; bool playing_widget_;
#ifdef HAVE_DBUS
bool taskbar_progress_;
#endif
BehaviourSettingsPage::AddBehaviour doubleclick_addmode_; BehaviourSettingsPage::AddBehaviour doubleclick_addmode_;
BehaviourSettingsPage::PlayBehaviour doubleclick_playmode_; BehaviourSettingsPage::PlayBehaviour doubleclick_playmode_;
BehaviourSettingsPage::PlaylistAddBehaviour doubleclick_playlist_addmode_; BehaviourSettingsPage::PlaylistAddBehaviour doubleclick_playlist_addmode_;

View File

@ -122,6 +122,9 @@
</property> </property>
<item> <item>
<widget class="QToolButton" name="back_button"> <widget class="QToolButton" name="back_button">
<property name="accessibleName">
<string>MenuPopupToolButton</string>
</property>
<property name="iconSize"> <property name="iconSize">
<size> <size>
<width>32</width> <width>32</width>
@ -135,6 +138,9 @@
</item> </item>
<item> <item>
<widget class="QToolButton" name="pause_play_button"> <widget class="QToolButton" name="pause_play_button">
<property name="accessibleName">
<string>MenuPopupToolButton</string>
</property>
<property name="iconSize"> <property name="iconSize">
<size> <size>
<width>32</width> <width>32</width>
@ -151,6 +157,9 @@
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="accessibleName">
<string>MenuPopupToolButton</string>
</property>
<property name="iconSize"> <property name="iconSize">
<size> <size>
<width>32</width> <width>32</width>
@ -167,6 +176,9 @@
</item> </item>
<item> <item>
<widget class="QToolButton" name="forward_button"> <widget class="QToolButton" name="forward_button">
<property name="accessibleName">
<string>MenuPopupToolButton</string>
</property>
<property name="iconSize"> <property name="iconSize">
<size> <size>
<width>32</width> <width>32</width>
@ -205,6 +217,9 @@
</item> </item>
<item> <item>
<widget class="QToolButton" name="button_love"> <widget class="QToolButton" name="button_love">
<property name="accessibleName">
<string>MenuPopupToolButton</string>
</property>
<property name="iconSize"> <property name="iconSize">
<size> <size>
<width>32</width> <width>32</width>

View File

@ -22,7 +22,6 @@
#include "config.h" #include "config.h"
#include <functional> #include <functional>
#include <limits>
#include <QObject> #include <QObject>
#include <QMimeData> #include <QMimeData>
@ -120,13 +119,15 @@ void MergedProxyModel::RemoveSubModel(const QModelIndex &source_parent) {
// Remove all the children of the item that got deleted // Remove all the children of the item that got deleted
QModelIndex proxy_parent = mapFromSource(source_parent); QModelIndex proxy_parent = mapFromSource(source_parent);
if (proxy_parent.isValid()) {
// We can't know how many children it had, since we can't dereference it const int row_count = rowCount(proxy_parent);
// FIXME: This is a bad idea. if (row_count > 0) {
resetting_model_ = submodel; resetting_model_ = submodel;
beginRemoveRows(proxy_parent, 0, std::numeric_limits<int>::max() - 1); beginRemoveRows(proxy_parent, 0, row_count - 1);
endRemoveRows(); endRemoveRows();
resetting_model_ = nullptr; resetting_model_ = nullptr;
}
}
// Delete all the mappings that reference the submodel // Delete all the mappings that reference the submodel
auto it = p_->mappings_.get<tag_by_pointer>().begin(); auto it = p_->mappings_.get<tag_by_pointer>().begin();
@ -192,10 +193,15 @@ void MergedProxyModel::SubModelAboutToBeReset() {
QModelIndex source_parent = merge_points_.value(submodel); QModelIndex source_parent = merge_points_.value(submodel);
QModelIndex proxy_parent = mapFromSource(source_parent); QModelIndex proxy_parent = mapFromSource(source_parent);
resetting_model_ = submodel; if (proxy_parent.isValid()) {
beginRemoveRows(proxy_parent, 0, submodel->rowCount()); const int row_count = submodel->rowCount();
endRemoveRows(); if (row_count > 0) {
resetting_model_ = nullptr; resetting_model_ = submodel;
beginRemoveRows(proxy_parent, 0, row_count - 1);
endRemoveRows();
resetting_model_ = nullptr;
}
}
// Delete all the mappings that reference the submodel // Delete all the mappings that reference the submodel
auto it = p_->mappings_.get<tag_by_pointer>().begin(); auto it = p_->mappings_.get<tag_by_pointer>().begin();
@ -418,7 +424,7 @@ QStringList MergedProxyModel::mimeTypes() const {
QStringList ret; QStringList ret;
ret << sourceModel()->mimeTypes(); ret << sourceModel()->mimeTypes();
QList<QAbstractItemModel*> models = merge_points_.keys(); const QList<QAbstractItemModel*> models = merge_points_.keys();
for (const QAbstractItemModel *model : models) { for (const QAbstractItemModel *model : models) {
ret << model->mimeTypes(); ret << model->mimeTypes();
} }
@ -500,7 +506,7 @@ QAbstractItemModel *MergedProxyModel::GetModel(const QModelIndex &source_index)
// This is essentially const_cast<QAbstractItemModel*>(source_index.model()), but without the const_cast // This is essentially const_cast<QAbstractItemModel*>(source_index.model()), but without the const_cast
const QAbstractItemModel *const_model = source_index.model(); const QAbstractItemModel *const_model = source_index.model();
if (const_model == sourceModel()) return sourceModel(); if (const_model == sourceModel()) return sourceModel();
QList<QAbstractItemModel*> submodels = merge_points_.keys(); const QList<QAbstractItemModel*> submodels = merge_points_.keys();
for (QAbstractItemModel *submodel : submodels) { for (QAbstractItemModel *submodel : submodels) {
if (submodel == const_model) return submodel; if (submodel == const_model) return submodel;
} }
@ -516,7 +522,7 @@ void MergedProxyModel::DataChanged(const QModelIndex &top_left, const QModelInde
void MergedProxyModel::LayoutAboutToBeChanged() { void MergedProxyModel::LayoutAboutToBeChanged() {
old_merge_points_.clear(); old_merge_points_.clear();
QList<QAbstractItemModel*> models = merge_points_.keys(); const QList<QAbstractItemModel*> models = merge_points_.keys();
for (QAbstractItemModel *model : models) { for (QAbstractItemModel *model : models) {
old_merge_points_[model] = merge_points_.value(model); old_merge_points_[model] = merge_points_.value(model);
} }
@ -525,7 +531,7 @@ void MergedProxyModel::LayoutAboutToBeChanged() {
void MergedProxyModel::LayoutChanged() { void MergedProxyModel::LayoutChanged() {
QList<QAbstractItemModel*> models = merge_points_.keys(); const QList<QAbstractItemModel*> models = merge_points_.keys();
for (QAbstractItemModel *model : models) { for (QAbstractItemModel *model : models) {
if (!old_merge_points_.contains(model)) continue; if (!old_merge_points_.contains(model)) continue;

View File

@ -94,9 +94,9 @@ const QDBusArgument &operator>>(const QDBusArgument &arg, MaybePlaylist &playlis
namespace mpris { namespace mpris {
const char *Mpris2::kMprisObjectPath = "/org/mpris/MediaPlayer2"; constexpr char kMprisObjectPath[] = "/org/mpris/MediaPlayer2";
const char *Mpris2::kServiceName = "org.mpris.MediaPlayer2.strawberry"; constexpr char kServiceName[] = "org.mpris.MediaPlayer2.strawberry";
const char *Mpris2::kFreedesktopPath = "org.freedesktop.DBus.Properties"; constexpr char kFreedesktopPath[] = "org.freedesktop.DBus.Properties";
Mpris2::Mpris2(Application *app, QObject *parent) Mpris2::Mpris2(Application *app, QObject *parent)
: QObject(parent), : QObject(parent),
@ -108,13 +108,13 @@ Mpris2::Mpris2(Application *app, QObject *parent)
new Mpris2Player(this); new Mpris2Player(this);
new Mpris2Playlists(this); new Mpris2Playlists(this);
if (!QDBusConnection::sessionBus().registerService(kServiceName)) { if (!QDBusConnection::sessionBus().registerService(QLatin1String(kServiceName))) {
qLog(Warning) << "Failed to register" << QString(kServiceName) << "on the session bus"; qLog(Warning) << "Failed to register" << kServiceName << "on the session bus";
return; return;
} }
if (!QDBusConnection::sessionBus().registerObject(kMprisObjectPath, this)) { if (!QDBusConnection::sessionBus().registerObject(QLatin1String(kMprisObjectPath), this)) {
qLog(Warning) << "Failed to register" << QString(kMprisObjectPath) << "on the session bus"; qLog(Warning) << "Failed to register" << kMprisObjectPath << "on the session bus";
return; return;
} }
@ -131,18 +131,18 @@ Mpris2::Mpris2(Application *app, QObject *parent)
app_name_[0] = app_name_[0].toUpper(); app_name_[0] = app_name_[0].toUpper();
QStringList data_dirs = QString(qgetenv("XDG_DATA_DIRS")).split(":"); QStringList data_dirs = QString::fromUtf8(qgetenv("XDG_DATA_DIRS")).split(QLatin1Char(':'));
if (!data_dirs.contains("/usr/local/share")) { if (!data_dirs.contains(QStringLiteral("/usr/local/share"))) {
data_dirs.append("/usr/local/share"); data_dirs.append(QStringLiteral("/usr/local/share"));
} }
if (!data_dirs.contains("/usr/share")) { if (!data_dirs.contains(QStringLiteral("/usr/share"))) {
data_dirs.append("/usr/share"); data_dirs.append(QStringLiteral("/usr/share"));
} }
for (const QString &data_dir : data_dirs) { for (const QString &data_dir : data_dirs) {
const QString desktopfilepath = QString("%1/applications/%2.desktop").arg(data_dir, QGuiApplication::desktopFileName()); const QString desktopfilepath = QStringLiteral("%1/applications/%2.desktop").arg(data_dir, QGuiApplication::desktopFileName());
if (QFile::exists(desktopfilepath)) { if (QFile::exists(desktopfilepath)) {
desktopfilepath_ = desktopfilepath; desktopfilepath_ = desktopfilepath;
break; break;
@ -150,7 +150,7 @@ Mpris2::Mpris2(Application *app, QObject *parent)
} }
if (desktopfilepath_.isEmpty()) { if (desktopfilepath_.isEmpty()) {
desktopfilepath_ = QGuiApplication::desktopFileName() + ".desktop"; desktopfilepath_ = QGuiApplication::desktopFileName() + QStringLiteral(".desktop");
} }
} }
@ -165,37 +165,37 @@ void Mpris2::EngineStateChanged(EngineBase::State newState) {
if (newState != EngineBase::State::Playing && newState != EngineBase::State::Paused) { if (newState != EngineBase::State::Playing && newState != EngineBase::State::Paused) {
last_metadata_ = QVariantMap(); last_metadata_ = QVariantMap();
EmitNotification("Metadata"); EmitNotification(QStringLiteral("Metadata"));
} }
EmitNotification("CanPlay"); EmitNotification(QStringLiteral("CanPlay"));
EmitNotification("CanPause"); EmitNotification(QStringLiteral("CanPause"));
EmitNotification("PlaybackStatus", PlaybackStatus(newState)); EmitNotification(QStringLiteral("PlaybackStatus"), PlaybackStatus(newState));
if (newState == EngineBase::State::Playing) EmitNotification("CanSeek", CanSeek(newState)); if (newState == EngineBase::State::Playing) EmitNotification(QStringLiteral("CanSeek"), CanSeek(newState));
} }
void Mpris2::VolumeChanged() { void Mpris2::VolumeChanged() {
EmitNotification("Volume"); EmitNotification(QStringLiteral("Volume"));
} }
void Mpris2::ShuffleModeChanged() { EmitNotification("Shuffle"); } void Mpris2::ShuffleModeChanged() { EmitNotification(QStringLiteral("Shuffle")); }
void Mpris2::RepeatModeChanged() { void Mpris2::RepeatModeChanged() {
EmitNotification("LoopStatus"); EmitNotification(QStringLiteral("LoopStatus"));
EmitNotification("CanGoNext", CanGoNext()); EmitNotification(QStringLiteral("CanGoNext"), CanGoNext());
EmitNotification("CanGoPrevious", CanGoPrevious()); EmitNotification(QStringLiteral("CanGoPrevious"), CanGoPrevious());
} }
void Mpris2::EmitNotification(const QString &name, const QVariant &value) { void Mpris2::EmitNotification(const QString &name, const QVariant &value) {
EmitNotification(name, value, "org.mpris.MediaPlayer2.Player"); EmitNotification(name, value, QStringLiteral("org.mpris.MediaPlayer2.Player"));
} }
void Mpris2::EmitNotification(const QString &name, const QVariant &value, const QString &mprisEntity) { void Mpris2::EmitNotification(const QString &name, const QVariant &value, const QString &mprisEntity) {
QDBusMessage msg = QDBusMessage::createSignal(kMprisObjectPath, kFreedesktopPath, "PropertiesChanged"); QDBusMessage msg = QDBusMessage::createSignal(QLatin1String(kMprisObjectPath), QLatin1String(kFreedesktopPath), QStringLiteral("PropertiesChanged"));
QVariantMap map; QVariantMap map;
map.insert(name, value); map.insert(name, value);
QVariantList args = QVariantList() << mprisEntity << map << QStringList(); QVariantList args = QVariantList() << mprisEntity << map << QStringList();
@ -207,18 +207,18 @@ void Mpris2::EmitNotification(const QString &name, const QVariant &value, const
void Mpris2::EmitNotification(const QString &name) { void Mpris2::EmitNotification(const QString &name) {
QVariant value; QVariant value;
if (name == "PlaybackStatus") value = PlaybackStatus(); if (name == QStringLiteral("PlaybackStatus")) value = PlaybackStatus();
else if (name == "LoopStatus") value = LoopStatus(); else if (name == QStringLiteral("LoopStatus")) value = LoopStatus();
else if (name == "Shuffle") value = Shuffle(); else if (name == QStringLiteral("Shuffle")) value = Shuffle();
else if (name == "Metadata") value = Metadata(); else if (name == QStringLiteral("Metadata")) value = Metadata();
else if (name == "Rating") value = Rating(); else if (name == QStringLiteral("Rating")) value = Rating();
else if (name == "Volume") value = Volume(); else if (name == QStringLiteral("Volume")) value = Volume();
else if (name == "Position") value = Position(); else if (name == QStringLiteral("Position")) value = Position();
else if (name == "CanPlay") value = CanPlay(); else if (name == QStringLiteral("CanPlay")) value = CanPlay();
else if (name == "CanPause") value = CanPause(); else if (name == QStringLiteral("CanPause")) value = CanPause();
else if (name == "CanSeek") value = CanSeek(); else if (name == QStringLiteral("CanSeek")) value = CanSeek();
else if (name == "CanGoNext") value = CanGoNext(); else if (name == QStringLiteral("CanGoNext")) value = CanGoNext();
else if (name == "CanGoPrevious") value = CanGoPrevious(); else if (name == QStringLiteral("CanGoPrevious")) value = CanGoPrevious();
if (value.isValid()) EmitNotification(name, value); if (value.isValid()) EmitNotification(name, value);
@ -240,49 +240,49 @@ QString Mpris2::DesktopEntryAbsolutePath() const {
} }
QString Mpris2::DesktopEntry() const { return QGuiApplication::desktopFileName() + ".desktop"; } QString Mpris2::DesktopEntry() const { return QGuiApplication::desktopFileName() + QStringLiteral(".desktop"); }
QStringList Mpris2::SupportedUriSchemes() const { QStringList Mpris2::SupportedUriSchemes() const {
static QStringList res = QStringList() << "file" static QStringList res = QStringList() << QStringLiteral("file")
<< "http" << QStringLiteral("http")
<< "cdda" << QStringLiteral("cdda")
<< "smb" << QStringLiteral("smb")
<< "sftp"; << QStringLiteral("sftp");
return res; return res;
} }
QStringList Mpris2::SupportedMimeTypes() const { QStringList Mpris2::SupportedMimeTypes() const {
static QStringList res = QStringList() << "x-content/audio-player" static QStringList res = QStringList() << QStringLiteral("x-content/audio-player")
<< "application/ogg" << QStringLiteral("application/ogg")
<< "application/x-ogg" << QStringLiteral("application/x-ogg")
<< "application/x-ogm-audio" << QStringLiteral("application/x-ogm-audio")
<< "audio/flac" << QStringLiteral("audio/flac")
<< "audio/ogg" << QStringLiteral("audio/ogg")
<< "audio/vorbis" << QStringLiteral("audio/vorbis")
<< "audio/aac" << QStringLiteral("audio/aac")
<< "audio/mp4" << QStringLiteral("audio/mp4")
<< "audio/mpeg" << QStringLiteral("audio/mpeg")
<< "audio/mpegurl" << QStringLiteral("audio/mpegurl")
<< "audio/vnd.rn-realaudio" << QStringLiteral("audio/vnd.rn-realaudio")
<< "audio/x-flac" << QStringLiteral("audio/x-flac")
<< "audio/x-oggflac" << QStringLiteral("audio/x-oggflac")
<< "audio/x-vorbis" << QStringLiteral("audio/x-vorbis")
<< "audio/x-vorbis+ogg" << QStringLiteral("audio/x-vorbis+ogg")
<< "audio/x-speex" << QStringLiteral("audio/x-speex")
<< "audio/x-wav" << QStringLiteral("audio/x-wav")
<< "audio/x-wavpack" << QStringLiteral("audio/x-wavpack")
<< "audio/x-ape" << QStringLiteral("audio/x-ape")
<< "audio/x-mp3" << QStringLiteral("audio/x-mp3")
<< "audio/x-mpeg" << QStringLiteral("audio/x-mpeg")
<< "audio/x-mpegurl" << QStringLiteral("audio/x-mpegurl")
<< "audio/x-ms-wma" << QStringLiteral("audio/x-ms-wma")
<< "audio/x-musepack" << QStringLiteral("audio/x-musepack")
<< "audio/x-pn-realaudio" << QStringLiteral("audio/x-pn-realaudio")
<< "audio/x-scpls" << QStringLiteral("audio/x-scpls")
<< "video/x-ms-asf"; << QStringLiteral("video/x-ms-asf");
return res; return res;
@ -299,9 +299,9 @@ QString Mpris2::PlaybackStatus() const {
QString Mpris2::PlaybackStatus(EngineBase::State state) const { QString Mpris2::PlaybackStatus(EngineBase::State state) const {
switch (state) { switch (state) {
case EngineBase::State::Playing: return "Playing"; case EngineBase::State::Playing: return QStringLiteral("Playing");
case EngineBase::State::Paused: return "Paused"; case EngineBase::State::Paused: return QStringLiteral("Paused");
default: return "Stopped"; default: return QStringLiteral("Stopped");
} }
} }
@ -309,14 +309,14 @@ QString Mpris2::PlaybackStatus(EngineBase::State state) const {
QString Mpris2::LoopStatus() const { QString Mpris2::LoopStatus() const {
if (!app_->playlist_manager()->sequence()) { if (!app_->playlist_manager()->sequence()) {
return "None"; return QStringLiteral("None");
} }
switch (app_->playlist_manager()->sequence()->repeat_mode()) { switch (app_->playlist_manager()->active() ? app_->playlist_manager()->active()->RepeatMode() : app_->playlist_manager()->sequence()->repeat_mode()) {
case PlaylistSequence::RepeatMode::Album: case PlaylistSequence::RepeatMode::Album:
case PlaylistSequence::RepeatMode::Playlist: return "Playlist"; case PlaylistSequence::RepeatMode::Playlist: return QStringLiteral("Playlist");
case PlaylistSequence::RepeatMode::Track: return "Track"; case PlaylistSequence::RepeatMode::Track: return QStringLiteral("Track");
default: return "None"; default: return QStringLiteral("None");
} }
} }
@ -325,13 +325,13 @@ void Mpris2::SetLoopStatus(const QString &value) {
PlaylistSequence::RepeatMode mode = PlaylistSequence::RepeatMode::Off; PlaylistSequence::RepeatMode mode = PlaylistSequence::RepeatMode::Off;
if (value == "None") { if (value == QStringLiteral("None")) {
mode = PlaylistSequence::RepeatMode::Off; mode = PlaylistSequence::RepeatMode::Off;
} }
else if (value == "Track") { else if (value == QStringLiteral("Track")) {
mode = PlaylistSequence::RepeatMode::Track; mode = PlaylistSequence::RepeatMode::Track;
} }
else if (value == "Playlist") { else if (value == QStringLiteral("Playlist")) {
mode = PlaylistSequence::RepeatMode::Playlist; mode = PlaylistSequence::RepeatMode::Playlist;
} }
@ -351,7 +351,8 @@ void Mpris2::SetRate(double rate) {
bool Mpris2::Shuffle() const { bool Mpris2::Shuffle() const {
return app_->playlist_manager()->sequence()->shuffle_mode() != PlaylistSequence::ShuffleMode::Off; const PlaylistSequence::ShuffleMode shuffle_mode = app_->playlist_manager()->active() ? app_->playlist_manager()->active()->ShuffleMode() : app_->playlist_manager()->sequence()->shuffle_mode();
return shuffle_mode != PlaylistSequence::ShuffleMode::Off;
} }
@ -379,19 +380,19 @@ void Mpris2::SetRating(double rating) {
} }
QString Mpris2::current_track_id() const { QDBusObjectPath Mpris2::current_track_id() const {
return QString("/org/strawberrymusicplayer/strawberry/Track/%1").arg(QString::number(app_->playlist_manager()->active()->current_row())); return QDBusObjectPath(QStringLiteral("/org/strawberrymusicplayer/strawberry/Track/%1").arg(QString::number(app_->playlist_manager()->active()->current_row())));
} }
// We send Metadata change notification as soon as the process of changing song starts... // We send Metadata change notification as soon as the process of changing song starts...
void Mpris2::CurrentSongChanged(const Song &song) { void Mpris2::CurrentSongChanged(const Song &song) {
AlbumCoverLoaded(song); AlbumCoverLoaded(song);
EmitNotification("CanPlay"); EmitNotification(QStringLiteral("CanPlay"));
EmitNotification("CanPause"); EmitNotification(QStringLiteral("CanPause"));
EmitNotification("CanGoNext", CanGoNext()); EmitNotification(QStringLiteral("CanGoNext"), CanGoNext());
EmitNotification("CanGoPrevious", CanGoPrevious()); EmitNotification(QStringLiteral("CanGoPrevious"), CanGoPrevious());
EmitNotification("CanSeek", CanSeek()); EmitNotification(QStringLiteral("CanSeek"), CanSeek());
} }
@ -402,7 +403,7 @@ void Mpris2::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &re
song.ToXesam(&last_metadata_); song.ToXesam(&last_metadata_);
using mpris::AddMetadata; using mpris::AddMetadata;
AddMetadata("mpris:trackid", current_track_id(), &last_metadata_); AddMetadata(QStringLiteral("mpris:trackid"), current_track_id(), &last_metadata_);
QUrl cover_url; QUrl cover_url;
if (result.album_cover.cover_url.isValid() && result.album_cover.cover_url.isLocalFile() && QFile(result.album_cover.cover_url.toLocalFile()).exists()) { if (result.album_cover.cover_url.isValid() && result.album_cover.cover_url.isLocalFile() && QFile(result.album_cover.cover_url.toLocalFile()).exists()) {
@ -419,13 +420,13 @@ void Mpris2::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &re
} }
if (cover_url.isValid()) { if (cover_url.isValid()) {
AddMetadata("mpris:artUrl", cover_url.toString(), &last_metadata_); AddMetadata(QStringLiteral("mpris:artUrl"), cover_url.toString(), &last_metadata_);
} }
AddMetadata("year", song.year(), &last_metadata_); AddMetadata(QStringLiteral("year"), song.year(), &last_metadata_);
AddMetadata("bitrate", song.bitrate(), &last_metadata_); AddMetadata(QStringLiteral("bitrate"), song.bitrate(), &last_metadata_);
EmitNotification("Metadata", last_metadata_); EmitNotification(QStringLiteral("Metadata"), last_metadata_);
} }
@ -459,7 +460,7 @@ bool Mpris2::CanPlay() const {
// This one's a bit different than MPRIS 1 - we want this to be true even when the song is already paused or stopped. // This one's a bit different than MPRIS 1 - we want this to be true even when the song is already paused or stopped.
bool Mpris2::CanPause() const { bool Mpris2::CanPause() const {
return (app_->player()->GetCurrentItem() && app_->player()->GetState() == EngineBase::State::Playing && !(app_->player()->GetCurrentItem()->options() & PlaylistItem::Option::PauseDisabled)) || PlaybackStatus() == "Paused" || PlaybackStatus() == "Stopped"; return (app_->player()->GetCurrentItem() && app_->player()->GetState() == EngineBase::State::Playing && !(app_->player()->GetCurrentItem()->options() & PlaylistItem::Option::PauseDisabled)) || PlaybackStatus() == QStringLiteral("Paused") || PlaybackStatus() == QStringLiteral("Stopped");
} }
bool Mpris2::CanSeek() const { return CanSeek(app_->player()->GetState()); } bool Mpris2::CanSeek() const { return CanSeek(app_->player()->GetState()); }
@ -512,7 +513,7 @@ void Mpris2::Seek(qint64 offset) {
void Mpris2::SetPosition(const QDBusObjectPath &trackId, qint64 offset) { void Mpris2::SetPosition(const QDBusObjectPath &trackId, qint64 offset) {
if (CanSeek() && trackId.path() == current_track_id() && offset >= 0) { if (CanSeek() && trackId == current_track_id() && offset >= 0) {
offset *= kNsecPerUsec; offset *= kNsecPerUsec;
if (offset < app_->player()->GetCurrentItem()->Metadata().length_nanosec()) { if (offset < app_->player()->GetCurrentItem()->Metadata().length_nanosec()) {
@ -566,12 +567,12 @@ quint32 Mpris2::PlaylistCount() const {
return app_->playlist_manager()->GetAllPlaylists().size(); return app_->playlist_manager()->GetAllPlaylists().size();
} }
QStringList Mpris2::Orderings() const { return QStringList() << "User"; } QStringList Mpris2::Orderings() const { return QStringList() << QStringLiteral("User"); }
namespace { namespace {
QDBusObjectPath MakePlaylistPath(int id) { QDBusObjectPath MakePlaylistPath(int id) {
return QDBusObjectPath(QString("/org/strawberrymusicplayer/strawberry/PlaylistId/%1").arg(id)); return QDBusObjectPath(QStringLiteral("/org/strawberrymusicplayer/strawberry/PlaylistId/%1").arg(id));
} }
} // namespace } // namespace
@ -593,7 +594,7 @@ MaybePlaylist Mpris2::ActivePlaylist() const {
void Mpris2::ActivatePlaylist(const QDBusObjectPath &playlist_id) { void Mpris2::ActivatePlaylist(const QDBusObjectPath &playlist_id) {
QStringList split_path = playlist_id.path().split('/'); QStringList split_path = playlist_id.path().split(QLatin1Char('/'));
qLog(Debug) << Q_FUNC_INFO << playlist_id.path() << split_path; qLog(Debug) << Q_FUNC_INFO << playlist_id.path() << split_path;
if (split_path.isEmpty()) { if (split_path.isEmpty()) {
return; return;
@ -647,7 +648,7 @@ void Mpris2::PlaylistChangedSlot(Playlist *playlist) {
void Mpris2::PlaylistCollectionChanged(Playlist *playlist) { void Mpris2::PlaylistCollectionChanged(Playlist *playlist) {
Q_UNUSED(playlist); Q_UNUSED(playlist);
EmitNotification("PlaylistCount", "", "org.mpris.MediaPlayer2.Playlists"); EmitNotification(QStringLiteral("PlaylistCount"), QLatin1String(""), QStringLiteral("org.mpris.MediaPlayer2.Playlists"));
} }
} // namespace mpris } // namespace mpris

View File

@ -225,17 +225,13 @@ class Mpris2 : public QObject {
QString PlaybackStatus(EngineBase::State state) const; QString PlaybackStatus(EngineBase::State state) const;
QString current_track_id() const; QDBusObjectPath current_track_id() const;
bool CanSeek(EngineBase::State state) const; bool CanSeek(EngineBase::State state) const;
QString DesktopEntryAbsolutePath() const; QString DesktopEntryAbsolutePath() const;
private: private:
static const char *kMprisObjectPath;
static const char *kServiceName;
static const char *kFreedesktopPath;
Application *app_; Application *app_;
QString app_name_; QString app_name_;

View File

@ -28,6 +28,7 @@
#include <QDateTime> #include <QDateTime>
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
#include <QDBusObjectPath>
namespace mpris { namespace mpris {
@ -55,8 +56,12 @@ inline void AddMetadata(const QString &key, const QDateTime &metadata, QVariantM
if (metadata.isValid()) (*map)[key] = metadata; if (metadata.isValid()) (*map)[key] = metadata;
} }
inline void AddMetadata(const QString &key, const QDBusObjectPath &metadata, QVariantMap *map) {
if (metadata.path().length() > 0) (*map)[key] = QVariant::fromValue<QDBusObjectPath>(metadata);
}
inline QString AsMPRISDateTimeType(const qint64 time) { inline QString AsMPRISDateTimeType(const qint64 time) {
return time != -1 ? QDateTime::fromSecsSinceEpoch(time).toString(Qt::ISODate) : ""; return time != -1 ? QDateTime::fromSecsSinceEpoch(time).toString(Qt::ISODate) : QLatin1String("");
} }
} // namespace mpris } // namespace mpris

View File

@ -89,12 +89,12 @@ class MusicStorage {
virtual bool GetSupportedFiletypes(QList<Song::FileType> *ret) { Q_UNUSED(ret); return true; } virtual bool GetSupportedFiletypes(QList<Song::FileType> *ret) { Q_UNUSED(ret); return true; }
virtual bool StartCopy(QList<Song::FileType> *supported_types) { Q_UNUSED(supported_types); return true; } virtual bool StartCopy(QList<Song::FileType> *supported_types) { Q_UNUSED(supported_types); return true; }
virtual bool CopyToStorage(const CopyJob &job) = 0; virtual bool CopyToStorage(const CopyJob &job, QString &error_text) = 0;
virtual void FinishCopy(bool success) { Q_UNUSED(success); } virtual bool FinishCopy(bool success, QString &error_text) { Q_UNUSED(error_text); return success; }
virtual void StartDelete() {} virtual void StartDelete() {}
virtual bool DeleteFromStorage(const DeleteJob &job) = 0; virtual bool DeleteFromStorage(const DeleteJob &job) = 0;
virtual void FinishDelete(bool success) { Q_UNUSED(success); } virtual bool FinishDelete(bool success, QString &error_text) { Q_UNUSED(error_text); return success; }
virtual void Eject() {} virtual void Eject() {}

View File

@ -46,18 +46,18 @@ QNetworkReply *NetworkAccessManager::createRequest(Operation op, const QNetworkR
QByteArray user_agent; QByteArray user_agent;
if (request.hasRawHeader("User-Agent")) { if (request.hasRawHeader("User-Agent")) {
user_agent = request.rawHeader("User-Agent"); user_agent = request.header(QNetworkRequest::UserAgentHeader).toByteArray();
} }
else { else {
user_agent = QString("%1 %2").arg(QCoreApplication::applicationName(), QCoreApplication::applicationVersion()).toUtf8(); user_agent = QStringLiteral("%1 %2").arg(QCoreApplication::applicationName(), QCoreApplication::applicationVersion()).toUtf8();
} }
QNetworkRequest new_request(request); QNetworkRequest new_request(request);
new_request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); new_request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
new_request.setRawHeader("User-Agent", user_agent); new_request.setHeader(QNetworkRequest::UserAgentHeader, user_agent);
if (op == QNetworkAccessManager::PostOperation && !new_request.header(QNetworkRequest::ContentTypeHeader).isValid()) { if (op == QNetworkAccessManager::PostOperation && !new_request.header(QNetworkRequest::ContentTypeHeader).isValid()) {
new_request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); new_request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/x-www-form-urlencoded"));
} }
// Prefer the cache unless the caller has changed the setting already // Prefer the cache unless the caller has changed the setting already

View File

@ -29,6 +29,7 @@
#include <QSettings> #include <QSettings>
#include "core/logging.h" #include "core/logging.h"
#include "core/settings.h"
#include "networkproxyfactory.h" #include "networkproxyfactory.h"
NetworkProxyFactory *NetworkProxyFactory::sInstance = nullptr; NetworkProxyFactory *NetworkProxyFactory::sInstance = nullptr;
@ -43,11 +44,10 @@ NetworkProxyFactory::NetworkProxyFactory()
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
// Linux uses environment variables to pass proxy configuration information, which systemProxyForQuery doesn't support for some reason. // Linux uses environment variables to pass proxy configuration information, which systemProxyForQuery doesn't support for some reason.
QStringList urls; const QStringList urls = QStringList() << QString::fromLocal8Bit(qgetenv("HTTP_PROXY"))
urls << QString::fromLocal8Bit(qgetenv("HTTP_PROXY")); << QString::fromLocal8Bit(qgetenv("http_proxy"))
urls << QString::fromLocal8Bit(qgetenv("http_proxy")); << QString::fromLocal8Bit(qgetenv("ALL_PROXY"))
urls << QString::fromLocal8Bit(qgetenv("ALL_PROXY")); << QString::fromLocal8Bit(qgetenv("all_proxy"));
urls << QString::fromLocal8Bit(qgetenv("all_proxy"));
qLog(Debug) << "Detected system proxy URLs:" << urls; qLog(Debug) << "Detected system proxy URLs:" << urls;
@ -78,7 +78,7 @@ void NetworkProxyFactory::ReloadSettings() {
QMutexLocker l(&mutex_); QMutexLocker l(&mutex_);
QSettings s; Settings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup);
mode_ = static_cast<Mode>(s.value("mode", static_cast<int>(Mode::System)).toInt()); mode_ = static_cast<Mode>(s.value("mode", static_cast<int>(Mode::System)).toInt());
@ -112,7 +112,7 @@ QList<QNetworkProxy> NetworkProxyFactory::queryProxy(const QNetworkProxyQuery &q
ret.setPort(env_url_.port()); ret.setPort(env_url_.port());
ret.setUser(env_url_.userName()); ret.setUser(env_url_.userName());
ret.setPassword(env_url_.password()); ret.setPassword(env_url_.password());
if (env_url_.scheme().startsWith("http")) { if (env_url_.scheme().startsWith(QLatin1String("http"))) {
ret.setType(QNetworkProxy::HttpProxy); ret.setType(QNetworkProxy::HttpProxy);
} }
else { else {

View File

@ -25,6 +25,7 @@
#include <algorithm> #include <algorithm>
#include <memory> #include <memory>
#include <chrono>
#include <QtGlobal> #include <QtGlobal>
#include <QObject> #include <QObject>
@ -36,6 +37,7 @@
#include <QSettings> #include <QSettings>
#include "core/logging.h" #include "core/logging.h"
#include "core/settings.h"
#include "utilities/timeconstants.h" #include "utilities/timeconstants.h"
#include "scoped_ptr.h" #include "scoped_ptr.h"
@ -67,6 +69,8 @@
#include "settings/behavioursettingspage.h" #include "settings/behavioursettingspage.h"
#include "settings/playlistsettingspage.h" #include "settings/playlistsettingspage.h"
using namespace std::chrono_literals;
using std::make_shared; using std::make_shared;
const char *Player::kSettingsGroup = "Player"; const char *Player::kSettingsGroup = "Player";
@ -80,6 +84,7 @@ Player::Player(Application *app, QObject *parent)
#endif #endif
analyzer_(nullptr), analyzer_(nullptr),
equalizer_(nullptr), equalizer_(nullptr),
timer_save_volume_(new QTimer(this)),
stream_change_type_(EngineBase::TrackChangeType::First), stream_change_type_(EngineBase::TrackChangeType::First),
autoscroll_(Playlist::AutoScroll::Maybe), autoscroll_(Playlist::AutoScroll::Maybe),
last_state_(EngineBase::State::Empty), last_state_(EngineBase::State::Empty),
@ -91,15 +96,20 @@ Player::Player(Application *app, QObject *parent)
greyout_(true), greyout_(true),
menu_previousmode_(BehaviourSettingsPage::PreviousBehaviour::DontRestart), menu_previousmode_(BehaviourSettingsPage::PreviousBehaviour::DontRestart),
seek_step_sec_(10), seek_step_sec_(10),
volume_increment_(5),
play_offset_nanosec_(0) { play_offset_nanosec_(0) {
QSettings s; Settings s;
s.beginGroup(BackendSettingsPage::kSettingsGroup); s.beginGroup(BackendSettingsPage::kSettingsGroup);
EngineBase::Type enginetype = EngineBase::TypeFromName(s.value("engine", EngineBase::Name(EngineBase::Type::GStreamer)).toString().toLower()); EngineBase::Type enginetype = EngineBase::TypeFromName(s.value("engine", EngineBase::Name(EngineBase::Type::GStreamer)).toString().toLower());
s.endGroup(); s.endGroup();
CreateEngine(enginetype); CreateEngine(enginetype);
timer_save_volume_->setSingleShot(true);
timer_save_volume_->setInterval(5s);
QObject::connect(timer_save_volume_, &QTimer::timeout, this, &Player::SaveVolume);
} }
EngineBase::Type Player::CreateEngine(EngineBase::Type enginetype) { EngineBase::Type Player::CreateEngine(EngineBase::Type enginetype) {
@ -134,7 +144,7 @@ EngineBase::Type Player::CreateEngine(EngineBase::Type enginetype) {
} }
if (use_enginetype != enginetype) { // Engine was set to something else. Reset output and device. if (use_enginetype != enginetype) { // Engine was set to something else. Reset output and device.
QSettings s; Settings s;
s.beginGroup(BackendSettingsPage::kSettingsGroup); s.beginGroup(BackendSettingsPage::kSettingsGroup);
s.setValue("engine", EngineBase::Name(use_enginetype)); s.setValue("engine", EngineBase::Name(use_enginetype));
s.setValue("output", engine_->DefaultOutput()); s.setValue("output", engine_->DefaultOutput());
@ -154,7 +164,7 @@ EngineBase::Type Player::CreateEngine(EngineBase::Type enginetype) {
void Player::Init() { void Player::Init() {
QSettings s; Settings s;
if (!engine_) { if (!engine_) {
s.beginGroup(BackendSettingsPage::kSettingsGroup); s.beginGroup(BackendSettingsPage::kSettingsGroup);
@ -198,7 +208,7 @@ void Player::Init() {
void Player::ReloadSettings() { void Player::ReloadSettings() {
QSettings s; Settings s;
s.beginGroup(PlaylistSettingsPage::kSettingsGroup); s.beginGroup(PlaylistSettingsPage::kSettingsGroup);
continue_on_error_ = s.value("continue_on_error", false).toBool(); continue_on_error_ = s.value("continue_on_error", false).toBool();
@ -208,6 +218,7 @@ void Player::ReloadSettings() {
s.beginGroup(BehaviourSettingsPage::kSettingsGroup); s.beginGroup(BehaviourSettingsPage::kSettingsGroup);
menu_previousmode_ = static_cast<BehaviourSettingsPage::PreviousBehaviour>(s.value("menu_previousmode", static_cast<int>(BehaviourSettingsPage::PreviousBehaviour::DontRestart)).toInt()); menu_previousmode_ = static_cast<BehaviourSettingsPage::PreviousBehaviour>(s.value("menu_previousmode", static_cast<int>(BehaviourSettingsPage::PreviousBehaviour::DontRestart)).toInt());
seek_step_sec_ = s.value("seek_step_sec", 10).toInt(); seek_step_sec_ = s.value("seek_step_sec", 10).toInt();
volume_increment_ = s.value("volume_increment", 5).toUInt();
s.endGroup(); s.endGroup();
engine_->ReloadSettings(); engine_->ReloadSettings();
@ -216,7 +227,7 @@ void Player::ReloadSettings() {
void Player::LoadVolume() { void Player::LoadVolume() {
QSettings s; Settings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup);
const uint volume = s.value("volume", 100).toInt(); const uint volume = s.value("volume", 100).toInt();
s.endGroup(); s.endGroup();
@ -227,7 +238,7 @@ void Player::LoadVolume() {
void Player::SaveVolume() { void Player::SaveVolume() {
QSettings s; Settings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup);
s.setValue("volume", volume_); s.setValue("volume", volume_);
s.endGroup(); s.endGroup();
@ -278,7 +289,7 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
if (is_current) NextItem(stream_change_type_, autoscroll_); if (is_current) NextItem(stream_change_type_, autoscroll_);
break; break;
case UrlHandler::LoadResult::Type::TrackAvailable: { case UrlHandler::LoadResult::Type::TrackAvailable:{
qLog(Debug) << "URL handler for" << result.media_url_ << "returned" << result.stream_url_; qLog(Debug) << "URL handler for" << result.media_url_ << "returned" << result.stream_url_;
@ -386,7 +397,7 @@ void Player::NextItem(const EngineBase::TrackChangeFlags change, const Playlist:
// If we received too many errors in auto change, with repeat enabled, we stop // If we received too many errors in auto change, with repeat enabled, we stop
if (change & EngineBase::TrackChangeType::Auto) { if (change & EngineBase::TrackChangeType::Auto) {
const PlaylistSequence::RepeatMode repeat_mode = active_playlist->sequence()->repeat_mode(); const PlaylistSequence::RepeatMode repeat_mode = active_playlist->RepeatMode();
if (repeat_mode != PlaylistSequence::RepeatMode::Off) { if (repeat_mode != PlaylistSequence::RepeatMode::Off) {
if ((repeat_mode == PlaylistSequence::RepeatMode::Track && nb_errors_received_ >= 3) || (nb_errors_received_ >= app_->playlist_manager()->active()->filter()->rowCount())) { if ((repeat_mode == PlaylistSequence::RepeatMode::Track && nb_errors_received_ >= 3) || (nb_errors_received_ >= app_->playlist_manager()->active()->filter()->rowCount())) {
// We received too many "Error" state changes: probably looping over a playlist which contains only unavailable elements: stop now. // We received too many "Error" state changes: probably looping over a playlist which contains only unavailable elements: stop now.
@ -428,7 +439,8 @@ void Player::PlayPlaylistInternal(const EngineBase::TrackChangeFlags change, con
play_offset_nanosec_ = 0; play_offset_nanosec_ = 0;
Playlist *playlist = nullptr; Playlist *playlist = nullptr;
for (Playlist *p : app_->playlist_manager()->GetAllPlaylists()) { const QList<Playlist*> playlists = app_->playlist_manager()->GetAllPlaylists();
for (Playlist *p : playlists) {
if (playlist_name == app_->playlist_manager()->GetPlaylistName(p->id())) { if (playlist_name == app_->playlist_manager()->GetPlaylistName(p->id())) {
playlist = p; playlist = p;
break; break;
@ -492,7 +504,7 @@ void Player::PlayPause(const quint64 offset_nanosec, const Playlist::AutoScroll
emit Resumed(); emit Resumed();
break; break;
case EngineBase::State::Playing: { case EngineBase::State::Playing:{
if (current_item_->options() & PlaylistItem::Option::PauseDisabled) { if (current_item_->options() & PlaylistItem::Option::PauseDisabled) {
Stop(); Stop();
} }
@ -506,7 +518,7 @@ void Player::PlayPause(const quint64 offset_nanosec, const Playlist::AutoScroll
case EngineBase::State::Empty: case EngineBase::State::Empty:
case EngineBase::State::Error: case EngineBase::State::Error:
case EngineBase::State::Idle: { case EngineBase::State::Idle:{
pause_time_ = QDateTime(); pause_time_ = QDateTime();
play_offset_nanosec_ = offset_nanosec; play_offset_nanosec_ = offset_nanosec;
app_->playlist_manager()->SetActivePlaylist(app_->playlist_manager()->current_id()); app_->playlist_manager()->SetActivePlaylist(app_->playlist_manager()->current_id());
@ -526,7 +538,7 @@ void Player::UnPause() {
if (current_item_ && pause_time_.isValid()) { if (current_item_ && pause_time_.isValid()) {
const Song &song = current_item_->Metadata(); const Song &song = current_item_->Metadata();
if (url_handlers_.contains(song.url().scheme()) && song.stream_url_can_expire()) { if (url_handlers_.contains(song.url().scheme()) && song.stream_url_can_expire()) {
const quint64 time = QDateTime::currentDateTime().toSecsSinceEpoch() - pause_time_.toSecsSinceEpoch(); const quint64 time = QDateTime::currentSecsSinceEpoch() - pause_time_.toSecsSinceEpoch();
if (time >= 30) { // Stream URL might be expired. if (time >= 30) { // Stream URL might be expired.
qLog(Debug) << "Re-requesting stream URL for" << song.url(); qLog(Debug) << "Re-requesting stream URL for" << song.url();
play_offset_nanosec_ = engine_->position_nanosec(); play_offset_nanosec_ = engine_->position_nanosec();
@ -561,6 +573,7 @@ void Player::Stop(const bool stop_after) {
engine_->Stop(stop_after); engine_->Stop(stop_after);
app_->playlist_manager()->active()->set_current_row(-1); app_->playlist_manager()->active()->set_current_row(-1);
app_->playlist_manager()->active()->reset_played_indexes();
current_item_.reset(); current_item_.reset();
pause_time_ = QDateTime(); pause_time_ = QDateTime();
play_offset_nanosec_ = 0; play_offset_nanosec_ = 0;
@ -658,6 +671,7 @@ void Player::SetVolumeFromSlider(const int value) {
volume_ = volume; volume_ = volume;
engine_->SetVolume(volume); engine_->SetVolume(volume);
emit VolumeChanged(volume); emit VolumeChanged(volume);
timer_save_volume_->start();
} }
} }
@ -668,6 +682,7 @@ void Player::SetVolumeFromEngine(const uint volume) {
if (new_volume != volume_) { if (new_volume != volume_) {
volume_ = new_volume; volume_ = new_volume;
emit VolumeChanged(new_volume); emit VolumeChanged(new_volume);
timer_save_volume_->start();
} }
} }
@ -679,6 +694,7 @@ void Player::SetVolume(const uint volume) {
volume_ = new_volume; volume_ = new_volume;
engine_->SetVolume(new_volume); engine_->SetVolume(new_volume);
emit VolumeChanged(new_volume); emit VolumeChanged(new_volume);
timer_save_volume_->start();
} }
} }
@ -686,7 +702,7 @@ void Player::SetVolume(const uint volume) {
void Player::VolumeUp() { void Player::VolumeUp() {
uint old_volume = GetVolume(); uint old_volume = GetVolume();
uint new_volume = std::min(old_volume + 5, static_cast<uint>(100)); uint new_volume = std::min(old_volume + volume_increment_, static_cast<uint>(100));
if (new_volume == old_volume) return; if (new_volume == old_volume) return;
SetVolume(new_volume); SetVolume(new_volume);
@ -695,7 +711,7 @@ void Player::VolumeUp() {
void Player::VolumeDown() { void Player::VolumeDown() {
uint old_volume = GetVolume(); uint old_volume = GetVolume();
uint new_volume = static_cast<uint>(std::max(static_cast<int>(old_volume) - 5, 0)); uint new_volume = static_cast<uint>(std::max(static_cast<int>(old_volume) - static_cast<int>(volume_increment_), 0));
if (new_volume == old_volume) return; if (new_volume == old_volume) return;
SetVolume(new_volume); SetVolume(new_volume);

View File

@ -39,6 +39,7 @@
#include "playlist/playlistitem.h" #include "playlist/playlistitem.h"
#include "settings/behavioursettingspage.h" #include "settings/behavioursettingspage.h"
class QTimer;
class Application; class Application;
class Song; class Song;
class AnalyzerContainer; class AnalyzerContainer;
@ -223,6 +224,7 @@ class Player : public PlayerInterface {
#endif #endif
AnalyzerContainer *analyzer_; AnalyzerContainer *analyzer_;
SharedPtr<Equalizer> equalizer_; SharedPtr<Equalizer> equalizer_;
QTimer *timer_save_volume_;
PlaylistItemPtr current_item_; PlaylistItemPtr current_item_;
@ -242,6 +244,7 @@ class Player : public PlayerInterface {
bool greyout_; bool greyout_;
BehaviourSettingsPage::PreviousBehaviour menu_previousmode_; BehaviourSettingsPage::PreviousBehaviour menu_previousmode_;
int seek_step_sec_; int seek_step_sec_;
uint volume_increment_;
QDateTime pause_time_; QDateTime pause_time_;
quint64 play_offset_nanosec_; quint64 play_offset_nanosec_;

View File

@ -38,8 +38,8 @@ SystemTrayIcon::SystemTrayIcon(QObject *parent)
: QSystemTrayIcon(parent), : QSystemTrayIcon(parent),
menu_(new QMenu), menu_(new QMenu),
app_name_(QCoreApplication::applicationName()), app_name_(QCoreApplication::applicationName()),
pixmap_playing_(":/pictures/tiny-play.png"), pixmap_playing_(QStringLiteral(":/pictures/tiny-play.png")),
pixmap_paused_(":/pictures/tiny-pause.png"), pixmap_paused_(QStringLiteral(":/pictures/tiny-pause.png")),
action_play_pause_(nullptr), action_play_pause_(nullptr),
action_stop_(nullptr), action_stop_(nullptr),
action_stop_after_this_track_(nullptr), action_stop_after_this_track_(nullptr),
@ -51,8 +51,8 @@ SystemTrayIcon::SystemTrayIcon(QObject *parent)
app_name_[0] = app_name_[0].toUpper(); app_name_[0] = app_name_[0].toUpper();
const QIcon icon = IconLoader::Load("strawberry"); const QIcon icon = IconLoader::Load(QStringLiteral("strawberry"));
const QIcon icon_grey = IconLoader::Load("strawberry-grey"); const QIcon icon_grey = IconLoader::Load(QStringLiteral("strawberry-grey"));
pixmap_normal_ = icon.pixmap(48, QIcon::Normal); pixmap_normal_ = icon.pixmap(48, QIcon::Normal);
if (icon_grey.isNull()) { if (icon_grey.isNull()) {
pixmap_grey_ = icon.pixmap(48, QIcon::Disabled); pixmap_grey_ = icon.pixmap(48, QIcon::Disabled);
@ -143,7 +143,7 @@ void SystemTrayIcon::SetPlaying(bool enable_play_pause) {
action_stop_->setEnabled(true); action_stop_->setEnabled(true);
action_stop_after_this_track_->setEnabled(true); action_stop_after_this_track_->setEnabled(true);
action_play_pause_->setIcon(IconLoader::Load("media-playback-pause")); action_play_pause_->setIcon(IconLoader::Load(QStringLiteral("media-playback-pause")));
action_play_pause_->setText(tr("Pause")); action_play_pause_->setText(tr("Pause"));
action_play_pause_->setEnabled(enable_play_pause); action_play_pause_->setEnabled(enable_play_pause);
@ -156,7 +156,7 @@ void SystemTrayIcon::SetPaused() {
action_stop_->setEnabled(true); action_stop_->setEnabled(true);
action_stop_after_this_track_->setEnabled(true); action_stop_after_this_track_->setEnabled(true);
action_play_pause_->setIcon(IconLoader::Load("media-playback-start")); action_play_pause_->setIcon(IconLoader::Load(QStringLiteral("media-playback-start")));
action_play_pause_->setText(tr("Play")); action_play_pause_->setText(tr("Play"));
action_play_pause_->setEnabled(true); action_play_pause_->setEnabled(true);
@ -170,7 +170,7 @@ void SystemTrayIcon::SetStopped() {
action_stop_->setEnabled(false); action_stop_->setEnabled(false);
action_stop_after_this_track_->setEnabled(false); action_stop_after_this_track_->setEnabled(false);
action_play_pause_->setIcon(IconLoader::Load("media-playback-start")); action_play_pause_->setIcon(IconLoader::Load(QStringLiteral("media-playback-start")));
action_play_pause_->setText(tr("Play")); action_play_pause_->setText(tr("Play"));
action_play_pause_->setEnabled(true); action_play_pause_->setEnabled(true);

120
src/core/settings.cpp Normal file
View File

@ -0,0 +1,120 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <QSettings>
#include <QVariant>
#include <QString>
#include "settings.h"
Settings::Settings(QObject *parent)
: QSettings(parent) {}
Settings::Settings(const QString &filename, const Format format, QObject *parent)
: QSettings(filename, format, parent) {}
// Compatibility with older Qt versions
#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0)
void Settings::beginGroup(const char *prefix) {
QSettings::beginGroup(QLatin1String(prefix));
}
void Settings::beginGroup(const QString &prefix) {
QSettings::beginGroup(prefix);
}
bool Settings::contains(const char *key) const {
return QSettings::contains(QLatin1String(key));
}
bool Settings::contains(const QString &key) const {
return QSettings::contains(key);
}
QVariant Settings::value(const char *key, const QVariant &default_value) const {
return QSettings::value(QLatin1String(key), default_value);
}
QVariant Settings::value(const QString &key, const QVariant &default_value) const {
return QSettings::value(key, default_value);
}
void Settings::setValue(const char *key, const QVariant &value) {
QSettings::setValue(QLatin1String(key), value);
}
void Settings::setValue(const QString &key, const QVariant &value) {
QSettings::setValue(key, value);
}
int Settings::beginReadArray(const char *prefix) {
return QSettings::beginReadArray(QLatin1String(prefix));
}
int Settings::beginReadArray(const QString &prefix) {
return QSettings::beginReadArray(prefix);
}
void Settings::beginWriteArray(const char *prefix, int size) {
QSettings::beginWriteArray(QLatin1String(prefix), size);
}
void Settings::beginWriteArray(const QString &prefix, int size) {
QSettings::beginWriteArray(prefix, size);
}
void Settings::remove(const char *key) {
QSettings::remove(QLatin1String(key));
}
void Settings::remove(const QString &key) {
QSettings::remove(key);
}
#endif

51
src/core/settings.h Normal file
View File

@ -0,0 +1,51 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SETTINGS_H
#define SETTINGS_H
#include <QSettings>
#include <QObject>
#include <QVariant>
class Settings : public QSettings {
Q_OBJECT
public:
explicit Settings(QObject *parent = nullptr);
explicit Settings(const QString &filename, const Format format, QObject *parent = nullptr);
#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0) // Compatibility with older Qt versions
void beginGroup(const char *prefix);
void beginGroup(const QString &prefix);
bool contains(const char *key) const;
bool contains(const QString &key) const;
QVariant value(const char *key, const QVariant &default_value = QVariant()) const;
QVariant value(const QString &key, const QVariant &default_value = QVariant()) const;
void setValue(const char *key, const QVariant &value);
void setValue(const QString &key, const QVariant &value);
int beginReadArray(const char *prefix);
int beginReadArray(const QString &prefix);
void beginWriteArray(const char *prefix, int size = -1);
void beginWriteArray(const QString &prefix, int size = -1);
void remove(const char *key);
void remove(const QString &key);
#endif
};
#endif // SETTINGS_H

View File

@ -30,7 +30,7 @@ DefaultSettingsProvider::DefaultSettingsProvider() = default;
void DefaultSettingsProvider::set_group(const char *group) { void DefaultSettingsProvider::set_group(const char *group) {
while (!backend_.group().isEmpty()) backend_.endGroup(); while (!backend_.group().isEmpty()) backend_.endGroup();
backend_.beginGroup(group); backend_.beginGroup(QLatin1String(group));
} }
QVariant DefaultSettingsProvider::value(const QString &key, const QVariant &default_value) const { QVariant DefaultSettingsProvider::value(const QString &key, const QVariant &default_value) const {

View File

@ -99,12 +99,14 @@ QModelIndex SimpleTreeModel<T>::parent(const QModelIndex &idx) const {
template<typename T> template<typename T>
int SimpleTreeModel<T>::rowCount(const QModelIndex &parent) const { int SimpleTreeModel<T>::rowCount(const QModelIndex &parent) const {
T *item = IndexToItem(parent); T *item = IndexToItem(parent);
if (!item) return 0;
return item->children.count(); return item->children.count();
} }
template<typename T> template<typename T>
bool SimpleTreeModel<T>::hasChildren(const QModelIndex &parent) const { bool SimpleTreeModel<T>::hasChildren(const QModelIndex &parent) const {
T *item = IndexToItem(parent); T *item = IndexToItem(parent);
if (!item) return false;
if (item->lazy_loaded) if (item->lazy_loaded)
return !item->children.isEmpty(); return !item->children.isEmpty();
else else
@ -114,13 +116,13 @@ bool SimpleTreeModel<T>::hasChildren(const QModelIndex &parent) const {
template<typename T> template<typename T>
bool SimpleTreeModel<T>::canFetchMore(const QModelIndex &parent) const { bool SimpleTreeModel<T>::canFetchMore(const QModelIndex &parent) const {
T *item = IndexToItem(parent); T *item = IndexToItem(parent);
return !item->lazy_loaded; return item && !item->lazy_loaded;
} }
template<typename T> template<typename T>
void SimpleTreeModel<T>::fetchMore(const QModelIndex &parent) { void SimpleTreeModel<T>::fetchMore(const QModelIndex &parent) {
T *item = IndexToItem(parent); T *item = IndexToItem(parent);
if (!item->lazy_loaded) { if (item && !item->lazy_loaded) {
LazyPopulate(item); LazyPopulate(item);
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -42,6 +42,7 @@
#include <QIcon> #include <QIcon>
class SqlQuery; class SqlQuery;
class QSqlRecord;
class EngineMetadata; class EngineMetadata;
@ -112,7 +113,9 @@ class Song {
}; };
static const QStringList kColumns; static const QStringList kColumns;
static const QStringList kRowIdColumns;
static const QString kColumnSpec; static const QString kColumnSpec;
static const QString kRowIdColumnSpec;
static const QString kBindSpec; static const QString kBindSpec;
static const QString kUpdateSpec; static const QString kUpdateSpec;
@ -123,9 +126,12 @@ class Song {
static const QString kFtsBindSpec; static const QString kFtsBindSpec;
static const QString kFtsUpdateSpec; static const QString kFtsUpdateSpec;
static const QRegularExpression kAlbumRemoveDisc; using RegularExpressionList = QList<QRegularExpression>;
static const QRegularExpression kAlbumRemoveMisc; static const RegularExpressionList kAlbumDisc;
static const QRegularExpression kTitleRemoveMisc; static const RegularExpressionList kRemastered;
static const RegularExpressionList kExplicit;
static const RegularExpressionList kAlbumMisc;
static const RegularExpressionList kTitleMisc;
static const QStringList kArticles; static const QStringList kArticles;
@ -332,6 +338,7 @@ class Song {
void clear_art_automatic(); void clear_art_automatic();
void clear_art_manual(); void clear_art_manual();
bool write_tags_supported() const;
bool additional_tags_supported() const; bool additional_tags_supported() const;
bool albumartist_supported() const; bool albumartist_supported() const;
bool composer_supported() const; bool composer_supported() const;
@ -346,6 +353,7 @@ class Song {
static bool save_embedded_cover_supported(const FileType filetype); static bool save_embedded_cover_supported(const FileType filetype);
bool save_embedded_cover_supported() const { return url().isLocalFile() && save_embedded_cover_supported(filetype()) && !has_cue(); }; bool save_embedded_cover_supported() const { return url().isLocalFile() && save_embedded_cover_supported(filetype()) && !has_cue(); };
static int ColumnIndex(const QString &field);
static QString JoinSpec(const QString &table); static QString JoinSpec(const QString &table);
// Pretty accessors // Pretty accessors
@ -412,7 +420,9 @@ class Song {
void Init(const QString &title, const QString &artist, const QString &album, const qint64 length_nanosec); void Init(const QString &title, const QString &artist, const QString &album, const qint64 length_nanosec);
void Init(const QString &title, const QString &artist, const QString &album, const qint64 beginning, const qint64 end); void Init(const QString &title, const QString &artist, const QString &album, const qint64 beginning, const qint64 end);
void InitFromProtobuf(const spb::tagreader::SongMetadata &pb); void InitFromProtobuf(const spb::tagreader::SongMetadata &pb);
void InitFromQuery(const SqlRow &query, const bool reliable_metadata); void InitFromQuery(const QSqlRecord &r, const bool reliable_metadata, const int col = 0);
void InitFromQuery(const SqlQuery &query, const bool reliable_metadata, const int col = 0);
void InitFromQuery(const SqlRow &row, const bool reliable_metadata, const int col = 0);
void InitFromFilePartial(const QString &filename, const QFileInfo &fileinfo); void InitFromFilePartial(const QString &filename, const QFileInfo &fileinfo);
void InitArtManual(); void InitArtManual();
void InitArtAutomatic(); void InitArtAutomatic();
@ -430,7 +440,9 @@ class Song {
// Save // Save
void BindToQuery(SqlQuery *query) const; void BindToQuery(SqlQuery *query) const;
void BindToFtsQuery(SqlQuery *query) const; void BindToFtsQuery(SqlQuery *query) const;
#ifdef HAVE_DBUS
void ToXesam(QVariantMap *map) const; void ToXesam(QVariantMap *map) const;
#endif
void ToProtobuf(spb::tagreader::SongMetadata *pb) const; void ToProtobuf(spb::tagreader::SongMetadata *pb) const;
bool MergeFromEngineMetadata(const EngineMetadata &engine_metadata); bool MergeFromEngineMetadata(const EngineMetadata &engine_metadata);
@ -443,6 +455,14 @@ class Song {
// It is more efficient to use IsOnSameAlbum, but this function can be used when you need to hash the key to do fast lookups. // It is more efficient to use IsOnSameAlbum, but this function can be used when you need to hash the key to do fast lookups.
QString AlbumKey() const; QString AlbumKey() const;
static bool ContainsRegexList(const QString &str, const RegularExpressionList &regex_list);
static QString StripRegexList(QString str, const RegularExpressionList &regex_list);
static bool AlbumContainsDisc(const QString &album);
static QString AlbumRemoveDisc(const QString &album);
static QString AlbumRemoveMisc(const QString &album);
static QString AlbumRemoveDiscMisc(const QString &album);
static QString TitleRemoveMisc(const QString &title);
private: private:
struct Private; struct Private;

View File

@ -82,15 +82,15 @@ SongLoader::SongLoader(SharedPtr<CollectionBackendInterface> collection_backend,
success_(false) { success_(false) {
if (sRawUriSchemes.isEmpty()) { if (sRawUriSchemes.isEmpty()) {
sRawUriSchemes << "udp" sRawUriSchemes << QStringLiteral("udp")
<< "mms" << QStringLiteral("mms")
<< "mmsh" << QStringLiteral("mmsh")
<< "mmst" << QStringLiteral("mmst")
<< "mmsu" << QStringLiteral("mmsu")
<< "rtsp" << QStringLiteral("rtsp")
<< "rtspu" << QStringLiteral("rtspu")
<< "rtspt" << QStringLiteral("rtspt")
<< "rtsph"; << QStringLiteral("rtsph");
} }
timeout_timer_->setSingleShot(true); timeout_timer_->setSingleShot(true);
@ -237,8 +237,8 @@ SongLoader::Result SongLoader::LoadLocal(const QString &filename) {
QSqlDatabase db(collection_backend_->db()->Connect()); QSqlDatabase db(collection_backend_->db()->Connect());
CollectionQuery query(db, collection_backend_->songs_table(), collection_backend_->fts_table()); CollectionQuery query(db, collection_backend_->songs_table(), collection_backend_->fts_table());
query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec); query.SetColumnSpec(QStringLiteral("%songs_table.ROWID, ") + Song::kColumnSpec);
query.AddWhere("url", url.toEncoded()); query.AddWhere(QStringLiteral("url"), url.toEncoded());
if (query.Exec() && query.Next()) { if (query.Exec() && query.Next()) {
// We may have many results when the file has many sections // We may have many results when the file has many sections
@ -302,7 +302,7 @@ SongLoader::Result SongLoader::LoadLocalAsync(const QString &filename) {
// It's a CUE - create virtual tracks // It's a CUE - create virtual tracks
QFile cue(matching_cue); QFile cue(matching_cue);
if (cue.open(QIODevice::ReadOnly)) { if (cue.open(QIODevice::ReadOnly)) {
const SongList songs = cue_parser_->Load(&cue, matching_cue, QDir(filename.section('/', 0, -2))); const SongList songs = cue_parser_->Load(&cue, matching_cue, QDir(filename.section(QLatin1Char('/'), 0, -2)));
cue.close(); cue.close();
for (const Song &song : songs) { for (const Song &song : songs) {
if (song.is_valid()) songs_ << song; if (song.is_valid()) songs_ << song;
@ -520,7 +520,7 @@ SongLoader::Result SongLoader::LoadRemote() {
} }
QEventLoop loop; QEventLoop loop;
loop.connect(this, &SongLoader::LoadRemoteFinished, &loop, &QEventLoop::quit); QObject::connect(this, &SongLoader::LoadRemoteFinished, &loop, &QEventLoop::quit);
// Start "playing" // Start "playing"
pipeline_ = pipeline; pipeline_ = pipeline;
@ -542,9 +542,9 @@ void SongLoader::TypeFound(GstElement*, uint, GstCaps *caps, void *self) {
if (instance->state_ != State::WaitingForType) return; if (instance->state_ != State::WaitingForType) return;
// Check the mimetype // Check the mimetype
instance->mime_type_ = gst_structure_get_name(gst_caps_get_structure(caps, 0)); instance->mime_type_ = QString::fromUtf8(gst_structure_get_name(gst_caps_get_structure(caps, 0)));
qLog(Debug) << "Mime type is" << instance->mime_type_; qLog(Debug) << "Mime type is" << instance->mime_type_;
if (instance->mime_type_ == "text/plain" || instance->mime_type_ == "text/uri-list") { if (instance->mime_type_ == QStringLiteral("text/plain") || instance->mime_type_ == QStringLiteral("text/uri-list")) {
// Yeah it might be a playlist, let's get some data and have a better look // Yeah it might be a playlist, let's get some data and have a better look
instance->state_ = State::WaitingForMagic; instance->state_ = State::WaitingForMagic;
return; return;
@ -634,12 +634,12 @@ void SongLoader::ErrorMessageReceived(GstMessage *msg) {
qLog(Error) << error->message; qLog(Error) << error->message;
qLog(Error) << debugs; qLog(Error) << debugs;
QString message_str = error->message; QString message_str = QString::fromUtf8(error->message);
g_error_free(error); g_error_free(error);
g_free(debugs); g_free(debugs);
if (state_ == State::WaitingForType && message_str == gst_error_get_message(GST_STREAM_ERROR, GST_STREAM_ERROR_TYPE_NOT_FOUND)) { if (state_ == State::WaitingForType && message_str == QString::fromUtf8(gst_error_get_message(GST_STREAM_ERROR, GST_STREAM_ERROR_TYPE_NOT_FOUND))) {
// Don't give up - assume it's a playlist and see if one of our parsers can read it. // Don't give up - assume it's a playlist and see if one of our parsers can read it.
state_ = State::WaitingForMagic; state_ = State::WaitingForMagic;
return; return;
@ -697,10 +697,10 @@ void SongLoader::MagicReady() {
qLog(Debug) << "Magic says" << parser_->name(); qLog(Debug) << "Magic says" << parser_->name();
if (parser_->name() == "ASX/INI" && url_.scheme() == "http") { if (parser_->name() == QStringLiteral("ASX/INI") && url_.scheme() == QStringLiteral("http")) {
// This is actually a weird MS-WMSP stream. Changing the protocol to MMS from HTTP makes it playable. // This is actually a weird MS-WMSP stream. Changing the protocol to MMS from HTTP makes it playable.
parser_ = nullptr; parser_ = nullptr;
url_.setScheme("mms"); url_.setScheme(QStringLiteral("mms"));
StopTypefindAsync(true); StopTypefindAsync(true);
} }

View File

@ -1,6 +1,6 @@
/* /*
* Strawberry Music Player * Strawberry Music Player
* Copyright 2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2021-2024, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -38,13 +38,13 @@ void SqlQuery::BindValue(const QString &placeholder, const QVariant &value) {
void SqlQuery::BindStringValue(const QString &placeholder, const QString &value) { void SqlQuery::BindStringValue(const QString &placeholder, const QString &value) {
BindValue(placeholder, value.isNull() ? "" : value); BindValue(placeholder, value.isNull() ? QLatin1String("") : value);
} }
void SqlQuery::BindUrlValue(const QString &placeholder, const QUrl &value) { void SqlQuery::BindUrlValue(const QString &placeholder, const QUrl &value) {
BindValue(placeholder, value.isValid() ? value.toString(QUrl::FullyEncoded) : ""); BindValue(placeholder, value.isValid() ? value.toString(QUrl::FullyEncoded) : QLatin1String(""));
} }

View File

@ -1,6 +1,6 @@
/* /*
* Strawberry Music Player * Strawberry Music Player
* Copyright 2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2021-2024, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -29,12 +29,15 @@
#include <QString> #include <QString>
#include <QSqlDatabase> #include <QSqlDatabase>
#include <QSqlQuery> #include <QSqlQuery>
#include <QSqlRecord>
class SqlQuery : public QSqlQuery { class SqlQuery : public QSqlQuery {
public: public:
explicit SqlQuery(const QSqlDatabase &db) : QSqlQuery(db) {} explicit SqlQuery(const QSqlDatabase &db) : QSqlQuery(db) {}
int columns() const { return QSqlQuery::record().count(); }
void BindValue(const QString &placeholder, const QVariant &value); void BindValue(const QString &placeholder, const QVariant &value);
void BindStringValue(const QString &placeholder, const QString &value); void BindStringValue(const QString &placeholder, const QString &value);
void BindUrlValue(const QString &placeholder, const QUrl &value); void BindUrlValue(const QString &placeholder, const QUrl &value);

View File

@ -1,6 +1,6 @@
/* /*
* Strawberry Music Player * Strawberry Music Player
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -20,74 +20,22 @@
#include "config.h" #include "config.h"
#include <QVariant> #include <QVariant>
#include <QString>
#include <QUrl>
#include <QSqlQuery>
#include <QSqlRecord> #include <QSqlRecord>
#include "sqlrow.h" #include "sqlrow.h"
SqlRow::SqlRow(const QSqlQuery &query) { Init(query); } SqlRow::SqlRow(const SqlQuery &query) { Init(query); }
void SqlRow::Init(const QSqlQuery &query) { void SqlRow::Init(const SqlQuery &query) {
const QSqlRecord r = query.record(); record_ = query.record();
for (int i = 0; i < r.count(); ++i) {
columns_by_number_.insert(i, query.value(i));
const QString field_name = r.fieldName(i);
if (!columns_by_name_.contains(field_name) || columns_by_name_[field_name].isNull()) {
columns_by_name_.insert(field_name, query.value(i));
}
}
} }
const QVariant SqlRow::value(const int number) const { const QVariant SqlRow::value(const int n) const {
if (columns_by_number_.contains(number)) { Q_ASSERT(n < record_.count());
return columns_by_number_[number];
} return record_.value(n);
else {
return QVariant();
}
} }
const QVariant SqlRow::value(const QString &name) const {
if (columns_by_name_.contains(name)) {
return columns_by_name_[name];
}
else {
return QVariant();
}
}
QString SqlRow::ValueToString(const QString &n) const {
return value(n).isNull() ? QString() : value(n).toString();
}
QUrl SqlRow::ValueToUrl(const QString &n) const {
return value(n).isNull() ? QUrl() : QUrl(value(n).toString());
}
int SqlRow::ValueToInt(const QString &n) const {
return value(n).isNull() ? -1 : value(n).toInt();
}
uint SqlRow::ValueToUInt(const QString &n) const {
return value(n).isNull() || value(n).toInt() < 0 ? 0 : value(n).toInt();
}
qint64 SqlRow::ValueToLongLong(const QString &n) const {
return value(n).isNull() ? -1 : value(n).toLongLong();
}
float SqlRow::ValueToFloat(const QString &n) const {
return value(n).isNull() ? -1.0F : value(n).toFloat();
}
bool SqlRow::ValueToBool(const QString &n) const {
return !value(n).isNull() && value(n).toInt() == 1;
}

View File

@ -1,6 +1,6 @@
/* /*
* Strawberry Music Player * Strawberry Music Player
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -24,33 +24,23 @@
#include <QList> #include <QList>
#include <QVariant> #include <QVariant>
#include <QUrl> #include <QSqlRecord>
#include <QSqlQuery>
#include "sqlquery.h"
class SqlRow { class SqlRow {
public: public:
SqlRow(const QSqlQuery &query); explicit SqlRow(const SqlQuery &query);
const QVariant value(const int number) const; int columns() const { return record_.count(); }
const QVariant value(const QString &name) const; const QSqlRecord &record() const { return record_; }
const QVariant value(const int n) const;
QString ValueToString(const QString &n) const;
QUrl ValueToUrl(const QString &n) const;
int ValueToInt(const QString &n) const;
uint ValueToUInt(const QString &n) const;
qint64 ValueToLongLong(const QString &n) const;
float ValueToFloat(const QString &n) const;
bool ValueToBool(const QString& n) const;
private: private:
SqlRow(); void Init(const SqlQuery &query);
void Init(const QSqlQuery &query);
QMap<int, QVariant> columns_by_number_;
QMap<QString, QVariant> columns_by_name_;
QSqlRecord record_;
}; };
using SqlRowList = QList<SqlRow>; using SqlRowList = QList<SqlRow>;

View File

@ -176,7 +176,8 @@ void StyleHelper::setBaseColor(const QColor &newcolor) {
if (color.isValid() && color != m_baseColor) { if (color.isValid() && color != m_baseColor) {
m_baseColor = color; m_baseColor = color;
for (QWidget *w : QApplication::topLevelWidgets()) { const QList<QWidget*> widgets = QApplication::topLevelWidgets();
for (QWidget *w : widgets) {
w->update(); w->update();
} }
} }

View File

@ -90,32 +90,32 @@ void StyleSheetLoader::UpdateStyleSheet(QWidget *widget, SharedPtr<StyleSheetDat
#else #else
alt.setAlpha(130); alt.setAlpha(130);
#endif #endif
stylesheet.replace("%palette-alternate-base", QString("rgba(%1,%2,%3,%4)").arg(alt.red()).arg(alt.green()).arg(alt.blue()).arg(alt.alpha())); stylesheet.replace(QLatin1String("%palette-alternate-base"), QStringLiteral("rgba(%1,%2,%3,%4)").arg(alt.red()).arg(alt.green()).arg(alt.blue()).arg(alt.alpha()));
} }
ReplaceColor(&stylesheet, "Window", p, QPalette::Window); ReplaceColor(&stylesheet, QStringLiteral("Window"), p, QPalette::Window);
ReplaceColor(&stylesheet, "Background", p, QPalette::Window); ReplaceColor(&stylesheet, QStringLiteral("Background"), p, QPalette::Window);
ReplaceColor(&stylesheet, "WindowText", p, QPalette::WindowText); ReplaceColor(&stylesheet, QStringLiteral("WindowText"), p, QPalette::WindowText);
ReplaceColor(&stylesheet, "Base", p, QPalette::Base); ReplaceColor(&stylesheet, QStringLiteral("Base"), p, QPalette::Base);
ReplaceColor(&stylesheet, "AlternateBase", p, QPalette::AlternateBase); ReplaceColor(&stylesheet, QStringLiteral("AlternateBase"), p, QPalette::AlternateBase);
ReplaceColor(&stylesheet, "ToolTipBase", p, QPalette::ToolTipBase); ReplaceColor(&stylesheet, QStringLiteral("ToolTipBase"), p, QPalette::ToolTipBase);
ReplaceColor(&stylesheet, "ToolTipText", p, QPalette::ToolTipText); ReplaceColor(&stylesheet, QStringLiteral("ToolTipText"), p, QPalette::ToolTipText);
ReplaceColor(&stylesheet, "Text", p, QPalette::Text); ReplaceColor(&stylesheet, QStringLiteral("Text"), p, QPalette::Text);
ReplaceColor(&stylesheet, "Button", p, QPalette::Button); ReplaceColor(&stylesheet, QStringLiteral("Button"), p, QPalette::Button);
ReplaceColor(&stylesheet, "ButtonText", p, QPalette::ButtonText); ReplaceColor(&stylesheet, QStringLiteral("ButtonText"), p, QPalette::ButtonText);
ReplaceColor(&stylesheet, "BrightText", p, QPalette::BrightText); ReplaceColor(&stylesheet, QStringLiteral("BrightText"), p, QPalette::BrightText);
ReplaceColor(&stylesheet, "Light", p, QPalette::Light); ReplaceColor(&stylesheet, QStringLiteral("Light"), p, QPalette::Light);
ReplaceColor(&stylesheet, "Midlight", p, QPalette::Midlight); ReplaceColor(&stylesheet, QStringLiteral("Midlight"), p, QPalette::Midlight);
ReplaceColor(&stylesheet, "Dark", p, QPalette::Dark); ReplaceColor(&stylesheet, QStringLiteral("Dark"), p, QPalette::Dark);
ReplaceColor(&stylesheet, "Mid", p, QPalette::Mid); ReplaceColor(&stylesheet, QStringLiteral("Mid"), p, QPalette::Mid);
ReplaceColor(&stylesheet, "Shadow", p, QPalette::Shadow); ReplaceColor(&stylesheet, QStringLiteral("Shadow"), p, QPalette::Shadow);
ReplaceColor(&stylesheet, "Highlight", p, QPalette::Highlight); ReplaceColor(&stylesheet, QStringLiteral("Highlight"), p, QPalette::Highlight);
ReplaceColor(&stylesheet, "HighlightedText", p, QPalette::HighlightedText); ReplaceColor(&stylesheet, QStringLiteral("HighlightedText"), p, QPalette::HighlightedText);
ReplaceColor(&stylesheet, "Link", p, QPalette::Link); ReplaceColor(&stylesheet, QStringLiteral("Link"), p, QPalette::Link);
ReplaceColor(&stylesheet, "LinkVisited", p, QPalette::LinkVisited); ReplaceColor(&stylesheet, QStringLiteral("LinkVisited"), p, QPalette::LinkVisited);
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
stylesheet.replace("macos", "*"); stylesheet.replace(QStringLiteral("macos"), QStringLiteral("*"));
#endif #endif
if (stylesheet != styledata->stylesheet_current_) { if (stylesheet != styledata->stylesheet_current_) {
@ -127,9 +127,9 @@ void StyleSheetLoader::UpdateStyleSheet(QWidget *widget, SharedPtr<StyleSheetDat
void StyleSheetLoader::ReplaceColor(QString *css, const QString &name, const QPalette &palette, const QPalette::ColorRole role) { void StyleSheetLoader::ReplaceColor(QString *css, const QString &name, const QPalette &palette, const QPalette::ColorRole role) {
css->replace("%palette-" + name + "-lighter", palette.color(role).lighter().name(), Qt::CaseInsensitive); css->replace(QStringLiteral("%palette-") + name + QStringLiteral("-lighter"), palette.color(role).lighter().name(), Qt::CaseInsensitive);
css->replace("%palette-" + name + "-darker", palette.color(role).darker().name(), Qt::CaseInsensitive); css->replace(QStringLiteral("%palette-") + name + QStringLiteral("-darker"), palette.color(role).darker().name(), Qt::CaseInsensitive);
css->replace("%palette-" + name, palette.color(role).name(), Qt::CaseInsensitive); css->replace(QStringLiteral("%palette-") + name, palette.color(role).name(), Qt::CaseInsensitive);
} }

View File

@ -29,7 +29,6 @@
#include <QByteArray> #include <QByteArray>
#include <QString> #include <QString>
#include <QImage> #include <QImage>
#include <QSettings>
#include "core/logging.h" #include "core/logging.h"
#include "core/workerpool.h" #include "core/workerpool.h"
@ -37,7 +36,10 @@
#include "song.h" #include "song.h"
#include "tagreaderclient.h" #include "tagreaderclient.h"
const char *TagReaderClient::kWorkerExecutableName = "strawberry-tagreader"; namespace {
constexpr char kWorkerExecutableName[] = "strawberry-tagreader";
}
TagReaderClient *TagReaderClient::sInstance = nullptr; TagReaderClient *TagReaderClient::sInstance = nullptr;
TagReaderClient::TagReaderClient(QObject *parent) : QObject(parent), worker_pool_(new WorkerPool<HandlerType>(this)) { TagReaderClient::TagReaderClient(QObject *parent) : QObject(parent), worker_pool_(new WorkerPool<HandlerType>(this)) {
@ -45,7 +47,7 @@ TagReaderClient::TagReaderClient(QObject *parent) : QObject(parent), worker_pool
sInstance = this; sInstance = this;
original_thread_ = thread(); original_thread_ = thread();
worker_pool_->SetExecutableName(kWorkerExecutableName); worker_pool_->SetExecutableName(QLatin1String(kWorkerExecutableName));
QObject::connect(worker_pool_, &WorkerPool<HandlerType>::WorkerFailedToStart, this, &TagReaderClient::WorkerFailedToStart); QObject::connect(worker_pool_, &WorkerPool<HandlerType>::WorkerFailedToStart, this, &TagReaderClient::WorkerFailedToStart);
} }

View File

@ -48,8 +48,6 @@ class TagReaderClient : public QObject {
using HandlerType = AbstractMessageHandler<spb::tagreader::Message>; using HandlerType = AbstractMessageHandler<spb::tagreader::Message>;
using ReplyType = HandlerType::ReplyType; using ReplyType = HandlerType::ReplyType;
static const char *kWorkerExecutableName;
void Start(); void Start();
void ExitAsync(); void ExitAsync();

View File

@ -46,9 +46,9 @@ ThreadSafeNetworkDiskCache::ThreadSafeNetworkDiskCache(QObject *parent) : QAbstr
if (!sCache) { if (!sCache) {
sCache = new QNetworkDiskCache; sCache = new QNetworkDiskCache;
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
sCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/strawberry/networkcache"); sCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QStringLiteral("/strawberry/networkcache"));
#else #else
sCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/networkcache"); sCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QStringLiteral("/networkcache"));
#endif #endif
} }

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