New context with albums and lyrics +++ much more
* Added new lyrics provider with lyrics from AudD and API Seeds * New improved context widget with albums and lyrics * Fixed playing and context widget getting stuck in play mode when there was an error * Changed icons for artists in collection, tidal and cover manager * Removed "search" icon from "Search automatically" checkbox (right click) that looked ugly * Removed some unused widgets from the src/widgets directory * Fixed initial size of window and side panel * Fixed saving window size correctly
11
Changelog
|
@ -2,6 +2,17 @@ Strawberry Music Player
|
||||||
=======================
|
=======================
|
||||||
ChangeLog
|
ChangeLog
|
||||||
|
|
||||||
|
Unreleased:
|
||||||
|
|
||||||
|
* Added new lyrics provider with lyrics from AudD and API Seeds
|
||||||
|
* New improved context widget with albums and lyrics
|
||||||
|
* Fixed playing and context widget getting stuck in play mode when there was an error
|
||||||
|
* Changed icons for artists in collection, tidal and cover manager
|
||||||
|
* Removed "search" icon from "Search automatically" checkbox (right click) that looked ugly
|
||||||
|
* Removed some unused widgets from the src/widgets directory
|
||||||
|
* Fixed initial size of window and side panel
|
||||||
|
* Fixed saving window size correctly
|
||||||
|
|
||||||
Version 0.2.1:
|
Version 0.2.1:
|
||||||
|
|
||||||
* Fixed crash with newer Qt
|
* Fixed crash with newer Qt
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
<file>schema/schema.sql</file>
|
<file>schema/schema.sql</file>
|
||||||
<file>schema/schema-1.sql</file>
|
<file>schema/schema-1.sql</file>
|
||||||
<file>schema/device-schema.sql</file>
|
<file>schema/device-schema.sql</file>
|
||||||
<file>style/mainwindow.css</file>
|
<file>style/strawberry.css</file>
|
||||||
<file>style/statusview.css</file>
|
|
||||||
<file>misc/playing_tooltip.txt</file>
|
<file>misc/playing_tooltip.txt</file>
|
||||||
<file>pictures/strawberry.png</file>
|
<file>pictures/strawberry.png</file>
|
||||||
<file>pictures/strawbs-transparent.png</file>
|
<file>pictures/strawbs-transparent.png</file>
|
||||||
|
@ -98,8 +97,6 @@
|
||||||
<file>icons/128x128/speaker.png</file>
|
<file>icons/128x128/speaker.png</file>
|
||||||
<file>icons/128x128/star-grey.png</file>
|
<file>icons/128x128/star-grey.png</file>
|
||||||
<file>icons/128x128/star.png</file>
|
<file>icons/128x128/star.png</file>
|
||||||
<file>icons/128x128/strawberry-panel-grey.png</file>
|
|
||||||
<file>icons/128x128/strawberry-panel.png</file>
|
|
||||||
<file>icons/128x128/strawberry.png</file>
|
<file>icons/128x128/strawberry.png</file>
|
||||||
<file>icons/128x128/strawberry.svg</file>
|
<file>icons/128x128/strawberry.svg</file>
|
||||||
<file>icons/128x128/tools-wizard.png</file>
|
<file>icons/128x128/tools-wizard.png</file>
|
||||||
|
@ -188,8 +185,6 @@
|
||||||
<file>icons/64x64/speaker.png</file>
|
<file>icons/64x64/speaker.png</file>
|
||||||
<file>icons/64x64/star-grey.png</file>
|
<file>icons/64x64/star-grey.png</file>
|
||||||
<file>icons/64x64/star.png</file>
|
<file>icons/64x64/star.png</file>
|
||||||
<file>icons/64x64/strawberry-panel-grey.png</file>
|
|
||||||
<file>icons/64x64/strawberry-panel.png</file>
|
|
||||||
<file>icons/64x64/strawberry.png</file>
|
<file>icons/64x64/strawberry.png</file>
|
||||||
<file>icons/64x64/tools-wizard.png</file>
|
<file>icons/64x64/tools-wizard.png</file>
|
||||||
<file>icons/64x64/view-choose.png</file>
|
<file>icons/64x64/view-choose.png</file>
|
||||||
|
@ -280,8 +275,6 @@
|
||||||
<file>icons/48x48/speaker.png</file>
|
<file>icons/48x48/speaker.png</file>
|
||||||
<file>icons/48x48/star-grey.png</file>
|
<file>icons/48x48/star-grey.png</file>
|
||||||
<file>icons/48x48/star.png</file>
|
<file>icons/48x48/star.png</file>
|
||||||
<file>icons/48x48/strawberry-panel-grey.png</file>
|
|
||||||
<file>icons/48x48/strawberry-panel.png</file>
|
|
||||||
<file>icons/48x48/strawberry.png</file>
|
<file>icons/48x48/strawberry.png</file>
|
||||||
<file>icons/48x48/tools-wizard.png</file>
|
<file>icons/48x48/tools-wizard.png</file>
|
||||||
<file>icons/48x48/view-choose.png</file>
|
<file>icons/48x48/view-choose.png</file>
|
||||||
|
@ -372,8 +365,6 @@
|
||||||
<file>icons/32x32/speaker.png</file>
|
<file>icons/32x32/speaker.png</file>
|
||||||
<file>icons/32x32/star-grey.png</file>
|
<file>icons/32x32/star-grey.png</file>
|
||||||
<file>icons/32x32/star.png</file>
|
<file>icons/32x32/star.png</file>
|
||||||
<file>icons/32x32/strawberry-panel-grey.png</file>
|
|
||||||
<file>icons/32x32/strawberry-panel.png</file>
|
|
||||||
<file>icons/32x32/strawberry.png</file>
|
<file>icons/32x32/strawberry.png</file>
|
||||||
<file>icons/32x32/strawberry.svg</file>
|
<file>icons/32x32/strawberry.svg</file>
|
||||||
<file>icons/32x32/tools-wizard.png</file>
|
<file>icons/32x32/tools-wizard.png</file>
|
||||||
|
@ -465,8 +456,6 @@
|
||||||
<file>icons/22x22/speaker.png</file>
|
<file>icons/22x22/speaker.png</file>
|
||||||
<file>icons/22x22/star-grey.png</file>
|
<file>icons/22x22/star-grey.png</file>
|
||||||
<file>icons/22x22/star.png</file>
|
<file>icons/22x22/star.png</file>
|
||||||
<file>icons/22x22/strawberry-panel-grey.png</file>
|
|
||||||
<file>icons/22x22/strawberry-panel.png</file>
|
|
||||||
<file>icons/22x22/strawberry.png</file>
|
<file>icons/22x22/strawberry.png</file>
|
||||||
<file>icons/22x22/strawberry.svg</file>
|
<file>icons/22x22/strawberry.svg</file>
|
||||||
<file>icons/22x22/tools-wizard.png</file>
|
<file>icons/22x22/tools-wizard.png</file>
|
||||||
|
@ -481,6 +470,7 @@
|
||||||
<file>icons/22x22/xine.png</file>
|
<file>icons/22x22/xine.png</file>
|
||||||
<file>icons/22x22/zoom-in.png</file>
|
<file>icons/22x22/zoom-in.png</file>
|
||||||
<file>icons/22x22/zoom-out.png</file>
|
<file>icons/22x22/zoom-out.png</file>
|
||||||
<file>icons/22x22/tidal.png</file>
|
<file>icons/22x22/tidal.png</file>
|
||||||
|
<file>fonts/HumongousofEternitySt.ttf</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
Before Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 363 KiB |
Before Width: | Height: | Size: 628 KiB |
|
@ -1,11 +0,0 @@
|
||||||
StatusView {
|
|
||||||
background: white;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
QVBoxLayout {
|
|
||||||
background: white;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
QScrollArea {
|
|
||||||
background: qpalette(base);
|
|
||||||
}
|
|
|
@ -52,3 +52,20 @@ darwin {
|
||||||
darwin QMenu {
|
darwin QMenu {
|
||||||
font-size: 13pt;
|
font-size: 13pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#scrollarea_play {
|
||||||
|
background-color: white;
|
||||||
|
font: 11pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scrollarea_stop {
|
||||||
|
background-color: white;
|
||||||
|
font: 11pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scrollAreaWidgetContents_stop {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
#scrollAreaWidgetContents_play {
|
||||||
|
background-color: white;
|
||||||
|
}
|
|
@ -107,7 +107,6 @@ set(SOURCES
|
||||||
core/urlhandler.cpp
|
core/urlhandler.cpp
|
||||||
core/utilities.cpp
|
core/utilities.cpp
|
||||||
core/scangiomodulepath.cpp
|
core/scangiomodulepath.cpp
|
||||||
core/flowlayout.cpp
|
|
||||||
core/iconloader.cpp
|
core/iconloader.cpp
|
||||||
core/qtsystemtrayicon.cpp
|
core/qtsystemtrayicon.cpp
|
||||||
core/standarditemiconloader.cpp
|
core/standarditemiconloader.cpp
|
||||||
|
@ -129,6 +128,10 @@ set(SOURCES
|
||||||
equalizer/equalizer.cpp
|
equalizer/equalizer.cpp
|
||||||
equalizer/equalizerslider.cpp
|
equalizer/equalizerslider.cpp
|
||||||
|
|
||||||
|
context/contextview.cpp
|
||||||
|
context/contextalbumsmodel.cpp
|
||||||
|
context/contextalbumsview.cpp
|
||||||
|
|
||||||
collection/collection.cpp
|
collection/collection.cpp
|
||||||
collection/collectionmodel.cpp
|
collection/collectionmodel.cpp
|
||||||
collection/collectionbackend.cpp
|
collection/collectionbackend.cpp
|
||||||
|
@ -218,8 +221,6 @@ set(SOURCES
|
||||||
widgets/autoexpandingtreeview.cpp
|
widgets/autoexpandingtreeview.cpp
|
||||||
widgets/busyindicator.cpp
|
widgets/busyindicator.cpp
|
||||||
widgets/clickablelabel.cpp
|
widgets/clickablelabel.cpp
|
||||||
widgets/didyoumean.cpp
|
|
||||||
widgets/elidedlabel.cpp
|
|
||||||
widgets/fancytabwidget.cpp
|
widgets/fancytabwidget.cpp
|
||||||
widgets/favoritewidget.cpp
|
widgets/favoritewidget.cpp
|
||||||
widgets/fileview.cpp
|
widgets/fileview.cpp
|
||||||
|
@ -230,14 +231,9 @@ set(SOURCES
|
||||||
widgets/lineedit.cpp
|
widgets/lineedit.cpp
|
||||||
widgets/linetextedit.cpp
|
widgets/linetextedit.cpp
|
||||||
widgets/multiloadingindicator.cpp
|
widgets/multiloadingindicator.cpp
|
||||||
widgets/statusview.cpp
|
|
||||||
widgets/playingwidget.cpp
|
widgets/playingwidget.cpp
|
||||||
widgets/osd.cpp
|
widgets/osd.cpp
|
||||||
widgets/osdpretty.cpp
|
widgets/osdpretty.cpp
|
||||||
widgets/prettyimage.cpp
|
|
||||||
widgets/prettyimageview.cpp
|
|
||||||
widgets/progressitemdelegate.cpp
|
|
||||||
widgets/ratingwidget.cpp
|
|
||||||
widgets/renametablineedit.cpp
|
widgets/renametablineedit.cpp
|
||||||
widgets/sliderwidget.cpp
|
widgets/sliderwidget.cpp
|
||||||
widgets/stickyslider.cpp
|
widgets/stickyslider.cpp
|
||||||
|
@ -246,7 +242,6 @@ set(SOURCES
|
||||||
widgets/trackslider.cpp
|
widgets/trackslider.cpp
|
||||||
widgets/tracksliderpopup.cpp
|
widgets/tracksliderpopup.cpp
|
||||||
widgets/tracksliderslider.cpp
|
widgets/tracksliderslider.cpp
|
||||||
widgets/widgetfadehelper.cpp
|
|
||||||
widgets/loginstatewidget.cpp
|
widgets/loginstatewidget.cpp
|
||||||
|
|
||||||
musicbrainz/acoustidclient.cpp
|
musicbrainz/acoustidclient.cpp
|
||||||
|
@ -271,6 +266,7 @@ set(SOURCES
|
||||||
internet/internetmodel.cpp
|
internet/internetmodel.cpp
|
||||||
internet/internetservice.cpp
|
internet/internetservice.cpp
|
||||||
internet/internetplaylistitem.cpp
|
internet/internetplaylistitem.cpp
|
||||||
|
|
||||||
tidal/tidalservice.cpp
|
tidal/tidalservice.cpp
|
||||||
tidal/tidalsearch.cpp
|
tidal/tidalsearch.cpp
|
||||||
tidal/tidalsearchview.cpp
|
tidal/tidalsearchview.cpp
|
||||||
|
@ -278,6 +274,13 @@ set(SOURCES
|
||||||
tidal/tidalsearchsortmodel.cpp
|
tidal/tidalsearchsortmodel.cpp
|
||||||
tidal/tidalsearchitemdelegate.cpp
|
tidal/tidalsearchitemdelegate.cpp
|
||||||
|
|
||||||
|
lyrics/lyricsproviders.cpp
|
||||||
|
lyrics/lyricsprovider.cpp
|
||||||
|
lyrics/lyricsfetcher.cpp
|
||||||
|
lyrics/lyricsfetchersearch.cpp
|
||||||
|
lyrics/auddlyricsprovider.cpp
|
||||||
|
lyrics/apiseedslyricsprovider.cpp
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set(HEADERS
|
set(HEADERS
|
||||||
|
@ -309,6 +312,10 @@ set(HEADERS
|
||||||
|
|
||||||
equalizer/equalizer.h
|
equalizer/equalizer.h
|
||||||
equalizer/equalizerslider.h
|
equalizer/equalizerslider.h
|
||||||
|
|
||||||
|
context/contextview.h
|
||||||
|
context/contextalbumsmodel.h
|
||||||
|
context/contextalbumsview.h
|
||||||
|
|
||||||
collection/collection.h
|
collection/collection.h
|
||||||
collection/collectionmodel.h
|
collection/collectionmodel.h
|
||||||
|
@ -391,8 +398,6 @@ set(HEADERS
|
||||||
widgets/autoexpandingtreeview.h
|
widgets/autoexpandingtreeview.h
|
||||||
widgets/busyindicator.h
|
widgets/busyindicator.h
|
||||||
widgets/clickablelabel.h
|
widgets/clickablelabel.h
|
||||||
widgets/didyoumean.h
|
|
||||||
widgets/elidedlabel.h
|
|
||||||
widgets/fancytabwidget.h
|
widgets/fancytabwidget.h
|
||||||
widgets/favoritewidget.h
|
widgets/favoritewidget.h
|
||||||
widgets/fileview.h
|
widgets/fileview.h
|
||||||
|
@ -402,14 +407,9 @@ set(HEADERS
|
||||||
widgets/lineedit.h
|
widgets/lineedit.h
|
||||||
widgets/linetextedit.h
|
widgets/linetextedit.h
|
||||||
widgets/multiloadingindicator.h
|
widgets/multiloadingindicator.h
|
||||||
widgets/statusview.h
|
|
||||||
widgets/playingwidget.h
|
widgets/playingwidget.h
|
||||||
widgets/osd.h
|
widgets/osd.h
|
||||||
widgets/osdpretty.h
|
widgets/osdpretty.h
|
||||||
widgets/prettyimage.h
|
|
||||||
widgets/prettyimageview.h
|
|
||||||
widgets/progressitemdelegate.h
|
|
||||||
widgets/ratingwidget.h
|
|
||||||
widgets/renametablineedit.h
|
widgets/renametablineedit.h
|
||||||
widgets/sliderwidget.h
|
widgets/sliderwidget.h
|
||||||
widgets/stickyslider.h
|
widgets/stickyslider.h
|
||||||
|
@ -417,7 +417,6 @@ set(HEADERS
|
||||||
widgets/trackslider.h
|
widgets/trackslider.h
|
||||||
widgets/tracksliderpopup.h
|
widgets/tracksliderpopup.h
|
||||||
widgets/tracksliderslider.h
|
widgets/tracksliderslider.h
|
||||||
widgets/widgetfadehelper.h
|
|
||||||
widgets/loginstatewidget.h
|
widgets/loginstatewidget.h
|
||||||
|
|
||||||
musicbrainz/acoustidclient.h
|
musicbrainz/acoustidclient.h
|
||||||
|
@ -447,6 +446,13 @@ set(HEADERS
|
||||||
tidal/tidalsearch.h
|
tidal/tidalsearch.h
|
||||||
tidal/tidalsearchview.h
|
tidal/tidalsearchview.h
|
||||||
tidal/tidalsearchmodel.h
|
tidal/tidalsearchmodel.h
|
||||||
|
|
||||||
|
lyrics/lyricsproviders.h
|
||||||
|
lyrics/lyricsprovider.h
|
||||||
|
lyrics/lyricsfetcher.h
|
||||||
|
lyrics/lyricsfetchersearch.h
|
||||||
|
lyrics/auddlyricsprovider.h
|
||||||
|
lyrics/apiseedslyricsprovider.h
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -454,6 +460,8 @@ set(UI
|
||||||
|
|
||||||
core/mainwindow.ui
|
core/mainwindow.ui
|
||||||
|
|
||||||
|
context/contextviewcontainer.ui
|
||||||
|
|
||||||
collection/groupbydialog.ui
|
collection/groupbydialog.ui
|
||||||
collection/collectionfilterwidget.ui
|
collection/collectionfilterwidget.ui
|
||||||
collection/collectionviewcontainer.ui
|
collection/collectionviewcontainer.ui
|
||||||
|
|
|
@ -57,7 +57,6 @@ SCollection::SCollection(Application *app, QObject *parent)
|
||||||
backend_->Init(app->database(), kSongsTable, kDirsTable, kSubdirsTable, kFtsTable);
|
backend_->Init(app->database(), kSongsTable, kDirsTable, kSubdirsTable, kFtsTable);
|
||||||
|
|
||||||
model_ = new CollectionModel(backend_, app_, this);
|
model_ = new CollectionModel(backend_, app_, this);
|
||||||
|
|
||||||
|
|
||||||
ReloadSettings();
|
ReloadSettings();
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,6 @@ CollectionBackend::CollectionBackend(QObject *parent)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void CollectionBackend::Init(Database *db, const QString &songs_table, const QString &dirs_table, const QString &subdirs_table, const QString &fts_table) {
|
void CollectionBackend::Init(Database *db, const QString &songs_table, const QString &dirs_table, const QString &subdirs_table, const QString &fts_table) {
|
||||||
|
|
||||||
db_ = db;
|
db_ = db;
|
||||||
songs_table_ = songs_table;
|
songs_table_ = songs_table;
|
||||||
dirs_table_ = dirs_table;
|
dirs_table_ = dirs_table;
|
||||||
|
@ -219,8 +218,6 @@ void CollectionBackend::UpdateTotalArtistCount() {
|
||||||
q.exec();
|
q.exec();
|
||||||
if (db_->CheckErrors(q)) return;
|
if (db_->CheckErrors(q)) return;
|
||||||
if (!q.next()) return;
|
if (!q.next()) return;
|
||||||
|
|
||||||
//qLog(Debug) << "TotalArtist: " << q.value(0).toInt();
|
|
||||||
|
|
||||||
emit TotalArtistCountUpdated(q.value(0).toInt());
|
emit TotalArtistCountUpdated(q.value(0).toInt());
|
||||||
|
|
||||||
|
@ -236,8 +233,6 @@ void CollectionBackend::UpdateTotalAlbumCount() {
|
||||||
q.exec();
|
q.exec();
|
||||||
if (db_->CheckErrors(q)) return;
|
if (db_->CheckErrors(q)) return;
|
||||||
if (!q.next()) return;
|
if (!q.next()) return;
|
||||||
|
|
||||||
//qLog(Debug) << "TotalAlbum: " << q.value(0).toInt();
|
|
||||||
|
|
||||||
emit TotalAlbumCountUpdated(q.value(0).toInt());
|
emit TotalAlbumCountUpdated(q.value(0).toInt());
|
||||||
|
|
||||||
|
@ -530,7 +525,7 @@ void CollectionBackend::MarkSongsUnavailable(const SongList &songs, bool unavail
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList CollectionBackend::GetAll(const QString &column, const QueryOptions &opt) {
|
QStringList CollectionBackend::GetAll(const QString &column, const QueryOptions &opt) {
|
||||||
|
|
||||||
CollectionQuery query(opt);
|
CollectionQuery query(opt);
|
||||||
query.SetColumnSpec("DISTINCT " + column);
|
query.SetColumnSpec("DISTINCT " + column);
|
||||||
query.AddCompilationRequirement(false);
|
query.AddCompilationRequirement(false);
|
||||||
|
@ -547,6 +542,7 @@ QStringList CollectionBackend::GetAll(const QString &column, const QueryOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList CollectionBackend::GetAllArtists(const QueryOptions &opt) {
|
QStringList CollectionBackend::GetAllArtists(const QueryOptions &opt) {
|
||||||
|
|
||||||
return GetAll("artist", opt);
|
return GetAll("artist", opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -596,8 +592,7 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbumsByArtist(const QString
|
||||||
return GetAlbums(artist, QString(), false, opt);
|
return GetAlbums(artist, QString(), false, opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionBackend::AlbumList CollectionBackend::GetAlbumsByAlbumArtist(
|
CollectionBackend::AlbumList CollectionBackend::GetAlbumsByAlbumArtist(const QString &album_artist, const QueryOptions &opt) {
|
||||||
const QString &album_artist, const QueryOptions &opt) {
|
|
||||||
return GetAlbums(QString(), album_artist, false, opt);
|
return GetAlbums(QString(), album_artist, false, opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -629,6 +624,7 @@ SongList CollectionBackend::ExecCollectionQuery(CollectionQuery *query) {
|
||||||
ret << song;
|
ret << song;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Song CollectionBackend::GetSongById(int id) {
|
Song CollectionBackend::GetSongById(int id) {
|
||||||
|
@ -638,7 +634,6 @@ Song CollectionBackend::GetSongById(int id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
SongList CollectionBackend::GetSongsById(const QList<int> &ids) {
|
SongList CollectionBackend::GetSongsById(const QList<int> &ids) {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
|
@ -658,7 +653,6 @@ SongList CollectionBackend::GetSongsById(const QStringList &ids) {
|
||||||
}
|
}
|
||||||
|
|
||||||
SongList CollectionBackend::GetSongsByForeignId(const QStringList &ids, const QString &table, const QString &column) {
|
SongList CollectionBackend::GetSongsByForeignId(const QStringList &ids, const QString &table, const QString &column) {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
|
@ -687,7 +681,6 @@ Song CollectionBackend::GetSongById(int id, QSqlDatabase &db) {
|
||||||
}
|
}
|
||||||
|
|
||||||
SongList CollectionBackend::GetSongsById(const QStringList &ids, QSqlDatabase &db) {
|
SongList CollectionBackend::GetSongsById(const QStringList &ids, QSqlDatabase &db) {
|
||||||
|
|
||||||
QString in = ids.join(",");
|
QString in = ids.join(",");
|
||||||
|
|
||||||
QSqlQuery q(db);
|
QSqlQuery q(db);
|
||||||
|
@ -705,7 +698,6 @@ SongList CollectionBackend::GetSongsById(const QStringList &ids, QSqlDatabase &d
|
||||||
}
|
}
|
||||||
|
|
||||||
Song CollectionBackend::GetSongByUrl(const QUrl &url, qint64 beginning) {
|
Song CollectionBackend::GetSongByUrl(const QUrl &url, qint64 beginning) {
|
||||||
|
|
||||||
CollectionQuery query;
|
CollectionQuery query;
|
||||||
query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
|
query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
|
||||||
query.AddWhere("filename", url.toEncoded());
|
query.AddWhere("filename", url.toEncoded());
|
||||||
|
@ -719,7 +711,6 @@ Song CollectionBackend::GetSongByUrl(const QUrl &url, qint64 beginning) {
|
||||||
}
|
}
|
||||||
|
|
||||||
SongList CollectionBackend::GetSongsByUrl(const QUrl &url) {
|
SongList CollectionBackend::GetSongsByUrl(const QUrl &url) {
|
||||||
|
|
||||||
CollectionQuery query;
|
CollectionQuery query;
|
||||||
query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
|
query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
|
||||||
query.AddWhere("filename", url.toEncoded());
|
query.AddWhere("filename", url.toEncoded());
|
||||||
|
@ -757,6 +748,7 @@ SongList CollectionBackend::GetCompilationSongs(const QString &album, const Quer
|
||||||
ret << song;
|
ret << song;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::UpdateCompilations() {
|
void CollectionBackend::UpdateCompilations() {
|
||||||
|
@ -933,7 +925,7 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &artist, c
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::UpdateManualAlbumArtAsync(const QString &artist, const QString &albumartist, const QString &album, const QString &art) {
|
void CollectionBackend::UpdateManualAlbumArtAsync(const QString &artist, const QString &albumartist, const QString &album, const QString &art) {
|
||||||
|
|
||||||
metaObject()->invokeMethod(this, "UpdateManualAlbumArt", Qt::QueuedConnection, Q_ARG(QString, artist), Q_ARG(QString, albumartist), Q_ARG(QString, album), Q_ARG(QString, art));
|
metaObject()->invokeMethod(this, "UpdateManualAlbumArt", Qt::QueuedConnection, Q_ARG(QString, artist), Q_ARG(QString, albumartist), Q_ARG(QString, album), Q_ARG(QString, art));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,8 @@
|
||||||
#include "collectionbackend.h"
|
#include "collectionbackend.h"
|
||||||
#include "collectiondirectorymodel.h"
|
#include "collectiondirectorymodel.h"
|
||||||
|
|
||||||
|
using std::shared_ptr;
|
||||||
|
|
||||||
CollectionDirectoryModel::CollectionDirectoryModel(CollectionBackend *backend, QObject *parent)
|
CollectionDirectoryModel::CollectionDirectoryModel(CollectionBackend *backend, QObject *parent)
|
||||||
: QStandardItemModel(parent),
|
: QStandardItemModel(parent),
|
||||||
dir_icon_(IconLoader::Load("document-open-folder")),
|
dir_icon_(IconLoader::Load("document-open-folder")),
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
|
|
||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
|
#include "core/logging.h"
|
||||||
#include "collectionmodel.h"
|
#include "collectionmodel.h"
|
||||||
#include "collectionquery.h"
|
#include "collectionquery.h"
|
||||||
#include "savedgroupingmanager.h"
|
#include "savedgroupingmanager.h"
|
||||||
|
@ -58,6 +59,7 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
||||||
filter_delay_(new QTimer(this)),
|
filter_delay_(new QTimer(this)),
|
||||||
filter_applies_to_model_(true),
|
filter_applies_to_model_(true),
|
||||||
delay_behaviour_(DelayedOnLargeLibraries) {
|
delay_behaviour_(DelayedOnLargeLibraries) {
|
||||||
|
|
||||||
ui_->setupUi(this);
|
ui_->setupUi(this);
|
||||||
|
|
||||||
// Add the available fields to the tooltip here instead of the ui file to prevent that they get translated by mistake.
|
// Add the available fields to the tooltip here instead of the ui file to prevent that they get translated by mistake.
|
||||||
|
|
|
@ -85,8 +85,8 @@ CollectionModel::CollectionModel(CollectionBackend *backend, Application *app, Q
|
||||||
total_song_count_(0),
|
total_song_count_(0),
|
||||||
total_artist_count_(0),
|
total_artist_count_(0),
|
||||||
total_album_count_(0),
|
total_album_count_(0),
|
||||||
artist_icon_(IconLoader::Load("guitar")),
|
artist_icon_(IconLoader::Load("folder-sound")),
|
||||||
album_icon_(IconLoader::Load("cd")),
|
album_icon_(IconLoader::Load("cdcase")),
|
||||||
playlists_dir_icon_(IconLoader::Load("folder-sound")),
|
playlists_dir_icon_(IconLoader::Load("folder-sound")),
|
||||||
playlist_icon_(IconLoader::Load("albums")),
|
playlist_icon_(IconLoader::Load("albums")),
|
||||||
init_task_id_(-1),
|
init_task_id_(-1),
|
||||||
|
@ -109,10 +109,9 @@ CollectionModel::CollectionModel(CollectionBackend *backend, Application *app, Q
|
||||||
//icon_cache_->setCacheDirectory(Utilities::GetConfigPath(Utilities::Path_CacheRoot) + "/pixmapcache");
|
//icon_cache_->setCacheDirectory(Utilities::GetConfigPath(Utilities::Path_CacheRoot) + "/pixmapcache");
|
||||||
//icon_cache_->setMaximumCacheSize(CollectionModel::kIconCacheSize);
|
//icon_cache_->setMaximumCacheSize(CollectionModel::kIconCacheSize);
|
||||||
|
|
||||||
//QIcon nocover = IconLoader::Load("nocover");
|
QIcon nocover = IconLoader::Load("cdcase");
|
||||||
//QIcon nocover(":/pictures/noalbumart.png");
|
no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||||
//no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
//no_cover_icon_ = QPixmap(":/pictures/noalbumart.png").scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||||
no_cover_icon_ = QPixmap(":/pictures/noalbumart.png").scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
||||||
|
|
||||||
connect(backend_, SIGNAL(SongsDiscovered(SongList)), SLOT(SongsDiscovered(SongList)));
|
connect(backend_, SIGNAL(SongsDiscovered(SongList)), SLOT(SongsDiscovered(SongList)));
|
||||||
connect(backend_, SIGNAL(SongsDeleted(SongList)), SLOT(SongsDeleted(SongList)));
|
connect(backend_, SIGNAL(SongsDeleted(SongList)), SLOT(SongsDeleted(SongList)));
|
||||||
|
@ -159,7 +158,6 @@ void CollectionModel::SaveGrouping(QString name) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void CollectionModel::Init(bool async) {
|
void CollectionModel::Init(bool async) {
|
||||||
|
|
||||||
if (async) {
|
if (async) {
|
||||||
|
@ -717,6 +715,7 @@ CollectionModel::QueryResult CollectionModel::RunQuery(CollectionItem *parent) {
|
||||||
|
|
||||||
// Execute the query
|
// Execute the query
|
||||||
QMutexLocker l(backend_->db()->Mutex());
|
QMutexLocker l(backend_->db()->Mutex());
|
||||||
|
|
||||||
if (!backend_->ExecQuery(&q)) return result;
|
if (!backend_->ExecQuery(&q)) return result;
|
||||||
|
|
||||||
while (q.Next()) {
|
while (q.Next()) {
|
||||||
|
@ -751,7 +750,6 @@ void CollectionModel::PostQuery(CollectionItem *parent, const CollectionModel::Q
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionModel::LazyPopulate(CollectionItem *parent, bool signal) {
|
void CollectionModel::LazyPopulate(CollectionItem *parent, bool signal) {
|
||||||
|
|
||||||
if (parent->lazy_loaded) return;
|
if (parent->lazy_loaded) return;
|
||||||
parent->lazy_loaded = true;
|
parent->lazy_loaded = true;
|
||||||
|
|
||||||
|
@ -761,7 +759,6 @@ void CollectionModel::LazyPopulate(CollectionItem *parent, bool signal) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionModel::ResetAsync() {
|
void CollectionModel::ResetAsync() {
|
||||||
|
|
||||||
QFuture<CollectionModel::QueryResult> future = QtConcurrent::run(this, &CollectionModel::RunQuery, root_);
|
QFuture<CollectionModel::QueryResult> future = QtConcurrent::run(this, &CollectionModel::RunQuery, root_);
|
||||||
NewClosure(future, this, SLOT(ResetAsyncQueryFinished(QFuture<CollectionModel::QueryResult>)), future);
|
NewClosure(future, this, SLOT(ResetAsyncQueryFinished(QFuture<CollectionModel::QueryResult>)), future);
|
||||||
|
|
||||||
|
@ -1033,13 +1030,13 @@ CollectionItem *CollectionModel::ItemFromQuery(GroupBy type, bool signal, bool c
|
||||||
item->key = QString::number(bitrate);
|
item->key = QString::number(bitrate);
|
||||||
item->sort_text = SortTextForNumber(bitrate) + " ";
|
item->sort_text = SortTextForNumber(bitrate) + " ";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GroupBy_Samplerate:
|
case GroupBy_Samplerate:
|
||||||
samplerate = qMax(0, row.value(0).toInt());
|
samplerate = qMax(0, row.value(0).toInt());
|
||||||
item->key = QString::number(samplerate);
|
item->key = QString::number(samplerate);
|
||||||
item->sort_text = SortTextForNumber(samplerate) + " ";
|
item->sort_text = SortTextForNumber(samplerate) + " ";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GroupBy_Bitdepth:
|
case GroupBy_Bitdepth:
|
||||||
bitdepth = qMax(0, row.value(0).toInt());
|
bitdepth = qMax(0, row.value(0).toInt());
|
||||||
item->key = QString::number(bitdepth);
|
item->key = QString::number(bitdepth);
|
||||||
|
|
|
@ -31,11 +31,12 @@
|
||||||
#include <QSqlQuery>
|
#include <QSqlQuery>
|
||||||
|
|
||||||
#include "collectionquery.h"
|
#include "collectionquery.h"
|
||||||
|
#include "core/logging.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
|
|
||||||
QueryOptions::QueryOptions() : max_age_(-1), query_mode_(QueryMode_All) {}
|
QueryOptions::QueryOptions() : max_age_(-1), query_mode_(QueryMode_All) {}
|
||||||
|
|
||||||
CollectionQuery::CollectionQuery(const QueryOptions& options)
|
CollectionQuery::CollectionQuery(const QueryOptions &options)
|
||||||
: include_unavailable_(false), join_with_fts_(false), limit_(-1) {
|
: include_unavailable_(false), join_with_fts_(false), limit_(-1) {
|
||||||
|
|
||||||
if (!options.filter().isEmpty()) {
|
if (!options.filter().isEmpty()) {
|
||||||
|
|
|
@ -298,7 +298,7 @@ bool CollectionView::RestoreLevelFocus(const QModelIndex &parent) {
|
||||||
if (!last_selected_song_.url().isEmpty()) {
|
if (!last_selected_song_.url().isEmpty()) {
|
||||||
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);
|
||||||
for (const Song& song : songs) {
|
for (const Song &song : songs) {
|
||||||
if (song == last_selected_song_) {
|
if (song == last_selected_song_) {
|
||||||
setCurrentIndex(current);
|
setCurrentIndex(current);
|
||||||
return true;
|
return true;
|
||||||
|
@ -338,9 +338,9 @@ void CollectionView::ReloadSettings() {
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
|
|
||||||
settings.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
settings.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
||||||
SetAutoOpen(settings.value("auto_open", true).toBool());
|
SetAutoOpen(settings.value("auto_open", false).toBool());
|
||||||
|
|
||||||
if (app_ != nullptr) {
|
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());
|
||||||
}
|
}
|
||||||
|
@ -437,7 +437,7 @@ void CollectionView::paintEvent(QPaintEvent *event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionView::mouseReleaseEvent(QMouseEvent *e) {
|
void CollectionView::mouseReleaseEvent(QMouseEvent *e) {
|
||||||
|
|
||||||
QTreeView::mouseReleaseEvent(e);
|
QTreeView::mouseReleaseEvent(e);
|
||||||
|
|
||||||
if (total_song_count_ == 0) {
|
if (total_song_count_ == 0) {
|
||||||
|
@ -494,7 +494,7 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
||||||
int regular_elements = 0;
|
int regular_elements = 0;
|
||||||
int regular_editable = 0;
|
int regular_editable = 0;
|
||||||
|
|
||||||
for (const QModelIndex& index : selected_indexes) {
|
for (const QModelIndex &index : selected_indexes) {
|
||||||
regular_elements++;
|
regular_elements++;
|
||||||
if(app_->collection_model()->data(index, CollectionModel::Role_Editable).toBool()) {
|
if(app_->collection_model()->data(index, CollectionModel::Role_Editable).toBool()) {
|
||||||
regular_editable++;
|
regular_editable++;
|
||||||
|
@ -559,8 +559,7 @@ void CollectionView::ShowInVarious(bool on) {
|
||||||
QList<Song> all_of_album = app_->collection_backend()->GetSongsByAlbum(album);
|
QList<Song> 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()) &&
|
if (!albums.contains(album, s.artist()) && !other_artists.contains(s.artist())) {
|
||||||
!other_artists.contains(s.artist())) {
|
|
||||||
other_artists.insert(s.artist());
|
other_artists.insert(s.artist());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -586,7 +585,7 @@ void CollectionView::ShowInVarious(bool on) {
|
||||||
void CollectionView::Load() {
|
void CollectionView::Load() {
|
||||||
|
|
||||||
QMimeData *data = model()->mimeData(selectedIndexes());
|
QMimeData *data = model()->mimeData(selectedIndexes());
|
||||||
if (MimeData* mime_data = qobject_cast<MimeData*>(data)) {
|
if (MimeData *mime_data = qobject_cast<MimeData*>(data)) {
|
||||||
mime_data->clear_first_ = true;
|
mime_data->clear_first_ = true;
|
||||||
}
|
}
|
||||||
emit AddToPlaylistSignal(data);
|
emit AddToPlaylistSignal(data);
|
||||||
|
|
|
@ -43,11 +43,6 @@ CollectionViewContainer::CollectionViewContainer(QWidget *parent) : QWidget(pare
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionViewContainer::~CollectionViewContainer() { delete ui_; }
|
CollectionViewContainer::~CollectionViewContainer() { delete ui_; }
|
||||||
|
|
||||||
CollectionView *CollectionViewContainer::view() const { return ui_->view; }
|
CollectionView *CollectionViewContainer::view() const { return ui_->view; }
|
||||||
|
CollectionFilterWidget *CollectionViewContainer::filter() const { return ui_->filter; }
|
||||||
CollectionFilterWidget *CollectionViewContainer::filter() const {
|
|
||||||
return ui_->filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CollectionViewContainer::ReloadSettings() { view()->ReloadSettings(); }
|
void CollectionViewContainer::ReloadSettings() { view()->ReloadSettings(); }
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>400</width>
|
<width>300</width>
|
||||||
<height>300</height>
|
<height>300</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
|
|
|
@ -0,0 +1,527 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* This code was part of Clementine.
|
||||||
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
|
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QtAlgorithms>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QMimeData>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QList>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QRegExp>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QPixmapCache>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QtDebug>
|
||||||
|
|
||||||
|
#include "core/application.h"
|
||||||
|
#include "core/closure.h"
|
||||||
|
#include "core/database.h"
|
||||||
|
#include "core/iconloader.h"
|
||||||
|
#include "core/logging.h"
|
||||||
|
#include "collection/collectionquery.h"
|
||||||
|
#include "collection/collectionbackend.h"
|
||||||
|
#include "collection/collectionitem.h"
|
||||||
|
#include "collection/sqlrow.h"
|
||||||
|
#include "playlist/playlistmanager.h"
|
||||||
|
#include "playlist/songmimedata.h"
|
||||||
|
#include "covermanager/albumcoverloader.h"
|
||||||
|
|
||||||
|
#include "contextalbumsmodel.h"
|
||||||
|
|
||||||
|
using std::placeholders::_1;
|
||||||
|
using std::placeholders::_2;
|
||||||
|
|
||||||
|
const int ContextAlbumsModel::kPrettyCoverSize = 32;
|
||||||
|
const qint64 ContextAlbumsModel::kIconCacheSize = 100000000; //~100MB
|
||||||
|
|
||||||
|
ContextAlbumsModel::ContextAlbumsModel(CollectionBackend *backend, Application *app, QObject *parent) :
|
||||||
|
SimpleTreeModel<CollectionItem>(new CollectionItem(this), parent),
|
||||||
|
backend_(backend),
|
||||||
|
app_(app),
|
||||||
|
artist_icon_(IconLoader::Load("folder-sound")),
|
||||||
|
album_icon_(IconLoader::Load("cdcase")),
|
||||||
|
playlists_dir_icon_(IconLoader::Load("folder-sound")),
|
||||||
|
playlist_icon_(IconLoader::Load("albums")),
|
||||||
|
use_pretty_covers_(true)
|
||||||
|
{
|
||||||
|
|
||||||
|
root_->lazy_loaded = true;
|
||||||
|
|
||||||
|
cover_loader_options_.desired_height_ = kPrettyCoverSize;
|
||||||
|
cover_loader_options_.pad_output_image_ = true;
|
||||||
|
cover_loader_options_.scale_output_image_ = true;
|
||||||
|
|
||||||
|
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(AlbumArtLoaded(quint64, QImage)));
|
||||||
|
|
||||||
|
QIcon nocover = IconLoader::Load("cdcase");
|
||||||
|
no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextAlbumsModel::~ContextAlbumsModel() { delete root_; }
|
||||||
|
|
||||||
|
void ContextAlbumsModel::set_pretty_covers(bool use_pretty_covers) {
|
||||||
|
|
||||||
|
if (use_pretty_covers != use_pretty_covers_) {
|
||||||
|
use_pretty_covers_ = use_pretty_covers;
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextAlbumsModel::AddSongs(const SongList &songs) {
|
||||||
|
|
||||||
|
for (const Song &song : songs) {
|
||||||
|
if (song_nodes_.contains(song.id())) continue;
|
||||||
|
|
||||||
|
// Before we can add each song we need to make sure the required container items already exist in the tree.
|
||||||
|
|
||||||
|
// Find parent containers in the tree
|
||||||
|
CollectionItem *container = root_;
|
||||||
|
|
||||||
|
// Does it exist already?
|
||||||
|
if (!container_nodes_.contains(song.album())) {
|
||||||
|
// Create the container
|
||||||
|
container_nodes_[song.album()] = ItemFromSong(CollectionItem::Type_Container, true, container, song, 0);
|
||||||
|
}
|
||||||
|
container = container_nodes_[song.album()];
|
||||||
|
if (!container->lazy_loaded) continue;
|
||||||
|
|
||||||
|
// We've gone all the way down to the deepest level and everything was already lazy loaded, so now we have to create the song in the container.
|
||||||
|
song_nodes_[song.id()] = ItemFromSong(CollectionItem::Type_Song, true, container, song, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ContextAlbumsModel::AlbumIconPixmapCacheKey(const QModelIndex &index) const {
|
||||||
|
|
||||||
|
QStringList path;
|
||||||
|
QModelIndex index_copy(index);
|
||||||
|
while (index_copy.isValid()) {
|
||||||
|
path.prepend(index_copy.data().toString());
|
||||||
|
index_copy = index_copy.parent();
|
||||||
|
}
|
||||||
|
return "contextalbumsart:" + path.join("/");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ContextAlbumsModel::AlbumIcon(const QModelIndex &index) {
|
||||||
|
|
||||||
|
CollectionItem *item = IndexToItem(index);
|
||||||
|
if (!item) return no_cover_icon_;
|
||||||
|
|
||||||
|
// Check the cache for a pixmap we already loaded.
|
||||||
|
const QString cache_key = AlbumIconPixmapCacheKey(index);
|
||||||
|
|
||||||
|
QPixmap cached_pixmap;
|
||||||
|
if (QPixmapCache::find(cache_key, &cached_pixmap)) {
|
||||||
|
return cached_pixmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maybe we're loading a pixmap already?
|
||||||
|
if (pending_cache_keys_.contains(cache_key)) {
|
||||||
|
return no_cover_icon_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No art is cached and we're not loading it already. Load art for the first song in the album.
|
||||||
|
SongList songs = GetChildSongs(index);
|
||||||
|
if (!songs.isEmpty()) {
|
||||||
|
const quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, songs.first());
|
||||||
|
pending_art_[id] = ItemAndCacheKey(item, cache_key);
|
||||||
|
pending_cache_keys_.insert(cache_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return no_cover_icon_;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextAlbumsModel::AlbumArtLoaded(quint64 id, const QImage &image) {
|
||||||
|
|
||||||
|
ItemAndCacheKey item_and_cache_key = pending_art_.take(id);
|
||||||
|
CollectionItem *item = item_and_cache_key.first;
|
||||||
|
const QString &cache_key = item_and_cache_key.second;
|
||||||
|
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
pending_cache_keys_.remove(cache_key);
|
||||||
|
|
||||||
|
// Insert this image in the cache.
|
||||||
|
if (image.isNull()) {
|
||||||
|
// Set the no_cover image so we don't continually try to load art.
|
||||||
|
QPixmapCache::insert(cache_key, no_cover_icon_);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//qLog(Debug) << cache_key;
|
||||||
|
QPixmap image_pixmap;
|
||||||
|
image_pixmap = QPixmap::fromImage(image);
|
||||||
|
QPixmapCache::insert(cache_key, image_pixmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QModelIndex index = ItemToIndex(item);
|
||||||
|
emit dataChanged(index, index);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ContextAlbumsModel::data(const QModelIndex &index, int role) const {
|
||||||
|
|
||||||
|
const CollectionItem *item = IndexToItem(index);
|
||||||
|
|
||||||
|
// Handle a special case for returning album artwork instead of a generic CD icon.
|
||||||
|
// This is here instead of in the other data() function to let us use the QModelIndex& version of GetChildSongs,
|
||||||
|
// which satisfies const-ness, instead of the CollectionItem *version, which doesn't.
|
||||||
|
if (use_pretty_covers_) {
|
||||||
|
bool is_album_node = false;
|
||||||
|
if (role == Qt::DecorationRole && item->type == CollectionItem::Type_Container) {
|
||||||
|
is_album_node = (item->container_level == 0);
|
||||||
|
}
|
||||||
|
if (is_album_node) {
|
||||||
|
// It has const behaviour some of the time - that's ok right?
|
||||||
|
return const_cast<ContextAlbumsModel*>(this)->AlbumIcon(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data(item, role);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ContextAlbumsModel::data(const CollectionItem *item, int role) const {
|
||||||
|
|
||||||
|
switch (role) {
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
case Qt::ToolTipRole:
|
||||||
|
return item->DisplayText();
|
||||||
|
|
||||||
|
case Qt::DecorationRole:
|
||||||
|
switch (item->type) {
|
||||||
|
case CollectionItem::Type_PlaylistContainer:
|
||||||
|
return playlists_dir_icon_;
|
||||||
|
case CollectionItem::Type_Container:
|
||||||
|
if (item->type == CollectionItem::Type_Container && item->container_level == 0) { return album_icon_; }
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Role_Type:
|
||||||
|
return item->type;
|
||||||
|
|
||||||
|
case Role_IsDivider:
|
||||||
|
return item->type == CollectionItem::Type_Divider;
|
||||||
|
|
||||||
|
case Role_ContainerType:
|
||||||
|
return item->type;
|
||||||
|
|
||||||
|
case Role_Key:
|
||||||
|
return item->key;
|
||||||
|
|
||||||
|
case Role_Artist:
|
||||||
|
return item->metadata.artist();
|
||||||
|
|
||||||
|
case Role_Editable:
|
||||||
|
if (!item->lazy_loaded) {
|
||||||
|
const_cast<ContextAlbumsModel*>(this)->LazyPopulate(const_cast<CollectionItem*>(item), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item->type == CollectionItem::Type_Container) {
|
||||||
|
// if we have even one non editable item as a child, we ourselves are not available for edit
|
||||||
|
if (!item->children.isEmpty()) {
|
||||||
|
for (CollectionItem *child : item->children) {
|
||||||
|
if (!data(child, role).toBool()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item->type == CollectionItem::Type_Song) {
|
||||||
|
return item->metadata.IsEditable();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Role_SortText:
|
||||||
|
return item->SortText();
|
||||||
|
}
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextAlbumsModel::QueryResult ContextAlbumsModel::RunQuery(CollectionItem *parent) {
|
||||||
|
|
||||||
|
QueryResult result;
|
||||||
|
CollectionQuery q(query_options_);
|
||||||
|
q.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
|
||||||
|
|
||||||
|
// Walk up through the item's parents adding filters as necessary
|
||||||
|
CollectionItem *p = parent;
|
||||||
|
while (p && p->type == CollectionItem::Type_Container) {
|
||||||
|
if (p->container_level == 0) {
|
||||||
|
q.AddWhere("album", p->key);
|
||||||
|
}
|
||||||
|
p = p->parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the query
|
||||||
|
QMutexLocker l(backend_->db()->Mutex());
|
||||||
|
|
||||||
|
if (!backend_->ExecQuery(&q)) return result;
|
||||||
|
|
||||||
|
while (q.Next()) {
|
||||||
|
result.rows << SqlRow(q);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextAlbumsModel::PostQuery(CollectionItem *parent, const ContextAlbumsModel::QueryResult &result, bool signal) {
|
||||||
|
|
||||||
|
int child_level = (parent == root_ ? 0 : parent->container_level + 1);
|
||||||
|
|
||||||
|
for (const SqlRow &row : result.rows) {
|
||||||
|
|
||||||
|
CollectionItem::Type item_type = (parent == root_ ? CollectionItem::Type_Container : CollectionItem::Type_Song);
|
||||||
|
|
||||||
|
if (signal) beginInsertRows(ItemToIndex(parent), parent->children.count(), parent->children.count());
|
||||||
|
|
||||||
|
CollectionItem *item = new CollectionItem(item_type, parent);
|
||||||
|
item->container_level = child_level;
|
||||||
|
item->metadata.InitFromQuery(row, true);
|
||||||
|
item->key = item->metadata.title();
|
||||||
|
item->display_text = item->metadata.TitleWithCompilationArtist();
|
||||||
|
item->sort_text = SortTextForSong(item->metadata);
|
||||||
|
if (parent != root_) item->lazy_loaded = true;
|
||||||
|
|
||||||
|
if (signal) endInsertRows();
|
||||||
|
|
||||||
|
if (parent == root_) container_nodes_[item->key] = item;
|
||||||
|
else song_nodes_[item->metadata.id()] = item;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextAlbumsModel::LazyPopulate(CollectionItem *parent, bool signal) {
|
||||||
|
|
||||||
|
if (parent->lazy_loaded) return;
|
||||||
|
parent->lazy_loaded = true;
|
||||||
|
|
||||||
|
QueryResult result = RunQuery(parent);
|
||||||
|
PostQuery(parent, result, signal);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextAlbumsModel::Reset() {
|
||||||
|
|
||||||
|
beginResetModel();
|
||||||
|
delete root_;
|
||||||
|
song_nodes_.clear();
|
||||||
|
container_nodes_.clear();
|
||||||
|
pending_art_.clear();
|
||||||
|
|
||||||
|
root_ = new CollectionItem(this);
|
||||||
|
root_->lazy_loaded = false;
|
||||||
|
endResetModel();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectionItem *ContextAlbumsModel::ItemFromSong(CollectionItem::Type item_type, bool signal, CollectionItem *parent, const Song &s, int container_level) {
|
||||||
|
|
||||||
|
if (signal) beginInsertRows(ItemToIndex(parent), parent->children.count(), parent->children.count());
|
||||||
|
|
||||||
|
CollectionItem *item = new CollectionItem(item_type, parent);
|
||||||
|
item->container_level = container_level;
|
||||||
|
|
||||||
|
if (item->key.isNull()) item->key = s.album();
|
||||||
|
//if (item->key.isNull()) item->key = s.effective_albumartist();
|
||||||
|
item->display_text = TextOrUnknown(item->key);
|
||||||
|
item->sort_text = SortTextForArtist(item->key);
|
||||||
|
|
||||||
|
if (item_type == CollectionItem::Type_Song) item->lazy_loaded = true;
|
||||||
|
if (signal) endInsertRows();
|
||||||
|
|
||||||
|
return item;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ContextAlbumsModel::TextOrUnknown(const QString &text) {
|
||||||
|
|
||||||
|
if (text.isEmpty()) return tr("Unknown");
|
||||||
|
return text;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ContextAlbumsModel::SortText(QString text) {
|
||||||
|
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
text = " unknown";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text = text.toLower();
|
||||||
|
}
|
||||||
|
text = text.remove(QRegExp("[^\\w ]"));
|
||||||
|
|
||||||
|
return text;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ContextAlbumsModel::SortTextForArtist(QString artist) {
|
||||||
|
|
||||||
|
artist = SortText(artist);
|
||||||
|
|
||||||
|
if (artist.startsWith("the ")) {
|
||||||
|
artist = artist.right(artist.length() - 4) + ", the";
|
||||||
|
}
|
||||||
|
else if (artist.startsWith("a ")) {
|
||||||
|
artist = artist.right(artist.length() - 2) + ", a";
|
||||||
|
}
|
||||||
|
else if (artist.startsWith("an ")) {
|
||||||
|
artist = artist.right(artist.length() - 3) + ", an";
|
||||||
|
}
|
||||||
|
|
||||||
|
return artist;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ContextAlbumsModel::SortTextForSong(const Song &song) {
|
||||||
|
|
||||||
|
QString ret = QString::number(qMax(0, song.disc()) * 1000 + qMax(0, song.track()));
|
||||||
|
ret.prepend(QString("0").repeated(6 - ret.length()));
|
||||||
|
ret.append(song.url().toString());
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::ItemFlags ContextAlbumsModel::flags(const QModelIndex &index) const {
|
||||||
|
|
||||||
|
switch (IndexToItem(index)->type) {
|
||||||
|
case CollectionItem::Type_Song:
|
||||||
|
case CollectionItem::Type_Container:
|
||||||
|
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled;
|
||||||
|
case CollectionItem::Type_Divider:
|
||||||
|
case CollectionItem::Type_Root:
|
||||||
|
case CollectionItem::Type_LoadingIndicator:
|
||||||
|
default:
|
||||||
|
return Qt::ItemIsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList ContextAlbumsModel::mimeTypes() const {
|
||||||
|
return QStringList() << "text/uri-list";
|
||||||
|
}
|
||||||
|
|
||||||
|
QMimeData *ContextAlbumsModel::mimeData(const QModelIndexList &indexes) const {
|
||||||
|
|
||||||
|
if (indexes.isEmpty()) return nullptr;
|
||||||
|
|
||||||
|
SongMimeData *data = new SongMimeData;
|
||||||
|
QList<QUrl> urls;
|
||||||
|
QSet<int> song_ids;
|
||||||
|
|
||||||
|
data->backend = backend_;
|
||||||
|
|
||||||
|
for (const QModelIndex &index : indexes) {
|
||||||
|
GetChildSongs(IndexToItem(index), &urls, &data->songs, &song_ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
data->setUrls(urls);
|
||||||
|
data->name_for_new_playlist_ = PlaylistManager::GetNameForNewPlaylist(data->songs);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ContextAlbumsModel::CompareItems(const CollectionItem *a, const CollectionItem *b) const {
|
||||||
|
|
||||||
|
QVariant left(data(a, ContextAlbumsModel::Role_SortText));
|
||||||
|
QVariant right(data(b, ContextAlbumsModel::Role_SortText));
|
||||||
|
|
||||||
|
if (left.type() == QVariant::Int) return left.toInt() < right.toInt();
|
||||||
|
return left.toString() < right.toString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextAlbumsModel::GetChildSongs(CollectionItem *item, QList<QUrl> *urls, SongList *songs, QSet<int> *song_ids) const {
|
||||||
|
|
||||||
|
switch (item->type) {
|
||||||
|
case CollectionItem::Type_Container: {
|
||||||
|
const_cast<ContextAlbumsModel*>(this)->LazyPopulate(item);
|
||||||
|
|
||||||
|
QList<CollectionItem*> children = item->children;
|
||||||
|
qSort(children.begin(), children.end(), std::bind(&ContextAlbumsModel::CompareItems, this, _1, _2));
|
||||||
|
|
||||||
|
for (CollectionItem *child : children)
|
||||||
|
GetChildSongs(child, urls, songs, song_ids);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CollectionItem::Type_Song:
|
||||||
|
urls->append(item->metadata.url());
|
||||||
|
if (!song_ids->contains(item->metadata.id())) {
|
||||||
|
songs->append(item->metadata);
|
||||||
|
song_ids->insert(item->metadata.id());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SongList ContextAlbumsModel::GetChildSongs(const QModelIndexList &indexes) const {
|
||||||
|
|
||||||
|
QList<QUrl> dontcare;
|
||||||
|
SongList ret;
|
||||||
|
QSet<int> song_ids;
|
||||||
|
|
||||||
|
for (const QModelIndex &index : indexes) {
|
||||||
|
GetChildSongs(IndexToItem(index), &dontcare, &ret, &song_ids);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SongList ContextAlbumsModel::GetChildSongs(const QModelIndex &index) const {
|
||||||
|
return GetChildSongs(QModelIndexList() << index);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ContextAlbumsModel::canFetchMore(const QModelIndex &parent) const {
|
||||||
|
|
||||||
|
if (!parent.isValid()) return false;
|
||||||
|
|
||||||
|
CollectionItem *item = IndexToItem(parent);
|
||||||
|
return !item->lazy_loaded;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* This code was part of Clementine.
|
||||||
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
|
* Copyright 2013, Jonas Kvinge <jonas@strawbs.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 CONTEXTALBUMSMODEL_H
|
||||||
|
#define CONTEXTALBUMSMODEL_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QAbstractItemModel>
|
||||||
|
#include <QList>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QPair>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QMimeData>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QIcon>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QNetworkDiskCache>
|
||||||
|
#include <QSettings>
|
||||||
|
|
||||||
|
#include "core/simpletreemodel.h"
|
||||||
|
#include "core/song.h"
|
||||||
|
#include "collection/collectionquery.h"
|
||||||
|
#include "collection/collectionitem.h"
|
||||||
|
#include "collection/sqlrow.h"
|
||||||
|
#include "covermanager/albumcoverloaderoptions.h"
|
||||||
|
|
||||||
|
class Application;
|
||||||
|
class CollectionBackend;
|
||||||
|
class CollectionItem;
|
||||||
|
|
||||||
|
class ContextAlbumsModel : public SimpleTreeModel<CollectionItem> {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
ContextAlbumsModel(CollectionBackend *backend, Application *app, QObject *parent = nullptr);
|
||||||
|
~ContextAlbumsModel();
|
||||||
|
|
||||||
|
static const int kPrettyCoverSize;
|
||||||
|
static const qint64 kIconCacheSize;
|
||||||
|
|
||||||
|
enum Role {
|
||||||
|
Role_Type = Qt::UserRole + 1,
|
||||||
|
Role_ContainerType,
|
||||||
|
Role_SortText,
|
||||||
|
Role_Key,
|
||||||
|
Role_Artist,
|
||||||
|
Role_IsDivider,
|
||||||
|
Role_Editable,
|
||||||
|
LastRole
|
||||||
|
};
|
||||||
|
|
||||||
|
struct QueryResult {
|
||||||
|
QueryResult() {}
|
||||||
|
SqlRowList rows;
|
||||||
|
};
|
||||||
|
|
||||||
|
CollectionBackend *backend() const { return backend_; }
|
||||||
|
|
||||||
|
void GetChildSongs(CollectionItem *item, QList<QUrl> *urls, SongList *songs, QSet<int> *song_ids) const;
|
||||||
|
SongList GetChildSongs(const QModelIndex &index) const;
|
||||||
|
SongList GetChildSongs(const QModelIndexList &indexes) const;
|
||||||
|
|
||||||
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
|
||||||
|
Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||||
|
QStringList mimeTypes() const;
|
||||||
|
QMimeData *mimeData(const QModelIndexList &indexes) const;
|
||||||
|
bool canFetchMore(const QModelIndex &parent) const;
|
||||||
|
|
||||||
|
void set_pretty_covers(bool use_pretty_covers);
|
||||||
|
bool use_pretty_covers() const { return use_pretty_covers_; }
|
||||||
|
|
||||||
|
static QString TextOrUnknown(const QString &text);
|
||||||
|
static QString SortText(QString text);
|
||||||
|
static QString SortTextForArtist(QString artist);
|
||||||
|
static QString SortTextForSong(const Song &song);
|
||||||
|
|
||||||
|
void Reset();
|
||||||
|
void AddSongs(const SongList &songs);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void LazyPopulate(CollectionItem *item) { LazyPopulate(item, true); }
|
||||||
|
void LazyPopulate(CollectionItem *item, bool signal);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void AlbumArtLoaded(quint64 id, const QImage &image);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QueryResult RunQuery(CollectionItem *parent);
|
||||||
|
void PostQuery(CollectionItem *parent, const QueryResult &result, bool signal);
|
||||||
|
CollectionItem *ItemFromSong(CollectionItem::Type item_type, bool signal, CollectionItem *parent, const Song &s, int container_level);
|
||||||
|
|
||||||
|
QString AlbumIconPixmapCacheKey(const QModelIndex &index) const;
|
||||||
|
QVariant AlbumIcon(const QModelIndex &index);
|
||||||
|
QVariant data(const CollectionItem *item, int role) const;
|
||||||
|
bool CompareItems(const CollectionItem *a, const CollectionItem *b) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
CollectionBackend *backend_;
|
||||||
|
Application *app_;
|
||||||
|
QueryOptions query_options_;
|
||||||
|
QMap<int, CollectionItem*> song_nodes_;
|
||||||
|
QMap<QString, CollectionItem*> container_nodes_;
|
||||||
|
QIcon artist_icon_;
|
||||||
|
QIcon album_icon_;
|
||||||
|
QPixmap no_cover_icon_;
|
||||||
|
QIcon playlists_dir_icon_;
|
||||||
|
QIcon playlist_icon_;
|
||||||
|
QNetworkDiskCache *icon_cache_;
|
||||||
|
bool use_pretty_covers_;
|
||||||
|
AlbumCoverLoaderOptions cover_loader_options_;
|
||||||
|
typedef QPair<CollectionItem*, QString> ItemAndCacheKey;
|
||||||
|
QMap<quint64, ItemAndCacheKey> pending_art_;
|
||||||
|
QSet<QString> pending_cache_keys_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CONTEXTALBUMSMODEL_H
|
|
@ -0,0 +1,532 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* This code was part of Clementine.
|
||||||
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
|
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <qcoreevent.h>
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QItemSelectionModel>
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
|
#include <QAbstractItemView>
|
||||||
|
#include <QStyleOptionViewItem>
|
||||||
|
#include <QAction>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QList>
|
||||||
|
#include <QLocale>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QMimeData>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QPalette>
|
||||||
|
#include <QPen>
|
||||||
|
#include <QPoint>
|
||||||
|
#include <QRect>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QSize>
|
||||||
|
#include <QToolTip>
|
||||||
|
#include <QTreeView>
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QWhatsThis>
|
||||||
|
#include <QBrush>
|
||||||
|
#include <QColor>
|
||||||
|
#include <QFont>
|
||||||
|
#include <QFontMetrics>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QIcon>
|
||||||
|
#include <QLinearGradient>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QtEvents>
|
||||||
|
|
||||||
|
#include "core/application.h"
|
||||||
|
#include "core/iconloader.h"
|
||||||
|
#include "core/mimedata.h"
|
||||||
|
#include "core/utilities.h"
|
||||||
|
#include "collection/collectionbackend.h"
|
||||||
|
#include "collection/collectiondirectorymodel.h"
|
||||||
|
#include "collection/collectionitem.h"
|
||||||
|
#include "device/devicemanager.h"
|
||||||
|
#include "device/devicestatefiltermodel.h"
|
||||||
|
#include "dialogs/edittagdialog.h"
|
||||||
|
#ifdef HAVE_GSTREAMER
|
||||||
|
#include "dialogs/organisedialog.h"
|
||||||
|
#endif
|
||||||
|
#include "settings/collectionsettingspage.h"
|
||||||
|
|
||||||
|
#include "contextview.h"
|
||||||
|
#include "contextalbumsmodel.h"
|
||||||
|
#include "contextalbumsview.h"
|
||||||
|
|
||||||
|
ContextItemDelegate::ContextItemDelegate(QObject *parent) : QStyledItemDelegate(parent) {}
|
||||||
|
|
||||||
|
void ContextItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const {
|
||||||
|
|
||||||
|
const bool is_divider = index.data(ContextAlbumsModel::Role_IsDivider).toBool();
|
||||||
|
|
||||||
|
if (is_divider) {
|
||||||
|
QString text(index.data().toString());
|
||||||
|
|
||||||
|
painter->save();
|
||||||
|
|
||||||
|
QRect text_rect(opt.rect);
|
||||||
|
|
||||||
|
// Does this item have an icon?
|
||||||
|
QPixmap pixmap;
|
||||||
|
QVariant decoration = index.data(Qt::DecorationRole);
|
||||||
|
if (!decoration.isNull()) {
|
||||||
|
if (decoration.canConvert<QPixmap>()) {
|
||||||
|
pixmap = decoration.value<QPixmap>();
|
||||||
|
}
|
||||||
|
else if (decoration.canConvert<QIcon>()) {
|
||||||
|
pixmap = decoration.value<QIcon>().pixmap(opt.decorationSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the icon at the left of the text rectangle
|
||||||
|
if (!pixmap.isNull()) {
|
||||||
|
QRect icon_rect(text_rect.topLeft(), opt.decorationSize);
|
||||||
|
const int padding = (text_rect.height() - icon_rect.height()) / 2;
|
||||||
|
icon_rect.adjust(padding, padding, padding, padding);
|
||||||
|
text_rect.moveLeft(icon_rect.right() + padding + 6);
|
||||||
|
|
||||||
|
if (pixmap.size() != opt.decorationSize) {
|
||||||
|
pixmap = pixmap.scaled(opt.decorationSize, Qt::KeepAspectRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
painter->drawPixmap(icon_rect, pixmap);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text_rect.setLeft(text_rect.left() + 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the text
|
||||||
|
QFont bold_font(opt.font);
|
||||||
|
bold_font.setBold(true);
|
||||||
|
|
||||||
|
painter->setPen(opt.palette.color(QPalette::Text));
|
||||||
|
painter->setFont(bold_font);
|
||||||
|
painter->drawText(text_rect, text);
|
||||||
|
|
||||||
|
// Draw the line under the item
|
||||||
|
QColor line_color = opt.palette.color(QPalette::Text);
|
||||||
|
QLinearGradient grad_color(opt.rect.bottomLeft(), opt.rect.bottomRight());
|
||||||
|
const double fade_start_end = (opt.rect.width()/3.0)/opt.rect.width();
|
||||||
|
line_color.setAlphaF(0.0);
|
||||||
|
grad_color.setColorAt(0, line_color);
|
||||||
|
line_color.setAlphaF(0.5);
|
||||||
|
grad_color.setColorAt(fade_start_end, line_color);
|
||||||
|
grad_color.setColorAt(1.0 - fade_start_end, line_color);
|
||||||
|
line_color.setAlphaF(0.0);
|
||||||
|
grad_color.setColorAt(1, line_color);
|
||||||
|
painter->setPen(QPen(grad_color, 1));
|
||||||
|
painter->drawLine(opt.rect.bottomLeft(), opt.rect.bottomRight());
|
||||||
|
|
||||||
|
painter->restore();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!is_divider) QStyledItemDelegate::paint(painter, opt, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ContextItemDelegate::helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) {
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
Q_UNUSED(option);
|
||||||
|
|
||||||
|
if (!event || !view) return false;
|
||||||
|
|
||||||
|
QHelpEvent *he = static_cast<QHelpEvent*>(event);
|
||||||
|
QString text = displayText(index.data(), QLocale::system());
|
||||||
|
|
||||||
|
if (text.isEmpty() || !he) return false;
|
||||||
|
|
||||||
|
switch (event->type()) {
|
||||||
|
case QEvent::ToolTip: {
|
||||||
|
QRect displayed_text;
|
||||||
|
QSize real_text;
|
||||||
|
bool is_elided = false;
|
||||||
|
|
||||||
|
real_text = sizeHint(option, index);
|
||||||
|
displayed_text = view->visualRect(index);
|
||||||
|
is_elided = displayed_text.width() < real_text.width();
|
||||||
|
|
||||||
|
if (is_elided) {
|
||||||
|
QToolTip::showText(he->globalPos(), text, view);
|
||||||
|
}
|
||||||
|
else if (index.data(Qt::ToolTipRole).isValid()) {
|
||||||
|
// If the item has a tooltip text, display it
|
||||||
|
QString tooltip_text = index.data(Qt::ToolTipRole).toString();
|
||||||
|
QToolTip::showText(he->globalPos(), tooltip_text, view);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// in case that another text was previously displayed
|
||||||
|
QToolTip::hideText();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case QEvent::QueryWhatsThis:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case QEvent::WhatsThis:
|
||||||
|
QWhatsThis::showText(he->globalPos(), text, view);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextAlbumsView::ContextAlbumsView(QWidget *parent)
|
||||||
|
: AutoExpandingTreeView(parent),
|
||||||
|
app_(nullptr),
|
||||||
|
context_menu_(nullptr),
|
||||||
|
is_in_keyboard_search_(false),
|
||||||
|
model_(nullptr)
|
||||||
|
{
|
||||||
|
|
||||||
|
setStyleSheet("border: none;");
|
||||||
|
|
||||||
|
setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
|
||||||
|
setItemDelegate(new ContextItemDelegate(this));
|
||||||
|
setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||||
|
setHeaderHidden(true);
|
||||||
|
setAllColumnsShowFocus(true);
|
||||||
|
setDragEnabled(true);
|
||||||
|
setDragDropMode(QAbstractItemView::DragOnly);
|
||||||
|
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
|
SetAddOnDoubleClick(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextAlbumsView::~ContextAlbumsView() {}
|
||||||
|
|
||||||
|
void ContextAlbumsView::SaveFocus() {
|
||||||
|
|
||||||
|
QModelIndex current = currentIndex();
|
||||||
|
QVariant type = model()->data(current, ContextAlbumsModel::Role_Type);
|
||||||
|
if (!type.isValid() || !(type.toInt() == CollectionItem::Type_Song || type.toInt() == CollectionItem::Type_Container || type.toInt() == CollectionItem::Type_Divider)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_selected_path_.clear();
|
||||||
|
last_selected_song_ = Song();
|
||||||
|
last_selected_container_ = QString();
|
||||||
|
|
||||||
|
switch (type.toInt()) {
|
||||||
|
case CollectionItem::Type_Song: {
|
||||||
|
QModelIndex index = current;
|
||||||
|
SongList songs = model_->GetChildSongs(index);
|
||||||
|
if (!songs.isEmpty()) {
|
||||||
|
last_selected_song_ = songs.last();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CollectionItem::Type_Container:
|
||||||
|
case CollectionItem::Type_Divider: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveContainerPath(current);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextAlbumsView::SaveContainerPath(const QModelIndex &child) {
|
||||||
|
|
||||||
|
|
||||||
|
// return;
|
||||||
|
|
||||||
|
QModelIndex current = model()->parent(child);
|
||||||
|
QVariant type = model()->data(current, ContextAlbumsModel::Role_Type);
|
||||||
|
if (!type.isValid() || !(type.toInt() == CollectionItem::Type_Container || type.toInt() == CollectionItem::Type_Divider)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString text = model()->data(current, ContextAlbumsModel::Role_SortText).toString();
|
||||||
|
last_selected_path_ << text;
|
||||||
|
SaveContainerPath(current);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextAlbumsView::RestoreFocus() {
|
||||||
|
|
||||||
|
if (last_selected_container_.isEmpty() && last_selected_song_.url().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RestoreLevelFocus();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ContextAlbumsView::RestoreLevelFocus(const QModelIndex &parent) {
|
||||||
|
|
||||||
|
if (model()->canFetchMore(parent)) {
|
||||||
|
model()->fetchMore(parent);
|
||||||
|
}
|
||||||
|
int rows = model()->rowCount(parent);
|
||||||
|
for (int i = 0; i < rows; i++) {
|
||||||
|
QModelIndex current = model()->index(i, 0, parent);
|
||||||
|
QVariant type = model()->data(current, ContextAlbumsModel::Role_Type);
|
||||||
|
switch (type.toInt()) {
|
||||||
|
case CollectionItem::Type_Song:
|
||||||
|
if (!last_selected_song_.url().isEmpty()) {
|
||||||
|
QModelIndex index = qobject_cast<QSortFilterProxyModel*>(model())->mapToSource(current);
|
||||||
|
SongList songs = model_->GetChildSongs(index);
|
||||||
|
for (const Song &song : songs) {
|
||||||
|
if (song == last_selected_song_) {
|
||||||
|
setCurrentIndex(current);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextAlbumsView::ReloadSettings() {
|
||||||
|
|
||||||
|
QSettings settings;
|
||||||
|
|
||||||
|
settings.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
||||||
|
SetAutoOpen(settings.value("auto_open", true).toBool());
|
||||||
|
|
||||||
|
if (app_ && model_) {
|
||||||
|
model_->set_pretty_covers(settings.value("pretty_covers", true).toBool());
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.endGroup();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextAlbumsView::SetApplication(Application *app) {
|
||||||
|
|
||||||
|
app_ = app;
|
||||||
|
|
||||||
|
model_ = new ContextAlbumsModel(app_->collection_backend(), app_, this);
|
||||||
|
model_->Reset();
|
||||||
|
|
||||||
|
setModel(model_);
|
||||||
|
|
||||||
|
connect(model_, SIGNAL(modelAboutToBeReset()), this, SLOT(SaveFocus()));
|
||||||
|
connect(model_, SIGNAL(modelReset()), this, SLOT(RestoreFocus()));
|
||||||
|
|
||||||
|
ReloadSettings();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextAlbumsView::paintEvent(QPaintEvent *event) {
|
||||||
|
QTreeView::paintEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextAlbumsView::mouseReleaseEvent(QMouseEvent *e) {
|
||||||
|
QTreeView::mouseReleaseEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextAlbumsView::contextMenuEvent(QContextMenuEvent *e) {
|
||||||
|
|
||||||
|
if (!context_menu_) {
|
||||||
|
context_menu_ = new QMenu(this);
|
||||||
|
//context_menu_->setStyleSheet("background-color: #3DADE8;");
|
||||||
|
|
||||||
|
add_to_playlist_ = context_menu_->addAction(IconLoader::Load("media-play"), tr("Append to current playlist"), this, SLOT(AddToPlaylist()));
|
||||||
|
load_ = context_menu_->addAction(IconLoader::Load("media-play"), tr("Replace current playlist"), this, SLOT(Load()));
|
||||||
|
open_in_new_playlist_ = context_menu_->addAction(IconLoader::Load("document-new"), tr("Open in new playlist"), this, SLOT(OpenInNewPlaylist()));
|
||||||
|
|
||||||
|
context_menu_->addSeparator();
|
||||||
|
add_to_playlist_enqueue_ = context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue track"), this, SLOT(AddToPlaylistEnqueue()));
|
||||||
|
|
||||||
|
#ifdef HAVE_GSTREAMER
|
||||||
|
context_menu_->addSeparator();
|
||||||
|
organise_ = context_menu_->addAction(IconLoader::Load("edit-copy"), tr("Organise files..."), this, SLOT(Organise()));
|
||||||
|
copy_to_device_ = context_menu_->addAction(IconLoader::Load("device"), tr("Copy to device..."), this, SLOT(CopyToDevice()));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
context_menu_->addSeparator();
|
||||||
|
edit_track_ = context_menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit track information..."), this, SLOT(EditTracks()));
|
||||||
|
edit_tracks_ = context_menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit tracks information..."), this, SLOT(EditTracks()));
|
||||||
|
show_in_browser_ = context_menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, SLOT(ShowInBrowser()));
|
||||||
|
|
||||||
|
context_menu_->addSeparator();
|
||||||
|
|
||||||
|
#ifdef HAVE_GSTREAMER
|
||||||
|
copy_to_device_->setDisabled(app_->device_manager()->connected_devices_model()->rowCount() == 0);
|
||||||
|
connect(app_->device_manager()->connected_devices_model(), SIGNAL(IsEmptyChanged(bool)), copy_to_device_, SLOT(setDisabled(bool)));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
context_menu_index_ = indexAt(e->pos());
|
||||||
|
if (!context_menu_index_.isValid()) return;
|
||||||
|
QModelIndexList selected_indexes = selectionModel()->selectedRows();
|
||||||
|
|
||||||
|
int regular_elements = 0;
|
||||||
|
int regular_editable = 0;
|
||||||
|
|
||||||
|
for (const QModelIndex &index : selected_indexes) {
|
||||||
|
regular_elements++;
|
||||||
|
if(model_->data(index, ContextAlbumsModel::Role_Editable).toBool()) {
|
||||||
|
regular_editable++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check if custom plugin actions should be enabled / visible
|
||||||
|
const int songs_selected = regular_elements;
|
||||||
|
const bool regular_elements_only = songs_selected == regular_elements && regular_elements > 0;
|
||||||
|
|
||||||
|
// in all modes
|
||||||
|
load_->setEnabled(songs_selected);
|
||||||
|
add_to_playlist_->setEnabled(songs_selected);
|
||||||
|
open_in_new_playlist_->setEnabled(songs_selected);
|
||||||
|
add_to_playlist_enqueue_->setEnabled(songs_selected);
|
||||||
|
|
||||||
|
// if neither edit_track not edit_tracks are available, we show disabled edit_track element
|
||||||
|
edit_track_->setVisible(regular_editable <= 1);
|
||||||
|
edit_track_->setEnabled(regular_editable == 1);
|
||||||
|
|
||||||
|
#ifdef HAVE_GSTREAMER
|
||||||
|
organise_->setVisible(regular_elements_only);
|
||||||
|
copy_to_device_->setVisible(regular_elements_only);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// only when all selected items are editable
|
||||||
|
#ifdef HAVE_GSTREAMER
|
||||||
|
organise_->setEnabled(regular_elements == regular_editable);
|
||||||
|
copy_to_device_->setEnabled(regular_elements == regular_editable);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
context_menu_->popup(e->globalPos());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextAlbumsView::Load() {
|
||||||
|
|
||||||
|
QMimeData *data = model()->mimeData(selectedIndexes());
|
||||||
|
if (MimeData *mime_data = qobject_cast<MimeData*>(data)) {
|
||||||
|
mime_data->clear_first_ = true;
|
||||||
|
}
|
||||||
|
emit AddToPlaylistSignal(data);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextAlbumsView::AddToPlaylist() {
|
||||||
|
|
||||||
|
emit AddToPlaylistSignal(model()->mimeData(selectedIndexes()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextAlbumsView::AddToPlaylistEnqueue() {
|
||||||
|
|
||||||
|
QMimeData *data = model()->mimeData(selectedIndexes());
|
||||||
|
if (MimeData* mime_data = qobject_cast<MimeData*>(data)) {
|
||||||
|
mime_data->enqueue_now_ = true;
|
||||||
|
}
|
||||||
|
emit AddToPlaylistSignal(data);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextAlbumsView::OpenInNewPlaylist() {
|
||||||
|
|
||||||
|
QMimeData *data = model()->mimeData(selectedIndexes());
|
||||||
|
if (MimeData* mime_data = qobject_cast<MimeData*>(data)) {
|
||||||
|
mime_data->open_in_new_playlist_ = true;
|
||||||
|
}
|
||||||
|
emit AddToPlaylistSignal(data);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextAlbumsView::scrollTo(const QModelIndex &index, ScrollHint hint) {
|
||||||
|
|
||||||
|
if (is_in_keyboard_search_)
|
||||||
|
QTreeView::scrollTo(index, QAbstractItemView::PositionAtTop);
|
||||||
|
else
|
||||||
|
QTreeView::scrollTo(index, hint);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SongList ContextAlbumsView::GetSelectedSongs() const {
|
||||||
|
QModelIndexList selected_indexes = selectionModel()->selectedRows();
|
||||||
|
return model_->GetChildSongs(selected_indexes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_GSTREAMER
|
||||||
|
void ContextAlbumsView::Organise() {
|
||||||
|
|
||||||
|
if (!organise_dialog_)
|
||||||
|
organise_dialog_.reset(new OrganiseDialog(app_->task_manager()));
|
||||||
|
|
||||||
|
organise_dialog_->SetDestinationModel(app_->collection_model()->directory_model());
|
||||||
|
organise_dialog_->SetCopy(false);
|
||||||
|
if (organise_dialog_->SetSongs(GetSelectedSongs()))
|
||||||
|
organise_dialog_->show();
|
||||||
|
else {
|
||||||
|
QMessageBox::warning(this, tr("Error"), tr("None of the selected songs were suitable for copying to a device"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void ContextAlbumsView::EditTracks() {
|
||||||
|
|
||||||
|
if (!edit_tag_dialog_) {
|
||||||
|
edit_tag_dialog_.reset(new EditTagDialog(app_, this));
|
||||||
|
}
|
||||||
|
edit_tag_dialog_->SetSongs(GetSelectedSongs());
|
||||||
|
edit_tag_dialog_->show();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_GSTREAMER
|
||||||
|
void ContextAlbumsView::CopyToDevice() {
|
||||||
|
|
||||||
|
if (!organise_dialog_)
|
||||||
|
organise_dialog_.reset(new OrganiseDialog(app_->task_manager()));
|
||||||
|
|
||||||
|
organise_dialog_->SetDestinationModel(app_->device_manager()->connected_devices_model(), true);
|
||||||
|
organise_dialog_->SetCopy(true);
|
||||||
|
organise_dialog_->SetSongs(GetSelectedSongs());
|
||||||
|
organise_dialog_->show();
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void ContextAlbumsView::ShowInBrowser() {
|
||||||
|
|
||||||
|
QList<QUrl> urls;
|
||||||
|
for (const Song &song : GetSelectedSongs()) {
|
||||||
|
urls << song.url();
|
||||||
|
}
|
||||||
|
|
||||||
|
Utilities::OpenInFileBrowser(urls);
|
||||||
|
}
|
|
@ -0,0 +1,151 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* This code was part of Clementine.
|
||||||
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
|
* Copyright 2013, Jonas Kvinge <jonas@strawbs.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 CONTEXTALBUMSVIEW_H
|
||||||
|
#define CONTEXTALBUMSVIEW_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QAbstractItemModel>
|
||||||
|
#include <QAbstractItemView>
|
||||||
|
#include <QString>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QStyleOption>
|
||||||
|
#include <QStyledItemDelegate>
|
||||||
|
#include <QStyleOptionViewItem>
|
||||||
|
#include <QAction>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QtEvents>
|
||||||
|
|
||||||
|
#include "core/song.h"
|
||||||
|
#include "widgets/autoexpandingtreeview.h"
|
||||||
|
|
||||||
|
class Application;
|
||||||
|
class EditTagDialog;
|
||||||
|
#ifdef HAVE_GSTREAMER
|
||||||
|
class OrganiseDialog;
|
||||||
|
#endif
|
||||||
|
class ContextAlbumsModel;
|
||||||
|
|
||||||
|
class ContextItemDelegate : public QStyledItemDelegate {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
ContextItemDelegate(QObject *parent);
|
||||||
|
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index);
|
||||||
|
};
|
||||||
|
|
||||||
|
class ContextAlbumsView : public AutoExpandingTreeView {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
ContextAlbumsView(QWidget *parent = nullptr);
|
||||||
|
~ContextAlbumsView();
|
||||||
|
|
||||||
|
// Returns Songs currently selected in the collection view.
|
||||||
|
// Please note that the selection is recursive meaning that if for example an album is selected this will return all of it's songs.
|
||||||
|
SongList GetSelectedSongs() const;
|
||||||
|
|
||||||
|
void SetApplication(Application *app);
|
||||||
|
|
||||||
|
// QTreeView
|
||||||
|
void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible);
|
||||||
|
|
||||||
|
ContextAlbumsModel *albums_model() { return model_; }
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void ReloadSettings();
|
||||||
|
|
||||||
|
void SaveFocus();
|
||||||
|
void RestoreFocus();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void ShowConfigDialog();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// QWidget
|
||||||
|
void paintEvent(QPaintEvent *event);
|
||||||
|
void mouseReleaseEvent(QMouseEvent *e);
|
||||||
|
void contextMenuEvent(QContextMenuEvent *e);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void Load();
|
||||||
|
void AddToPlaylist();
|
||||||
|
void AddToPlaylistEnqueue();
|
||||||
|
void OpenInNewPlaylist();
|
||||||
|
#ifdef HAVE_GSTREAMER
|
||||||
|
void Organise();
|
||||||
|
void CopyToDevice();
|
||||||
|
#endif
|
||||||
|
void EditTracks();
|
||||||
|
void ShowInBrowser();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void RecheckIsEmpty();
|
||||||
|
bool RestoreLevelFocus(const QModelIndex &parent = QModelIndex());
|
||||||
|
void SaveContainerPath(const QModelIndex &child);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Application *app_;
|
||||||
|
|
||||||
|
QMenu *context_menu_;
|
||||||
|
QModelIndex context_menu_index_;
|
||||||
|
QAction *load_;
|
||||||
|
QAction *add_to_playlist_;
|
||||||
|
QAction *add_to_playlist_enqueue_;
|
||||||
|
QAction *open_in_new_playlist_;
|
||||||
|
#ifdef HAVE_GSTREAMER
|
||||||
|
QAction *organise_;
|
||||||
|
QAction *copy_to_device_;
|
||||||
|
#endif
|
||||||
|
QAction *delete_;
|
||||||
|
QAction *edit_track_;
|
||||||
|
QAction *edit_tracks_;
|
||||||
|
QAction *show_in_browser_;
|
||||||
|
|
||||||
|
#ifdef HAVE_GSTREAMER
|
||||||
|
std::unique_ptr<OrganiseDialog> organise_dialog_;
|
||||||
|
#endif
|
||||||
|
std::unique_ptr<EditTagDialog> edit_tag_dialog_;
|
||||||
|
|
||||||
|
bool is_in_keyboard_search_;
|
||||||
|
|
||||||
|
// Save focus
|
||||||
|
Song last_selected_song_;
|
||||||
|
QString last_selected_container_;
|
||||||
|
QSet<QString> last_selected_path_;
|
||||||
|
|
||||||
|
ContextAlbumsModel *model_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CONTEXTALBUMSVIEW_H
|
||||||
|
|
|
@ -0,0 +1,654 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QIODevice>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QList>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QString>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QMovie>
|
||||||
|
#include <QAction>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QPalette>
|
||||||
|
#include <QBrush>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QSizePolicy>
|
||||||
|
#include <QTimeLine>
|
||||||
|
#include <QtEvents>
|
||||||
|
#include <QFontDatabase>
|
||||||
|
|
||||||
|
#include "core/application.h"
|
||||||
|
#include "core/logging.h"
|
||||||
|
#include "core/player.h"
|
||||||
|
#include "core/song.h"
|
||||||
|
#include "core/utilities.h"
|
||||||
|
#include "core/iconloader.h"
|
||||||
|
#include "engine/engine_fwd.h"
|
||||||
|
#include "engine/enginebase.h"
|
||||||
|
#include "engine/enginetype.h"
|
||||||
|
#include "engine/enginedevice.h"
|
||||||
|
#include "engine/devicefinder.h"
|
||||||
|
#include "collection/collection.h"
|
||||||
|
#include "collection/collectionbackend.h"
|
||||||
|
#include "collection/collectionquery.h"
|
||||||
|
#include "collection/collectionmodel.h"
|
||||||
|
#include "collection/collectionview.h"
|
||||||
|
#include "covermanager/albumcoverchoicecontroller.h"
|
||||||
|
#include "covermanager/albumcoverloader.h"
|
||||||
|
#include "covermanager/currentartloader.h"
|
||||||
|
#include "lyrics/lyricsfetcher.h"
|
||||||
|
|
||||||
|
#include "contextview.h"
|
||||||
|
#include "contextalbumsmodel.h"
|
||||||
|
#include "ui_contextviewcontainer.h"
|
||||||
|
|
||||||
|
using std::unique_ptr;
|
||||||
|
|
||||||
|
const char *ContextView::kSettingsGroup = "ContextView";
|
||||||
|
|
||||||
|
ContextView::ContextView(QWidget *parent) :
|
||||||
|
QWidget(parent),
|
||||||
|
app_(nullptr),
|
||||||
|
ui_(new Ui_ContextViewContainer),
|
||||||
|
collectionview_(nullptr),
|
||||||
|
menu_(new QMenu(this)),
|
||||||
|
timeline_fade_(new QTimeLine(1000, this)),
|
||||||
|
image_strawberry_(":/pictures/strawberry.png"),
|
||||||
|
album_cover_choice_controller_(new AlbumCoverChoiceController(this)),
|
||||||
|
lyrics_fetcher_(nullptr),
|
||||||
|
active_(false),
|
||||||
|
downloading_covers_(false)
|
||||||
|
{
|
||||||
|
|
||||||
|
ui_->setupUi(this);
|
||||||
|
ui_->widget_stacked->setCurrentWidget(ui_->widget_stop);
|
||||||
|
ui_->label_play_album->installEventFilter(this);
|
||||||
|
|
||||||
|
QFontDatabase::addApplicationFont(":/fonts/HumongousofEternitySt.ttf");
|
||||||
|
|
||||||
|
connect(timeline_fade_, SIGNAL(valueChanged(qreal)), SLOT(FadePreviousTrack(qreal)));
|
||||||
|
timeline_fade_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0
|
||||||
|
|
||||||
|
cover_loader_options_.desired_height_ = 300;
|
||||||
|
cover_loader_options_.pad_output_image_ = true;
|
||||||
|
cover_loader_options_.scale_output_image_ = true;
|
||||||
|
pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_strawberry_));
|
||||||
|
|
||||||
|
AddActions();
|
||||||
|
LoadSettings();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextView::~ContextView() { delete ui_; }
|
||||||
|
|
||||||
|
void ContextView::LoadSettings() {
|
||||||
|
|
||||||
|
QSettings s;
|
||||||
|
s.beginGroup(kSettingsGroup);
|
||||||
|
|
||||||
|
action_show_data_->setChecked(s.value("show_data", true).toBool());
|
||||||
|
action_show_output_->setChecked(s.value("show_output", true).toBool());
|
||||||
|
action_show_albums_->setChecked(s.value("show_albums", true).toBool());
|
||||||
|
action_show_lyrics_->setChecked(s.value("show_lyrics", false).toBool());
|
||||||
|
album_cover_choice_controller_->search_cover_auto_action()->setChecked(s.value("search_for_cover_auto", true).toBool());
|
||||||
|
|
||||||
|
s.endGroup();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::SetApplication(Application *app) {
|
||||||
|
|
||||||
|
app_ = app;
|
||||||
|
|
||||||
|
connect(app_->current_art_loader(), SIGNAL(ArtLoaded(Song, QString, QImage)), SLOT(AlbumArtLoaded(Song, QString, QImage)));
|
||||||
|
|
||||||
|
album_cover_choice_controller_->SetApplication(app_);
|
||||||
|
connect(album_cover_choice_controller_, SIGNAL(AutomaticCoverSearchDone()), this, SLOT(AutomaticCoverSearchDone()));
|
||||||
|
connect(album_cover_choice_controller_->cover_from_file_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromFile()));
|
||||||
|
connect(album_cover_choice_controller_->cover_to_file_action(), SIGNAL(triggered()), this, SLOT(SaveCoverToFile()));
|
||||||
|
connect(album_cover_choice_controller_->cover_from_url_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromURL()));
|
||||||
|
connect(album_cover_choice_controller_->search_for_cover_action(), SIGNAL(triggered()), this, SLOT(SearchForCover()));
|
||||||
|
connect(album_cover_choice_controller_->unset_cover_action(), SIGNAL(triggered()), this, SLOT(UnsetCover()));
|
||||||
|
connect(album_cover_choice_controller_->show_cover_action(), SIGNAL(triggered()), this, SLOT(ShowCover()));
|
||||||
|
connect(album_cover_choice_controller_->search_cover_auto_action(), SIGNAL(triggered()), this, SLOT(SearchCoverAutomatically()));
|
||||||
|
|
||||||
|
ui_->widget_play_albums->SetApplication(app_);
|
||||||
|
|
||||||
|
lyrics_fetcher_ = new LyricsFetcher(app_->lyrics_providers(), this);
|
||||||
|
connect(lyrics_fetcher_, SIGNAL(LyricsFetched(quint64, const QString)), this, SLOT(UpdateLyrics(quint64, const QString)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::SetCollectionView(CollectionView *collectionview) {
|
||||||
|
collectionview_ = collectionview;
|
||||||
|
connect(collectionview_, SIGNAL(TotalSongCountUpdated_()), this, SLOT(UpdateNoSong()));
|
||||||
|
connect(collectionview_, SIGNAL(TotalArtistCountUpdated_()), this, SLOT(UpdateNoSong()));
|
||||||
|
connect(collectionview_, SIGNAL(TotalAlbumCountUpdated_()), this, SLOT(UpdateNoSong()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::AddActions() {
|
||||||
|
|
||||||
|
action_show_data_ = new QAction(tr("Show song technical data"), this);
|
||||||
|
action_show_data_->setCheckable(true);
|
||||||
|
action_show_data_->setChecked(true);
|
||||||
|
|
||||||
|
action_show_output_ = new QAction(tr("Show engine and device"), this);
|
||||||
|
action_show_output_->setCheckable(true);
|
||||||
|
action_show_output_->setChecked(true);
|
||||||
|
|
||||||
|
action_show_albums_ = new QAction(tr("Show albums by artist"), this);
|
||||||
|
action_show_albums_->setCheckable(true);
|
||||||
|
action_show_albums_->setChecked(true);
|
||||||
|
|
||||||
|
action_show_lyrics_ = new QAction(tr("Show song lyrics"), this);
|
||||||
|
action_show_lyrics_->setCheckable(true);
|
||||||
|
action_show_lyrics_->setChecked(false);
|
||||||
|
|
||||||
|
menu_->addAction(action_show_data_);
|
||||||
|
menu_->addAction(action_show_output_);
|
||||||
|
menu_->addAction(action_show_albums_);
|
||||||
|
menu_->addAction(action_show_lyrics_);
|
||||||
|
menu_->addSeparator();
|
||||||
|
|
||||||
|
connect(action_show_data_, SIGNAL(triggered()), this, SLOT(ActionShowData()));
|
||||||
|
connect(action_show_output_, SIGNAL(triggered()), this, SLOT(ActionShowOutput()));
|
||||||
|
connect(action_show_albums_, SIGNAL(triggered()), this, SLOT(ActionShowAlbums()));
|
||||||
|
connect(action_show_lyrics_, SIGNAL(triggered()), this, SLOT(ActionShowLyrics()));
|
||||||
|
|
||||||
|
QList<QAction*> cover_actions = album_cover_choice_controller_->GetAllActions();
|
||||||
|
cover_actions.append(album_cover_choice_controller_->search_cover_auto_action());
|
||||||
|
menu_->addActions(cover_actions);
|
||||||
|
menu_->addSeparator();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::Playing() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::Stopped() {
|
||||||
|
|
||||||
|
active_ = false;
|
||||||
|
song_ = song_empty_;
|
||||||
|
downloading_covers_ = false;
|
||||||
|
prev_artist_ = QString();
|
||||||
|
lyrics_ = QString();
|
||||||
|
SetImage(image_strawberry_);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::Error() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::UpdateNoSong() {
|
||||||
|
NoSong();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::SongChanged(const Song &song) {
|
||||||
|
|
||||||
|
image_previous_ = image_original_;
|
||||||
|
prev_artist_ = song_.artist();
|
||||||
|
lyrics_ = QString();
|
||||||
|
song_ = song;
|
||||||
|
UpdateSong();
|
||||||
|
update();
|
||||||
|
if (action_show_lyrics_->isChecked()) lyrics_fetcher_->Search(song.artist(), song.album(), song.title());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::SetText(QLabel *label, int value, const QString &suffix, const QString &def) {
|
||||||
|
label->setText(value <= 0 ? def : (QString::number(value) + " " + suffix));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::NoSong() {
|
||||||
|
|
||||||
|
ui_->label_stop_top->setStyleSheet(
|
||||||
|
"font: 20pt \"Humongous of Eternity St\";"
|
||||||
|
"font-weight: Regular;"
|
||||||
|
);
|
||||||
|
|
||||||
|
ui_->label_stop_top->setText("No song playing");
|
||||||
|
|
||||||
|
QString html = QString(
|
||||||
|
"%1 songs<br />\n"
|
||||||
|
"%2 artists<br />\n"
|
||||||
|
"%3 albums<br />\n"
|
||||||
|
)
|
||||||
|
.arg(collectionview_->TotalSongs())
|
||||||
|
.arg(collectionview_->TotalArtists())
|
||||||
|
.arg(collectionview_->TotalAlbums())
|
||||||
|
;
|
||||||
|
|
||||||
|
ui_->label_stop_summary->setStyleSheet(
|
||||||
|
"font: 12pt;"
|
||||||
|
"font-weight: regular;"
|
||||||
|
);
|
||||||
|
ui_->label_stop_summary->setText(html);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::UpdateSong() {
|
||||||
|
|
||||||
|
QList <QLabel *> labels_play_data;
|
||||||
|
|
||||||
|
labels_play_data << ui_->label_filetype
|
||||||
|
<< ui_->filetype
|
||||||
|
<< ui_->label_length
|
||||||
|
<< ui_->length
|
||||||
|
<< ui_->label_samplerate
|
||||||
|
<< ui_->samplerate
|
||||||
|
<< ui_->label_bitdepth
|
||||||
|
<< ui_->bitdepth
|
||||||
|
<< ui_->label_bitrate
|
||||||
|
<< ui_->bitrate;
|
||||||
|
|
||||||
|
ui_->label_play_top->setStyleSheet(
|
||||||
|
"font: 11pt;"
|
||||||
|
"font-weight: regular;"
|
||||||
|
);
|
||||||
|
ui_->label_play_top->setText( QString("<b>%1 - %2</b><br/>%3").arg(song_.PrettyTitle().toHtmlEscaped(), song_.artist().toHtmlEscaped(), song_.album().toHtmlEscaped()));
|
||||||
|
|
||||||
|
if (action_show_data_->isChecked()) {
|
||||||
|
for (QLabel *l : labels_play_data) {
|
||||||
|
l->setEnabled(true);
|
||||||
|
l->setVisible(true);
|
||||||
|
l->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
|
||||||
|
}
|
||||||
|
ui_->layout_play_data->setEnabled(true);
|
||||||
|
ui_->filetype->setText(song_.TextForFiletype());
|
||||||
|
ui_->length->setText(Utilities::PrettyTimeNanosec(song_.length_nanosec()));
|
||||||
|
SetText(ui_->samplerate, song_.samplerate(), "Hz");
|
||||||
|
SetText(ui_->bitdepth, song_.bitdepth(), "Bit");
|
||||||
|
SetText(ui_->bitrate, song_.bitrate(), tr("kbps"));
|
||||||
|
ui_->spacer_play_data->changeSize(20, 20, QSizePolicy::Fixed);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (QLabel *l : labels_play_data) {
|
||||||
|
l->setEnabled(false);
|
||||||
|
l->setVisible(false);
|
||||||
|
l->setMaximumSize(0, 0);
|
||||||
|
}
|
||||||
|
ui_->layout_play_data->setEnabled(false);
|
||||||
|
ui_->filetype->clear();
|
||||||
|
ui_->length->clear();
|
||||||
|
ui_->samplerate->clear();
|
||||||
|
ui_->bitdepth->clear();
|
||||||
|
ui_->bitrate->clear();
|
||||||
|
ui_->spacer_play_data->changeSize(0, 0, QSizePolicy::Fixed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action_show_output_->isChecked()) {
|
||||||
|
Engine::EngineType enginetype(Engine::None);
|
||||||
|
if (app_->player()->engine()) enginetype = app_->player()->engine()->type();
|
||||||
|
QIcon icon_engine = IconLoader::Load(EngineName(enginetype), 32);
|
||||||
|
|
||||||
|
ui_->label_engine->setVisible(true);
|
||||||
|
ui_->label_engine->setMaximumSize(60, QWIDGETSIZE_MAX);
|
||||||
|
ui_->label_engine_icon->setVisible(true);
|
||||||
|
ui_->label_engine_icon->setPixmap(icon_engine.pixmap(icon_engine.availableSizes().last()));
|
||||||
|
ui_->label_engine_icon->setMinimumSize(32, 32);
|
||||||
|
ui_->label_engine_icon->setMaximumSize(32, 32);
|
||||||
|
ui_->engine->setVisible(true);
|
||||||
|
ui_->engine->setText(EngineDescription(enginetype));
|
||||||
|
ui_->engine->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
|
||||||
|
ui_->spacer_play_output->changeSize(20, 20, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
DeviceFinder::Device device;
|
||||||
|
for (DeviceFinder *f : app_->enginedevice()->device_finders_) {
|
||||||
|
for (const DeviceFinder::Device &d : f->ListDevices()) {
|
||||||
|
if (d.value != app_->player()->engine()->device()) continue;
|
||||||
|
device = d;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (device.value.isValid()) {
|
||||||
|
ui_->label_device->setVisible(true);
|
||||||
|
ui_->label_device->setMaximumSize(60, QWIDGETSIZE_MAX);
|
||||||
|
ui_->label_device_icon->setVisible(true);
|
||||||
|
ui_->label_device_icon->setMinimumSize(32, 32);
|
||||||
|
ui_->label_device_icon->setMaximumSize(32, 32);
|
||||||
|
ui_->device->setVisible(true);
|
||||||
|
ui_->device->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
|
||||||
|
QIcon icon_device = IconLoader::Load(device.iconname, 32);
|
||||||
|
ui_->label_device_icon->setPixmap(icon_device.pixmap(icon_device.availableSizes().last()));
|
||||||
|
ui_->device->setText(device.description);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ui_->label_device->setVisible(false);
|
||||||
|
ui_->label_device->setMaximumSize(0, 0);
|
||||||
|
ui_->label_device_icon->setVisible(false);
|
||||||
|
ui_->label_device_icon->setMinimumSize(0, 0);
|
||||||
|
ui_->label_device_icon->setMaximumSize(0, 0);
|
||||||
|
ui_->label_device_icon->clear();
|
||||||
|
ui_->device->clear();
|
||||||
|
ui_->device->setVisible(false);
|
||||||
|
ui_->device->setMaximumSize(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ui_->label_engine->setVisible(false);
|
||||||
|
ui_->label_engine->setMaximumSize(0, 0);
|
||||||
|
ui_->label_engine_icon->clear();
|
||||||
|
ui_->label_engine_icon->setVisible(false);
|
||||||
|
ui_->label_engine_icon->setMinimumSize(0, 0);
|
||||||
|
ui_->label_engine_icon->setMaximumSize(0, 0);
|
||||||
|
ui_->engine->clear();
|
||||||
|
ui_->engine->setVisible(false);
|
||||||
|
ui_->engine->setMaximumSize(0, 0);
|
||||||
|
ui_->spacer_play_output->changeSize(0, 0, QSizePolicy::Fixed);
|
||||||
|
ui_->label_device->setVisible(false);
|
||||||
|
ui_->label_device->setMaximumSize(0, 0);
|
||||||
|
ui_->label_device_icon->setVisible(false);
|
||||||
|
ui_->label_device_icon->setMinimumSize(0, 0);
|
||||||
|
ui_->label_device_icon->setMaximumSize(0, 0);
|
||||||
|
ui_->label_device_icon->clear();
|
||||||
|
ui_->device->clear();
|
||||||
|
ui_->device->setVisible(false);
|
||||||
|
ui_->device->setMaximumSize(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action_show_albums_->isChecked() && prev_artist_ != song_.artist()) {
|
||||||
|
const QueryOptions opt;
|
||||||
|
CollectionBackend::AlbumList albumlist;
|
||||||
|
ui_->widget_play_albums->albums_model()->Reset();
|
||||||
|
albumlist = app_->collection_backend()->GetAlbumsByArtist(song_.artist(), opt);
|
||||||
|
if (albumlist.count() > 1) {
|
||||||
|
ui_->label_play_albums->setVisible(true);
|
||||||
|
ui_->label_play_albums->setMinimumSize(0, 20);
|
||||||
|
ui_->label_play_albums->setText(QString("Albums by %1").arg( song_.artist().toHtmlEscaped()));
|
||||||
|
ui_->label_play_albums->setStyleSheet("background-color: #3DADE8; color: rgb(255, 255, 255); font: 11pt;");
|
||||||
|
for (CollectionBackend::Album album : albumlist) {
|
||||||
|
SongList songs = app_->collection_backend()->GetSongs(song_.artist(), album.album_name, opt);
|
||||||
|
ui_->widget_play_albums->albums_model()->AddSongs(songs);
|
||||||
|
}
|
||||||
|
ui_->widget_play_albums->setEnabled(true);
|
||||||
|
ui_->widget_play_albums->setVisible(true);
|
||||||
|
ui_->spacer_play_albums1->changeSize(20, 10, QSizePolicy::Fixed);
|
||||||
|
ui_->spacer_play_albums2->changeSize(20, 20, QSizePolicy::Fixed);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ui_->label_play_albums->clear();
|
||||||
|
ui_->label_play_albums->setVisible(false);
|
||||||
|
ui_->label_play_albums->setMinimumSize(0, 0);
|
||||||
|
ui_->widget_play_albums->setEnabled(false);
|
||||||
|
ui_->widget_play_albums->setVisible(false);
|
||||||
|
ui_->spacer_play_albums1->changeSize(0, 0, QSizePolicy::Fixed);
|
||||||
|
ui_->spacer_play_albums2->changeSize(0, 0, QSizePolicy::Fixed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!action_show_albums_->isChecked()) {
|
||||||
|
ui_->label_play_albums->clear();
|
||||||
|
ui_->label_play_albums->setVisible(false);
|
||||||
|
ui_->label_play_albums->setMinimumSize(0, 0);
|
||||||
|
ui_->widget_play_albums->albums_model()->Reset();
|
||||||
|
ui_->widget_play_albums->setEnabled(false);
|
||||||
|
ui_->widget_play_albums->setVisible(false);
|
||||||
|
ui_->spacer_play_albums1->changeSize(0, 0, QSizePolicy::Fixed);
|
||||||
|
ui_->spacer_play_albums2->changeSize(0, 0, QSizePolicy::Fixed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action_show_lyrics_->isChecked()) {
|
||||||
|
ui_->label_play_lyrics->setText(lyrics_);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ui_->label_play_lyrics->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
ui_->widget_stacked->setCurrentWidget(ui_->widget_play);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::UpdateLyrics(quint64 id, const QString lyrics) {
|
||||||
|
|
||||||
|
lyrics_ = lyrics;
|
||||||
|
if (action_show_lyrics_->isChecked()) {
|
||||||
|
ui_->label_play_lyrics->setText(lyrics);
|
||||||
|
}
|
||||||
|
else ui_->label_play_lyrics->clear();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ContextView::eventFilter(QObject *object, QEvent *event) {
|
||||||
|
|
||||||
|
switch(event->type()) {
|
||||||
|
case QEvent::Paint:{
|
||||||
|
handlePaintEvent(object, event);
|
||||||
|
}
|
||||||
|
default:{
|
||||||
|
return QObject::eventFilter(object, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::handlePaintEvent(QObject *object, QEvent *event) {
|
||||||
|
|
||||||
|
if (object == ui_->label_play_album) {
|
||||||
|
PaintEventAlbum(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::PaintEventAlbum(QEvent *event) {
|
||||||
|
|
||||||
|
QPainter p(ui_->label_play_album);
|
||||||
|
|
||||||
|
DrawImage(&p);
|
||||||
|
|
||||||
|
// Draw the previous track's image if we're fading
|
||||||
|
if (!pixmap_previous_.isNull()) {
|
||||||
|
p.setOpacity(pixmap_previous_opacity_);
|
||||||
|
p.drawPixmap(0, 0, pixmap_previous_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::DrawImage(QPainter *p) {
|
||||||
|
|
||||||
|
p->drawPixmap(0, 0, 300, 300, pixmap_current_);
|
||||||
|
if ((downloading_covers_) && (spinner_animation_)) {
|
||||||
|
p->drawPixmap(50, 50, 16, 16, spinner_animation_->currentPixmap());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::FadePreviousTrack(qreal value) {
|
||||||
|
|
||||||
|
pixmap_previous_opacity_ = value;
|
||||||
|
if (qFuzzyCompare(pixmap_previous_opacity_, qreal(0.0))) {
|
||||||
|
image_previous_ = QImage();
|
||||||
|
pixmap_previous_ = QPixmap();
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
|
||||||
|
if (value == 0 && !active_) {
|
||||||
|
ui_->widget_stacked->setCurrentWidget(ui_->widget_stop);
|
||||||
|
NoSong();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::contextMenuEvent(QContextMenuEvent *e) {
|
||||||
|
if (menu_ && ui_->widget_stacked->currentWidget() == ui_->widget_play) menu_->popup(mapToGlobal(e->pos()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::mouseReleaseEvent(QMouseEvent *) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::dragEnterEvent(QDragEnterEvent *e) {
|
||||||
|
QWidget::dragEnterEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::dropEvent(QDropEvent *e) {
|
||||||
|
QWidget::dropEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::ScaleCover() {
|
||||||
|
|
||||||
|
pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_original_));
|
||||||
|
update();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::AlbumArtLoaded(const Song &song, const QString&, const QImage &image) {
|
||||||
|
|
||||||
|
if (song == song_) {}
|
||||||
|
else {
|
||||||
|
qLog(Error) << __PRETTY_FUNCTION__ << "Ignoring" << song.title() << "because current song is" << song_.title();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
active_ = true;
|
||||||
|
downloading_covers_ = false;
|
||||||
|
SetImage(image);
|
||||||
|
GetCoverAutomatically();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::SetImage(const QImage &image) {
|
||||||
|
|
||||||
|
// Cache the current pixmap so we can fade between them
|
||||||
|
pixmap_previous_ = QPixmap(size());
|
||||||
|
pixmap_previous_.fill(palette().background().color());
|
||||||
|
pixmap_previous_opacity_ = 1.0;
|
||||||
|
|
||||||
|
QPainter p(&pixmap_previous_);
|
||||||
|
DrawImage(&p);
|
||||||
|
p.end();
|
||||||
|
|
||||||
|
image_original_ = image;
|
||||||
|
|
||||||
|
ScaleCover();
|
||||||
|
|
||||||
|
// Were we waiting for this cover to load before we started fading?
|
||||||
|
if (!pixmap_previous_.isNull() && timeline_fade_) {
|
||||||
|
timeline_fade_->stop();
|
||||||
|
timeline_fade_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0
|
||||||
|
timeline_fade_->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ContextView::GetCoverAutomatically() {
|
||||||
|
|
||||||
|
// Search for cover automatically?
|
||||||
|
bool search = !song_.has_manually_unset_cover() && song_.art_automatic().isEmpty() && song_.art_manual().isEmpty() && !song_.artist().isEmpty() && !song_.album().isEmpty();
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
downloading_covers_ = true;
|
||||||
|
album_cover_choice_controller_->SearchCoverAutomatically(song_);
|
||||||
|
|
||||||
|
// Show a spinner animation
|
||||||
|
spinner_animation_.reset(new QMovie(":/pictures/spinner.gif", QByteArray(), this));
|
||||||
|
connect(spinner_animation_.get(), SIGNAL(updated(const QRect&)), SLOT(update()));
|
||||||
|
spinner_animation_->start();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
return search;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::AutomaticCoverSearchDone() {
|
||||||
|
|
||||||
|
downloading_covers_ = false;
|
||||||
|
spinner_animation_.reset();
|
||||||
|
update();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::ActionShowData() {
|
||||||
|
QSettings s;
|
||||||
|
s.beginGroup(kSettingsGroup);
|
||||||
|
s.setValue("show_data", action_show_data_->isChecked());
|
||||||
|
s.endGroup();
|
||||||
|
UpdateSong();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::ActionShowOutput() {
|
||||||
|
QSettings s;
|
||||||
|
s.beginGroup(kSettingsGroup);
|
||||||
|
s.setValue("show_output", action_show_output_->isChecked());
|
||||||
|
s.endGroup();
|
||||||
|
UpdateSong();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::ActionShowAlbums() {
|
||||||
|
QSettings s;
|
||||||
|
s.beginGroup(kSettingsGroup);
|
||||||
|
s.setValue("show_albums", action_show_albums_->isChecked());
|
||||||
|
s.endGroup();
|
||||||
|
prev_artist_ = QString();
|
||||||
|
UpdateSong();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::ActionShowLyrics() {
|
||||||
|
QSettings s;
|
||||||
|
s.beginGroup(kSettingsGroup);
|
||||||
|
s.setValue("show_lyrics", action_show_lyrics_->isChecked());
|
||||||
|
s.endGroup();
|
||||||
|
UpdateSong();
|
||||||
|
if (lyrics_.isEmpty() && action_show_lyrics_->isChecked()) lyrics_fetcher_->Search(song_.artist(), song_.album(), song_.title());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::LoadCoverFromFile() {
|
||||||
|
album_cover_choice_controller_->LoadCoverFromFile(&song_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::LoadCoverFromURL() {
|
||||||
|
album_cover_choice_controller_->LoadCoverFromURL(&song_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::SearchForCover() {
|
||||||
|
album_cover_choice_controller_->SearchForCover(&song_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::SaveCoverToFile() {
|
||||||
|
album_cover_choice_controller_->SaveCoverToFile(song_, image_original_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::UnsetCover() {
|
||||||
|
album_cover_choice_controller_->UnsetCover(&song_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::ShowCover() {
|
||||||
|
album_cover_choice_controller_->ShowCover(song_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextView::SearchCoverAutomatically() {
|
||||||
|
|
||||||
|
QSettings s;
|
||||||
|
s.beginGroup(kSettingsGroup);
|
||||||
|
s.setValue("search_for_cover_auto", album_cover_choice_controller_->search_cover_auto_action()->isChecked());
|
||||||
|
s.endGroup();
|
||||||
|
|
||||||
|
GetCoverAutomatically();
|
||||||
|
|
||||||
|
}
|
|
@ -17,8 +17,8 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef STATUSVIEW_H
|
#ifndef CONTEXTVIEW_H
|
||||||
#define STATUSVIEW_H
|
#define CONTEXTVIEW_H
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
@ -31,52 +31,48 @@
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QMovie>
|
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
#include <QMovie>
|
||||||
#include <QTimeLine>
|
#include <QTimeLine>
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QLabel>
|
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QScrollArea>
|
#include <QLabel>
|
||||||
#include <QBoxLayout>
|
|
||||||
#include <QtEvents>
|
#include <QtEvents>
|
||||||
|
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "covermanager/albumcoverloaderoptions.h"
|
#include "covermanager/albumcoverloaderoptions.h"
|
||||||
|
|
||||||
class QEvent;
|
#include "ui_contextviewcontainer.h"
|
||||||
class QContextMenuEvent;
|
|
||||||
class QDragEnterEvent;
|
using std::unique_ptr;
|
||||||
class QDropEvent;
|
|
||||||
class QMouseEvent;
|
|
||||||
|
|
||||||
class Application;
|
class Application;
|
||||||
class CollectionView;
|
class CollectionView;
|
||||||
class CollectionViewContainer;
|
class CollectionModel;
|
||||||
class AlbumCoverChoiceController;
|
class AlbumCoverChoiceController;
|
||||||
|
class Ui_ContextViewContainer;
|
||||||
|
class ContextAlbumsView;
|
||||||
|
class LyricsFetcher;
|
||||||
|
|
||||||
class StatusView : public QWidget {
|
class ContextView : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
StatusView(CollectionViewContainer *collectionviewcontainer, QWidget *parent = nullptr);
|
ContextView(QWidget *parent = nullptr);
|
||||||
~StatusView();
|
~ContextView();
|
||||||
|
|
||||||
static const char* kSettingsGroup;
|
|
||||||
static const int kPadding;
|
|
||||||
static const int kGradientHead;
|
|
||||||
static const int kGradientTail;
|
|
||||||
static const int kMaxCoverSize;
|
|
||||||
static const int kBottomOffset;
|
|
||||||
static const int kTopBorder;
|
|
||||||
|
|
||||||
void SetApplication(Application *app);
|
void SetApplication(Application *app);
|
||||||
|
void SetCollectionView(CollectionView *collectionview);
|
||||||
|
|
||||||
public slots:
|
ContextAlbumsView *albums() { return ui_->widget_play_albums; }
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void UpdateNoSong();
|
||||||
|
void Playing();
|
||||||
|
void Stopped();
|
||||||
|
void Error();
|
||||||
void SongChanged(const Song &song);
|
void SongChanged(const Song &song);
|
||||||
void SongFinished();
|
void AlbumArtLoaded(const Song &song, const QString &uri, const QImage &image);
|
||||||
void AlbumArtLoaded(const Song& metadata, const QString &uri, const QImage &image);
|
|
||||||
void FadePreviousTrack(qreal value);
|
|
||||||
|
|
||||||
void LoadCoverFromFile();
|
void LoadCoverFromFile();
|
||||||
void SaveCoverToFile();
|
void SaveCoverToFile();
|
||||||
|
@ -86,92 +82,83 @@ public slots:
|
||||||
void ShowCover();
|
void ShowCover();
|
||||||
void SearchCoverAutomatically();
|
void SearchCoverAutomatically();
|
||||||
void AutomaticCoverSearchDone();
|
void AutomaticCoverSearchDone();
|
||||||
|
void UpdateLyrics(quint64 id, const QString lyrics);
|
||||||
private:
|
|
||||||
QVBoxLayout *layout_;
|
|
||||||
QScrollArea *scrollarea_;
|
|
||||||
QVBoxLayout *container_layout_;
|
|
||||||
QWidget *container_widget_;
|
|
||||||
|
|
||||||
QWidget *widget_stopped_;
|
private:
|
||||||
QWidget *widget_playing_;
|
|
||||||
QVBoxLayout *layout_playing_;
|
|
||||||
QVBoxLayout *layout_stopped_;
|
|
||||||
QLabel *label_stopped_top_;
|
|
||||||
QLabel *label_stopped_logo_;
|
|
||||||
QLabel *label_stopped_text_;
|
|
||||||
QLabel *label_playing_top_;
|
|
||||||
QLabel *label_playing_album_;
|
|
||||||
QLabel *label_playing_text_;
|
|
||||||
|
|
||||||
QPixmap *pixmap_album_;
|
enum WidgetState {
|
||||||
QPainter *painter_album_;
|
//State_None = 0,
|
||||||
|
State_Playing,
|
||||||
|
State_Stopped
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *kSettingsGroup;
|
||||||
|
static const int kPadding;
|
||||||
|
static const int kGradientHead;
|
||||||
|
static const int kGradientTail;
|
||||||
|
static const int kMaxCoverSize;
|
||||||
|
static const int kBottomOffset;
|
||||||
|
static const int kTopBorder;
|
||||||
|
|
||||||
|
Application *app_;
|
||||||
|
Ui_ContextViewContainer *ui_;
|
||||||
CollectionView *collectionview_;
|
CollectionView *collectionview_;
|
||||||
|
WidgetState widgetstate_;
|
||||||
|
QMenu *menu_;
|
||||||
|
QTimeLine *timeline_fade_;
|
||||||
|
QImage image_strawberry_;
|
||||||
|
AlbumCoverChoiceController *album_cover_choice_controller_;
|
||||||
|
LyricsFetcher *lyrics_fetcher_;
|
||||||
|
bool active_;
|
||||||
|
bool downloading_covers_;
|
||||||
|
|
||||||
|
QAction *action_show_data_;
|
||||||
|
QAction *action_show_output_;
|
||||||
|
QAction *action_show_albums_;
|
||||||
|
QAction *action_show_lyrics_;
|
||||||
AlbumCoverLoaderOptions cover_loader_options_;
|
AlbumCoverLoaderOptions cover_loader_options_;
|
||||||
|
Song song_;
|
||||||
QImage original_;
|
Song song_empty_;
|
||||||
|
Song song_prev_;
|
||||||
void CreateWidget();
|
QImage image_original_;
|
||||||
void NoSongWidget();
|
QImage image_previous_;
|
||||||
void SongWidget();
|
QPixmap *pixmap_album_;
|
||||||
|
QPixmap pixmap_current_;
|
||||||
|
QPixmap pixmap_previous_;
|
||||||
|
QPainter *painter_album_;
|
||||||
|
qreal pixmap_previous_opacity_;
|
||||||
|
std::unique_ptr<QMovie> spinner_animation_;
|
||||||
|
|
||||||
|
QString prev_artist_;
|
||||||
|
QString lyrics_;
|
||||||
|
|
||||||
|
void LoadSettings();
|
||||||
void AddActions();
|
void AddActions();
|
||||||
|
void SetText(QLabel *label, int value, const QString &suffix, const QString &def = QString());
|
||||||
|
void NoSong();
|
||||||
|
void UpdateSong();
|
||||||
void SetImage(const QImage &image);
|
void SetImage(const QImage &image);
|
||||||
void DrawImage(QPainter *p);
|
void DrawImage(QPainter *p);
|
||||||
void ScaleCover();
|
void ScaleCover();
|
||||||
bool GetCoverAutomatically();
|
bool GetCoverAutomatically();
|
||||||
|
|
||||||
Application *app_;
|
|
||||||
AlbumCoverChoiceController *album_cover_choice_controller_;
|
|
||||||
|
|
||||||
QAction *fit_cover_width_action_;
|
protected:
|
||||||
|
|
||||||
bool visible_;
|
|
||||||
int small_ideal_height_;
|
|
||||||
int total_height_;
|
|
||||||
bool fit_width_;
|
|
||||||
QTimeLine *fade_animation_;
|
|
||||||
QImage image_blank_;
|
|
||||||
QImage image_nosong_;
|
|
||||||
|
|
||||||
// Information about the current track
|
|
||||||
Song metadata_;
|
|
||||||
QPixmap pixmap_current_;
|
|
||||||
|
|
||||||
// Holds the last track while we're fading to the new track
|
|
||||||
QPixmap pixmap_previous_;
|
|
||||||
qreal pixmap_previous_opacity_;
|
|
||||||
|
|
||||||
std::unique_ptr<QMovie> spinner_animation_;
|
|
||||||
bool downloading_covers_;
|
|
||||||
bool stopped_;
|
|
||||||
bool playing_;
|
|
||||||
|
|
||||||
enum WidgetState {
|
|
||||||
None = 0,
|
|
||||||
Playing,
|
|
||||||
Stopped
|
|
||||||
};
|
|
||||||
WidgetState widgetstate_;
|
|
||||||
QMenu *menu_;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool eventFilter(QObject *, QEvent *);
|
bool eventFilter(QObject *, QEvent *);
|
||||||
void handlePaintEvent(QObject *object, QEvent *event);
|
void handlePaintEvent(QObject *object, QEvent *event);
|
||||||
void paintEvent_album(QEvent *event);
|
void PaintEventAlbum(QEvent *event);
|
||||||
void contextMenuEvent(QContextMenuEvent *e);
|
void contextMenuEvent(QContextMenuEvent *e);
|
||||||
void mouseReleaseEvent(QMouseEvent *);
|
void mouseReleaseEvent(QMouseEvent *);
|
||||||
void dragEnterEvent(QDragEnterEvent *e);
|
void dragEnterEvent(QDragEnterEvent *e);
|
||||||
void dropEvent(QDropEvent *e);
|
void dropEvent(QDropEvent *e);
|
||||||
void UpdateSong();
|
|
||||||
void NoSong();
|
|
||||||
void SwitchWidgets(WidgetState state);
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void UpdateNoSong();
|
void ActionShowData();
|
||||||
|
void ActionShowOutput();
|
||||||
|
void ActionShowAlbums();
|
||||||
|
void ActionShowLyrics();
|
||||||
|
void FadePreviousTrack(qreal value);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // STATUSVIEW_H
|
#endif // CONTEXTVIEW_H
|
||||||
|
|
|
@ -0,0 +1,567 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>ContextViewContainer</class>
|
||||||
|
<widget class="QWidget" name="ContextViewContainer">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>400</width>
|
||||||
|
<height>927</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="layout_container">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QStackedWidget" name="widget_stacked">
|
||||||
|
<widget class="QWidget" name="widget_stop">
|
||||||
|
<layout class="QVBoxLayout" name="widget_layout_stop">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="layout_stop">
|
||||||
|
<item>
|
||||||
|
<widget class="QScrollArea" name="scrollarea_stop">
|
||||||
|
<property name="widgetResizable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="scrollAreaWidgetContents_stop">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>396</width>
|
||||||
|
<height>923</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_stop_top">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>70</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>70</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>No song playing</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_stop_logo">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>300</width>
|
||||||
|
<height>300</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>300</width>
|
||||||
|
<height>300</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="pixmap">
|
||||||
|
<pixmap resource="../../data/data.qrc">:/pictures/strawberry.png</pixmap>
|
||||||
|
</property>
|
||||||
|
<property name="scaledContents">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_stop_summary">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="spacer_stop_bottom">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>53</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="widget_play">
|
||||||
|
<layout class="QVBoxLayout" name="widget_layout_play">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="layout_play">
|
||||||
|
<item>
|
||||||
|
<widget class="QScrollArea" name="scrollarea_play">
|
||||||
|
<property name="widgetResizable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="scrollAreaWidgetContents_play">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>396</width>
|
||||||
|
<height>923</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_play_top">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>70</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>70</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_play_album">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>300</width>
|
||||||
|
<height>300</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>300</width>
|
||||||
|
<height>300</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="spacer_play">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QGridLayout" name="layout_play_data">
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="label_bitdepth">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Bit depth</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QLabel" name="samplerate">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLabel" name="length">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_length">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Length</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLabel" name="filetype">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="label_samplerate">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Samplerate</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QLabel" name="bitdepth">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label_filetype">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Filetype</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QLabel" name="label_bitrate">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Bitrate</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="QLabel" name="bitrate">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="spacer_play_data">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QGridLayout" name="layout_play_grid">
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLabel" name="label_device_icon">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>32</width>
|
||||||
|
<height>32</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<widget class="QLabel" name="device">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>100</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="2">
|
||||||
|
<widget class="QLabel" name="engine">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>100</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLabel" name="label_engine_icon">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>32</width>
|
||||||
|
<height>32</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label_engine">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>60</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Engine</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_device">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>60</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Device</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="spacer_play_output">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_play_albums">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>300</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="indent">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="spacer_play_albums1">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>10</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="ContextAlbumsView" name="widget_play_albums" native="true"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="spacer_play_albums2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_play_lyrics">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="spacer_play_bottom">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>ContextAlbumsView</class>
|
||||||
|
<extends>QWidget</extends>
|
||||||
|
<header>context/contextalbumsview.h</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<resources>
|
||||||
|
<include location="../../data/data.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -52,6 +52,11 @@
|
||||||
#include "covermanager/discogscoverprovider.h"
|
#include "covermanager/discogscoverprovider.h"
|
||||||
#include "covermanager/musicbrainzcoverprovider.h"
|
#include "covermanager/musicbrainzcoverprovider.h"
|
||||||
|
|
||||||
|
#include "lyrics/lyricsproviders.h"
|
||||||
|
#include "lyrics/lyricsprovider.h"
|
||||||
|
#include "lyrics/auddlyricsprovider.h"
|
||||||
|
#include "lyrics/apiseedslyricsprovider.h"
|
||||||
|
|
||||||
#include "internet/internetmodel.h"
|
#include "internet/internetmodel.h"
|
||||||
#include "tidal/tidalsearch.h"
|
#include "tidal/tidalsearch.h"
|
||||||
|
|
||||||
|
@ -60,18 +65,18 @@ bool Application::kIsPortable = false;
|
||||||
class ApplicationImpl {
|
class ApplicationImpl {
|
||||||
public:
|
public:
|
||||||
ApplicationImpl(Application *app) :
|
ApplicationImpl(Application *app) :
|
||||||
tag_reader_client_([=]() {
|
tag_reader_client_([=]() {
|
||||||
TagReaderClient *client = new TagReaderClient(app);
|
TagReaderClient *client = new TagReaderClient(app);
|
||||||
app->MoveToNewThread(client);
|
app->MoveToNewThread(client);
|
||||||
client->Start();
|
client->Start();
|
||||||
return client;
|
return client;
|
||||||
}),
|
}),
|
||||||
database_([=]() {
|
database_([=]() {
|
||||||
Database *db = new Database(app, app);
|
Database *db = new Database(app, app);
|
||||||
app->MoveToNewThread(db);
|
app->MoveToNewThread(db);
|
||||||
DoInAMinuteOrSo(db, SLOT(DoBackup()));
|
DoInAMinuteOrSo(db, SLOT(DoBackup()));
|
||||||
return db;
|
return db;
|
||||||
}),
|
}),
|
||||||
appearance_([=]() { return new Appearance(app); }),
|
appearance_([=]() { return new Appearance(app); }),
|
||||||
task_manager_([=]() { return new TaskManager(app); }),
|
task_manager_([=]() { return new TaskManager(app); }),
|
||||||
player_([=]() { return new Player(app, app); }),
|
player_([=]() { return new Player(app, app); }),
|
||||||
|
@ -88,10 +93,10 @@ class ApplicationImpl {
|
||||||
CoverProviders *cover_providers = new CoverProviders(app);
|
CoverProviders *cover_providers = new CoverProviders(app);
|
||||||
// Initialize the repository of cover providers.
|
// Initialize the repository of cover providers.
|
||||||
#ifdef HAVE_LIBLASTFM
|
#ifdef HAVE_LIBLASTFM
|
||||||
cover_providers->AddProvider(new LastFmCoverProvider(app));
|
cover_providers->AddProvider(new LastFmCoverProvider(app));
|
||||||
#endif
|
#endif
|
||||||
cover_providers->AddProvider(new AmazonCoverProvider(app));
|
cover_providers->AddProvider(new AmazonCoverProvider(app));
|
||||||
cover_providers->AddProvider(new DiscogsCoverProvider(app));
|
cover_providers->AddProvider(new DiscogsCoverProvider(app));
|
||||||
cover_providers->AddProvider(new MusicbrainzCoverProvider(app));
|
cover_providers->AddProvider(new MusicbrainzCoverProvider(app));
|
||||||
return cover_providers;
|
return cover_providers;
|
||||||
}),
|
}),
|
||||||
|
@ -102,7 +107,13 @@ class ApplicationImpl {
|
||||||
}),
|
}),
|
||||||
current_art_loader_([=]() { return new CurrentArtLoader(app, app); }),
|
current_art_loader_([=]() { return new CurrentArtLoader(app, app); }),
|
||||||
internet_model_([=]() { return new InternetModel(app, app); }),
|
internet_model_([=]() { return new InternetModel(app, app); }),
|
||||||
tidal_search_([=]() { return new TidalSearch(app, app); })
|
tidal_search_([=]() { return new TidalSearch(app, app); }),
|
||||||
|
lyrics_providers_([=]() {
|
||||||
|
LyricsProviders *lyrics_providers = new LyricsProviders(app);
|
||||||
|
lyrics_providers->AddProvider(new AuddLyricsProvider(app));
|
||||||
|
lyrics_providers->AddProvider(new APISeedsLyricsProvider(app));
|
||||||
|
return lyrics_providers;
|
||||||
|
})
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
Lazy<TagReaderClient> tag_reader_client_;
|
Lazy<TagReaderClient> tag_reader_client_;
|
||||||
|
@ -120,6 +131,7 @@ class ApplicationImpl {
|
||||||
Lazy<CurrentArtLoader> current_art_loader_;
|
Lazy<CurrentArtLoader> current_art_loader_;
|
||||||
Lazy<InternetModel> internet_model_;
|
Lazy<InternetModel> internet_model_;
|
||||||
Lazy<TidalSearch> tidal_search_;
|
Lazy<TidalSearch> tidal_search_;
|
||||||
|
Lazy<LyricsProviders> lyrics_providers_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -227,3 +239,7 @@ InternetModel* Application::internet_model() const {
|
||||||
TidalSearch* Application::tidal_search() const {
|
TidalSearch* Application::tidal_search() const {
|
||||||
return p_->tidal_search_.get();
|
return p_->tidal_search_.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LyricsProviders *Application::lyrics_providers() const {
|
||||||
|
return p_->lyrics_providers_.get();
|
||||||
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ class AlbumCoverLoader;
|
||||||
class CurrentArtLoader;
|
class CurrentArtLoader;
|
||||||
class InternetModel;
|
class InternetModel;
|
||||||
class TidalSearch;
|
class TidalSearch;
|
||||||
|
class LyricsProviders;
|
||||||
|
|
||||||
class Application : public QObject {
|
class Application : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -84,6 +85,8 @@ class Application : public QObject {
|
||||||
InternetModel *internet_model() const;
|
InternetModel *internet_model() const;
|
||||||
TidalSearch *tidal_search() const;
|
TidalSearch *tidal_search() const;
|
||||||
|
|
||||||
|
LyricsProviders *lyrics_providers() const;
|
||||||
|
|
||||||
void MoveToNewThread(QObject *object);
|
void MoveToNewThread(QObject *object);
|
||||||
void MoveToThread(QObject *object, QThread *thread);
|
void MoveToThread(QObject *object, QThread *thread);
|
||||||
|
|
||||||
|
|
|
@ -507,15 +507,14 @@ void Database::ExecSchemaCommands(QSqlDatabase &db, const QString &schema, int s
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database::ExecSongTablesCommands(QSqlDatabase &db, const QStringList &song_tables, const QStringList &commands) {
|
void Database::ExecSongTablesCommands(QSqlDatabase &db, const QStringList &song_tables, const QStringList &commands) {
|
||||||
|
|
||||||
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(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_") &&
|
if (table.startsWith("device_") && command.contains(QString(kMagicAllSongsTables) + "_fts")) {
|
||||||
command.contains(QString(kMagicAllSongsTables) + "_fts")) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -526,7 +525,8 @@ void Database::ExecSongTablesCommands(QSqlDatabase &db, const QStringList &song_
|
||||||
if (CheckErrors(query))
|
if (CheckErrors(query))
|
||||||
qFatal("Unable to update music collection database");
|
qFatal("Unable to update music collection database");
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
QSqlQuery query(db.exec(command));
|
QSqlQuery query(db.exec(command));
|
||||||
if (CheckErrors(query)) qFatal("Unable to update music collection database");
|
if (CheckErrors(query)) qFatal("Unable to update music collection database");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,193 +0,0 @@
|
||||||
/****************************************************************************
|
|
||||||
**
|
|
||||||
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
|
|
||||||
** All rights reserved.
|
|
||||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
|
||||||
**
|
|
||||||
** This file is part of the examples of the Qt Toolkit.
|
|
||||||
**
|
|
||||||
** $QT_BEGIN_LICENSE:BSD$
|
|
||||||
** You may use this file under the terms of the BSD license as follows:
|
|
||||||
**
|
|
||||||
** "Redistribution and use in source and binary forms, with or without
|
|
||||||
** modification, are permitted provided that the following conditions are
|
|
||||||
** met:
|
|
||||||
** * Redistributions of source code must retain the above copyright
|
|
||||||
** notice, this list of conditions and the following disclaimer.
|
|
||||||
** * Redistributions in binary form must reproduce the above copyright
|
|
||||||
** notice, this list of conditions and the following disclaimer in
|
|
||||||
** the documentation and/or other materials provided with the
|
|
||||||
** distribution.
|
|
||||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
|
||||||
** the names of its contributors may be used to endorse or promote
|
|
||||||
** products derived from this software without specific prior written
|
|
||||||
** permission.
|
|
||||||
**
|
|
||||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
|
||||||
** $QT_END_LICENSE$
|
|
||||||
**
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QWidget>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QLayout>
|
|
||||||
#include <QLayoutItem>
|
|
||||||
#include <QStyle>
|
|
||||||
#include <QSize>
|
|
||||||
#include <QPoint>
|
|
||||||
#include <QRect>
|
|
||||||
#include <QSizePolicy>
|
|
||||||
|
|
||||||
#include "flowlayout.h"
|
|
||||||
|
|
||||||
//! [1]
|
|
||||||
FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing)
|
|
||||||
: QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing)
|
|
||||||
{
|
|
||||||
setContentsMargins(margin, margin, margin, margin);
|
|
||||||
}
|
|
||||||
|
|
||||||
FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing)
|
|
||||||
: m_hSpace(hSpacing), m_vSpace(vSpacing) {
|
|
||||||
setContentsMargins(margin, margin, margin, margin);
|
|
||||||
}
|
|
||||||
//! [1]
|
|
||||||
|
|
||||||
//! [2]
|
|
||||||
FlowLayout::~FlowLayout() {
|
|
||||||
QLayoutItem* item;
|
|
||||||
while ((item = takeAt(0))) delete item;
|
|
||||||
}
|
|
||||||
//! [2]
|
|
||||||
|
|
||||||
//! [3]
|
|
||||||
void FlowLayout::addItem(QLayoutItem* item) { itemList.append(item); }
|
|
||||||
//! [3]
|
|
||||||
|
|
||||||
//! [4]
|
|
||||||
int FlowLayout::horizontalSpacing() const {
|
|
||||||
if (m_hSpace >= 0) {
|
|
||||||
return m_hSpace;
|
|
||||||
} else {
|
|
||||||
return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int FlowLayout::verticalSpacing() const {
|
|
||||||
if (m_vSpace >= 0) {
|
|
||||||
return m_vSpace;
|
|
||||||
} else {
|
|
||||||
return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//! [4]
|
|
||||||
|
|
||||||
//! [5]
|
|
||||||
int FlowLayout::count() const { return itemList.size(); }
|
|
||||||
|
|
||||||
QLayoutItem* FlowLayout::itemAt(int index) const {
|
|
||||||
return itemList.value(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
QLayoutItem* FlowLayout::takeAt(int index) {
|
|
||||||
if (index >= 0 && index < itemList.size())
|
|
||||||
return itemList.takeAt(index);
|
|
||||||
else
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
//! [5]
|
|
||||||
|
|
||||||
//! [6]
|
|
||||||
Qt::Orientations FlowLayout::expandingDirections() const { return 0; }
|
|
||||||
//! [6]
|
|
||||||
|
|
||||||
//! [7]
|
|
||||||
bool FlowLayout::hasHeightForWidth() const { return true; }
|
|
||||||
|
|
||||||
int FlowLayout::heightForWidth(int width) const {
|
|
||||||
int height = doLayout(QRect(0, 0, width, 0), true);
|
|
||||||
return height;
|
|
||||||
}
|
|
||||||
//! [7]
|
|
||||||
|
|
||||||
//! [8]
|
|
||||||
void FlowLayout::setGeometry(const QRect& rect) {
|
|
||||||
QLayout::setGeometry(rect);
|
|
||||||
doLayout(rect, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
QSize FlowLayout::sizeHint() const { return minimumSize(); }
|
|
||||||
|
|
||||||
QSize FlowLayout::minimumSize() const {
|
|
||||||
QSize size;
|
|
||||||
for (QLayoutItem* item : itemList)
|
|
||||||
size = size.expandedTo(item->minimumSize());
|
|
||||||
|
|
||||||
size += QSize(2 * margin(), 2 * margin());
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
//! [8]
|
|
||||||
|
|
||||||
//! [9]
|
|
||||||
int FlowLayout::doLayout(const QRect& rect, bool testOnly) const {
|
|
||||||
int left, top, right, bottom;
|
|
||||||
getContentsMargins(&left, &top, &right, &bottom);
|
|
||||||
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
|
|
||||||
int x = effectiveRect.x();
|
|
||||||
int y = effectiveRect.y();
|
|
||||||
int lineHeight = 0;
|
|
||||||
//! [9]
|
|
||||||
|
|
||||||
//! [10]
|
|
||||||
for (QLayoutItem* item : itemList) {
|
|
||||||
QWidget* wid = item->widget();
|
|
||||||
int spaceX = horizontalSpacing();
|
|
||||||
if (spaceX == -1)
|
|
||||||
spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
|
|
||||||
int spaceY = verticalSpacing();
|
|
||||||
if (spaceY == -1)
|
|
||||||
spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
|
|
||||||
//! [10]
|
|
||||||
//! [11]
|
|
||||||
int nextX = x + item->sizeHint().width() + spaceX;
|
|
||||||
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
|
|
||||||
x = effectiveRect.x();
|
|
||||||
y = y + lineHeight + spaceY;
|
|
||||||
nextX = x + item->sizeHint().width() + spaceX;
|
|
||||||
lineHeight = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!testOnly) item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
|
|
||||||
|
|
||||||
x = nextX;
|
|
||||||
lineHeight = qMax(lineHeight, item->sizeHint().height());
|
|
||||||
}
|
|
||||||
return y + lineHeight - rect.y() + bottom;
|
|
||||||
}
|
|
||||||
//! [11]
|
|
||||||
//! [12]
|
|
||||||
int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const {
|
|
||||||
QObject* parent = this->parent();
|
|
||||||
if (!parent) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else if (parent->isWidgetType()) {
|
|
||||||
QWidget *pw = static_cast<QWidget *>(parent);
|
|
||||||
return pw->style()->pixelMetric(pm, 0, pw);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return static_cast<QLayout *>(parent)->spacing();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//! [12]
|
|
|
@ -1,84 +0,0 @@
|
||||||
/****************************************************************************
|
|
||||||
**
|
|
||||||
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
|
|
||||||
** All rights reserved.
|
|
||||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
|
||||||
**
|
|
||||||
** This file is part of the examples of the Qt Toolkit.
|
|
||||||
**
|
|
||||||
** $QT_BEGIN_LICENSE:BSD$
|
|
||||||
** You may use this file under the terms of the BSD license as follows:
|
|
||||||
**
|
|
||||||
** "Redistribution and use in source and binary forms, with or without
|
|
||||||
** modification, are permitted provided that the following conditions are
|
|
||||||
** met:
|
|
||||||
** * Redistributions of source code must retain the above copyright
|
|
||||||
** notice, this list of conditions and the following disclaimer.
|
|
||||||
** * Redistributions in binary form must reproduce the above copyright
|
|
||||||
** notice, this list of conditions and the following disclaimer in
|
|
||||||
** the documentation and/or other materials provided with the
|
|
||||||
** distribution.
|
|
||||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
|
||||||
** the names of its contributors may be used to endorse or promote
|
|
||||||
** products derived from this software without specific prior written
|
|
||||||
** permission.
|
|
||||||
**
|
|
||||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
|
||||||
** $QT_END_LICENSE$
|
|
||||||
**
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
#ifndef FLOWLAYOUT_H
|
|
||||||
#define FLOWLAYOUT_H
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include <QLayout>
|
|
||||||
#include <QWidget>
|
|
||||||
#include <QStyle>
|
|
||||||
#include <QList>
|
|
||||||
#include <QSize>
|
|
||||||
#include <QLayoutItem>
|
|
||||||
#include <QRect>
|
|
||||||
|
|
||||||
//! [0]
|
|
||||||
class FlowLayout : public QLayout {
|
|
||||||
public:
|
|
||||||
FlowLayout(QWidget* parent, int margin = -1, int hSpacing = -1, int vSpacing = -1);
|
|
||||||
FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1);
|
|
||||||
~FlowLayout();
|
|
||||||
|
|
||||||
void addItem(QLayoutItem *item);
|
|
||||||
int horizontalSpacing() const;
|
|
||||||
int verticalSpacing() const;
|
|
||||||
Qt::Orientations expandingDirections() const;
|
|
||||||
bool hasHeightForWidth() const;
|
|
||||||
int heightForWidth(int) const;
|
|
||||||
int count() const;
|
|
||||||
QLayoutItem *itemAt(int index) const;
|
|
||||||
QSize minimumSize() const;
|
|
||||||
void setGeometry(const QRect &rect);
|
|
||||||
QSize sizeHint() const;
|
|
||||||
QLayoutItem *takeAt(int index);
|
|
||||||
|
|
||||||
private:
|
|
||||||
int doLayout(const QRect &rect, bool testOnly) const;
|
|
||||||
int smartSpacing(QStyle::PixelMetric pm) const;
|
|
||||||
|
|
||||||
QList<QLayoutItem *> itemList;
|
|
||||||
int m_hSpace;
|
|
||||||
int m_vSpace;
|
|
||||||
};
|
|
||||||
//! [0]
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -31,34 +31,24 @@
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "iconloader.h"
|
#include "iconloader.h"
|
||||||
|
|
||||||
QList<int> IconLoader::sizes_;
|
QIcon IconLoader::Load(const QString &name, const int size) {
|
||||||
QString IconDefault(":/icons/64x64/strawberry.png");
|
|
||||||
|
|
||||||
void IconLoader::Init() {
|
|
||||||
|
|
||||||
sizes_.clear();
|
|
||||||
sizes_ << 22 << 32 << 48 << 64;
|
|
||||||
|
|
||||||
if (!QFile::exists(IconDefault)) {
|
|
||||||
qLog(Error) << "Default icon does not exist" << IconDefault;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
QIcon IconLoader::Load(const QString &name) {
|
|
||||||
|
|
||||||
QIcon ret;
|
QIcon ret;
|
||||||
|
|
||||||
|
QList<int> sizes;
|
||||||
|
sizes.clear();
|
||||||
|
if (size == 0) { sizes << 22 << 32 << 48 << 64; }
|
||||||
|
else sizes << size;
|
||||||
|
|
||||||
if (name.isEmpty()) {
|
if (name.isEmpty()) {
|
||||||
qLog(Warning) << "Icon name is empty!";
|
qLog(Error) << "Icon name is empty!";
|
||||||
ret.addFile(IconDefault, QSize(64, 64));
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString path(":icons/%1x%2/%3.png");
|
const QString path(":icons/%1x%2/%3.png");
|
||||||
for (int size : sizes_) {
|
for (int s : sizes) {
|
||||||
QString filename(path.arg(size).arg(size).arg(name));
|
QString filename(path.arg(s).arg(s).arg(name));
|
||||||
if (QFile::exists(filename)) ret.addFile(filename, QSize(size, size));
|
if (QFile::exists(filename)) ret.addFile(filename, QSize(s, s));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load icon from system theme only if it hasn't been found
|
// Load icon from system theme only if it hasn't been found
|
||||||
|
@ -68,10 +58,6 @@ QIcon IconLoader::Load(const QString &name) {
|
||||||
qLog(Warning) << "Couldn't load icon" << name;
|
qLog(Warning) << "Couldn't load icon" << name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret.isNull()) {
|
|
||||||
ret.addFile(IconDefault, QSize(64, 64));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,14 +27,10 @@
|
||||||
|
|
||||||
class IconLoader {
|
class IconLoader {
|
||||||
public:
|
public:
|
||||||
|
static QIcon Load(const QString &name, const int size = 0);
|
||||||
static void Init();
|
|
||||||
static QIcon Load(const QString &name);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
IconLoader() {}
|
IconLoader() {}
|
||||||
|
|
||||||
static QList<int> sizes_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ICONLOADER_H
|
#endif // ICONLOADER_H
|
||||||
|
|
||||||
|
|
|
@ -200,9 +200,6 @@ int main(int argc, char* argv[]) {
|
||||||
|
|
||||||
// Resources
|
// Resources
|
||||||
Q_INIT_RESOURCE(data);
|
Q_INIT_RESOURCE(data);
|
||||||
|
|
||||||
// Icons
|
|
||||||
IconLoader::Init();
|
|
||||||
|
|
||||||
Application app;
|
Application app;
|
||||||
|
|
||||||
|
|
|
@ -89,12 +89,13 @@
|
||||||
#include "widgets/fancytabwidget.h"
|
#include "widgets/fancytabwidget.h"
|
||||||
#include "widgets/playingwidget.h"
|
#include "widgets/playingwidget.h"
|
||||||
#include "widgets/sliderwidget.h"
|
#include "widgets/sliderwidget.h"
|
||||||
#include "widgets/statusview.h"
|
|
||||||
#include "widgets/fileview.h"
|
#include "widgets/fileview.h"
|
||||||
#include "widgets/multiloadingindicator.h"
|
#include "widgets/multiloadingindicator.h"
|
||||||
#include "widgets/osd.h"
|
#include "widgets/osd.h"
|
||||||
#include "widgets/stylehelper.h"
|
#include "widgets/stylehelper.h"
|
||||||
#include "widgets/trackslider.h"
|
#include "widgets/trackslider.h"
|
||||||
|
#include "context/contextview.h"
|
||||||
|
#include "collection/collectionview.h"
|
||||||
#include "collection/collection.h"
|
#include "collection/collection.h"
|
||||||
#include "collection/collectionbackend.h"
|
#include "collection/collectionbackend.h"
|
||||||
#include "collection/collectiondirectorymodel.h"
|
#include "collection/collectiondirectorymodel.h"
|
||||||
|
@ -158,12 +159,12 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
||||||
osd_(osd),
|
osd_(osd),
|
||||||
edit_tag_dialog_(std::bind(&MainWindow::CreateEditTagDialog, this)),
|
edit_tag_dialog_(std::bind(&MainWindow::CreateEditTagDialog, this)),
|
||||||
global_shortcuts_(new GlobalShortcuts(this)),
|
global_shortcuts_(new GlobalShortcuts(this)),
|
||||||
|
context_view_(new ContextView(this)),
|
||||||
collection_view_(new CollectionViewContainer(this)),
|
collection_view_(new CollectionViewContainer(this)),
|
||||||
status_view_(new StatusView(collection_view_, this)),
|
|
||||||
file_view_(new FileView(this)),
|
file_view_(new FileView(this)),
|
||||||
playlist_list_(new PlaylistListContainer(this)),
|
|
||||||
device_view_container_(new DeviceViewContainer(this)),
|
device_view_container_(new DeviceViewContainer(this)),
|
||||||
device_view_(device_view_container_->view()),
|
device_view_(device_view_container_->view()),
|
||||||
|
playlist_list_(new PlaylistListContainer(this)),
|
||||||
settings_dialog_(std::bind(&MainWindow::CreateSettingsDialog, this)),
|
settings_dialog_(std::bind(&MainWindow::CreateSettingsDialog, this)),
|
||||||
cover_manager_([=]() {
|
cover_manager_([=]() {
|
||||||
AlbumCoverManager *cover_manager = new AlbumCoverManager(app, app->collection_backend());
|
AlbumCoverManager *cover_manager = new AlbumCoverManager(app, app->collection_backend());
|
||||||
|
@ -195,7 +196,8 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
||||||
collection_sort_model_(new QSortFilterProxyModel(this)),
|
collection_sort_model_(new QSortFilterProxyModel(this)),
|
||||||
track_position_timer_(new QTimer(this)),
|
track_position_timer_(new QTimer(this)),
|
||||||
track_slider_timer_(new QTimer(this)),
|
track_slider_timer_(new QTimer(this)),
|
||||||
was_maximized_(false),
|
initialised_(false),
|
||||||
|
was_maximized_(true),
|
||||||
saved_playback_position_(0),
|
saved_playback_position_(0),
|
||||||
saved_playback_state_(Engine::Empty),
|
saved_playback_state_(Engine::Empty),
|
||||||
doubleclick_addmode_(AddBehaviour_Append),
|
doubleclick_addmode_(AddBehaviour_Append),
|
||||||
|
@ -214,7 +216,8 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ui_->multi_loading_indicator->SetTaskManager(app_->task_manager());
|
ui_->multi_loading_indicator->SetTaskManager(app_->task_manager());
|
||||||
status_view_->SetApplication(app_);
|
context_view_->SetApplication(app_);
|
||||||
|
context_view_->SetCollectionView(collection_view_->view());
|
||||||
ui_->widget_playing->SetApplication(app_);
|
ui_->widget_playing->SetApplication(app_);
|
||||||
|
|
||||||
int volume = app_->player()->GetVolume();
|
int volume = app_->player()->GetVolume();
|
||||||
|
@ -225,7 +228,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
||||||
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(status_view_, IconLoader::Load("strawberry"), tr("Status"));
|
ui_->tabs->addTab(context_view_, IconLoader::Load("strawberry"), tr("Context"));
|
||||||
ui_->tabs->addTab(collection_view_, IconLoader::Load("vinyl"), tr("Collection"));
|
ui_->tabs->addTab(collection_view_, IconLoader::Load("vinyl"), tr("Collection"));
|
||||||
ui_->tabs->addTab(file_view_, IconLoader::Load("document-open"), tr("Files"));
|
ui_->tabs->addTab(file_view_, IconLoader::Load("document-open"), tr("Files"));
|
||||||
ui_->tabs->addTab(playlist_list_, IconLoader::Load("view-media-playlist"), tr("Playlists"));
|
ui_->tabs->addTab(playlist_list_, IconLoader::Load("view-media-playlist"), tr("Playlists"));
|
||||||
|
@ -435,6 +438,10 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
||||||
connect(ui_->playlist->view(), SIGNAL(BackgroundPropertyChanged()), SLOT(RefreshStyleSheet()));
|
connect(ui_->playlist->view(), SIGNAL(BackgroundPropertyChanged()), SLOT(RefreshStyleSheet()));
|
||||||
|
|
||||||
connect(ui_->track_slider, SIGNAL(ValueChangedSeconds(int)), app_->player(), SLOT(SeekTo(int)));
|
connect(ui_->track_slider, SIGNAL(ValueChangedSeconds(int)), app_->player(), SLOT(SeekTo(int)));
|
||||||
|
|
||||||
|
// Context connections
|
||||||
|
|
||||||
|
connect(context_view_->albums(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
||||||
|
|
||||||
// Collection connections
|
// Collection connections
|
||||||
connect(collection_view_->view(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
connect(collection_view_->view(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
||||||
|
@ -581,8 +588,12 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
||||||
connect(ui_->tabs, SIGNAL(CurrentChanged(int)), SLOT(TabSwitched()));
|
connect(ui_->tabs, SIGNAL(CurrentChanged(int)), SLOT(TabSwitched()));
|
||||||
connect(ui_->tabs, SIGNAL(CurrentChanged(int)), SLOT(SaveGeometry()));
|
connect(ui_->tabs, SIGNAL(CurrentChanged(int)), SLOT(SaveGeometry()));
|
||||||
|
|
||||||
// Status
|
// Context
|
||||||
ConnectStatusView(status_view_);
|
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), context_view_, SLOT(SongChanged(Song)));
|
||||||
|
connect(app_->player(), SIGNAL(PlaylistFinished()), context_view_, SLOT(Stopped()));
|
||||||
|
connect(app_->player(), SIGNAL(Playing()), context_view_, SLOT(Playing()));
|
||||||
|
connect(app_->player(), SIGNAL(Stopped()), context_view_, SLOT(Stopped()));
|
||||||
|
connect(app_->player(), SIGNAL(Error()), context_view_, SLOT(Error()));
|
||||||
|
|
||||||
// Analyzer
|
// Analyzer
|
||||||
//ui_->analyzer->SetEngine(app_->player()->engine());
|
//ui_->analyzer->SetEngine(app_->player()->engine());
|
||||||
|
@ -611,8 +622,12 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
||||||
// Playing widget
|
// Playing widget
|
||||||
qLog(Debug) << "Creating playing widget";
|
qLog(Debug) << "Creating playing widget";
|
||||||
ui_->widget_playing->set_ideal_height(ui_->status_bar->sizeHint().height() + ui_->player_controls->sizeHint().height());
|
ui_->widget_playing->set_ideal_height(ui_->status_bar->sizeHint().height() + ui_->player_controls->sizeHint().height());
|
||||||
|
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), ui_->widget_playing, SLOT(SongChanged(Song)));
|
||||||
|
connect(app_->player(), SIGNAL(PlaylistFinished()), ui_->widget_playing, SLOT(Stopped()));
|
||||||
|
connect(app_->player(), SIGNAL(Playing()), ui_->widget_playing, SLOT(Playing()));
|
||||||
connect(app_->player(), SIGNAL(Stopped()), ui_->widget_playing, SLOT(Stopped()));
|
connect(app_->player(), SIGNAL(Stopped()), ui_->widget_playing, SLOT(Stopped()));
|
||||||
//connect(ui_->widget_playing, SIGNAL(ShowAboveStatusBarChanged(bool)), SLOT(PlayingWidgetPositionChanged(bool)));
|
connect(app_->player(), SIGNAL(Error()), ui_->widget_playing, SLOT(Error()));
|
||||||
|
|
||||||
connect(ui_->action_console, SIGNAL(triggered()), SLOT(ShowConsole()));
|
connect(ui_->action_console, SIGNAL(triggered()), SLOT(ShowConsole()));
|
||||||
PlayingWidgetPositionChanged();
|
PlayingWidgetPositionChanged();
|
||||||
|
|
||||||
|
@ -622,7 +637,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
||||||
const_cast<QPalette&>(Appearance::kDefaultPalette) = QApplication::palette();
|
const_cast<QPalette&>(Appearance::kDefaultPalette) = QApplication::palette();
|
||||||
app_->appearance()->LoadUserTheme();
|
app_->appearance()->LoadUserTheme();
|
||||||
StyleSheetLoader *css_loader = new StyleSheetLoader(this);
|
StyleSheetLoader *css_loader = new StyleSheetLoader(this);
|
||||||
css_loader->SetStyleSheet(this, ":style/mainwindow.css");
|
css_loader->SetStyleSheet(this, ":/style/strawberry.css");
|
||||||
RefreshStyleSheet();
|
RefreshStyleSheet();
|
||||||
|
|
||||||
// Load playlists
|
// Load playlists
|
||||||
|
@ -643,15 +658,15 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
||||||
|
|
||||||
// Set last used geometry to position window on the correct monitor
|
// Set last used geometry to position window on the correct monitor
|
||||||
// Set window state only if the window was last maximized
|
// Set window state only if the window was last maximized
|
||||||
was_maximized_ = settings_.value("maximized", false).toBool();
|
was_maximized_ = settings_.value("maximized", true).toBool();
|
||||||
restoreGeometry(settings_.value("geometry").toByteArray());
|
|
||||||
if (was_maximized_) {
|
if (was_maximized_) setWindowState(windowState() | Qt::WindowMaximized);
|
||||||
setWindowState(windowState() | Qt::WindowMaximized);
|
else restoreGeometry(settings_.value("geometry").toByteArray());
|
||||||
}
|
|
||||||
|
|
||||||
if (!ui_->splitter->restoreState(settings_.value("splitter_state").toByteArray())) {
|
if (!ui_->splitter->restoreState(settings_.value("splitter_state").toByteArray())) {
|
||||||
ui_->splitter->setSizes(QList<int>() << 300 << width() - 300);
|
ui_->splitter->setSizes(QList<int>() << 250 << width() - 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui_->tabs->setCurrentIndex(settings_.value("current_tab", 1 /* Collection tab */ ).toInt());
|
ui_->tabs->setCurrentIndex(settings_.value("current_tab", 1 /* Collection tab */ ).toInt());
|
||||||
FancyTabWidget::Mode default_mode = FancyTabWidget::Mode_LargeSidebar;
|
FancyTabWidget::Mode default_mode = FancyTabWidget::Mode_LargeSidebar;
|
||||||
ui_->tabs->SetMode(FancyTabWidget::Mode(settings_.value("tab_mode", default_mode).toInt()));
|
ui_->tabs->SetMode(FancyTabWidget::Mode(settings_.value("tab_mode", default_mode).toInt()));
|
||||||
|
@ -715,6 +730,8 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
||||||
|
|
||||||
qLog(Debug) << "Started";
|
qLog(Debug) << "Started";
|
||||||
RefreshStyleSheet();
|
RefreshStyleSheet();
|
||||||
|
|
||||||
|
initialised_ = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -733,8 +750,6 @@ void MainWindow::ReloadSettings() {
|
||||||
bool showtrayicon = settings.value("showtrayicon", true).toBool();
|
bool showtrayicon = settings.value("showtrayicon", true).toBool();
|
||||||
settings.endGroup();
|
settings.endGroup();
|
||||||
|
|
||||||
//qLog(Debug) << "showtrayicon" << showtrayicon;
|
|
||||||
|
|
||||||
tray_icon_->SetVisible(showtrayicon);
|
tray_icon_->SetVisible(showtrayicon);
|
||||||
if (!showtrayicon && !isVisible()) show();
|
if (!showtrayicon && !isVisible()) show();
|
||||||
#endif
|
#endif
|
||||||
|
@ -766,7 +781,7 @@ void MainWindow::RefreshStyleSheet() {
|
||||||
setStyleSheet(styleSheet());
|
setStyleSheet(styleSheet());
|
||||||
}
|
}
|
||||||
void MainWindow::MediaStopped() {
|
void MainWindow::MediaStopped() {
|
||||||
|
|
||||||
setWindowTitle("Strawberry Music Player");
|
setWindowTitle("Strawberry Music Player");
|
||||||
|
|
||||||
ui_->action_stop->setEnabled(false);
|
ui_->action_stop->setEnabled(false);
|
||||||
|
@ -828,7 +843,6 @@ void MainWindow::VolumeChanged(int volume) {
|
||||||
|
|
||||||
void MainWindow::SongChanged(const Song &song) {
|
void MainWindow::SongChanged(const Song &song) {
|
||||||
|
|
||||||
//setWindowTitle(song.PrettyTitleWithArtist() + " --- Strawberry Music Player");
|
|
||||||
setWindowTitle(song.PrettyTitleWithArtist());
|
setWindowTitle(song.PrettyTitleWithArtist());
|
||||||
tray_icon_->SetProgress(0);
|
tray_icon_->SetProgress(0);
|
||||||
|
|
||||||
|
@ -853,7 +867,14 @@ void MainWindow::TrackSkipped(PlaylistItemPtr item) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::resizeEvent(QResizeEvent*) { SaveGeometry(); }
|
void MainWindow::changeEvent(QEvent *event) {
|
||||||
|
if (!initialised_) return;
|
||||||
|
SaveGeometry();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::resizeEvent(QResizeEvent *event) {
|
||||||
|
SaveGeometry();
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::TabSwitched() {
|
void MainWindow::TabSwitched() {
|
||||||
|
|
||||||
|
@ -870,10 +891,8 @@ void MainWindow::SaveGeometry() {
|
||||||
|
|
||||||
was_maximized_ = isMaximized();
|
was_maximized_ = isMaximized();
|
||||||
settings_.setValue("maximized", was_maximized_);
|
settings_.setValue("maximized", was_maximized_);
|
||||||
// Save the geometry only when mainwindow is not in maximized state
|
if (was_maximized_) settings_.remove("geometry");
|
||||||
if (!was_maximized_) {
|
else settings_.setValue("geometry", saveGeometry());
|
||||||
settings_.setValue("geometry", saveGeometry());
|
|
||||||
}
|
|
||||||
settings_.setValue("splitter_state", ui_->splitter->saveState());
|
settings_.setValue("splitter_state", ui_->splitter->saveState());
|
||||||
settings_.setValue("current_tab", ui_->tabs->currentIndex());
|
settings_.setValue("current_tab", ui_->tabs->currentIndex());
|
||||||
settings_.setValue("tab_mode", ui_->tabs->mode());
|
settings_.setValue("tab_mode", ui_->tabs->mode());
|
||||||
|
@ -2104,34 +2123,6 @@ void MainWindow::ShowQueueManager() {
|
||||||
queue_manager_->show();
|
queue_manager_->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
|
||||||
void MainWindow::ConnectInfoView(SongInfoBase *view) {
|
|
||||||
|
|
||||||
QObject::connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), view, SLOT(SongChanged(Song)));
|
|
||||||
QObject::connect(app_->player(), SIGNAL(PlaylistFinished()), view, SLOT(SongFinished()));
|
|
||||||
QObject::connect(app_->player(), SIGNAL(Stopped()), view, SLOT(SongFinished()));
|
|
||||||
|
|
||||||
QObject::connect(view, SIGNAL(ShowSettingsDialog()), SLOT(ShowSongInfoConfig()));
|
|
||||||
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void MainWindow::ConnectStatusView(StatusView *statusview) {
|
|
||||||
|
|
||||||
QObject::connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), statusview, SLOT(SongChanged(Song)));
|
|
||||||
QObject::connect(app_->player(), SIGNAL(PlaylistFinished()), statusview, SLOT(SongFinished()));
|
|
||||||
QObject::connect(app_->player(), SIGNAL(Stopped()), statusview, SLOT(SongFinished()));
|
|
||||||
|
|
||||||
//QObject::connect(statusview, SIGNAL(ShowSettingsDialog()), SLOT(ShowSongInfoConfig()));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
void MainWindow::ShowSongInfoConfig() {
|
|
||||||
OpenSettingsDialogAtPage(SettingsDialog::Page_SongInformation);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void MainWindow::PlaylistViewSelectionModelChanged() {
|
void MainWindow::PlaylistViewSelectionModelChanged() {
|
||||||
|
|
||||||
connect(ui_->playlist->view()->selectionModel(),SIGNAL(currentChanged(QModelIndex, QModelIndex)), SLOT(PlaylistCurrentChanged(QModelIndex)));
|
connect(ui_->playlist->view()->selectionModel(),SIGNAL(currentChanged(QModelIndex, QModelIndex)), SLOT(PlaylistCurrentChanged(QModelIndex)));
|
||||||
|
|
|
@ -59,6 +59,7 @@
|
||||||
class About;
|
class About;
|
||||||
class AlbumCoverManager;;
|
class AlbumCoverManager;;
|
||||||
class Application;
|
class Application;
|
||||||
|
class ContextView;
|
||||||
class CollectionViewContainer;
|
class CollectionViewContainer;
|
||||||
class CommandlineOptions;
|
class CommandlineOptions;
|
||||||
class DeviceView;
|
class DeviceView;
|
||||||
|
@ -73,7 +74,6 @@ class OrganiseDialog;
|
||||||
class PlaylistListContainer;
|
class PlaylistListContainer;
|
||||||
class QueueManager;
|
class QueueManager;
|
||||||
class Song;
|
class Song;
|
||||||
class StatusView;
|
|
||||||
class SystemTrayIcon;
|
class SystemTrayIcon;
|
||||||
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
|
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
|
||||||
class TagFetcher;
|
class TagFetcher;
|
||||||
|
@ -129,6 +129,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void keyPressEvent(QKeyEvent *event);
|
void keyPressEvent(QKeyEvent *event);
|
||||||
|
void changeEvent(QEvent *event);
|
||||||
void resizeEvent(QResizeEvent *event);
|
void resizeEvent(QResizeEvent *event);
|
||||||
void closeEvent(QCloseEvent *event);
|
void closeEvent(QCloseEvent *event);
|
||||||
|
|
||||||
|
@ -270,7 +271,6 @@ signals:
|
||||||
void SearchForAlbum();
|
void SearchForAlbum();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ConnectStatusView(StatusView *statusview);
|
|
||||||
|
|
||||||
void ApplyAddBehaviour(AddBehaviour b, MimeData *data) const;
|
void ApplyAddBehaviour(AddBehaviour b, MimeData *data) const;
|
||||||
void ApplyPlayBehaviour(PlayBehaviour b, MimeData *data) const;
|
void ApplyPlayBehaviour(PlayBehaviour b, MimeData *data) const;
|
||||||
|
@ -292,12 +292,12 @@ signals:
|
||||||
|
|
||||||
GlobalShortcuts *global_shortcuts_;
|
GlobalShortcuts *global_shortcuts_;
|
||||||
|
|
||||||
|
ContextView *context_view_;
|
||||||
CollectionViewContainer *collection_view_;
|
CollectionViewContainer *collection_view_;
|
||||||
StatusView *status_view_;
|
|
||||||
FileView *file_view_;
|
FileView *file_view_;
|
||||||
PlaylistListContainer *playlist_list_;
|
|
||||||
DeviceViewContainer *device_view_container_;
|
DeviceViewContainer *device_view_container_;
|
||||||
DeviceView *device_view_;
|
DeviceView *device_view_;
|
||||||
|
PlaylistListContainer *playlist_list_;
|
||||||
|
|
||||||
Lazy<SettingsDialog> settings_dialog_;
|
Lazy<SettingsDialog> settings_dialog_;
|
||||||
Lazy<AlbumCoverManager> cover_manager_;
|
Lazy<AlbumCoverManager> cover_manager_;
|
||||||
|
@ -354,6 +354,7 @@ signals:
|
||||||
QTimer *track_slider_timer_;
|
QTimer *track_slider_timer_;
|
||||||
QSettings settings_;
|
QSettings settings_;
|
||||||
|
|
||||||
|
bool initialised_;
|
||||||
bool was_maximized_;
|
bool was_maximized_;
|
||||||
int saved_playback_position_;
|
int saved_playback_position_;
|
||||||
Engine::State saved_playback_state_;
|
Engine::State saved_playback_state_;
|
||||||
|
|
|
@ -431,6 +431,7 @@ void Player::EngineStateChanged(Engine::State state) {
|
||||||
emit Playing();
|
emit Playing();
|
||||||
break;
|
break;
|
||||||
case Engine::Error:
|
case Engine::Error:
|
||||||
|
emit Error();
|
||||||
case Engine::Empty:
|
case Engine::Empty:
|
||||||
case Engine::Idle:
|
case Engine::Idle:
|
||||||
emit Stopped();
|
emit Stopped();
|
||||||
|
|
|
@ -95,10 +95,11 @@ class PlayerInterface : public QObject {
|
||||||
virtual void Play() = 0;
|
virtual void Play() = 0;
|
||||||
virtual void ShowOSD() = 0;
|
virtual void ShowOSD() = 0;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void Playing();
|
void Playing();
|
||||||
void Paused();
|
void Paused();
|
||||||
void Stopped();
|
void Stopped();
|
||||||
|
void Error();
|
||||||
void PlaylistFinished();
|
void PlaylistFinished();
|
||||||
void VolumeChanged(int volume);
|
void VolumeChanged(int volume);
|
||||||
void Error(const QString &message);
|
void Error(const QString &message);
|
||||||
|
|
|
@ -47,12 +47,12 @@ QtSystemTrayIcon::QtSystemTrayIcon(QObject *parent)
|
||||||
action_mute_(nullptr)
|
action_mute_(nullptr)
|
||||||
{
|
{
|
||||||
|
|
||||||
QIcon theme_icon = IconLoader::Load("strawberry-panel");
|
QIcon theme_icon = IconLoader::Load("strawberry", 48);
|
||||||
QIcon theme_icon_grey = IconLoader::Load("strawberry-panel-grey");
|
QIcon theme_icon_grey = IconLoader::Load("strawberry-grey", 48);
|
||||||
|
|
||||||
if (theme_icon.isNull() || theme_icon_grey.isNull()) {
|
if (theme_icon.isNull() || theme_icon_grey.isNull()) {
|
||||||
// Load the default icon
|
// Load the default icon
|
||||||
QIcon icon(":/icons/64x64/strawberry-panel.png");
|
QIcon icon(":/icons/48x48/strawberry.png");
|
||||||
normal_icon_ = icon.pixmap(48, QIcon::Normal);
|
normal_icon_ = icon.pixmap(48, QIcon::Normal);
|
||||||
grey_icon_ = icon.pixmap(48, QIcon::Disabled);
|
grey_icon_ = icon.pixmap(48, QIcon::Disabled);
|
||||||
}
|
}
|
||||||
|
|
|
@ -693,7 +693,7 @@ void Song::InitFromFilePartial(const QString &filename) {
|
||||||
|
|
||||||
TagLib::FileRef fileref(filename.toUtf8().constData());
|
TagLib::FileRef fileref(filename.toUtf8().constData());
|
||||||
//if (TagLib::FileRef::defaultFileExtensions().contains(suffix.toUtf8().constData())) {
|
//if (TagLib::FileRef::defaultFileExtensions().contains(suffix.toUtf8().constData())) {
|
||||||
if (fileref.file()) d->valid_ = true;
|
if (fileref.file() || (suffix == "dsf")) d->valid_ = true;
|
||||||
else {
|
else {
|
||||||
d->valid_ = false;
|
d->valid_ = false;
|
||||||
qLog(Error) << "File" << filename << "is not recognized by TagLib as a valid audio file.";
|
qLog(Error) << "File" << filename << "is not recognized by TagLib as a valid audio file.";
|
||||||
|
|
|
@ -118,9 +118,10 @@ SongLoader::Result SongLoader::Load(const QUrl &url) {
|
||||||
return LoadLocal(url_.toLocalFile());
|
return LoadLocal(url_.toLocalFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sRawUriSchemes.contains(url_.scheme()) || player_->HandlerForUrl(url) != nullptr) {
|
if (sRawUriSchemes.contains(url_.scheme()) || player_->HandlerForUrl(url)) {
|
||||||
// The URI scheme indicates that it can't possibly be a playlist,
|
// The URI scheme indicates that it can't possibly be a playlist,
|
||||||
// or we have a custom handler for the URL, so add it as a raw stream. AddAsRawStream();
|
// or we have a custom handler for the URL, so add it as a raw stream.
|
||||||
|
AddAsRawStream();
|
||||||
return Success;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,7 @@ AlbumCoverChoiceController::AlbumCoverChoiceController(QWidget *parent) :
|
||||||
unset_cover_ = new QAction(IconLoader::Load("list-remove"), tr("Unset cover"), this);
|
unset_cover_ = new QAction(IconLoader::Load("list-remove"), tr("Unset cover"), this);
|
||||||
show_cover_ = new QAction(IconLoader::Load("zoom-in"), tr("Show fullsize..."), this);
|
show_cover_ = new QAction(IconLoader::Load("zoom-in"), tr("Show fullsize..."), this);
|
||||||
|
|
||||||
search_cover_auto_ = new QAction(IconLoader::Load("search"), tr("Search automatically"), this);
|
search_cover_auto_ = new QAction(tr("Search automatically"), this);
|
||||||
search_cover_auto_->setCheckable(true);
|
search_cover_auto_->setCheckable(true);
|
||||||
search_cover_auto_->setChecked(false);
|
search_cover_auto_->setChecked(false);
|
||||||
|
|
||||||
|
|
|
@ -58,26 +58,26 @@ class AlbumCoverFetcherSearch : public QObject {
|
||||||
|
|
||||||
CoverSearchStatistics statistics() const { return statistics_; }
|
CoverSearchStatistics statistics() const { return statistics_; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
// It's the end of search (when there was no fetch-me-a-cover request).
|
// It's the end of search (when there was no fetch-me-a-cover request).
|
||||||
void SearchFinished(quint64, const CoverSearchResults& results);
|
void SearchFinished(quint64, const CoverSearchResults &results);
|
||||||
|
|
||||||
// It's the end of search and we've fetched a cover.
|
// It's the end of search and we've fetched a cover.
|
||||||
void AlbumCoverFetched(quint64, const QImage &cover);
|
void AlbumCoverFetched(quint64, const QImage &cover);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void ProviderSearchFinished(int id, const QList<CoverSearchResult> &results);
|
void ProviderSearchFinished(int id, const QList<CoverSearchResult> &results);
|
||||||
void ProviderCoverFetchFinished(RedirectFollower *reply);
|
void ProviderCoverFetchFinished(RedirectFollower *reply);
|
||||||
void TerminateSearch();
|
void TerminateSearch();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void AllProvidersFinished();
|
void AllProvidersFinished();
|
||||||
|
|
||||||
void FetchMoreImages();
|
void FetchMoreImages();
|
||||||
float ScoreImage(const QImage &image) const;
|
float ScoreImage(const QImage &image) const;
|
||||||
void SendBestImage();
|
void SendBestImage();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const int kSearchTimeoutMs;
|
static const int kSearchTimeoutMs;
|
||||||
static const int kImageLoadTimeoutMs;
|
static const int kImageLoadTimeoutMs;
|
||||||
static const int kTargetSize;
|
static const int kTargetSize;
|
||||||
|
|
|
@ -226,6 +226,7 @@ void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply) {
|
||||||
}
|
}
|
||||||
|
|
||||||
NextState(&task);
|
NextState(&task);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage AlbumCoverLoader::ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image) {
|
QImage AlbumCoverLoader::ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image) {
|
||||||
|
|
|
@ -92,9 +92,8 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec
|
||||||
cover_searcher_(nullptr),
|
cover_searcher_(nullptr),
|
||||||
cover_export_(nullptr),
|
cover_export_(nullptr),
|
||||||
cover_exporter_(new AlbumCoverExporter(this)),
|
cover_exporter_(new AlbumCoverExporter(this)),
|
||||||
artist_icon_(IconLoader::Load("guitar" )),
|
artist_icon_(IconLoader::Load("folder-sound" )),
|
||||||
all_artists_icon_(IconLoader::Load("cd" )),
|
all_artists_icon_(IconLoader::Load("vinyl" )),
|
||||||
//no_cover_icon_(IconLoader::Load("nocover")),
|
|
||||||
no_cover_icon_(":/pictures/noalbumart.png"),
|
no_cover_icon_(":/pictures/noalbumart.png"),
|
||||||
no_cover_image_(GenerateNoCoverImage(no_cover_icon_)),
|
no_cover_image_(GenerateNoCoverImage(no_cover_icon_)),
|
||||||
no_cover_item_icon_(QPixmap::fromImage(no_cover_image_)),
|
no_cover_item_icon_(QPixmap::fromImage(no_cover_image_)),
|
||||||
|
|
|
@ -111,7 +111,7 @@ void AmazonCoverProvider::QueryFinished(QNetworkReply *reply, int id) {
|
||||||
|
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
QString data=(QString)reply->readAll();
|
QString data(reply->readAll());
|
||||||
|
|
||||||
CoverSearchResults results;
|
CoverSearchResults results;
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
class LastFmCoverProvider : public CoverProvider {
|
class LastFmCoverProvider : public CoverProvider {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit LastFmCoverProvider(QObject *parent = nullptr);
|
explicit LastFmCoverProvider(QObject *parent = nullptr);
|
||||||
|
|
||||||
bool StartSearch(const QString &artist, const QString &album, int id);
|
bool StartSearch(const QString &artist, const QString &album, int id);
|
||||||
|
@ -45,10 +45,10 @@ public:
|
||||||
static const char *kApiKey;
|
static const char *kApiKey;
|
||||||
static const char *kSecret;
|
static const char *kSecret;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void QueryFinished(QNetworkReply *reply, int id);
|
void QueryFinished(QNetworkReply *reply, int id);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QNetworkAccessManager *network_;
|
QNetworkAccessManager *network_;
|
||||||
QMap <QNetworkReply *, int> pending_queries_;
|
QMap <QNetworkReply *, int> pending_queries_;
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ FilesystemDevice::FilesystemDevice(const QUrl &url, DeviceLister *lister, const
|
||||||
watcher_->set_backend(backend_);
|
watcher_->set_backend(backend_);
|
||||||
watcher_->set_task_manager(app_->task_manager());
|
watcher_->set_task_manager(app_->task_manager());
|
||||||
|
|
||||||
connect(backend_, SIGNAL(DirectoryDiscovered(Directory,SubdirectoryList)), watcher_, SLOT(AddDirectory(Directory,SubdirectoryList)));
|
connect(backend_, SIGNAL(DirectoryDiscovered(Directory, SubdirectoryList)), watcher_, SLOT(AddDirectory(Directory, SubdirectoryList)));
|
||||||
connect(backend_, SIGNAL(DirectoryDeleted(Directory)), watcher_, SLOT(RemoveDirectory(Directory)));
|
connect(backend_, SIGNAL(DirectoryDeleted(Directory)), watcher_, SLOT(RemoveDirectory(Directory)));
|
||||||
connect(watcher_, SIGNAL(NewOrUpdatedSongs(SongList)), backend_, SLOT(AddOrUpdateSongs(SongList)));
|
connect(watcher_, SIGNAL(NewOrUpdatedSongs(SongList)), backend_, SLOT(AddOrUpdateSongs(SongList)));
|
||||||
connect(watcher_, SIGNAL(SongsMTimeUpdated(SongList)), backend_, SLOT(UpdateMTimesOnly(SongList)));
|
connect(watcher_, SIGNAL(SongsMTimeUpdated(SongList)), backend_, SLOT(UpdateMTimesOnly(SongList)));
|
||||||
|
|
|
@ -101,6 +101,8 @@ QList<DeviceFinder::Device> AlsaDeviceFinder::ListDevices() {
|
||||||
device.description = QString("%1 %2").arg(snd_ctl_card_info_get_name(cardinfo)).arg(snd_pcm_info_get_name(pcminfo));
|
device.description = QString("%1 %2").arg(snd_ctl_card_info_get_name(cardinfo)).arg(snd_pcm_info_get_name(pcminfo));
|
||||||
device.value = QString("hw:%1,%2").arg(card).arg(dev);
|
device.value = QString("hw:%1,%2").arg(card).arg(dev);
|
||||||
device.iconname = GuessIconName(device.description);
|
device.iconname = GuessIconName(device.description);
|
||||||
|
device.card = card;
|
||||||
|
device.device = dev;
|
||||||
ret.append(device);
|
ret.append(device);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,8 @@ class DeviceFinder {
|
||||||
QString description;
|
QString description;
|
||||||
QVariant value;
|
QVariant value;
|
||||||
QString iconname;
|
QString iconname;
|
||||||
|
int card;
|
||||||
|
int device;
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual ~DeviceFinder() {}
|
virtual ~DeviceFinder() {}
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
|
|
||||||
#include "engine_fwd.h"
|
#include "engine_fwd.h"
|
||||||
#include "enginetype.h"
|
#include "enginetype.h"
|
||||||
|
#include "enginedevice.h"
|
||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
|
@ -119,6 +120,8 @@ public:
|
||||||
|
|
||||||
static const int kScopeSize = 1024;
|
static const int kScopeSize = 1024;
|
||||||
|
|
||||||
|
QVariant device() { return device_; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
virtual void SetEqualizerEnabled(bool) {}
|
virtual void SetEqualizerEnabled(bool) {}
|
||||||
virtual void SetEqualizerParameters(int preamp, const QList<int> &bandGains) {}
|
virtual void SetEqualizerParameters(int preamp, const QList<int> &bandGains) {}
|
||||||
|
|
|
@ -0,0 +1,183 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QList>
|
||||||
|
#include <QPair>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringBuilder>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonValue>
|
||||||
|
|
||||||
|
#include "core/closure.h"
|
||||||
|
#include "core/logging.h"
|
||||||
|
#include "core/network.h"
|
||||||
|
#include "core/utilities.h"
|
||||||
|
#include "lyricsprovider.h"
|
||||||
|
#include "lyricsfetcher.h"
|
||||||
|
#include "apiseedslyricsprovider.h"
|
||||||
|
|
||||||
|
const char *APISeedsLyricsProvider::kUrlSearch = "https://orion.apiseeds.com/api/music/lyric";
|
||||||
|
const char *APISeedsLyricsProvider::kAPIKeyB64 = "REdWenJhR245Qm03cnE5NlhoS1pTd0V5UVNCNjBtTWVEZlp0ZEttVXhKZTRRdnZSbTRYcmlaUVlaMlM3c0JQUw==";
|
||||||
|
|
||||||
|
APISeedsLyricsProvider::APISeedsLyricsProvider(QObject *parent) : LyricsProvider("APISeeds", parent), network_(new NetworkAccessManager(this)) {}
|
||||||
|
|
||||||
|
bool APISeedsLyricsProvider::StartSearch(const QString &artist, const QString &album, const QString &title, quint64 id) {
|
||||||
|
|
||||||
|
typedef QPair<QString, QString> Arg;
|
||||||
|
typedef QList<Arg> ArgList;
|
||||||
|
typedef QPair<QByteArray, QByteArray> EncodedArg;
|
||||||
|
|
||||||
|
ArgList args = ArgList();
|
||||||
|
args.append(Arg("apikey", QByteArray::fromBase64(kAPIKeyB64)));
|
||||||
|
|
||||||
|
QUrlQuery url_query;
|
||||||
|
for (const Arg &arg : args) {
|
||||||
|
EncodedArg encoded_arg(QUrl::toPercentEncoding(arg.first), QUrl::toPercentEncoding(arg.second));
|
||||||
|
url_query.addQueryItem(encoded_arg.first, encoded_arg.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl url(QString("%1/%2/%3").arg(kUrlSearch).arg(artist).arg(title));
|
||||||
|
url.setQuery(url_query);
|
||||||
|
QNetworkReply *reply = network_->get(QNetworkRequest(url));
|
||||||
|
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleSearchReply(QNetworkReply*, quint64, QString, QString)), reply, id, artist, title);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void APISeedsLyricsProvider::CancelSearch(quint64 id) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void APISeedsLyricsProvider::HandleSearchReply(QNetworkReply *reply, quint64 id, const QString artist, const QString title) {
|
||||||
|
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
QJsonObject json_obj = ExtractResult(reply, id);
|
||||||
|
if (json_obj.isEmpty()) return;
|
||||||
|
|
||||||
|
if (!json_obj.contains("artist") || !json_obj.contains("track")) {
|
||||||
|
Error(id, "APISeedsLyrics: Invalid Json reply, result is missing artist or track.", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QJsonObject json_artist(json_obj["artist"].toObject());
|
||||||
|
QJsonObject json_track(json_obj["track"].toObject());
|
||||||
|
if (!json_track.contains("text")) {
|
||||||
|
Error(id, "APISeedsLyrics: Invalid Json reply, track is missing text.", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LyricsSearchResults results;
|
||||||
|
LyricsSearchResult result;
|
||||||
|
result.artist = json_artist["name"].toString();
|
||||||
|
result.title = json_track["name"].toString();
|
||||||
|
result.lyrics = json_track["text"].toString();
|
||||||
|
result.score = 0.0;
|
||||||
|
if (result.artist.toLower() == artist.toLower()) result.score += 1.0;
|
||||||
|
if (result.title.toLower() == title.toLower()) result.score += 1.0;
|
||||||
|
|
||||||
|
results << result;
|
||||||
|
|
||||||
|
emit SearchFinished(id, results);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject APISeedsLyricsProvider::ExtractJsonObj(QNetworkReply *reply, quint64 id) {
|
||||||
|
|
||||||
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
QString failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||||
|
Error(id, failure_reason);
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray data(reply->readAll());
|
||||||
|
|
||||||
|
QJsonParseError error;
|
||||||
|
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||||
|
|
||||||
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
Error(id, "Reply from server missing Json data.");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json_doc.isNull() || json_doc.isEmpty()) {
|
||||||
|
Error(id, "Received empty Json document.");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json_doc.isObject()) {
|
||||||
|
Error(id, "Json document is not an object.");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject json_obj = json_doc.object();
|
||||||
|
if (json_obj.isEmpty()) {
|
||||||
|
Error(id, "Received empty Json object.");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_obj;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject APISeedsLyricsProvider::ExtractResult(QNetworkReply *reply, quint64 id) {
|
||||||
|
|
||||||
|
QJsonObject json_obj = ExtractJsonObj(reply, id);
|
||||||
|
if (json_obj.isEmpty()) return QJsonObject();
|
||||||
|
|
||||||
|
if (json_obj.contains("error")) {
|
||||||
|
Error(id, json_obj["error"].toString(), json_obj);
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json_obj.contains("result")) {
|
||||||
|
Error(id, "Json reply is missing result.", json_obj);
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject json_result = json_obj["result"].toObject();
|
||||||
|
if (json_result.isEmpty()) {
|
||||||
|
Error(id, "Json result object is empty.");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
return json_result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void APISeedsLyricsProvider::Error(quint64 id, QString error, QVariant debug) {
|
||||||
|
LyricsSearchResults results;
|
||||||
|
qLog(Error) << "APISeedsLyrics:" << error;
|
||||||
|
if (debug.isValid()) qLog(Debug) << debug;
|
||||||
|
emit SearchFinished(id, results);
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2018, 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 APISEEDSLYRICSPROVIDER_H
|
||||||
|
#define APISEEDSLYRICSPROVIDER_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QMetaType>
|
||||||
|
#include <QString>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
|
||||||
|
#include "lyricsprovider.h"
|
||||||
|
#include "lyricsfetcher.h"
|
||||||
|
|
||||||
|
class APISeedsLyricsProvider : public LyricsProvider {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit APISeedsLyricsProvider(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
bool StartSearch(const QString &artist, const QString &album, const QString &title, quint64 id);
|
||||||
|
void CancelSearch(quint64 id);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void HandleSearchReply(QNetworkReply *reply, quint64 id, const QString artist, const QString title);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const char *kUrlSearch;
|
||||||
|
static const char *kAPIKeyB64;
|
||||||
|
QNetworkAccessManager *network_;
|
||||||
|
void Error(quint64 id, QString error, QVariant debug = QVariant());
|
||||||
|
|
||||||
|
QJsonObject ExtractJsonObj(QNetworkReply *reply, quint64 id);
|
||||||
|
QJsonObject ExtractResult(QNetworkReply *reply, quint64 id);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // APISEEDSLYRICSPROVIDER_H
|
||||||
|
|
|
@ -0,0 +1,215 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QList>
|
||||||
|
#include <QPair>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringBuilder>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonValue>
|
||||||
|
|
||||||
|
#include "core/closure.h"
|
||||||
|
#include "core/logging.h"
|
||||||
|
#include "core/network.h"
|
||||||
|
#include "core/utilities.h"
|
||||||
|
#include "lyricsprovider.h"
|
||||||
|
#include "lyricsfetcher.h"
|
||||||
|
#include "auddlyricsprovider.h"
|
||||||
|
|
||||||
|
const char *AuddLyricsProvider::kUrlSearch = "https://api.audd.io/findLyrics/";
|
||||||
|
const char *AuddLyricsProvider::kAPITokenB64 = "ZjA0NjQ4YjgyNDM3ZTc1MjY3YjJlZDI5ZDBlMzQxZjk=";
|
||||||
|
|
||||||
|
AuddLyricsProvider::AuddLyricsProvider(QObject *parent) : LyricsProvider("AudD", parent), network_(new NetworkAccessManager(this)) {}
|
||||||
|
|
||||||
|
bool AuddLyricsProvider::StartSearch(const QString &artist, const QString &album, const QString &title, quint64 id) {
|
||||||
|
|
||||||
|
QString search(artist + " " + title);
|
||||||
|
|
||||||
|
typedef QPair<QString, QString> Arg;
|
||||||
|
typedef QList<Arg> ArgList;
|
||||||
|
|
||||||
|
typedef QPair<QByteArray, QByteArray> EncodedArg;
|
||||||
|
typedef QList<EncodedArg> EncodedArgList;
|
||||||
|
|
||||||
|
ArgList args = ArgList();
|
||||||
|
args.append(Arg("api_token", QByteArray::fromBase64(kAPITokenB64)));
|
||||||
|
args.append(Arg("q", search));
|
||||||
|
|
||||||
|
QUrlQuery url_query;
|
||||||
|
QUrl url(kUrlSearch);
|
||||||
|
|
||||||
|
for (const Arg &arg : args) {
|
||||||
|
EncodedArg encoded_arg(QUrl::toPercentEncoding(arg.first), QUrl::toPercentEncoding(arg.second));
|
||||||
|
url_query.addQueryItem(encoded_arg.first, encoded_arg.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
url.setQuery(url_query);
|
||||||
|
QNetworkReply *reply = network_->get(QNetworkRequest(url));
|
||||||
|
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleSearchReply(QNetworkReply*, quint64, QString, QString)), reply, id, artist, title);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AuddLyricsProvider::CancelSearch(quint64 id) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void AuddLyricsProvider::HandleSearchReply(QNetworkReply *reply, quint64 id, const QString artist, const QString title) {
|
||||||
|
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
QJsonArray json_result = ExtractResult(reply, id);
|
||||||
|
if (json_result.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LyricsSearchResults results;
|
||||||
|
for (const QJsonValue &value : json_result) {
|
||||||
|
if (!value.isObject()) {
|
||||||
|
qLog(Error) << "AuddLyrics: Invalid Json reply, result is not an object.";
|
||||||
|
qLog(Debug) << value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QJsonObject json_obj = value.toObject();
|
||||||
|
if (
|
||||||
|
!json_obj.contains("song_id") ||
|
||||||
|
!json_obj.contains("artist_id") ||
|
||||||
|
!json_obj.contains("title") ||
|
||||||
|
!json_obj.contains("artist") ||
|
||||||
|
!json_obj.contains("lyrics")
|
||||||
|
) {
|
||||||
|
qLog(Error) << "AuddLyrics: Invalid Json reply, result is missing data.";
|
||||||
|
qLog(Debug) << value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
LyricsSearchResult result;
|
||||||
|
result.artist = json_obj["artist"].toString();
|
||||||
|
result.title = json_obj["title"].toString();
|
||||||
|
result.lyrics = json_obj["lyrics"].toString();
|
||||||
|
result.score = 0.0;
|
||||||
|
if (result.artist.toLower() == artist.toLower()) result.score += 1.0;
|
||||||
|
if (result.title.toLower() == title.toLower()) result.score += 1.0;
|
||||||
|
|
||||||
|
results << result;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit SearchFinished(id, results);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject AuddLyricsProvider::ExtractJsonObj(QNetworkReply *reply, quint64 id) {
|
||||||
|
|
||||||
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
QString failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||||
|
Error(id, failure_reason);
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray data(reply->readAll());
|
||||||
|
|
||||||
|
QJsonParseError error;
|
||||||
|
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||||
|
|
||||||
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
Error(id, "Reply from server missing Json data.");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json_doc.isNull() || json_doc.isEmpty()) {
|
||||||
|
Error(id, "Received empty Json document.");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json_doc.isObject()) {
|
||||||
|
Error(id, "Json document is not an object.");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject json_obj = json_doc.object();
|
||||||
|
if (json_obj.isEmpty()) {
|
||||||
|
Error(id, "Received empty Json object.");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_obj;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray AuddLyricsProvider::ExtractResult(QNetworkReply *reply, quint64 id) {
|
||||||
|
|
||||||
|
QJsonObject json_obj = ExtractJsonObj(reply, id);
|
||||||
|
if (json_obj.isEmpty()) return QJsonArray();
|
||||||
|
|
||||||
|
if (!json_obj.contains("status")) {
|
||||||
|
Error(id, "Json reply is missing status.", json_obj);
|
||||||
|
return QJsonArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json_obj["status"].toString() == "error") {
|
||||||
|
if (!json_obj.contains("error")) {
|
||||||
|
Error(id, "Json reply is missing error status.", json_obj);
|
||||||
|
return QJsonArray();
|
||||||
|
}
|
||||||
|
QJsonObject json_error = json_obj["error"].toObject();
|
||||||
|
if (!json_error.contains("error_code") || !json_error.contains("error_message")) {
|
||||||
|
Error(id, "Json reply is missing error code or message.", json_error);
|
||||||
|
return QJsonArray();
|
||||||
|
}
|
||||||
|
QString error_code(json_error["error_code"].toString());
|
||||||
|
QString error_message(json_error["error_message"].toString());
|
||||||
|
Error(id, error_message);
|
||||||
|
return QJsonArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json_obj.contains("result")) {
|
||||||
|
Error(id, "Json reply is missing result.", json_obj);
|
||||||
|
return QJsonArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray json_result = json_obj["result"].toArray();
|
||||||
|
if (json_result.isEmpty()) {
|
||||||
|
Error(id, "No match.");
|
||||||
|
return QJsonArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AuddLyricsProvider::Error(quint64 id, QString error, QVariant debug) {
|
||||||
|
qLog(Error) << "AuddLyrics:" << error;
|
||||||
|
if (debug.isValid()) qLog(Debug) << debug;
|
||||||
|
LyricsSearchResults results;
|
||||||
|
emit SearchFinished(id, results);
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2018, 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 AUDDLYRICSPROVIDER_H
|
||||||
|
#define AUDDLYRICSPROVIDER_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QMetaType>
|
||||||
|
#include <QString>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
|
||||||
|
#include "lyricsprovider.h"
|
||||||
|
#include "lyricsfetcher.h"
|
||||||
|
|
||||||
|
class AuddLyricsProvider : public LyricsProvider {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AuddLyricsProvider(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
bool StartSearch(const QString &artist, const QString &album, const QString &title, quint64 id);
|
||||||
|
void CancelSearch(quint64 id);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void HandleSearchReply(QNetworkReply *reply, quint64 id, const QString artist, const QString title);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const char *kUrlSearch;
|
||||||
|
static const char *kAPITokenB64;
|
||||||
|
QNetworkAccessManager *network_;
|
||||||
|
void Error(quint64 id, QString error, QVariant debug = QVariant());
|
||||||
|
|
||||||
|
QJsonObject ExtractJsonObj(QNetworkReply *reply, quint64 id);
|
||||||
|
QJsonArray ExtractResult(QNetworkReply *reply, quint64 id);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // AUDDLYRICSPROVIDER_H
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "core/logging.h"
|
||||||
|
#include "lyricsfetcher.h"
|
||||||
|
#include "lyricsfetchersearch.h"
|
||||||
|
|
||||||
|
const int LyricsFetcher::kMaxConcurrentRequests = 5;
|
||||||
|
const QRegExp LyricsFetcher::kRemoveNonAlpha("[^a-zA-Z0-9\\d\\s]");
|
||||||
|
const QRegExp LyricsFetcher::kRemoveFromTitle(" ?-? ((\\(|\\[)?)(Remastered|Live) ?((\\)|\\])?)$");
|
||||||
|
|
||||||
|
LyricsFetcher::LyricsFetcher(LyricsProviders *lyrics_providers, QObject *parent)
|
||||||
|
: QObject(parent),
|
||||||
|
lyrics_providers_(lyrics_providers),
|
||||||
|
next_id_(0),
|
||||||
|
request_starter_(new QTimer(this))
|
||||||
|
{
|
||||||
|
|
||||||
|
request_starter_->setInterval(500);
|
||||||
|
connect(request_starter_, SIGNAL(timeout()), SLOT(StartRequests()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
quint64 LyricsFetcher::Search(const QString &artist, const QString &album, const QString &title) {
|
||||||
|
|
||||||
|
LyricsSearchRequest request;
|
||||||
|
request.artist = artist;
|
||||||
|
request.album = album;
|
||||||
|
request.album.remove(kRemoveNonAlpha);
|
||||||
|
request.album.remove(kRemoveFromTitle);
|
||||||
|
request.title = title;
|
||||||
|
request.title.remove(kRemoveNonAlpha);
|
||||||
|
request.title.remove(kRemoveFromTitle);
|
||||||
|
request.id = next_id_++;
|
||||||
|
AddRequest(request);
|
||||||
|
|
||||||
|
return request.id;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LyricsFetcher::AddRequest(const LyricsSearchRequest &req) {
|
||||||
|
|
||||||
|
queued_requests_.enqueue(req);
|
||||||
|
|
||||||
|
if (!request_starter_->isActive()) request_starter_->start();
|
||||||
|
|
||||||
|
if (active_requests_.size() < kMaxConcurrentRequests) StartRequests();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LyricsFetcher::Clear() {
|
||||||
|
|
||||||
|
queued_requests_.clear();
|
||||||
|
|
||||||
|
for (LyricsFetcherSearch *search : active_requests_.values()) {
|
||||||
|
search->Cancel();
|
||||||
|
search->deleteLater();
|
||||||
|
}
|
||||||
|
active_requests_.clear();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LyricsFetcher::StartRequests() {
|
||||||
|
|
||||||
|
if (queued_requests_.isEmpty()) {
|
||||||
|
request_starter_->stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!queued_requests_.isEmpty() && active_requests_.size() < kMaxConcurrentRequests) {
|
||||||
|
|
||||||
|
LyricsSearchRequest request = queued_requests_.dequeue();
|
||||||
|
|
||||||
|
LyricsFetcherSearch *search = new LyricsFetcherSearch(request, this);
|
||||||
|
active_requests_.insert(request.id, search);
|
||||||
|
|
||||||
|
connect(search, SIGNAL(SearchFinished(quint64, LyricsSearchResults)), SLOT(SingleSearchFinished(quint64, LyricsSearchResults)));
|
||||||
|
connect(search, SIGNAL(LyricsFetched(quint64, const QString&)), SLOT(SingleLyricsFetched(quint64, const QString&)));
|
||||||
|
|
||||||
|
search->Start(lyrics_providers_);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LyricsFetcher::SingleSearchFinished(quint64 request_id, LyricsSearchResults results) {
|
||||||
|
|
||||||
|
LyricsFetcherSearch *search = active_requests_.take(request_id);
|
||||||
|
if (!search) return;
|
||||||
|
search->deleteLater();
|
||||||
|
emit SearchFinished(request_id, results);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LyricsFetcher::SingleLyricsFetched(quint64 request_id, const QString &lyrics) {
|
||||||
|
|
||||||
|
LyricsFetcherSearch *search = active_requests_.take(request_id);
|
||||||
|
if (!search) return;
|
||||||
|
search->deleteLater();
|
||||||
|
emit LyricsFetched(request_id, lyrics);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2018, 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 LYRICSFETCHER_H
|
||||||
|
#define LYRICSFETCHER_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QQueue>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QList>
|
||||||
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
class LyricsProviders;
|
||||||
|
class LyricsFetcherSearch;
|
||||||
|
|
||||||
|
struct LyricsSearchRequest {
|
||||||
|
quint64 id;
|
||||||
|
QString artist;
|
||||||
|
QString album;
|
||||||
|
QString title;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LyricsSearchResult {
|
||||||
|
QString provider;
|
||||||
|
QString artist;
|
||||||
|
QString album;
|
||||||
|
QString title;
|
||||||
|
QString lyrics;
|
||||||
|
float score;
|
||||||
|
};
|
||||||
|
Q_DECLARE_METATYPE(LyricsSearchResult);
|
||||||
|
|
||||||
|
typedef QList<LyricsSearchResult> LyricsSearchResults;
|
||||||
|
Q_DECLARE_METATYPE(QList<LyricsSearchResult>);
|
||||||
|
|
||||||
|
class LyricsFetcher : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
LyricsFetcher(LyricsProviders *lyrics_providers, QObject *parent = nullptr);
|
||||||
|
virtual ~LyricsFetcher() {}
|
||||||
|
|
||||||
|
static const int kMaxConcurrentRequests;
|
||||||
|
static const QRegExp kRemoveNonAlpha;
|
||||||
|
static const QRegExp kRemoveFromTitle;
|
||||||
|
|
||||||
|
quint64 Search(const QString &artist, const QString &album, const QString &title);
|
||||||
|
void Clear();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void LyricsFetched(quint64, const QString &lyrics);
|
||||||
|
void SearchFinished(quint64, const LyricsSearchResults &results);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void SingleSearchFinished(quint64, LyricsSearchResults results);
|
||||||
|
void SingleLyricsFetched(quint64, const QString &lyrics);
|
||||||
|
void StartRequests();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void AddRequest(const LyricsSearchRequest &req);
|
||||||
|
|
||||||
|
LyricsProviders *lyrics_providers_;
|
||||||
|
quint64 next_id_;
|
||||||
|
|
||||||
|
QQueue<LyricsSearchRequest> queued_requests_;
|
||||||
|
QHash<quint64, LyricsFetcherSearch*> active_requests_;
|
||||||
|
|
||||||
|
QTimer *request_starter_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LYRICSFETCHER_H
|
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QtAlgorithms>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QList>
|
||||||
|
#include <QtDebug>
|
||||||
|
|
||||||
|
#include "core/closure.h"
|
||||||
|
#include "core/logging.h"
|
||||||
|
#include "lyricsfetcher.h"
|
||||||
|
#include "lyricsfetchersearch.h"
|
||||||
|
#include "lyricsprovider.h"
|
||||||
|
#include "lyricsproviders.h"
|
||||||
|
|
||||||
|
const int LyricsFetcherSearch::kSearchTimeoutMs = 6000;
|
||||||
|
|
||||||
|
LyricsFetcherSearch::LyricsFetcherSearch(
|
||||||
|
const LyricsSearchRequest &request, QObject *parent)
|
||||||
|
: QObject(parent),
|
||||||
|
request_(request),
|
||||||
|
cancel_requested_(false) {
|
||||||
|
|
||||||
|
QTimer::singleShot(kSearchTimeoutMs, this, SLOT(TerminateSearch()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LyricsFetcherSearch::TerminateSearch() {
|
||||||
|
|
||||||
|
for (int id : pending_requests_.keys()) {
|
||||||
|
pending_requests_.take(id)->CancelSearch(id);
|
||||||
|
}
|
||||||
|
AllProvidersFinished();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LyricsFetcherSearch::Start(LyricsProviders *lyrics_providers) {
|
||||||
|
|
||||||
|
for (LyricsProvider *provider : lyrics_providers->List()) {
|
||||||
|
connect(provider, SIGNAL(SearchFinished(quint64, QList<LyricsSearchResult>)), SLOT(ProviderSearchFinished(quint64, QList<LyricsSearchResult>)));
|
||||||
|
const int id = lyrics_providers->NextId();
|
||||||
|
const bool success = provider->StartSearch(request_.artist, request_.album, request_.title, id);
|
||||||
|
if (success) pending_requests_[id] = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pending_requests_.isEmpty()) TerminateSearch();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LyricsFetcherSearch::ProviderSearchFinished(quint64 id, const QList<LyricsSearchResult> &results) {
|
||||||
|
|
||||||
|
if (!pending_requests_.contains(id)) return;
|
||||||
|
LyricsProvider *provider = pending_requests_.take(id);
|
||||||
|
|
||||||
|
LyricsSearchResults results_copy(results);
|
||||||
|
for (int i = 0; i < results_copy.count(); ++i) {
|
||||||
|
results_copy[i].provider = provider->name();
|
||||||
|
}
|
||||||
|
|
||||||
|
results_.append(results_copy);
|
||||||
|
|
||||||
|
if (!pending_requests_.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AllProvidersFinished();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LyricsFetcherSearch::AllProvidersFinished() {
|
||||||
|
|
||||||
|
if (cancel_requested_) return;
|
||||||
|
|
||||||
|
if (!results_.isEmpty()) {
|
||||||
|
LyricsSearchResult result_use;
|
||||||
|
result_use.score = 0.0;
|
||||||
|
for (LyricsSearchResult result : results_) {
|
||||||
|
if (result_use.lyrics.isEmpty() || result.score > result_use.score) result_use = result;
|
||||||
|
}
|
||||||
|
emit LyricsFetched(request_.id, result_use.lyrics);
|
||||||
|
}
|
||||||
|
emit SearchFinished(request_.id, results_);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LyricsFetcherSearch::Cancel() {
|
||||||
|
|
||||||
|
cancel_requested_ = true;
|
||||||
|
|
||||||
|
if (!pending_requests_.isEmpty()) {
|
||||||
|
TerminateSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2018, 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 LYRICSFETCHERSEARCH_H
|
||||||
|
#define LYRICSFETCHERSEARCH_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QList>
|
||||||
|
#include <QMap>
|
||||||
|
|
||||||
|
#include "lyricsfetcher.h"
|
||||||
|
|
||||||
|
class LyricsProvider;
|
||||||
|
class LyricsProviders;
|
||||||
|
|
||||||
|
class LyricsFetcherSearch : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
LyricsFetcherSearch(const LyricsSearchRequest &request, QObject *parent);
|
||||||
|
|
||||||
|
void Start(LyricsProviders *cover_providers);
|
||||||
|
void Cancel();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void SearchFinished(quint64, const LyricsSearchResults &results);
|
||||||
|
void LyricsFetched(quint64, const QString &lyrics);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void ProviderSearchFinished(quint64 id, const QList<LyricsSearchResult> &results);
|
||||||
|
void TerminateSearch();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void AllProvidersFinished();
|
||||||
|
|
||||||
|
void SendBestImage();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const int kSearchTimeoutMs;
|
||||||
|
|
||||||
|
LyricsSearchRequest request_;
|
||||||
|
LyricsSearchResults results_;
|
||||||
|
QMap<int, LyricsProvider*> pending_requests_;
|
||||||
|
bool cancel_requested_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LYRICSFETCHERSEARCH_H
|
|
@ -1,7 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
|
||||||
*
|
*
|
||||||
* 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
|
||||||
|
@ -18,36 +17,12 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef ELIDEDLABEL_H
|
|
||||||
#define ELIDEDLABEL_H
|
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QWidget>
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QLabel>
|
|
||||||
#include <QtEvents>
|
|
||||||
|
|
||||||
class QResizeEvent;
|
#include "lyricsprovider.h"
|
||||||
|
|
||||||
class ElidedLabel : public QLabel {
|
LyricsProvider::LyricsProvider(const QString &name, QObject *parent)
|
||||||
Q_OBJECT
|
: QObject(parent), name_(name) {}
|
||||||
|
|
||||||
public:
|
|
||||||
ElidedLabel(QWidget *parent = nullptr);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void SetText(const QString &text);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void resizeEvent(QResizeEvent *e);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void UpdateText();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString text_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // ELIDEDLABEL_H
|
|
|
@ -1,7 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
|
||||||
*
|
*
|
||||||
* 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
|
||||||
|
@ -18,27 +17,36 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef PROGRESSITEMDELEGATE_H
|
#ifndef LYRICSPROVIDER_H
|
||||||
#define PROGRESSITEMDELEGATE_H
|
#define LYRICSPROVIDER_H
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QList>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QPainter>
|
|
||||||
#include <QStyleOption>
|
|
||||||
#include <QStyledItemDelegate>
|
|
||||||
#include <QStyleOptionViewItem>
|
|
||||||
|
|
||||||
class QModelIndex;
|
struct LyricsSearchResult;
|
||||||
|
|
||||||
class ProgressItemDelegate : public QStyledItemDelegate {
|
class LyricsProvider : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
|
||||||
ProgressItemDelegate(QObject* parent = nullptr);
|
|
||||||
|
|
||||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
|
public:
|
||||||
|
explicit LyricsProvider(const QString &name, QObject *parent);
|
||||||
|
|
||||||
|
QString name() const { return name_; }
|
||||||
|
|
||||||
|
virtual bool StartSearch(const QString &artist, const QString &album, const QString &title, quint64 id) = 0;
|
||||||
|
virtual void CancelSearch(quint64 id) {}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void SearchFinished(quint64 id, const QList<LyricsSearchResult>& results);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString name_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PROGRESSITEMDELEGATE_H
|
#endif // LYRICSPROVIDER_H
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QString>
|
||||||
|
#include <QtDebug>
|
||||||
|
|
||||||
|
#include "core/application.h"
|
||||||
|
#include "core/logging.h"
|
||||||
|
#include "lyricsprovider.h"
|
||||||
|
#include "lyricsproviders.h"
|
||||||
|
#include "lyricsfetcher.h"
|
||||||
|
|
||||||
|
LyricsProviders::LyricsProviders(QObject *parent) : QObject(parent) {}
|
||||||
|
|
||||||
|
void LyricsProviders::AddProvider(LyricsProvider *provider) {
|
||||||
|
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&mutex_);
|
||||||
|
lyrics_providers_.insert(provider, provider->name());
|
||||||
|
connect(provider, SIGNAL(destroyed()), SLOT(ProviderDestroyed()));
|
||||||
|
}
|
||||||
|
|
||||||
|
qLog(Debug) << "Registered lyrics provider" << provider->name();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LyricsProviders::RemoveProvider(LyricsProvider *provider) {
|
||||||
|
|
||||||
|
if (!provider) return;
|
||||||
|
|
||||||
|
// It's not safe to dereference provider at this point because it might have already been destroyed.
|
||||||
|
|
||||||
|
QString name;
|
||||||
|
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&mutex_);
|
||||||
|
name = lyrics_providers_.take(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.isNull()) {
|
||||||
|
qLog(Debug) << "Tried to remove a lyrics provider that was not registered";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qLog(Debug) << "Unregistered lyrics provider" << name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LyricsProviders::ProviderDestroyed() {
|
||||||
|
|
||||||
|
LyricsProvider *provider = static_cast<LyricsProvider*>(sender());
|
||||||
|
RemoveProvider(provider);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int LyricsProviders::NextId() { return next_id_.fetchAndAddRelaxed(1); }
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2018, 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 LYRICSPROVIDERS_H
|
||||||
|
#define LYRICSPROVIDERS_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QList>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QString>
|
||||||
|
#include <QAtomicInt>
|
||||||
|
|
||||||
|
class LyricsProvider;
|
||||||
|
|
||||||
|
class LyricsProviders : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit LyricsProviders(QObject *parent = nullptr);
|
||||||
|
void AddProvider(LyricsProvider *provider);
|
||||||
|
void RemoveProvider(LyricsProvider *provider);
|
||||||
|
QList<LyricsProvider*> List() const { return lyrics_providers_.keys(); }
|
||||||
|
bool HasAnyProviders() const { return !lyrics_providers_.isEmpty(); }
|
||||||
|
int NextId();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void ProviderDestroyed();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Q_DISABLE_COPY(LyricsProviders);
|
||||||
|
|
||||||
|
QMap<LyricsProvider *, QString> lyrics_providers_;
|
||||||
|
QMutex mutex_;
|
||||||
|
|
||||||
|
QAtomicInt next_id_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LYRICSPROVIDERS_H
|
|
@ -1448,9 +1448,9 @@ void Playlist::StopAfter(int row) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Playlist::SetStreamMetadata(const QUrl &url, const Song &song) {
|
void Playlist::SetStreamMetadata(const QUrl &url, const Song &song) {
|
||||||
|
|
||||||
//qLog(Debug) << "Setting metadata for" << url << "to" << song.artist() << song.title();
|
//qLog(Debug) << "Setting metadata for" << url << "to" << song.artist() << song.title();
|
||||||
|
|
||||||
if (!current_item()) return;
|
if (!current_item()) return;
|
||||||
|
|
||||||
if (current_item()->Url() != url) return;
|
if (current_item()->Url() != url) return;
|
||||||
|
@ -1881,14 +1881,18 @@ bool Playlist::ApplyValidityOnCurrentSong(const QUrl &url, bool valid) {
|
||||||
Song current_song = current->Metadata();
|
Song current_song = current->Metadata();
|
||||||
|
|
||||||
// If validity has changed, reload the item
|
// If validity has changed, reload the item
|
||||||
if(!current_song.is_cdda() && current_song.url() == url && current_song.is_valid() != QFile::exists(current_song.url().toLocalFile())) {
|
// FIXME: Why?
|
||||||
ReloadItems(QList<int>() << current_row());
|
// Removed this because it caused "Empty filename passed to function" errors when not using local filenames.
|
||||||
}
|
// It also causes Context and Playing widget to reload the image and getting stuck in playing mode when the URL is broken.
|
||||||
|
//if(!current_song.is_cdda() && current_song.url() == url && current_song.is_valid() != QFile::exists(current_song.url().toLocalFile())) {
|
||||||
|
//ReloadItems(QList<int>() << current_row());
|
||||||
|
//}
|
||||||
|
|
||||||
// Gray out the song if it's now broken; otherwise undo the gray color
|
// Gray out the song if it's now broken; otherwise undo the gray color
|
||||||
if (valid) {
|
if (valid) {
|
||||||
current->RemoveForegroundColor(kInvalidSongPriority);
|
current->RemoveForegroundColor(kInvalidSongPriority);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
current->SetForegroundColor(kInvalidSongPriority, kInvalidSongColor);
|
current->SetForegroundColor(kInvalidSongPriority, kInvalidSongColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -394,7 +394,7 @@ void PlaylistListContainer::contextMenuEvent(QContextMenuEvent *e) {
|
||||||
void PlaylistListContainer::ActivePlaying() {
|
void PlaylistListContainer::ActivePlaying() {
|
||||||
|
|
||||||
if (padded_play_icon_.isNull()) {
|
if (padded_play_icon_.isNull()) {
|
||||||
QPixmap pixmap(":pictures/tiny-play.png");
|
QPixmap pixmap(":/pictures/tiny-play.png");
|
||||||
QPixmap new_pixmap(QSize(pixmap.height(), pixmap.height()));
|
QPixmap new_pixmap(QSize(pixmap.height(), pixmap.height()));
|
||||||
new_pixmap.fill(Qt::transparent);
|
new_pixmap.fill(Qt::transparent);
|
||||||
|
|
||||||
|
@ -409,7 +409,7 @@ void PlaylistListContainer::ActivePlaying() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlaylistListContainer::ActivePaused() {
|
void PlaylistListContainer::ActivePaused() {
|
||||||
UpdateActiveIcon(active_playlist_id_, QIcon(":pictures/tiny-pause.png"));
|
UpdateActiveIcon(active_playlist_id_, QIcon(":/pictures/tiny-pause.png"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlaylistListContainer::ActiveStopped() {
|
void PlaylistListContainer::ActiveStopped() {
|
||||||
|
|
|
@ -584,7 +584,8 @@ void PlaylistView::RemoveSelected(bool deleting_from_disk) {
|
||||||
|
|
||||||
if (!deleting_from_disk) {
|
if (!deleting_from_disk) {
|
||||||
model()->removeRows(range.top(), range.height(), range.topLeft());
|
model()->removeRows(range.top(), range.height(), range.topLeft());
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
model()->removeRows(range.top(), range.height(), QModelIndex());
|
model()->removeRows(range.top(), range.height(), QModelIndex());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -938,13 +939,14 @@ void PlaylistView::ReloadSettings() {
|
||||||
|
|
||||||
header_->SetColumnWidth(Playlist::Column_Track, 0.02);
|
header_->SetColumnWidth(Playlist::Column_Track, 0.02);
|
||||||
header_->SetColumnWidth(Playlist::Column_Title, 0.16);
|
header_->SetColumnWidth(Playlist::Column_Title, 0.16);
|
||||||
header_->SetColumnWidth(Playlist::Column_Artist, 0.10);
|
header_->SetColumnWidth(Playlist::Column_Artist, 0.12);
|
||||||
header_->SetColumnWidth(Playlist::Column_Album, 0.10);
|
header_->SetColumnWidth(Playlist::Column_Album, 0.12);
|
||||||
header_->SetColumnWidth(Playlist::Column_Length, 0.03);
|
header_->SetColumnWidth(Playlist::Column_Length, 0.03);
|
||||||
header_->SetColumnWidth(Playlist::Column_Bitrate, 0.07);
|
|
||||||
header_->SetColumnWidth(Playlist::Column_Samplerate, 0.07);
|
header_->SetColumnWidth(Playlist::Column_Samplerate, 0.07);
|
||||||
header_->SetColumnWidth(Playlist::Column_Bitdepth, 0.07);
|
header_->SetColumnWidth(Playlist::Column_Bitdepth, 0.07);
|
||||||
|
header_->SetColumnWidth(Playlist::Column_Bitrate, 0.07);
|
||||||
header_->SetColumnWidth(Playlist::Column_Filetype, 0.06);
|
header_->SetColumnWidth(Playlist::Column_Filetype, 0.06);
|
||||||
|
header_->SetColumnWidth(Playlist::Column_Source, 0.06);
|
||||||
|
|
||||||
setting_initial_header_layout_ = false;
|
setting_initial_header_layout_ = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -219,10 +219,6 @@ bool TidalSearch::FindCachedPixmap(const TidalSearch::Result &result, QPixmap *p
|
||||||
return pixmap_cache_.find(result.pixmap_cache_key_, pixmap);
|
return pixmap_cache_.find(result.pixmap_cache_key_, pixmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TidalSearch::LoadArtAsync(int id, const Result &result) {
|
|
||||||
emit ArtLoaded(id, QImage());
|
|
||||||
}
|
|
||||||
|
|
||||||
int TidalSearch::LoadArtAsync(const TidalSearch::Result &result) {
|
int TidalSearch::LoadArtAsync(const TidalSearch::Result &result) {
|
||||||
|
|
||||||
const int id = art_searches_next_id_++;
|
const int id = art_searches_next_id_++;
|
||||||
|
@ -246,6 +242,7 @@ void TidalSearch::AlbumArtLoaded(quint64 id, const QImage &image) {
|
||||||
int orig_id = cover_loader_tasks_.take(id);
|
int orig_id = cover_loader_tasks_.take(id);
|
||||||
|
|
||||||
HandleLoadedArt(orig_id, image);
|
HandleLoadedArt(orig_id, image);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TidalSearch::HandleLoadedArt(int id, const QImage &image) {
|
void TidalSearch::HandleLoadedArt(int id, const QImage &image) {
|
||||||
|
|
|
@ -118,7 +118,6 @@ class TidalSearch : public QObject {
|
||||||
void HandleLoadedArt(int id, const QImage &image);
|
void HandleLoadedArt(int id, const QImage &image);
|
||||||
bool FindCachedPixmap(const TidalSearch::Result &result, QPixmap *pixmap) const;
|
bool FindCachedPixmap(const TidalSearch::Result &result, QPixmap *pixmap) const;
|
||||||
QString PixmapCacheKey(const TidalSearch::Result &result) const;
|
QString PixmapCacheKey(const TidalSearch::Result &result) const;
|
||||||
void LoadArtAsync(int id, const Result &result);
|
|
||||||
void MaybeSearchFinished(int id);
|
void MaybeSearchFinished(int id);
|
||||||
void ShowConfig() {}
|
void ShowConfig() {}
|
||||||
static QImage ScaleAndPad(const QImage &image);
|
static QImage ScaleAndPad(const QImage &image);
|
||||||
|
|
|
@ -40,13 +40,16 @@ TidalSearchModel::TidalSearchModel(TidalSearch *engine, QObject *parent)
|
||||||
engine_(engine),
|
engine_(engine),
|
||||||
proxy_(nullptr),
|
proxy_(nullptr),
|
||||||
use_pretty_covers_(true),
|
use_pretty_covers_(true),
|
||||||
artist_icon_(IconLoader::Load("guitar")) {
|
artist_icon_(IconLoader::Load("folder-sound")) {
|
||||||
|
|
||||||
group_by_[0] = CollectionModel::GroupBy_Artist;
|
group_by_[0] = CollectionModel::GroupBy_Artist;
|
||||||
group_by_[1] = CollectionModel::GroupBy_Album;
|
group_by_[1] = CollectionModel::GroupBy_Album;
|
||||||
group_by_[2] = CollectionModel::GroupBy_None;
|
group_by_[2] = CollectionModel::GroupBy_None;
|
||||||
|
|
||||||
|
QIcon nocover = IconLoader::Load("cdcase");
|
||||||
|
no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(CollectionModel::kPrettyCoverSize, CollectionModel::kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||||
|
|
||||||
no_cover_icon_ = QPixmap(":/pictures/noalbumart.png").scaled(CollectionModel::kPrettyCoverSize, CollectionModel::kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
//no_cover_icon_ = QPixmap(":/pictures/noalbumart.png").scaled(CollectionModel::kPrettyCoverSize, CollectionModel::kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||||
album_icon_ = no_cover_icon_;
|
album_icon_ = no_cover_icon_;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>437</width>
|
<width>400</width>
|
||||||
<height>633</height>
|
<height>633</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
|
@ -82,14 +82,14 @@
|
||||||
<item>
|
<item>
|
||||||
<widget class="QRadioButton" name="radiobutton_searchbyalbums">
|
<widget class="QRadioButton" name="radiobutton_searchbyalbums">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>albu&ms</string>
|
<string>a&lbums</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QRadioButton" name="radiobutton_searchbysongs">
|
<widget class="QRadioButton" name="radiobutton_searchbysongs">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>songs</string>
|
<string>son&gs</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -186,7 +186,7 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>435</width>
|
<width>398</width>
|
||||||
<height>533</height>
|
<height>533</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
|
@ -195,7 +195,7 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>9</x>
|
<x>9</x>
|
||||||
<y>109</y>
|
<y>109</y>
|
||||||
<width>420</width>
|
<width>336</width>
|
||||||
<height>100</height>
|
<height>100</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
|
@ -214,12 +214,6 @@
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_helptext">
|
<widget class="QLabel" name="label_helptext">
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>0</width>
|
|
||||||
<height>80</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Enter search terms above to find music</string>
|
<string>Enter search terms above to find music</string>
|
||||||
</property>
|
</property>
|
||||||
|
|
|
@ -741,7 +741,8 @@ Song *TidalService::ParseSong(TidalSearchContext *search_ctx, const int album_id
|
||||||
//if (i > 1) song.set_compilation_detected(true);
|
//if (i > 1) song.set_compilation_detected(true);
|
||||||
|
|
||||||
cover = cover.replace("-", "/");
|
cover = cover.replace("-", "/");
|
||||||
QUrl cover_url (QString("%1/images/%2/750x750.jpg").arg(kResourcesUrl).arg(cover));
|
//QUrl cover_url (QString("%1/images/%2/750x750.jpg").arg(kResourcesUrl).arg(cover));
|
||||||
|
QUrl cover_url (QString("%1/images/%2/320x320.jpg").arg(kResourcesUrl).arg(cover));
|
||||||
song.set_art_automatic(cover_url.toEncoded());
|
song.set_art_automatic(cover_url.toEncoded());
|
||||||
|
|
||||||
if (search_ctx->requests_song_.contains(id)) return search_ctx->requests_song_.value(id);
|
if (search_ctx->requests_song_.contains(id)) return search_ctx->requests_song_.value(id);
|
||||||
|
|
|
@ -34,17 +34,19 @@ const int AutoExpandingTreeView::kRowsToShow = 50;
|
||||||
|
|
||||||
AutoExpandingTreeView::AutoExpandingTreeView(QWidget *parent)
|
AutoExpandingTreeView::AutoExpandingTreeView(QWidget *parent)
|
||||||
: QTreeView(parent),
|
: QTreeView(parent),
|
||||||
auto_open_(true),
|
auto_open_(false),
|
||||||
expand_on_reset_(true),
|
expand_on_reset_(false),
|
||||||
add_on_double_click_(true),
|
add_on_double_click_(true),
|
||||||
ignore_next_click_(false)
|
ignore_next_click_(false)
|
||||||
{
|
{
|
||||||
setExpandsOnDoubleClick(false);
|
|
||||||
|
setExpandsOnDoubleClick(true);
|
||||||
setAnimated(true);
|
setAnimated(true);
|
||||||
|
|
||||||
connect(this, SIGNAL(expanded(QModelIndex)), SLOT(ItemExpanded(QModelIndex)));
|
connect(this, SIGNAL(expanded(QModelIndex)), SLOT(ItemExpanded(QModelIndex)));
|
||||||
connect(this, SIGNAL(clicked(QModelIndex)), SLOT(ItemClicked(QModelIndex)));
|
connect(this, SIGNAL(clicked(QModelIndex)), SLOT(ItemClicked(QModelIndex)));
|
||||||
connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(ItemDoubleClicked(QModelIndex)));
|
connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(ItemDoubleClicked(QModelIndex)));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AutoExpandingTreeView::reset() {
|
void AutoExpandingTreeView::reset() {
|
||||||
|
@ -158,8 +160,7 @@ void AutoExpandingTreeView::keyPressEvent(QKeyEvent *e) {
|
||||||
|
|
||||||
case Qt::Key_Left:
|
case Qt::Key_Left:
|
||||||
// Set focus on the root of the current branch
|
// Set focus on the root of the current branch
|
||||||
if (index.isValid() && index.parent() != rootIndex() &&
|
if (index.isValid() && index.parent() != rootIndex() && (!isExpanded(index) || model()->rowCount(index) == 0)) {
|
||||||
(!isExpanded(index) || model()->rowCount(index) == 0)) {
|
|
||||||
setCurrentIndex(index.parent());
|
setCurrentIndex(index.parent());
|
||||||
setFocus();
|
setFocus();
|
||||||
e->accept();
|
e->accept();
|
||||||
|
|
|
@ -1,184 +0,0 @@
|
||||||
/*
|
|
||||||
* Strawberry Music Player
|
|
||||||
* This file was part of Clementine.
|
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
|
||||||
*
|
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Strawberry is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QWidget>
|
|
||||||
#include <QFlags>
|
|
||||||
#include <QFontMetrics>
|
|
||||||
#include <QString>
|
|
||||||
#include <QStringBuilder>
|
|
||||||
#include <QIcon>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QPalette>
|
|
||||||
#include <QColor>
|
|
||||||
#include <QRect>
|
|
||||||
#include <QSize>
|
|
||||||
#include <QToolButton>
|
|
||||||
#include <QtEvents>
|
|
||||||
|
|
||||||
#include "didyoumean.h"
|
|
||||||
|
|
||||||
const int DidYouMean::kPadding = 3;
|
|
||||||
|
|
||||||
DidYouMean::DidYouMean(QWidget *buddy, QWidget *parent)
|
|
||||||
: QWidget(parent, Qt::ToolTip),
|
|
||||||
buddy_(buddy),
|
|
||||||
close_(new QToolButton(this)),
|
|
||||||
normal_font_(font()),
|
|
||||||
correction_font_(font()),
|
|
||||||
press_enter_font_(font()) {
|
|
||||||
|
|
||||||
// Close icon
|
|
||||||
close_->setToolTip(tr("Close"));
|
|
||||||
close_->setIcon(QIcon(":/qt-project.org/styles/macstyle/images/closedock-16.png"));
|
|
||||||
close_->setIconSize(QSize(16, 16));
|
|
||||||
connect(close_, SIGNAL(clicked()), SLOT(hide()));
|
|
||||||
|
|
||||||
// Cursors
|
|
||||||
setCursor(Qt::PointingHandCursor);
|
|
||||||
close_->setCursor(Qt::ArrowCursor);
|
|
||||||
|
|
||||||
// Fonts
|
|
||||||
correction_font_.setBold(true);
|
|
||||||
press_enter_font_.setBold(true);
|
|
||||||
press_enter_font_.setPointSizeF(7.5);
|
|
||||||
|
|
||||||
hide();
|
|
||||||
buddy_->installEventFilter(this);
|
|
||||||
|
|
||||||
// Texts
|
|
||||||
did_you_mean_ = tr("Did you mean") + ": ";
|
|
||||||
press_enter_ = "(" + tr("press enter") + ")";
|
|
||||||
|
|
||||||
// Texts' sizes
|
|
||||||
did_you_mean_size_ = QFontMetrics(normal_font_).width(did_you_mean_);
|
|
||||||
press_enter_size_ = QFontMetrics(press_enter_font_).width(press_enter_);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DidYouMean::eventFilter(QObject *object, QEvent *event) {
|
|
||||||
|
|
||||||
if (object != buddy_) {
|
|
||||||
return QObject::eventFilter(object, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (event->type()) {
|
|
||||||
case QEvent::Move:
|
|
||||||
case QEvent::Resize:
|
|
||||||
if (isVisible()) {
|
|
||||||
UpdateGeometry();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case QEvent::KeyPress:
|
|
||||||
if (!isVisible()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (static_cast<QKeyEvent*>(event)->key()) {
|
|
||||||
case Qt::Key_Return:
|
|
||||||
case Qt::Key_Enter:
|
|
||||||
emit Accepted(correction_);
|
|
||||||
// fallthrough
|
|
||||||
case Qt::Key_Escape:
|
|
||||||
hide();
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case QEvent::FocusOut:
|
|
||||||
case QEvent::WindowDeactivate:
|
|
||||||
hide();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return QObject::eventFilter(object, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DidYouMean::showEvent(QShowEvent*) {
|
|
||||||
UpdateGeometry();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DidYouMean::UpdateGeometry() {
|
|
||||||
const int text_height = fontMetrics().height();
|
|
||||||
const int height = text_height + kPadding * 2;
|
|
||||||
|
|
||||||
move(buddy_->mapToGlobal(buddy_->rect().bottomLeft()));
|
|
||||||
// Resize to len(text to display) + total number of padding added + size(close button), so the "Did you mean" widget is always fully displayed
|
|
||||||
|
|
||||||
resize(QSize(did_you_mean_size_ + QFontMetrics(correction_font_).width(correction_ + " ") + press_enter_size_ + kPadding * 6 + close_->width(), height));
|
|
||||||
|
|
||||||
close_->move(kPadding, kPadding);
|
|
||||||
close_->resize(text_height, text_height);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DidYouMean::paintEvent(QPaintEvent*) {
|
|
||||||
QPainter p(this);
|
|
||||||
|
|
||||||
// Draw the background
|
|
||||||
QColor bg(palette().color(QPalette::Inactive, QPalette::ToolTipBase));
|
|
||||||
p.fillRect(0, 0, width()-1, height()-1, bg);
|
|
||||||
|
|
||||||
// Border
|
|
||||||
p.setPen(Qt::black);
|
|
||||||
p.drawRect(0, 0, width()-1, height()-1);
|
|
||||||
|
|
||||||
// Text rectangle
|
|
||||||
QRect text_rect(kPadding + close_->width() + kPadding, kPadding, rect().width() - kPadding, rect().height() - kPadding);
|
|
||||||
|
|
||||||
// Text
|
|
||||||
p.setFont(normal_font_);
|
|
||||||
p.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, did_you_mean_);
|
|
||||||
text_rect.setLeft(text_rect.left() + p.fontMetrics().width(did_you_mean_));
|
|
||||||
|
|
||||||
p.setFont(correction_font_);
|
|
||||||
p.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, correction_);
|
|
||||||
text_rect.setLeft(text_rect.left() + p.fontMetrics().width(correction_ + " "));
|
|
||||||
|
|
||||||
p.setPen(palette().color(QPalette::Disabled, QPalette::Text));
|
|
||||||
p.setFont(press_enter_font_);
|
|
||||||
p.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, press_enter_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DidYouMean::SetCorrection(const QString &correction) {
|
|
||||||
correction_ = correction;
|
|
||||||
UpdateGeometry();
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DidYouMean::Show(const QString &correction) {
|
|
||||||
SetCorrection(correction);
|
|
||||||
show();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DidYouMean::mouseReleaseEvent(QMouseEvent *e) {
|
|
||||||
emit Accepted(correction_);
|
|
||||||
hide();
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
/*
|
|
||||||
* Strawberry Music Player
|
|
||||||
* This file was part of Clementine.
|
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
|
||||||
*
|
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Strawberry is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef DIDYOUMEAN_H
|
|
||||||
#define DIDYOUMEAN_H
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QWidget>
|
|
||||||
#include <QFont>
|
|
||||||
#include <QString>
|
|
||||||
#include <QToolButton>
|
|
||||||
#include <QtEvents>
|
|
||||||
|
|
||||||
class QEvent;
|
|
||||||
class QMouseEvent;
|
|
||||||
class QPaintEvent;
|
|
||||||
class QShowEvent;
|
|
||||||
|
|
||||||
class DidYouMean : public QWidget {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
DidYouMean(QWidget *buddy, QWidget *parent);
|
|
||||||
|
|
||||||
static const int kPadding;
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void SetCorrection(const QString& correction);
|
|
||||||
void Show(const QString& correction);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void Accepted(const QString& correction);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void paintEvent(QPaintEvent*);
|
|
||||||
void showEvent(QShowEvent*);
|
|
||||||
void mouseReleaseEvent(QMouseEvent *e);
|
|
||||||
bool eventFilter(QObject *object, QEvent *event);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void UpdateGeometry();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QWidget *buddy_;
|
|
||||||
QString correction_;
|
|
||||||
|
|
||||||
QToolButton *close_;
|
|
||||||
|
|
||||||
QFont normal_font_;
|
|
||||||
QFont correction_font_;
|
|
||||||
QFont press_enter_font_;
|
|
||||||
|
|
||||||
QString did_you_mean_;
|
|
||||||
QString press_enter_;
|
|
||||||
|
|
||||||
// Size of the text to display, according to QFonts above.
|
|
||||||
// Stored here to avoid to recompute them each time
|
|
||||||
int did_you_mean_size_;
|
|
||||||
int press_enter_size_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // DIDYOUMEAN_H
|
|
|
@ -1,46 +0,0 @@
|
||||||
/*
|
|
||||||
* Strawberry Music Player
|
|
||||||
* This file was part of Clementine.
|
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
|
||||||
*
|
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Strawberry is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <QWidget>
|
|
||||||
#include <QString>
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QFontMetrics>
|
|
||||||
#include <QtEvents>
|
|
||||||
|
|
||||||
#include "elidedlabel.h"
|
|
||||||
|
|
||||||
class QResizeEvent;
|
|
||||||
|
|
||||||
ElidedLabel::ElidedLabel(QWidget *parent) : QLabel(parent) {}
|
|
||||||
|
|
||||||
void ElidedLabel::SetText(const QString& text) {
|
|
||||||
text_ = text;
|
|
||||||
UpdateText();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ElidedLabel::resizeEvent(QResizeEvent *) {
|
|
||||||
UpdateText();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ElidedLabel::UpdateText() {
|
|
||||||
setText(fontMetrics().elidedText(text_, Qt::ElideRight, width() - 5));
|
|
||||||
}
|
|
|
@ -126,7 +126,8 @@ void FancyTabProxyStyle::drawControl(ControlElement element, const QStyleOption*
|
||||||
if (vertical_tabs) {
|
if (vertical_tabs) {
|
||||||
m = QTransform::fromTranslate(rect.left(), rect.bottom());
|
m = QTransform::fromTranslate(rect.left(), rect.bottom());
|
||||||
m.rotate(-90);
|
m.rotate(-90);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
m = QTransform::fromTranslate(rect.left(), rect.top());
|
m = QTransform::fromTranslate(rect.left(), rect.top());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,8 +315,7 @@ QSize FancyTabBar::tabSizeHint(bool minimum) const {
|
||||||
return QSize(width, iconHeight + spacing + fm.height());
|
return QSize(width, iconHeight + spacing + fm.height());
|
||||||
}
|
}
|
||||||
|
|
||||||
void FancyTabBar::paintEvent(QPaintEvent *event)
|
void FancyTabBar::paintEvent(QPaintEvent *event) {
|
||||||
{
|
|
||||||
Q_UNUSED(event)
|
Q_UNUSED(event)
|
||||||
QPainter p(this);
|
QPainter p(this);
|
||||||
|
|
||||||
|
@ -328,8 +328,7 @@ void FancyTabBar::paintEvent(QPaintEvent *event)
|
||||||
paintTab(&p, currentIndex());
|
paintTab(&p, currentIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FancyTab::event(QEvent* event)
|
bool FancyTab::event(QEvent* event) {
|
||||||
{
|
|
||||||
if (event->type() == QEvent::ToolTip) {
|
if (event->type() == QEvent::ToolTip) {
|
||||||
QFontMetrics metrics (font());
|
QFontMetrics metrics (font());
|
||||||
int text_width = metrics.width(text);
|
int text_width = metrics.width(text);
|
||||||
|
@ -338,7 +337,8 @@ bool FancyTab::event(QEvent* event)
|
||||||
// The text is elided: show the tooltip
|
// The text is elided: show the tooltip
|
||||||
QHelpEvent* he = static_cast<QHelpEvent*>(event);
|
QHelpEvent* he = static_cast<QHelpEvent*>(event);
|
||||||
QToolTip::showText(he->globalPos(), text);
|
QToolTip::showText(he->globalPos(), text);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
QToolTip::hideText();
|
QToolTip::hideText();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -346,30 +346,25 @@ bool FancyTab::event(QEvent* event)
|
||||||
return QWidget::event(event);
|
return QWidget::event(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FancyTab::enterEvent(QEvent*)
|
void FancyTab::enterEvent(QEvent*) {
|
||||||
{
|
|
||||||
fadeIn();
|
fadeIn();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FancyTab::leaveEvent(QEvent*)
|
void FancyTab::leaveEvent(QEvent*) {
|
||||||
{
|
|
||||||
fadeOut();
|
fadeOut();
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize FancyTabBar::sizeHint() const
|
QSize FancyTabBar::sizeHint() const {
|
||||||
{
|
|
||||||
QSize sh = tabSizeHint();
|
QSize sh = tabSizeHint();
|
||||||
return QSize(sh.width(), sh.height() * m_tabs.count());
|
return QSize(sh.width(), sh.height() * m_tabs.count());
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize FancyTabBar::minimumSizeHint() const
|
QSize FancyTabBar::minimumSizeHint() const {
|
||||||
{
|
|
||||||
QSize sh = tabSizeHint(true);
|
QSize sh = tabSizeHint(true);
|
||||||
return QSize(sh.width(), sh.height() * m_tabs.count());
|
return QSize(sh.width(), sh.height() * m_tabs.count());
|
||||||
}
|
}
|
||||||
|
|
||||||
QRect FancyTabBar::tabRect(int index) const
|
QRect FancyTabBar::tabRect(int index) const {
|
||||||
{
|
|
||||||
return m_tabs[index]->geometry();
|
return m_tabs[index]->geometry();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,13 +377,11 @@ void FancyTabBar::setTabToolTip(int index, const QString& toolTip) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This keeps the sidebar responsive since we get a repaint before loading the mode itself
|
// This keeps the sidebar responsive since we get a repaint before loading the mode itself
|
||||||
void FancyTabBar::emitCurrentIndex()
|
void FancyTabBar::emitCurrentIndex() {
|
||||||
{
|
|
||||||
emit currentChanged(m_currentIndex);
|
emit currentChanged(m_currentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FancyTabBar::mousePressEvent(QMouseEvent *e)
|
void FancyTabBar::mousePressEvent(QMouseEvent *e) {
|
||||||
{
|
|
||||||
e->accept();
|
e->accept();
|
||||||
for (int index = 0; index < m_tabs.count(); ++index) {
|
for (int index = 0; index < m_tabs.count(); ++index) {
|
||||||
if (tabRect(index).contains(e->pos())) {
|
if (tabRect(index).contains(e->pos())) {
|
||||||
|
@ -415,8 +408,7 @@ void FancyTabBar::addSpacer(int size) {
|
||||||
new QSpacerItem(0, size, QSizePolicy::Fixed, QSizePolicy::Maximum));
|
new QSpacerItem(0, size, QSizePolicy::Fixed, QSizePolicy::Maximum));
|
||||||
}
|
}
|
||||||
|
|
||||||
void FancyTabBar::paintTab(QPainter *painter, int tabIndex) const
|
void FancyTabBar::paintTab(QPainter *painter, int tabIndex) const {
|
||||||
{
|
|
||||||
if (!validIndex(tabIndex)) {
|
if (!validIndex(tabIndex)) {
|
||||||
qWarning("invalid index");
|
qWarning("invalid index");
|
||||||
return;
|
return;
|
||||||
|
@ -427,7 +419,7 @@ void FancyTabBar::paintTab(QPainter *painter, int tabIndex) const
|
||||||
bool selected = (tabIndex == m_currentIndex);
|
bool selected = (tabIndex == m_currentIndex);
|
||||||
|
|
||||||
if (selected) {
|
if (selected) {
|
||||||
//background
|
//background
|
||||||
painter->save();
|
painter->save();
|
||||||
QLinearGradient grad(rect.topLeft(), rect.topRight());
|
QLinearGradient grad(rect.topLeft(), rect.topRight());
|
||||||
grad.setColorAt(0, QColor(255, 255, 255, 140));
|
grad.setColorAt(0, QColor(255, 255, 255, 140));
|
||||||
|
@ -435,21 +427,21 @@ void FancyTabBar::paintTab(QPainter *painter, int tabIndex) const
|
||||||
painter->fillRect(rect.adjusted(0, 0, 0, -1), grad);
|
painter->fillRect(rect.adjusted(0, 0, 0, -1), grad);
|
||||||
painter->restore();
|
painter->restore();
|
||||||
|
|
||||||
//shadows
|
//shadows
|
||||||
painter->setPen(QColor(0, 0, 0, 110));
|
painter->setPen(QColor(0, 0, 0, 110));
|
||||||
painter->drawLine(rect.topLeft() + QPoint(1,-1), rect.topRight() - QPoint(0,1));
|
painter->drawLine(rect.topLeft() + QPoint(1,-1), rect.topRight() - QPoint(0,1));
|
||||||
painter->drawLine(rect.bottomLeft(), rect.bottomRight());
|
painter->drawLine(rect.bottomLeft(), rect.bottomRight());
|
||||||
painter->setPen(QColor(0, 0, 0, 40));
|
painter->setPen(QColor(0, 0, 0, 40));
|
||||||
painter->drawLine(rect.topLeft(), rect.bottomLeft());
|
painter->drawLine(rect.topLeft(), rect.bottomLeft());
|
||||||
|
|
||||||
//highlights
|
//highlights
|
||||||
painter->setPen(QColor(255, 255, 255, 50));
|
painter->setPen(QColor(255, 255, 255, 50));
|
||||||
painter->drawLine(rect.topLeft() + QPoint(0, -2), rect.topRight() - QPoint(0,2));
|
painter->drawLine(rect.topLeft() + QPoint(0, -2), rect.topRight() - QPoint(0,2));
|
||||||
painter->drawLine(rect.bottomLeft() + QPoint(0, 1), rect.bottomRight() + QPoint(0,1));
|
painter->drawLine(rect.bottomLeft() + QPoint(0, 1), rect.bottomRight() + QPoint(0,1));
|
||||||
painter->setPen(QColor(255, 255, 255, 40));
|
painter->setPen(QColor(255, 255, 255, 40));
|
||||||
painter->drawLine(rect.topLeft() + QPoint(0, 0), rect.topRight());
|
painter->drawLine(rect.topLeft() + QPoint(0, 0), rect.topRight());
|
||||||
painter->drawLine(rect.topRight() + QPoint(0, 1), rect.bottomRight() - QPoint(0, 1));
|
painter->drawLine(rect.topRight() + QPoint(0, 1), rect.bottomRight() - QPoint(0, 1));
|
||||||
painter->drawLine(rect.bottomLeft() + QPoint(0,-1), rect.bottomRight()-QPoint(0,1));
|
painter->drawLine(rect.bottomLeft() + QPoint(0,-1), rect.bottomRight()-QPoint(0,1));
|
||||||
}
|
}
|
||||||
|
|
||||||
QString tabText(painter->fontMetrics().elidedText(this->tabText(tabIndex), Qt::ElideRight, width()));
|
QString tabText(painter->fontMetrics().elidedText(this->tabText(tabIndex), Qt::ElideRight, width()));
|
||||||
|
@ -567,6 +559,7 @@ void FancyTabWidget::SetBackgroundPixmap(const QPixmap& pixmap) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FancyTabWidget::paintEvent(QPaintEvent*) {
|
void FancyTabWidget::paintEvent(QPaintEvent*) {
|
||||||
|
|
||||||
if (!use_background_) return;
|
if (!use_background_) return;
|
||||||
|
|
||||||
QPainter painter(this);
|
QPainter painter(this);
|
||||||
|
@ -602,9 +595,11 @@ int FancyTabWidget::currentIndex() const {
|
||||||
void FancyTabWidget::setCurrentIndex(int index) {
|
void FancyTabWidget::setCurrentIndex(int index) {
|
||||||
if (FancyTabBar* bar = qobject_cast<FancyTabBar*>(tab_bar_)) {
|
if (FancyTabBar* bar = qobject_cast<FancyTabBar*>(tab_bar_)) {
|
||||||
bar->setCurrentIndex(index);
|
bar->setCurrentIndex(index);
|
||||||
} else if (QTabBar* bar = qobject_cast<QTabBar*>(tab_bar_)) {
|
}
|
||||||
|
else if (QTabBar* bar = qobject_cast<QTabBar*>(tab_bar_)) {
|
||||||
bar->setCurrentIndex(index);
|
bar->setCurrentIndex(index);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
stack_->setCurrentIndex(index);
|
stack_->setCurrentIndex(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -709,8 +704,7 @@ void FancyTabWidget::AddMenuItem(QSignalMapper* mapper, QActionGroup* group, con
|
||||||
if (mode == mode_) action->setChecked(true);
|
if (mode == mode_) action->setChecked(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FancyTabWidget::MakeTabBar(QTabBar::Shape shape, bool text, bool icons,
|
void FancyTabWidget::MakeTabBar(QTabBar::Shape shape, bool text, bool icons, bool fancy) {
|
||||||
bool fancy) {
|
|
||||||
QTabBar* bar = new QTabBar(this);
|
QTabBar* bar = new QTabBar(this);
|
||||||
bar->setShape(shape);
|
bar->setShape(shape);
|
||||||
bar->setDocumentMode(true);
|
bar->setDocumentMode(true);
|
||||||
|
|
|
@ -49,6 +49,8 @@
|
||||||
#include "covermanager/currentartloader.h"
|
#include "covermanager/currentartloader.h"
|
||||||
#include "playingwidget.h"
|
#include "playingwidget.h"
|
||||||
|
|
||||||
|
using std::unique_ptr;
|
||||||
|
|
||||||
const char *PlayingWidget::kSettingsGroup = "PlayingWidget";
|
const char *PlayingWidget::kSettingsGroup = "PlayingWidget";
|
||||||
|
|
||||||
// Space between the cover and the details in small mode
|
// Space between the cover and the details in small mode
|
||||||
|
@ -58,20 +60,18 @@ const int PlayingWidget::kPadding = 2;
|
||||||
const int PlayingWidget::kGradientHead = 40;
|
const int PlayingWidget::kGradientHead = 40;
|
||||||
const int PlayingWidget::kGradientTail = 20;
|
const int PlayingWidget::kGradientTail = 20;
|
||||||
|
|
||||||
// Maximum height of the cover in large mode, and offset between the
|
// Maximum height of the cover in large mode, and offset between the bottom of the cover and bottom of the widget
|
||||||
// bottom of the cover and bottom of the widget
|
|
||||||
const int PlayingWidget::kMaxCoverSize = 260;
|
const int PlayingWidget::kMaxCoverSize = 260;
|
||||||
const int PlayingWidget::kBottomOffset = 0;
|
const int PlayingWidget::kBottomOffset = 0;
|
||||||
|
|
||||||
// Border for large mode
|
// Border for large mode
|
||||||
const int PlayingWidget::kTopBorder = 4;
|
const int PlayingWidget::kTopBorder = 4;
|
||||||
|
|
||||||
|
|
||||||
PlayingWidget::PlayingWidget(QWidget *parent)
|
PlayingWidget::PlayingWidget(QWidget *parent)
|
||||||
: QWidget(parent),
|
: QWidget(parent),
|
||||||
app_(nullptr),
|
app_(nullptr),
|
||||||
album_cover_choice_controller_(new AlbumCoverChoiceController(this)),
|
album_cover_choice_controller_(new AlbumCoverChoiceController(this)),
|
||||||
mode_(SmallSongDetails),
|
mode_(LargeSongDetails),
|
||||||
menu_(new QMenu(this)),
|
menu_(new QMenu(this)),
|
||||||
fit_cover_width_action_(nullptr),
|
fit_cover_width_action_(nullptr),
|
||||||
enabled_(false),
|
enabled_(false),
|
||||||
|
@ -79,12 +79,14 @@ PlayingWidget::PlayingWidget(QWidget *parent)
|
||||||
active_(false),
|
active_(false),
|
||||||
small_ideal_height_(0),
|
small_ideal_height_(0),
|
||||||
fit_width_(false),
|
fit_width_(false),
|
||||||
show_hide_animation_(new QTimeLine(500, this)),
|
timeline_show_hide_(new QTimeLine(500, this)),
|
||||||
fade_animation_(new QTimeLine(1000, this)),
|
timeline_fade_(new QTimeLine(1000, this)),
|
||||||
details_(new QTextDocument(this)),
|
details_(new QTextDocument(this)),
|
||||||
previous_track_opacity_(0.0),
|
pixmap_previous_track_opacity_(0.0),
|
||||||
downloading_covers_(false) {
|
downloading_covers_(false) {
|
||||||
|
|
||||||
|
SetHeight(0);
|
||||||
|
|
||||||
// Load settings
|
// Load settings
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(kSettingsGroup);
|
s.beginGroup(kSettingsGroup);
|
||||||
|
@ -129,26 +131,20 @@ PlayingWidget::PlayingWidget(QWidget *parent)
|
||||||
menu_->addSeparator();
|
menu_->addSeparator();
|
||||||
|
|
||||||
// Animations
|
// Animations
|
||||||
connect(show_hide_animation_, SIGNAL(frameChanged(int)), SLOT(SetHeight(int)));
|
connect(timeline_show_hide_, SIGNAL(frameChanged(int)), SLOT(SetHeight(int)));
|
||||||
setMaximumHeight(0);
|
connect(timeline_fade_, SIGNAL(valueChanged(qreal)), SLOT(FadePreviousTrack(qreal)));
|
||||||
|
timeline_fade_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0
|
||||||
connect(fade_animation_, SIGNAL(valueChanged(qreal)), SLOT(FadePreviousTrack(qreal)));
|
|
||||||
fade_animation_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0
|
|
||||||
|
|
||||||
// add placeholder text to get the correct height
|
// add placeholder text to get the correct height
|
||||||
if (mode_ == LargeSongDetails) {
|
if (mode_ == LargeSongDetails) {
|
||||||
details_->setDefaultStyleSheet(
|
details_->setDefaultStyleSheet("p { font-size: small; font-weight: bold; }");
|
||||||
"p {"
|
|
||||||
" font-size: small;"
|
|
||||||
" color: black;"
|
|
||||||
"}");
|
|
||||||
details_->setHtml(QString("<p align=center><i></i><br/><br/></p>"));
|
details_->setHtml(QString("<p align=center><i></i><br/><br/></p>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateHeight();
|
UpdateHeight();
|
||||||
|
|
||||||
connect(album_cover_choice_controller_, SIGNAL(AutomaticCoverSearchDone()), this, SLOT(AutomaticCoverSearchDone()));
|
connect(album_cover_choice_controller_, SIGNAL(AutomaticCoverSearchDone()), this, SLOT(AutomaticCoverSearchDone()));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayingWidget::~PlayingWidget() {
|
PlayingWidget::~PlayingWidget() {
|
||||||
|
@ -163,14 +159,34 @@ void PlayingWidget::SetApplication(Application *app) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayingWidget::CreateModeAction(Mode mode, const QString &text, QActionGroup *group, QSignalMapper* mapper) {
|
|
||||||
|
|
||||||
QAction* action = new QAction(text, group);
|
void PlayingWidget::SetEnabled() {
|
||||||
action->setCheckable(true);
|
enabled_ = true;
|
||||||
mapper->setMapping(action, mode);
|
if (!visible_ && active_) SetVisible(true);
|
||||||
connect(action, SIGNAL(triggered()), mapper, SLOT(map()));
|
}
|
||||||
|
|
||||||
if (mode == mode_) action->setChecked(true);
|
void PlayingWidget::SetDisabled() {
|
||||||
|
enabled_ = false;
|
||||||
|
if (visible_) SetVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayingWidget::SetVisible(bool visible) {
|
||||||
|
|
||||||
|
if (timeline_show_hide_->state() == QTimeLine::Running) {
|
||||||
|
if (timeline_show_hide_->direction() == QTimeLine::Backward && enabled_ && active_) {
|
||||||
|
timeline_show_hide_->toggleDirection();
|
||||||
|
}
|
||||||
|
if (timeline_show_hide_->direction() == QTimeLine::Forward && (!enabled_ || !active_)) {
|
||||||
|
timeline_show_hide_->toggleDirection();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visible == visible_) return;
|
||||||
|
|
||||||
|
timeline_show_hide_->setFrameRange(0, total_height_);
|
||||||
|
timeline_show_hide_->setDirection(visible ? QTimeLine::Forward : QTimeLine::Backward);
|
||||||
|
timeline_show_hide_->start();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,218 +198,22 @@ void PlayingWidget::set_ideal_height(int height) {
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize PlayingWidget::sizeHint() const {
|
QSize PlayingWidget::sizeHint() const {
|
||||||
|
|
||||||
return QSize(cover_loader_options_.desired_height_, total_height_);
|
return QSize(cover_loader_options_.desired_height_, total_height_);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayingWidget::UpdateHeight() {
|
void PlayingWidget::CreateModeAction(Mode mode, const QString &text, QActionGroup *group, QSignalMapper *mapper) {
|
||||||
|
|
||||||
switch (mode_) {
|
QAction *action = new QAction(text, group);
|
||||||
case SmallSongDetails:
|
action->setCheckable(true);
|
||||||
cover_loader_options_.desired_height_ = small_ideal_height_;
|
mapper->setMapping(action, mode);
|
||||||
total_height_ = small_ideal_height_;
|
connect(action, SIGNAL(triggered()), mapper, SLOT(map()));
|
||||||
break;
|
|
||||||
case LargeSongDetails:
|
|
||||||
if (fit_width_) cover_loader_options_.desired_height_ = width();
|
|
||||||
else cover_loader_options_.desired_height_ = qMin(kMaxCoverSize, width());
|
|
||||||
total_height_ = kTopBorder + cover_loader_options_.desired_height_ + kBottomOffset + details_->size().height();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the animation settings and resize the widget now if we're visible
|
if (mode == mode_) action->setChecked(true);
|
||||||
show_hide_animation_->setFrameRange(0, total_height_);
|
|
||||||
if (visible_ && show_hide_animation_->state() != QTimeLine::Running) setMaximumHeight(total_height_);
|
|
||||||
|
|
||||||
// Re-scale the current image
|
|
||||||
if (metadata_.is_valid()) {
|
|
||||||
ScaleCover();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tell Qt we've changed size
|
|
||||||
updateGeometry();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayingWidget::Stopped() {
|
|
||||||
|
|
||||||
active_ = false;
|
|
||||||
SetVisible(false);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayingWidget::UpdateDetailsText() {
|
|
||||||
|
|
||||||
QString html;
|
|
||||||
|
|
||||||
switch (mode_) {
|
|
||||||
case SmallSongDetails:
|
|
||||||
details_->setTextWidth(-1);
|
|
||||||
details_->setDefaultStyleSheet("");
|
|
||||||
html += "<p>";
|
|
||||||
break;
|
|
||||||
case LargeSongDetails:
|
|
||||||
details_->setTextWidth(cover_loader_options_.desired_height_);
|
|
||||||
if (fit_width_) {
|
|
||||||
details_->setDefaultStyleSheet(
|
|
||||||
"p {"
|
|
||||||
" font-size: small;"
|
|
||||||
"}");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
details_->setDefaultStyleSheet(
|
|
||||||
"p {"
|
|
||||||
" font-size: small;"
|
|
||||||
" color: black;"
|
|
||||||
"}");
|
|
||||||
}
|
|
||||||
html += "<p align=center>";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Make this configurable
|
|
||||||
html += QString("<i>%1</i><br/>%2<br/>%3").arg(metadata_.PrettyTitle().toHtmlEscaped(), metadata_.artist().toHtmlEscaped(), metadata_.album().toHtmlEscaped());
|
|
||||||
|
|
||||||
html += "</p>";
|
|
||||||
details_->setHtml(html);
|
|
||||||
|
|
||||||
// if something spans multiple lines the height needs to change
|
|
||||||
if (mode_ == LargeSongDetails) UpdateHeight();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayingWidget::ScaleCover() {
|
|
||||||
|
|
||||||
cover_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, original_));
|
|
||||||
update();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayingWidget::AlbumArtLoaded(const Song &metadata, const QString &, const QImage &image) {
|
|
||||||
|
|
||||||
active_ = true;
|
|
||||||
|
|
||||||
metadata_ = metadata;
|
|
||||||
downloading_covers_ = false;
|
|
||||||
|
|
||||||
SetImage(image);
|
|
||||||
|
|
||||||
// Search for cover automatically?
|
|
||||||
GetCoverAutomatically();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayingWidget::SetImage(const QImage &image) {
|
|
||||||
|
|
||||||
active_ = true;
|
|
||||||
|
|
||||||
if (visible_) {
|
|
||||||
// Cache the current pixmap so we can fade between them
|
|
||||||
previous_track_ = QPixmap(size());
|
|
||||||
previous_track_.fill(palette().background().color());
|
|
||||||
previous_track_opacity_ = 1.0;
|
|
||||||
QPainter p(&previous_track_);
|
|
||||||
DrawContents(&p);
|
|
||||||
p.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
original_ = image;
|
|
||||||
|
|
||||||
UpdateDetailsText();
|
|
||||||
ScaleCover();
|
|
||||||
|
|
||||||
if (enabled_ == true) SetVisible(true);
|
|
||||||
|
|
||||||
// Were we waiting for this cover to load before we started fading?
|
|
||||||
if (!previous_track_.isNull()) {
|
|
||||||
fade_animation_->start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayingWidget::SetHeight(int height) {
|
|
||||||
|
|
||||||
setMaximumHeight(height);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayingWidget::SetVisible(bool visible) {
|
|
||||||
|
|
||||||
if (visible == visible_) return;
|
|
||||||
visible_ = visible;
|
|
||||||
|
|
||||||
show_hide_animation_->setDirection(visible ? QTimeLine::Forward : QTimeLine::Backward);
|
|
||||||
show_hide_animation_->start();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayingWidget::paintEvent(QPaintEvent *e) {
|
|
||||||
|
|
||||||
QPainter p(this);
|
|
||||||
|
|
||||||
DrawContents(&p);
|
|
||||||
|
|
||||||
// Draw the previous track's image if we're fading
|
|
||||||
if (!previous_track_.isNull()) {
|
|
||||||
p.setOpacity(previous_track_opacity_);
|
|
||||||
p.drawPixmap(0, 0, previous_track_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayingWidget::DrawContents(QPainter *p) {
|
|
||||||
|
|
||||||
switch (mode_) {
|
|
||||||
case SmallSongDetails:
|
|
||||||
// Draw the cover
|
|
||||||
p->drawPixmap(0, 0, small_ideal_height_, small_ideal_height_, cover_);
|
|
||||||
if (downloading_covers_) {
|
|
||||||
p->drawPixmap(small_ideal_height_ - 18, 6, 16, 16, spinner_animation_->currentPixmap());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the details
|
|
||||||
p->translate(small_ideal_height_ + kPadding, 0);
|
|
||||||
details_->drawContents(p);
|
|
||||||
p->translate(-small_ideal_height_ - kPadding, 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LargeSongDetails:
|
|
||||||
// Work out how high the text is going to be
|
|
||||||
const int text_height = details_->size().height();
|
|
||||||
const int cover_size = fit_width_ ? width() : qMin(kMaxCoverSize, width());
|
|
||||||
const int x_offset = (width() - cover_loader_options_.desired_height_) / 2;
|
|
||||||
|
|
||||||
if (!fit_width_) {
|
|
||||||
// Draw the black background
|
|
||||||
//p->fillRect(QRect(0, kTopBorder, width(), height() - kTopBorder), Qt::black);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the cover
|
|
||||||
p->drawPixmap(x_offset, kTopBorder, cover_size, cover_size, cover_);
|
|
||||||
if (downloading_covers_) {
|
|
||||||
p->drawPixmap(x_offset + 45, 35, 16, 16, spinner_animation_->currentPixmap());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the text below
|
|
||||||
p->translate(x_offset, height() - text_height);
|
|
||||||
details_->drawContents(p);
|
|
||||||
p->translate(-x_offset, -height() + text_height);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayingWidget::FadePreviousTrack(qreal value) {
|
|
||||||
|
|
||||||
previous_track_opacity_ = value;
|
|
||||||
if (qFuzzyCompare(previous_track_opacity_, qreal(0.0))) {
|
|
||||||
previous_track_ = QPixmap();
|
|
||||||
}
|
|
||||||
|
|
||||||
update();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayingWidget::SetMode(int mode) {
|
void PlayingWidget::SetMode(int mode) {
|
||||||
|
|
||||||
mode_ = Mode(mode);
|
mode_ = Mode(mode);
|
||||||
|
|
||||||
if (mode_ == SmallSongDetails) {
|
if (mode_ == SmallSongDetails) {
|
||||||
|
@ -413,9 +233,222 @@ void PlayingWidget::SetMode(int mode) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlayingWidget::FitCoverWidth(bool fit) {
|
||||||
|
|
||||||
|
fit_width_ = fit;
|
||||||
|
UpdateHeight();
|
||||||
|
update();
|
||||||
|
|
||||||
|
QSettings s;
|
||||||
|
s.beginGroup(kSettingsGroup);
|
||||||
|
s.setValue("fit_cover_width", fit_width_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayingWidget::Playing() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayingWidget::Stopped() {
|
||||||
|
active_ = false;
|
||||||
|
SetVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayingWidget::Error() {
|
||||||
|
active_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayingWidget::SongChanged(const Song &song) {
|
||||||
|
song_ = song;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayingWidget::AlbumArtLoaded(const Song &song, const QString &, const QImage &image) {
|
||||||
|
|
||||||
|
if (song == song_) {}
|
||||||
|
else {
|
||||||
|
qLog(Error) << __PRETTY_FUNCTION__ << "Ignoring" << song.title() << "because current song is" << song_.title();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
active_ = true;
|
||||||
|
downloading_covers_ = false;
|
||||||
|
SetImage(image);
|
||||||
|
GetCoverAutomatically();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayingWidget::SetImage(const QImage &image) {
|
||||||
|
|
||||||
|
if (enabled_ && visible_ && active_) {
|
||||||
|
// Cache the current pixmap so we can fade between them
|
||||||
|
QSize psize(size());
|
||||||
|
if (size().height() <= 0) psize.setHeight(total_height_);
|
||||||
|
pixmap_previous_track_ = QPixmap(psize);
|
||||||
|
pixmap_previous_track_.fill(palette().background().color());
|
||||||
|
pixmap_previous_track_opacity_ = 1.0;
|
||||||
|
QPainter p(&pixmap_previous_track_);
|
||||||
|
DrawContents(&p);
|
||||||
|
p.end();
|
||||||
|
}
|
||||||
|
else { pixmap_previous_track_ = QPixmap(); }
|
||||||
|
|
||||||
|
image_original_ = image;
|
||||||
|
UpdateDetailsText();
|
||||||
|
ScaleCover();
|
||||||
|
|
||||||
|
if (enabled_ && active_) {
|
||||||
|
SetVisible(true);
|
||||||
|
// Were we waiting for this cover to load before we started fading?
|
||||||
|
if (!pixmap_previous_track_.isNull()) {
|
||||||
|
timeline_fade_->stop();
|
||||||
|
timeline_fade_->start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayingWidget::ScaleCover() {
|
||||||
|
pixmap_cover_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_original_));
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayingWidget::SetHeight(int height) {
|
||||||
|
|
||||||
|
setMaximumHeight(height);
|
||||||
|
update();
|
||||||
|
|
||||||
|
if (height >= total_height_) visible_ = true;
|
||||||
|
if (height <= 0) visible_ = false;
|
||||||
|
|
||||||
|
if (timeline_show_hide_->state() == QTimeLine::Running) {
|
||||||
|
if (timeline_show_hide_->direction() == QTimeLine::Backward && enabled_ && active_) {
|
||||||
|
timeline_show_hide_->toggleDirection();
|
||||||
|
}
|
||||||
|
if (timeline_show_hide_->direction() == QTimeLine::Forward && (!enabled_ || !active_)) {
|
||||||
|
timeline_show_hide_->toggleDirection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayingWidget::UpdateHeight() {
|
||||||
|
|
||||||
|
switch (mode_) {
|
||||||
|
case SmallSongDetails:
|
||||||
|
cover_loader_options_.desired_height_ = small_ideal_height_;
|
||||||
|
total_height_ = small_ideal_height_;
|
||||||
|
break;
|
||||||
|
case LargeSongDetails:
|
||||||
|
if (fit_width_) cover_loader_options_.desired_height_ = width();
|
||||||
|
else cover_loader_options_.desired_height_ = qMin(kMaxCoverSize, width());
|
||||||
|
total_height_ = kTopBorder + cover_loader_options_.desired_height_ + kBottomOffset + details_->size().height();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the animation settings and resize the widget now if we're visible
|
||||||
|
timeline_show_hide_->setFrameRange(0, total_height_);
|
||||||
|
if (visible_ && active_ && timeline_show_hide_->state() != QTimeLine::Running) setMaximumHeight(total_height_);
|
||||||
|
|
||||||
|
// Re-scale the current image
|
||||||
|
if (song_.is_valid()) {
|
||||||
|
ScaleCover();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tell Qt we've changed size
|
||||||
|
updateGeometry();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayingWidget::UpdateDetailsText() {
|
||||||
|
|
||||||
|
QString html("");
|
||||||
|
details_->setDefaultStyleSheet("p { font-size: small; font-weight: bold; }");
|
||||||
|
switch (mode_) {
|
||||||
|
case SmallSongDetails:
|
||||||
|
details_->setTextWidth(-1);
|
||||||
|
html += "<p>";
|
||||||
|
break;
|
||||||
|
case LargeSongDetails:
|
||||||
|
details_->setTextWidth(cover_loader_options_.desired_height_);
|
||||||
|
html += "<p align=center>";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += QString("%1<br/>%2<br/>%3").arg(song_.PrettyTitle().toHtmlEscaped(), song_.artist().toHtmlEscaped(), song_.album().toHtmlEscaped());
|
||||||
|
|
||||||
|
html += "</p>";
|
||||||
|
details_->setHtml(html);
|
||||||
|
|
||||||
|
// if something spans multiple lines the height needs to change
|
||||||
|
if (mode_ == LargeSongDetails) UpdateHeight();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayingWidget::paintEvent(QPaintEvent *e) {
|
||||||
|
|
||||||
|
QPainter p(this);
|
||||||
|
|
||||||
|
DrawContents(&p);
|
||||||
|
|
||||||
|
// Draw the previous track's image if we're fading
|
||||||
|
if (!pixmap_previous_track_.isNull()) {
|
||||||
|
p.setOpacity(pixmap_previous_track_opacity_);
|
||||||
|
p.drawPixmap(0, 0, pixmap_previous_track_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayingWidget::DrawContents(QPainter *p) {
|
||||||
|
|
||||||
|
switch (mode_) {
|
||||||
|
case SmallSongDetails:
|
||||||
|
// Draw the cover
|
||||||
|
p->drawPixmap(0, 0, small_ideal_height_, small_ideal_height_, pixmap_cover_);
|
||||||
|
if (downloading_covers_) {
|
||||||
|
p->drawPixmap(small_ideal_height_ - 18, 6, 16, 16, spinner_animation_->currentPixmap());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the details
|
||||||
|
p->translate(small_ideal_height_ + kPadding, 0);
|
||||||
|
details_->drawContents(p);
|
||||||
|
p->translate(-small_ideal_height_ - kPadding, 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LargeSongDetails:
|
||||||
|
// Work out how high the text is going to be
|
||||||
|
const int text_height = details_->size().height();
|
||||||
|
const int cover_size = fit_width_ ? width() : qMin(kMaxCoverSize, width());
|
||||||
|
const int x_offset = (width() - cover_loader_options_.desired_height_) / 2;
|
||||||
|
|
||||||
|
// Draw the cover
|
||||||
|
p->drawPixmap(x_offset, kTopBorder, cover_size, cover_size, pixmap_cover_);
|
||||||
|
if (downloading_covers_) {
|
||||||
|
p->drawPixmap(x_offset + 45, 35, 16, 16, spinner_animation_->currentPixmap());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the text below
|
||||||
|
p->translate(x_offset, height() - text_height);
|
||||||
|
details_->drawContents(p);
|
||||||
|
p->translate(-x_offset, -height() + text_height);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayingWidget::FadePreviousTrack(qreal value) {
|
||||||
|
|
||||||
|
if (!visible_) return;
|
||||||
|
|
||||||
|
pixmap_previous_track_opacity_ = value;
|
||||||
|
if (qFuzzyCompare(pixmap_previous_track_opacity_, qreal(0.0))) {
|
||||||
|
pixmap_previous_track_ = QPixmap();
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void PlayingWidget::resizeEvent(QResizeEvent* e) {
|
void PlayingWidget::resizeEvent(QResizeEvent* e) {
|
||||||
|
|
||||||
if (visible_ && e->oldSize() != e->size()) {
|
//if (visible_ && e->oldSize() != e->size()) {
|
||||||
|
if (e->oldSize() != e->size()) {
|
||||||
if (mode_ == LargeSongDetails) {
|
if (mode_ == LargeSongDetails) {
|
||||||
UpdateHeight();
|
UpdateHeight();
|
||||||
UpdateDetailsText();
|
UpdateDetailsText();
|
||||||
|
@ -435,51 +468,6 @@ void PlayingWidget::mouseReleaseEvent(QMouseEvent*) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayingWidget::FitCoverWidth(bool fit) {
|
|
||||||
|
|
||||||
fit_width_ = fit;
|
|
||||||
UpdateHeight();
|
|
||||||
update();
|
|
||||||
|
|
||||||
QSettings s;
|
|
||||||
s.beginGroup(kSettingsGroup);
|
|
||||||
s.setValue("fit_cover_width", fit_width_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayingWidget::LoadCoverFromFile() {
|
|
||||||
album_cover_choice_controller_->LoadCoverFromFile(&metadata_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayingWidget::LoadCoverFromURL() {
|
|
||||||
album_cover_choice_controller_->LoadCoverFromURL(&metadata_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayingWidget::SearchForCover() {
|
|
||||||
album_cover_choice_controller_->SearchForCover(&metadata_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayingWidget::SaveCoverToFile() {
|
|
||||||
album_cover_choice_controller_->SaveCoverToFile(metadata_, original_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayingWidget::UnsetCover() {
|
|
||||||
album_cover_choice_controller_->UnsetCover(&metadata_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayingWidget::ShowCover() {
|
|
||||||
album_cover_choice_controller_->ShowCover(metadata_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayingWidget::SearchCoverAutomatically() {
|
|
||||||
|
|
||||||
QSettings s;
|
|
||||||
s.beginGroup(kSettingsGroup);
|
|
||||||
s.setValue("search_for_cover_auto", album_cover_choice_controller_->search_cover_auto_action()->isChecked());
|
|
||||||
|
|
||||||
GetCoverAutomatically();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayingWidget::dragEnterEvent(QDragEnterEvent *e) {
|
void PlayingWidget::dragEnterEvent(QDragEnterEvent *e) {
|
||||||
|
|
||||||
if (AlbumCoverChoiceController::CanAcceptDrag(e)) {
|
if (AlbumCoverChoiceController::CanAcceptDrag(e)) {
|
||||||
|
@ -492,7 +480,7 @@ void PlayingWidget::dragEnterEvent(QDragEnterEvent *e) {
|
||||||
|
|
||||||
void PlayingWidget::dropEvent(QDropEvent *e) {
|
void PlayingWidget::dropEvent(QDropEvent *e) {
|
||||||
|
|
||||||
album_cover_choice_controller_->SaveCover(&metadata_, e);
|
album_cover_choice_controller_->SaveCover(&song_, e);
|
||||||
|
|
||||||
QWidget::dropEvent(e);
|
QWidget::dropEvent(e);
|
||||||
|
|
||||||
|
@ -503,14 +491,13 @@ bool PlayingWidget::GetCoverAutomatically() {
|
||||||
// Search for cover automatically?
|
// Search for cover automatically?
|
||||||
bool search =
|
bool search =
|
||||||
album_cover_choice_controller_->search_cover_auto_action()->isChecked() &&
|
album_cover_choice_controller_->search_cover_auto_action()->isChecked() &&
|
||||||
!metadata_.has_manually_unset_cover() &&
|
!song_.has_manually_unset_cover() &&
|
||||||
metadata_.art_automatic().isEmpty() && metadata_.art_manual().isEmpty() &&
|
song_.art_automatic().isEmpty() && song_.art_manual().isEmpty() &&
|
||||||
!metadata_.artist().isEmpty() && !metadata_.album().isEmpty();
|
!song_.artist().isEmpty() && !song_.album().isEmpty();
|
||||||
|
|
||||||
if (search) {
|
if (search) {
|
||||||
//qLog(Debug) << "GetCoverAutomatically";
|
|
||||||
downloading_covers_ = true;
|
downloading_covers_ = true;
|
||||||
album_cover_choice_controller_->SearchCoverAutomatically(metadata_);
|
album_cover_choice_controller_->SearchCoverAutomatically(song_);
|
||||||
|
|
||||||
// Show a spinner animation
|
// Show a spinner animation
|
||||||
spinner_animation_.reset(new QMovie(":/pictures/spinner.gif", QByteArray(), this));
|
spinner_animation_.reset(new QMovie(":/pictures/spinner.gif", QByteArray(), this));
|
||||||
|
@ -531,22 +518,38 @@ void PlayingWidget::AutomaticCoverSearchDone() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayingWidget::SetEnabled() {
|
void PlayingWidget::SearchCoverAutomatically() {
|
||||||
|
|
||||||
if (enabled_ == true) return;
|
QSettings s;
|
||||||
|
s.beginGroup(kSettingsGroup);
|
||||||
if ((active_ == true) && (visible_ == false)) SetVisible(true);
|
s.setValue("search_for_cover_auto", album_cover_choice_controller_->search_cover_auto_action()->isChecked());
|
||||||
|
s.endGroup();
|
||||||
|
|
||||||
enabled_ = true;
|
GetCoverAutomatically();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayingWidget::SetDisabled() {
|
void PlayingWidget::LoadCoverFromFile() {
|
||||||
|
album_cover_choice_controller_->LoadCoverFromFile(&song_);
|
||||||
if (enabled_ == false) return;
|
|
||||||
|
|
||||||
if (visible_ == true) SetVisible(false);
|
|
||||||
|
|
||||||
enabled_ = false;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlayingWidget::LoadCoverFromURL() {
|
||||||
|
album_cover_choice_controller_->LoadCoverFromURL(&song_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayingWidget::SearchForCover() {
|
||||||
|
album_cover_choice_controller_->SearchForCover(&song_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayingWidget::SaveCoverToFile() {
|
||||||
|
album_cover_choice_controller_->SaveCoverToFile(song_, image_original_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayingWidget::UnsetCover() {
|
||||||
|
album_cover_choice_controller_->UnsetCover(&song_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayingWidget::ShowCover() {
|
||||||
|
album_cover_choice_controller_->ShowCover(song_);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 2013, Jonas Kvinge <jonas@strawbs.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
|
||||||
|
@ -32,6 +33,7 @@
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
|
#include <QPainter>
|
||||||
#include <QSize>
|
#include <QSize>
|
||||||
#include <QSignalMapper>
|
#include <QSignalMapper>
|
||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
|
@ -45,12 +47,13 @@
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "covermanager/albumcoverloaderoptions.h"
|
#include "covermanager/albumcoverloaderoptions.h"
|
||||||
|
|
||||||
|
using std::unique_ptr;
|
||||||
|
|
||||||
class QContextMenuEvent;
|
class QContextMenuEvent;
|
||||||
class QDragEnterEvent;
|
class QDragEnterEvent;
|
||||||
class QDropEvent;
|
class QDropEvent;
|
||||||
class QMouseEvent;
|
class QMouseEvent;
|
||||||
class QPaintEvent;
|
class QPaintEvent;
|
||||||
class QPainter;
|
|
||||||
class QResizeEvent;
|
class QResizeEvent;
|
||||||
|
|
||||||
class AlbumCoverChoiceController;
|
class AlbumCoverChoiceController;
|
||||||
|
@ -63,35 +66,22 @@ class PlayingWidget : public QWidget {
|
||||||
PlayingWidget(QWidget *parent = nullptr);
|
PlayingWidget(QWidget *parent = nullptr);
|
||||||
~PlayingWidget();
|
~PlayingWidget();
|
||||||
|
|
||||||
static const char *kSettingsGroup;
|
|
||||||
static const int kPadding;
|
|
||||||
static const int kGradientHead;
|
|
||||||
static const int kGradientTail;
|
|
||||||
static const int kMaxCoverSize;
|
|
||||||
static const int kBottomOffset;
|
|
||||||
static const int kTopBorder;
|
|
||||||
|
|
||||||
// Values are saved in QSettings
|
|
||||||
enum Mode {
|
|
||||||
SmallSongDetails = 0,
|
|
||||||
LargeSongDetails = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
void SetApplication(Application *app);
|
void SetApplication(Application *app);
|
||||||
void SetEnabled();
|
void SetEnabled();
|
||||||
void SetDisabled();
|
void SetDisabled();
|
||||||
|
|
||||||
void set_ideal_height(int height);
|
void set_ideal_height(int height);
|
||||||
|
|
||||||
QSize sizeHint() const;
|
QSize sizeHint() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void ShowAboveStatusBarChanged(bool above);
|
void ShowAboveStatusBarChanged(bool above);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
void Playing();
|
||||||
void Stopped();
|
void Stopped();
|
||||||
|
void Error();
|
||||||
|
void SongChanged(const Song &song);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent *e);
|
void paintEvent(QPaintEvent *e);
|
||||||
void resizeEvent(QResizeEvent*);
|
void resizeEvent(QResizeEvent*);
|
||||||
void contextMenuEvent(QContextMenuEvent *e);
|
void contextMenuEvent(QContextMenuEvent *e);
|
||||||
|
@ -99,17 +89,11 @@ protected:
|
||||||
void dragEnterEvent(QDragEnterEvent *e);
|
void dragEnterEvent(QDragEnterEvent *e);
|
||||||
void dropEvent(QDropEvent *e);
|
void dropEvent(QDropEvent *e);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|
||||||
void SetMode(int mode);
|
void SetMode(int mode);
|
||||||
void FitCoverWidth(bool fit);
|
void FitCoverWidth(bool fit);
|
||||||
|
|
||||||
void AlbumArtLoaded(const Song &metadata, const QString &uri, const QImage &image);
|
|
||||||
|
|
||||||
void SetVisible(bool visible);
|
|
||||||
void SetHeight(int height);
|
|
||||||
|
|
||||||
void FadePreviousTrack(qreal value);
|
|
||||||
|
|
||||||
void LoadCoverFromFile();
|
void LoadCoverFromFile();
|
||||||
void SaveCoverToFile();
|
void SaveCoverToFile();
|
||||||
void LoadCoverFromURL();
|
void LoadCoverFromURL();
|
||||||
|
@ -119,49 +103,58 @@ private slots:
|
||||||
void SearchCoverAutomatically();
|
void SearchCoverAutomatically();
|
||||||
void AutomaticCoverSearchDone();
|
void AutomaticCoverSearchDone();
|
||||||
|
|
||||||
private:
|
void AlbumArtLoaded(const Song &song, const QString &uri, const QImage &image);
|
||||||
void CreateModeAction(Mode mode, const QString &text, QActionGroup *group, QSignalMapper *mapper);
|
void SetHeight(int height);
|
||||||
void UpdateDetailsText();
|
void FadePreviousTrack(qreal value);
|
||||||
void UpdateHeight();
|
|
||||||
void DrawContents(QPainter *p);
|
private:
|
||||||
void SetImage(const QImage &image);
|
|
||||||
void ScaleCover();
|
enum Mode {
|
||||||
bool GetCoverAutomatically();
|
SmallSongDetails = 0,
|
||||||
|
LargeSongDetails = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *kSettingsGroup;
|
||||||
|
static const int kPadding;
|
||||||
|
static const int kGradientHead;
|
||||||
|
static const int kGradientTail;
|
||||||
|
static const int kMaxCoverSize;
|
||||||
|
static const int kBottomOffset;
|
||||||
|
static const int kTopBorder;
|
||||||
|
|
||||||
private:
|
|
||||||
Application *app_;
|
Application *app_;
|
||||||
AlbumCoverChoiceController *album_cover_choice_controller_;
|
AlbumCoverChoiceController *album_cover_choice_controller_;
|
||||||
|
|
||||||
Mode mode_;
|
Mode mode_;
|
||||||
|
|
||||||
QMenu *menu_;
|
QMenu *menu_;
|
||||||
|
|
||||||
QAction *fit_cover_width_action_;
|
QAction *fit_cover_width_action_;
|
||||||
|
|
||||||
bool enabled_;
|
bool enabled_;
|
||||||
bool visible_;
|
bool visible_;
|
||||||
bool active_;
|
bool active_;
|
||||||
|
|
||||||
int small_ideal_height_;
|
int small_ideal_height_;
|
||||||
AlbumCoverLoaderOptions cover_loader_options_;
|
AlbumCoverLoaderOptions cover_loader_options_;
|
||||||
int total_height_;
|
int total_height_;
|
||||||
bool fit_width_;
|
bool fit_width_;
|
||||||
QTimeLine *show_hide_animation_;
|
QTimeLine *timeline_show_hide_;
|
||||||
QTimeLine *fade_animation_;
|
QTimeLine *timeline_fade_;
|
||||||
|
|
||||||
// Information about the current track
|
|
||||||
Song metadata_;
|
|
||||||
QPixmap cover_;
|
|
||||||
// A copy of the original, unscaled album cover.
|
|
||||||
QImage original_;
|
|
||||||
QTextDocument *details_;
|
QTextDocument *details_;
|
||||||
|
qreal pixmap_previous_track_opacity_;
|
||||||
// Holds the last track while we're fading to the new track
|
|
||||||
QPixmap previous_track_;
|
|
||||||
qreal previous_track_opacity_;
|
|
||||||
|
|
||||||
std::unique_ptr<QMovie> spinner_animation_;
|
|
||||||
bool downloading_covers_;
|
bool downloading_covers_;
|
||||||
|
|
||||||
|
Song song_;
|
||||||
|
QImage image_original_;
|
||||||
|
QPixmap pixmap_cover_;
|
||||||
|
QPixmap pixmap_previous_track_;
|
||||||
|
std::unique_ptr<QMovie> spinner_animation_;
|
||||||
|
|
||||||
|
void SetVisible(bool visible);
|
||||||
|
void CreateModeAction(Mode mode, const QString &text, QActionGroup *group, QSignalMapper *mapper);
|
||||||
|
void UpdateDetailsText();
|
||||||
|
void UpdateHeight();
|
||||||
|
void SetImage(const QImage &image);
|
||||||
|
void DrawContents(QPainter *p);
|
||||||
|
void ScaleCover();
|
||||||
|
bool GetCoverAutomatically();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PLAYINGWIDGET_H
|
#endif // PLAYINGWIDGET_H
|
||||||
|
|
|
@ -1,268 +0,0 @@
|
||||||
/*
|
|
||||||
* Strawberry Music Player
|
|
||||||
* This file was part of Clementine.
|
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
|
||||||
*
|
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Strawberry is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QWidget>
|
|
||||||
#include <QApplication>
|
|
||||||
#include <QtConcurrentRun>
|
|
||||||
#include <QDir>
|
|
||||||
#include <QFileDialog>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QVariant>
|
|
||||||
#include <QString>
|
|
||||||
#include <QStringBuilder>
|
|
||||||
#include <QUrl>
|
|
||||||
#include <QImage>
|
|
||||||
#include <QPixmap>
|
|
||||||
#include <QFrame>
|
|
||||||
#include <QFuture>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QPalette>
|
|
||||||
#include <QColor>
|
|
||||||
#include <QBrush>
|
|
||||||
#include <QPoint>
|
|
||||||
#include <QRect>
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QMenu>
|
|
||||||
#include <QScrollArea>
|
|
||||||
#include <QSizePolicy>
|
|
||||||
#include <QTransform>
|
|
||||||
#include <QDesktopWidget>
|
|
||||||
#include <QLinearGradient>
|
|
||||||
#include <QSettings>
|
|
||||||
#include <QFlags>
|
|
||||||
#include <QtDebug>
|
|
||||||
#include <QtEvents>
|
|
||||||
#include <QNetworkAccessManager>
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
|
|
||||||
#include "core/closure.h"
|
|
||||||
#include "core/iconloader.h"
|
|
||||||
#include "core/logging.h"
|
|
||||||
#include "core/network.h"
|
|
||||||
#include "prettyimage.h"
|
|
||||||
|
|
||||||
const int PrettyImage::kTotalHeight = 200;
|
|
||||||
const int PrettyImage::kReflectionHeight = 40;
|
|
||||||
const int PrettyImage::kImageHeight = PrettyImage::kTotalHeight - PrettyImage::kReflectionHeight;
|
|
||||||
|
|
||||||
const int PrettyImage::kMaxImageWidth = 300;
|
|
||||||
|
|
||||||
const char *PrettyImage::kSettingsGroup = "PrettyImageView";
|
|
||||||
|
|
||||||
PrettyImage::PrettyImage(const QUrl &url, QNetworkAccessManager* network, QWidget* parent)
|
|
||||||
: QWidget(parent),
|
|
||||||
network_(network),
|
|
||||||
state_(State_WaitingForLazyLoad),
|
|
||||||
url_(url),
|
|
||||||
menu_(nullptr) {
|
|
||||||
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
||||||
|
|
||||||
LazyLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrettyImage::LazyLoad() {
|
|
||||||
|
|
||||||
if (state_ != State_WaitingForLazyLoad) return;
|
|
||||||
|
|
||||||
// Start fetching the image
|
|
||||||
QNetworkReply* reply = network_->get(QNetworkRequest(url_));
|
|
||||||
RedirectFollower* follower = new RedirectFollower(reply);
|
|
||||||
state_ = State_Fetching;
|
|
||||||
NewClosure(follower, SIGNAL(finished()), this, SLOT(ImageFetched(RedirectFollower*)), follower);
|
|
||||||
}
|
|
||||||
|
|
||||||
QSize PrettyImage::image_size() const {
|
|
||||||
|
|
||||||
if (state_ != State_Finished) return QSize(kImageHeight * 1.6, kImageHeight);
|
|
||||||
|
|
||||||
QSize ret = image_.size();
|
|
||||||
ret.scale(kMaxImageWidth, kImageHeight, Qt::KeepAspectRatio);
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
QSize PrettyImage::sizeHint() const {
|
|
||||||
return QSize(image_size().width(), kTotalHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrettyImage::ImageFetched(RedirectFollower* follower) {
|
|
||||||
|
|
||||||
follower->deleteLater();
|
|
||||||
QNetworkReply* reply = follower->reply();
|
|
||||||
reply->deleteLater();
|
|
||||||
|
|
||||||
QImage image = QImage::fromData(reply->readAll());
|
|
||||||
if (image.isNull()) {
|
|
||||||
qLog(Debug) << "Image failed to load" << reply->request().url() << reply->error();
|
|
||||||
deleteLater();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
state_ = State_CreatingThumbnail;
|
|
||||||
image_ = image;
|
|
||||||
|
|
||||||
QFuture<QImage> future = QtConcurrent::run(image_, &QImage::scaled, image_size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
||||||
NewClosure(future, this, SLOT(ImageScaled(QFuture<QImage>)), future);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrettyImage::ImageScaled(QFuture<QImage> future) {
|
|
||||||
|
|
||||||
thumbnail_ = QPixmap::fromImage(future.result());
|
|
||||||
state_ = State_Finished;
|
|
||||||
|
|
||||||
updateGeometry();
|
|
||||||
update();
|
|
||||||
emit Loaded();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrettyImage::paintEvent(QPaintEvent* ) {
|
|
||||||
|
|
||||||
// Draw at the bottom of our area
|
|
||||||
QRect image_rect(QPoint(0, 0), image_size());
|
|
||||||
image_rect.moveBottom(kImageHeight);
|
|
||||||
|
|
||||||
QPainter p(this);
|
|
||||||
|
|
||||||
// Draw the main image
|
|
||||||
DrawThumbnail(&p, image_rect);
|
|
||||||
|
|
||||||
// Draw the reflection
|
|
||||||
// Figure out where to draw it
|
|
||||||
QRect reflection_rect(image_rect);
|
|
||||||
reflection_rect.moveTop(image_rect.bottom());
|
|
||||||
|
|
||||||
// Create the reflected pixmap
|
|
||||||
QImage reflection(reflection_rect.size(), QImage::Format_ARGB32_Premultiplied);
|
|
||||||
reflection.fill(palette().color(QPalette::Base).rgba());
|
|
||||||
QPainter reflection_painter(&reflection);
|
|
||||||
|
|
||||||
// Set up the transformation
|
|
||||||
QTransform transform;
|
|
||||||
transform.scale(1.0, -1.0);
|
|
||||||
transform.translate(0.0, -reflection_rect.height());
|
|
||||||
reflection_painter.setTransform(transform);
|
|
||||||
|
|
||||||
QRect fade_rect(reflection.rect().bottomLeft() - QPoint(0, kReflectionHeight), reflection.rect().bottomRight());
|
|
||||||
|
|
||||||
// Draw the reflection into the buffer
|
|
||||||
DrawThumbnail(&reflection_painter, reflection.rect());
|
|
||||||
|
|
||||||
// Make it fade out towards the bottom
|
|
||||||
QLinearGradient fade_gradient(fade_rect.topLeft(), fade_rect.bottomLeft());
|
|
||||||
fade_gradient.setColorAt(0.0, QColor(0, 0, 0, 0));
|
|
||||||
fade_gradient.setColorAt(1.0, QColor(0, 0, 0, 128));
|
|
||||||
|
|
||||||
reflection_painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
|
|
||||||
reflection_painter.fillRect(fade_rect, fade_gradient);
|
|
||||||
|
|
||||||
reflection_painter.end();
|
|
||||||
|
|
||||||
// Draw the reflection on the image
|
|
||||||
p.drawImage(reflection_rect, reflection);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrettyImage::DrawThumbnail(QPainter* p, const QRect& rect) {
|
|
||||||
|
|
||||||
switch (state_) {
|
|
||||||
case State_WaitingForLazyLoad:
|
|
||||||
case State_Fetching:
|
|
||||||
case State_CreatingThumbnail:
|
|
||||||
p->setPen(palette().color(QPalette::Disabled, QPalette::Text));
|
|
||||||
p->drawText(rect, Qt::AlignHCenter | Qt::AlignBottom, tr("Loading..."));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case State_Finished:
|
|
||||||
p->drawPixmap(rect, thumbnail_);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrettyImage::contextMenuEvent(QContextMenuEvent* e) {
|
|
||||||
|
|
||||||
if (e->pos().y() >= kImageHeight) return;
|
|
||||||
|
|
||||||
if (!menu_) {
|
|
||||||
menu_ = new QMenu(this);
|
|
||||||
menu_->addAction(IconLoader::Load("zoom-in"), tr("Show fullsize..."), this, SLOT(ShowFullsize()));
|
|
||||||
menu_->addAction(IconLoader::Load("document-save"), tr("Save image") + "...", this, SLOT(SaveAs()));
|
|
||||||
}
|
|
||||||
|
|
||||||
menu_->popup(e->globalPos());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrettyImage::ShowFullsize() {
|
|
||||||
|
|
||||||
// Work out how large to make the window, based on the size of the screen
|
|
||||||
QRect desktop_rect(QApplication::desktop()->availableGeometry(this));
|
|
||||||
QSize window_size(qMin(desktop_rect.width() - 20, image_.width()), qMin(desktop_rect.height() - 20, image_.height()));
|
|
||||||
|
|
||||||
// Create the window
|
|
||||||
QScrollArea* window = new QScrollArea;
|
|
||||||
window->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
||||||
window->setWindowTitle(tr("Strawberry image viewer"));
|
|
||||||
window->resize(window_size);
|
|
||||||
|
|
||||||
// Create the label that displays the image
|
|
||||||
QLabel* label = new QLabel(window);
|
|
||||||
label->setPixmap(QPixmap::fromImage(image_));
|
|
||||||
|
|
||||||
// Show the label in the window
|
|
||||||
window->setWidget(label);
|
|
||||||
window->setFrameShape(QFrame::NoFrame);
|
|
||||||
window->show();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrettyImage::SaveAs() {
|
|
||||||
|
|
||||||
QString filename = QFileInfo(url_.path()).fileName();
|
|
||||||
|
|
||||||
if (filename.isEmpty()) filename = "artwork.jpg";
|
|
||||||
|
|
||||||
QSettings s;
|
|
||||||
s.beginGroup(kSettingsGroup);
|
|
||||||
QString last_save_dir = s.value("last_save_dir", QDir::homePath()).toString();
|
|
||||||
|
|
||||||
QString path = last_save_dir.isEmpty() ? QDir::homePath() : last_save_dir;
|
|
||||||
QFileInfo path_info(path);
|
|
||||||
if (path_info.isDir()) {
|
|
||||||
path += "/" + filename;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
path = path_info.path() + "/" + filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
filename = QFileDialog::getSaveFileName(this, tr("Save image"), path);
|
|
||||||
if (filename.isEmpty()) return;
|
|
||||||
|
|
||||||
image_.save(filename);
|
|
||||||
|
|
||||||
s.setValue("last_save_dir", last_save_dir);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,101 +0,0 @@
|
||||||
/*
|
|
||||||
* Strawberry Music Player
|
|
||||||
* This file was part of Clementine.
|
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
|
||||||
*
|
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Strawberry is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef PRETTYIMAGE_H
|
|
||||||
#define PRETTYIMAGE_H
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QWidget>
|
|
||||||
#include <QFuture>
|
|
||||||
#include <QString>
|
|
||||||
#include <QUrl>
|
|
||||||
#include <QImage>
|
|
||||||
#include <QPixmap>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QRect>
|
|
||||||
#include <QSize>
|
|
||||||
#include <QMenu>
|
|
||||||
#include <QNetworkAccessManager>
|
|
||||||
#include <QtEvents>
|
|
||||||
|
|
||||||
class QContextMenuEvent;
|
|
||||||
class QPaintEvent;
|
|
||||||
|
|
||||||
class RedirectFollower;
|
|
||||||
|
|
||||||
class PrettyImage : public QWidget {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
PrettyImage(const QUrl &url, QNetworkAccessManager *network, QWidget *parent = nullptr);
|
|
||||||
|
|
||||||
static const int kTotalHeight;
|
|
||||||
static const int kReflectionHeight;
|
|
||||||
static const int kImageHeight;
|
|
||||||
|
|
||||||
static const int kMaxImageWidth;
|
|
||||||
|
|
||||||
static const char *kSettingsGroup;
|
|
||||||
|
|
||||||
QSize sizeHint() const;
|
|
||||||
QSize image_size() const;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void Loaded();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void LazyLoad();
|
|
||||||
void SaveAs();
|
|
||||||
void ShowFullsize();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void contextMenuEvent(QContextMenuEvent*);
|
|
||||||
void paintEvent(QPaintEvent*);
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void ImageFetched(RedirectFollower *reply);
|
|
||||||
void ImageScaled(QFuture<QImage> future);
|
|
||||||
|
|
||||||
private:
|
|
||||||
enum State {
|
|
||||||
State_WaitingForLazyLoad,
|
|
||||||
State_Fetching,
|
|
||||||
State_CreatingThumbnail,
|
|
||||||
State_Finished,
|
|
||||||
};
|
|
||||||
|
|
||||||
void DrawThumbnail(QPainter *p, const QRect &rect);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QNetworkAccessManager *network_;
|
|
||||||
State state_;
|
|
||||||
QUrl url_;
|
|
||||||
|
|
||||||
QImage image_;
|
|
||||||
QPixmap thumbnail_;
|
|
||||||
|
|
||||||
QMenu *menu_;
|
|
||||||
QString last_save_dir_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // PRETTYIMAGE_H
|
|
||||||
|
|
|
@ -1,191 +0,0 @@
|
||||||
/*
|
|
||||||
* Strawberry Music Player
|
|
||||||
* This file was part of Clementine.
|
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
|
||||||
*
|
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Strawberry is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QWidget>
|
|
||||||
#include <QUrl>
|
|
||||||
#include <QFrame>
|
|
||||||
#include <QPoint>
|
|
||||||
#include <QRect>
|
|
||||||
#include <QAbstractSlider>
|
|
||||||
#include <QEasingCurve>
|
|
||||||
#include <QLayout>
|
|
||||||
#include <QLayoutItem>
|
|
||||||
#include <QBoxLayout>
|
|
||||||
#include <QScrollBar>
|
|
||||||
#include <QSizePolicy>
|
|
||||||
#include <QPropertyAnimation>
|
|
||||||
#include <QScrollArea>
|
|
||||||
#include <QNetworkAccessManager>
|
|
||||||
#include <QtEvents>
|
|
||||||
|
|
||||||
#include "prettyimage.h"
|
|
||||||
#include "prettyimageview.h"
|
|
||||||
|
|
||||||
PrettyImageView::PrettyImageView(QNetworkAccessManager* network, QWidget* parent)
|
|
||||||
: QScrollArea(parent),
|
|
||||||
network_(network),
|
|
||||||
container_(new QWidget(this)),
|
|
||||||
layout_(new QHBoxLayout(container_)),
|
|
||||||
current_index_(-1),
|
|
||||||
scroll_animation_(new QPropertyAnimation(horizontalScrollBar(), "value", this)),
|
|
||||||
recursion_filter_(false)
|
|
||||||
{
|
|
||||||
|
|
||||||
setWidget(container_);
|
|
||||||
setWidgetResizable(true);
|
|
||||||
setMinimumHeight(PrettyImage::kTotalHeight + 10);
|
|
||||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
|
||||||
setFrameShape(QFrame::NoFrame);
|
|
||||||
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
||||||
|
|
||||||
scroll_animation_->setDuration(250);
|
|
||||||
scroll_animation_->setEasingCurve(QEasingCurve::InOutCubic);
|
|
||||||
connect(horizontalScrollBar(), SIGNAL(sliderReleased()), SLOT(ScrollBarReleased()));
|
|
||||||
connect(horizontalScrollBar(), SIGNAL(actionTriggered(int)), SLOT(ScrollBarAction(int)));
|
|
||||||
|
|
||||||
layout_->setSizeConstraint(QLayout::SetMinAndMaxSize);
|
|
||||||
layout_->setContentsMargins(6, 6, 6, 6);
|
|
||||||
layout_->setSpacing(6);
|
|
||||||
layout_->addSpacing(200);
|
|
||||||
layout_->addSpacing(200);
|
|
||||||
|
|
||||||
container_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PrettyImageView::eventFilter(QObject* obj, QEvent* event) {
|
|
||||||
|
|
||||||
// Work around infinite recursion in QScrollArea resizes.
|
|
||||||
if (recursion_filter_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
recursion_filter_ = true;
|
|
||||||
bool ret = QScrollArea::eventFilter(obj, event);
|
|
||||||
recursion_filter_ = false;
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrettyImageView::AddImage(const QUrl &url) {
|
|
||||||
|
|
||||||
PrettyImage *image = new PrettyImage(url, network_, container_);
|
|
||||||
connect(image, SIGNAL(destroyed()), SLOT(ScrollToCurrent()));
|
|
||||||
connect(image, SIGNAL(Loaded()), SLOT(ScrollToCurrent()));
|
|
||||||
|
|
||||||
layout_->insertWidget(layout_->count() - 1, image);
|
|
||||||
if (current_index_ == -1) ScrollTo(0);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrettyImageView::mouseReleaseEvent(QMouseEvent* e) {
|
|
||||||
|
|
||||||
// Find the image that was clicked on
|
|
||||||
QWidget* widget = container_->childAt(container_->mapFrom(this, e->pos()));
|
|
||||||
if (!widget) return;
|
|
||||||
|
|
||||||
// Get the index of that image
|
|
||||||
const int index = layout_->indexOf(widget) - 1;
|
|
||||||
if (index == -1) return;
|
|
||||||
|
|
||||||
if (index == current_index_) {
|
|
||||||
// Show the image fullsize
|
|
||||||
PrettyImage* pretty_image = qobject_cast<PrettyImage*>(widget);
|
|
||||||
if (pretty_image) {
|
|
||||||
pretty_image->ShowFullsize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Scroll to the image
|
|
||||||
ScrollTo(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrettyImageView::ScrollTo(int index, bool smooth) {
|
|
||||||
|
|
||||||
current_index_ = qBound(0, index, layout_->count() - 3);
|
|
||||||
const int layout_index = current_index_ + 1;
|
|
||||||
|
|
||||||
const QWidget* target_widget = layout_->itemAt(layout_index)->widget();
|
|
||||||
if (!target_widget) return;
|
|
||||||
|
|
||||||
const int current_x = horizontalScrollBar()->value();
|
|
||||||
const int target_x = target_widget->geometry().center().x() - width() / 2;
|
|
||||||
|
|
||||||
if (current_x == target_x) return;
|
|
||||||
|
|
||||||
if (smooth) {
|
|
||||||
scroll_animation_->setStartValue(current_x);
|
|
||||||
scroll_animation_->setEndValue(target_x);
|
|
||||||
scroll_animation_->start();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
scroll_animation_->stop();
|
|
||||||
horizontalScrollBar()->setValue(target_x);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrettyImageView::ScrollToCurrent() {
|
|
||||||
ScrollTo(current_index_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrettyImageView::ScrollBarReleased() {
|
|
||||||
// Find the nearest widget to where the scroll bar was released
|
|
||||||
const int current_x = horizontalScrollBar()->value() + width() / 2;
|
|
||||||
int layout_index = 1;
|
|
||||||
for (; layout_index<layout_->count() - 1 ; ++layout_index) {
|
|
||||||
const QWidget* widget = layout_->itemAt(layout_index)->widget();
|
|
||||||
if (widget && widget->geometry().right() > current_x) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollTo(layout_index - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrettyImageView::ScrollBarAction(int action) {
|
|
||||||
switch (action) {
|
|
||||||
case QAbstractSlider::SliderSingleStepAdd:
|
|
||||||
case QAbstractSlider::SliderPageStepAdd:
|
|
||||||
ScrollTo(current_index_ + 1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case QAbstractSlider::SliderSingleStepSub:
|
|
||||||
case QAbstractSlider::SliderPageStepSub:
|
|
||||||
ScrollTo(current_index_ - 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrettyImageView::resizeEvent(QResizeEvent* e) {
|
|
||||||
QScrollArea::resizeEvent(e);
|
|
||||||
ScrollTo(current_index_, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrettyImageView::wheelEvent(QWheelEvent* e) {
|
|
||||||
const int d = e->delta() > 0 ? -1 : 1;
|
|
||||||
ScrollTo(current_index_ + d, true);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
/*
|
|
||||||
* Strawberry Music Player
|
|
||||||
* This file was part of Clementine.
|
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
|
||||||
*
|
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Strawberry is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef PRETTYIMAGEVIEW_H
|
|
||||||
#define PRETTYIMAGEVIEW_H
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QWidget>
|
|
||||||
#include <QString>
|
|
||||||
#include <QUrl>
|
|
||||||
#include <QScrollArea>
|
|
||||||
#include <QBoxLayout>
|
|
||||||
#include <QPropertyAnimation>
|
|
||||||
#include <QNetworkAccessManager>
|
|
||||||
#include <QtEvents>
|
|
||||||
|
|
||||||
class QEvent;
|
|
||||||
class QMouseEvent;
|
|
||||||
class QResizeEvent;
|
|
||||||
class QWheelEvent;
|
|
||||||
|
|
||||||
class PrettyImageView : public QScrollArea {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
PrettyImageView(QNetworkAccessManager *network, QWidget *parent = nullptr);
|
|
||||||
|
|
||||||
static const char *kSettingsGroup;
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void AddImage(const QUrl& url);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void mouseReleaseEvent(QMouseEvent*);
|
|
||||||
void resizeEvent(QResizeEvent *e);
|
|
||||||
void wheelEvent(QWheelEvent *e);
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void ScrollBarReleased();
|
|
||||||
void ScrollBarAction(int action);
|
|
||||||
void ScrollTo(int index, bool smooth = true);
|
|
||||||
void ScrollToCurrent();
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool eventFilter(QObject*, QEvent*);
|
|
||||||
|
|
||||||
QNetworkAccessManager *network_;
|
|
||||||
|
|
||||||
QWidget *container_;
|
|
||||||
QHBoxLayout *layout_;
|
|
||||||
|
|
||||||
int current_index_;
|
|
||||||
QPropertyAnimation *scroll_animation_;
|
|
||||||
|
|
||||||
bool recursion_filter_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // PRETTYIMAGEVIEW_H
|
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
/*
|
|
||||||
* Strawberry Music Player
|
|
||||||
* This file was part of Clementine.
|
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
|
||||||
*
|
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Strawberry is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QAbstractItemModel>
|
|
||||||
#include <QStyledItemDelegate>
|
|
||||||
#include <QApplication>
|
|
||||||
#include <QVariant>
|
|
||||||
#include <QString>
|
|
||||||
#include <QStringBuilder>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QStyle>
|
|
||||||
#include <QRect>
|
|
||||||
#include <QStyleOptionProgressBar>
|
|
||||||
|
|
||||||
#include "progressitemdelegate.h"
|
|
||||||
|
|
||||||
ProgressItemDelegate::ProgressItemDelegate(QObject *parent)
|
|
||||||
: QStyledItemDelegate(parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgressItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
|
||||||
|
|
||||||
bool ok = false;
|
|
||||||
int progress = index.data().toInt(&ok);
|
|
||||||
|
|
||||||
if (ok) {
|
|
||||||
QStyleOptionProgressBar opt;
|
|
||||||
opt.rect = option.rect;
|
|
||||||
opt.minimum = 0;
|
|
||||||
opt.maximum = 100;
|
|
||||||
opt.progress = progress;
|
|
||||||
opt.text = QString::number(progress) + "%";
|
|
||||||
opt.textVisible = true;
|
|
||||||
|
|
||||||
QApplication::style()->drawControl(QStyle::CE_ProgressBar, &opt, painter);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
QStyledItemDelegate::paint(painter, option, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,165 +0,0 @@
|
||||||
/*
|
|
||||||
* Strawberry Music Player
|
|
||||||
* This file was part of Clementine.
|
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
|
||||||
*
|
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Strawberry is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QWidget>
|
|
||||||
#include <QPixmap>
|
|
||||||
#include <QFrame>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QRect>
|
|
||||||
#include <QPoint>
|
|
||||||
#include <QSize>
|
|
||||||
#include <QSizePolicy>
|
|
||||||
#include <QStyle>
|
|
||||||
#include <QStyleOption>
|
|
||||||
#include <QStylePainter>
|
|
||||||
#include <QtEvents>
|
|
||||||
|
|
||||||
#include "ratingwidget.h"
|
|
||||||
|
|
||||||
const int RatingPainter::kStarCount;
|
|
||||||
const int RatingPainter::kStarSize;
|
|
||||||
|
|
||||||
RatingPainter::RatingPainter() {
|
|
||||||
|
|
||||||
// Load the base pixmaps
|
|
||||||
QPixmap on(":/icons/64x64/star.png");
|
|
||||||
QPixmap off(":/icons/64x64/star-grey.png");
|
|
||||||
|
|
||||||
// Generate the 10 states, better to do it now than on the fly
|
|
||||||
for (int i = 0; i < kStarCount * 2 + 1; ++i) {
|
|
||||||
const float rating = float(i) / 2.0;
|
|
||||||
|
|
||||||
// Clear the pixmap
|
|
||||||
stars_[i] = QPixmap(kStarSize * kStarCount, kStarSize);
|
|
||||||
stars_[i].fill(Qt::transparent);
|
|
||||||
QPainter p(&stars_[i]);
|
|
||||||
|
|
||||||
// Draw the stars
|
|
||||||
int x = 0;
|
|
||||||
for (int i = 0; i < kStarCount; ++i, x += kStarSize) {
|
|
||||||
const QRect rect(x, 0, kStarSize, kStarSize);
|
|
||||||
|
|
||||||
if (rating - 0.25 <= i) {
|
|
||||||
// Totally empty
|
|
||||||
p.drawPixmap(rect, off);
|
|
||||||
} else if (rating - 0.75 <= i) {
|
|
||||||
// Half full
|
|
||||||
const QRect target_left(rect.x(), rect.y(), kStarSize/2, kStarSize);
|
|
||||||
const QRect target_right(rect.x() + kStarSize/2, rect.y(), kStarSize/2, kStarSize);
|
|
||||||
const QRect source_left(0, 0, kStarSize/2, kStarSize);
|
|
||||||
const QRect source_right(kStarSize/2, 0, kStarSize/2, kStarSize);
|
|
||||||
p.drawPixmap(target_left, on, source_left);
|
|
||||||
p.drawPixmap(target_right, off, source_right);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Totally full
|
|
||||||
p.drawPixmap(rect, on);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QRect RatingPainter::Contents(const QRect& rect) {
|
|
||||||
const int width = kStarSize * kStarCount;
|
|
||||||
const int x = rect.x() + (rect.width() - width) / 2;
|
|
||||||
|
|
||||||
return QRect(x, rect.y(), width, rect.height());
|
|
||||||
}
|
|
||||||
|
|
||||||
double RatingPainter::RatingForPos(const QPoint& pos, const QRect& rect) {
|
|
||||||
const QRect contents = Contents(rect);
|
|
||||||
const double raw = double(pos.x() - contents.left()) / contents.width();
|
|
||||||
|
|
||||||
// Round to the nearest 0.1
|
|
||||||
return double(int(raw * kStarCount * 2 + 0.5)) / (kStarCount * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RatingPainter::Paint(QPainter* painter, const QRect& rect, float rating) const {
|
|
||||||
|
|
||||||
QSize size(qMin(kStarSize*kStarCount, rect.width()), qMin(kStarSize, rect.height()));
|
|
||||||
QPoint pos(rect.center() - QPoint(size.width() / 2, size.height() / 2));
|
|
||||||
|
|
||||||
rating *= kStarCount;
|
|
||||||
|
|
||||||
// Draw the stars
|
|
||||||
const int star = qBound(0, int(rating*2.0 + 0.5), kStarCount*2);
|
|
||||||
painter->drawPixmap(QRect(pos, size), stars_[star], QRect(QPoint(0,0), size));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
RatingWidget::RatingWidget(QWidget* parent)
|
|
||||||
: QWidget(parent),
|
|
||||||
rating_(0.0),
|
|
||||||
hover_rating_(-1.0)
|
|
||||||
{
|
|
||||||
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
|
||||||
setMouseTracking(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
QSize RatingWidget::sizeHint() const {
|
|
||||||
const int frame_width = 1 + style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
|
|
||||||
return QSize(RatingPainter::kStarSize * (RatingPainter::kStarCount+2) + frame_width*2, RatingPainter::kStarSize + frame_width*2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RatingWidget::set_rating(float rating) {
|
|
||||||
rating_ = rating;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RatingWidget::paintEvent(QPaintEvent* e) {
|
|
||||||
|
|
||||||
QStylePainter p(this);
|
|
||||||
|
|
||||||
// Draw the background
|
|
||||||
QStyleOptionFrameV3 opt;
|
|
||||||
opt.initFrom(this);
|
|
||||||
opt.state |= QStyle::State_Sunken;
|
|
||||||
opt.frameShape = QFrame::StyledPanel;
|
|
||||||
opt.lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this);
|
|
||||||
opt.midLineWidth = 0;
|
|
||||||
|
|
||||||
p.drawPrimitive(QStyle::PE_PanelLineEdit, opt);
|
|
||||||
|
|
||||||
// Draw the stars
|
|
||||||
painter_.Paint(&p, rect(), hover_rating_ == -1.0 ? rating_ : hover_rating_);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void RatingWidget::mousePressEvent(QMouseEvent* e) {
|
|
||||||
rating_ = RatingPainter::RatingForPos(e->pos(), rect());
|
|
||||||
emit RatingChanged(rating_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RatingWidget::mouseMoveEvent(QMouseEvent* e) {
|
|
||||||
hover_rating_ = RatingPainter::RatingForPos(e->pos(), rect());
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RatingWidget::leaveEvent(QEvent*) {
|
|
||||||
hover_rating_ = -1.0;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
/*
|
|
||||||
* Strawberry Music Player
|
|
||||||
* This file was part of Clementine.
|
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
|
||||||
*
|
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Strawberry is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RATINGWIDGET_H
|
|
||||||
#define RATINGWIDGET_H
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QWidget>
|
|
||||||
#include <QString>
|
|
||||||
#include <QPixmap>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QRect>
|
|
||||||
#include <QSize>
|
|
||||||
#include <QPoint>
|
|
||||||
#include <QtEvents>
|
|
||||||
|
|
||||||
class QEvent;
|
|
||||||
class QMouseEvent;
|
|
||||||
class QPaintEvent;
|
|
||||||
|
|
||||||
class RatingPainter {
|
|
||||||
public:
|
|
||||||
RatingPainter();
|
|
||||||
|
|
||||||
static const int kStarCount = 5;
|
|
||||||
static const int kStarSize = 15;
|
|
||||||
static QRect Contents(const QRect& rect);
|
|
||||||
static double RatingForPos(const QPoint& pos, const QRect& rect);
|
|
||||||
|
|
||||||
void Paint(QPainter *painter, const QRect& rect, float rating) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QPixmap stars_[kStarCount*2+1];
|
|
||||||
};
|
|
||||||
|
|
||||||
class RatingWidget : public QWidget {
|
|
||||||
Q_OBJECT
|
|
||||||
Q_PROPERTY(float rating READ rating WRITE set_rating);
|
|
||||||
|
|
||||||
public:
|
|
||||||
RatingWidget(QWidget *parent = nullptr);
|
|
||||||
|
|
||||||
QSize sizeHint() const;
|
|
||||||
|
|
||||||
float rating() const { return rating_; }
|
|
||||||
void set_rating(float rating);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void RatingChanged(float rating);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void paintEvent(QPaintEvent*);
|
|
||||||
void mousePressEvent(QMouseEvent *e);
|
|
||||||
void mouseMoveEvent(QMouseEvent *e);
|
|
||||||
void leaveEvent(QEvent*);
|
|
||||||
|
|
||||||
private:
|
|
||||||
RatingPainter painter_;
|
|
||||||
float rating_;
|
|
||||||
float hover_rating_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // RATINGWIDGET_H
|
|
||||||
|
|
|
@ -1,582 +0,0 @@
|
||||||
/*
|
|
||||||
* Strawberry Music Player
|
|
||||||
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
|
|
||||||
*
|
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Strawberry is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QWidget>
|
|
||||||
#include <QIODevice>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QList>
|
|
||||||
#include <QVariant>
|
|
||||||
#include <QString>
|
|
||||||
#include <QImage>
|
|
||||||
#include <QPixmap>
|
|
||||||
#include <QMovie>
|
|
||||||
#include <QAction>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QPalette>
|
|
||||||
#include <QBrush>
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QLayout>
|
|
||||||
#include <QMenu>
|
|
||||||
#include <QScrollArea>
|
|
||||||
#include <QBoxLayout>
|
|
||||||
#include <QSettings>
|
|
||||||
#include <QSizePolicy>
|
|
||||||
#include <QTimeLine>
|
|
||||||
#include <QtEvents>
|
|
||||||
|
|
||||||
#include "core/application.h"
|
|
||||||
#include "core/logging.h"
|
|
||||||
#include "core/player.h"
|
|
||||||
#include "core/song.h"
|
|
||||||
#include "core/utilities.h"
|
|
||||||
#include "engine/engine_fwd.h"
|
|
||||||
#include "engine/enginebase.h"
|
|
||||||
#include "engine/enginetype.h"
|
|
||||||
#include "collection/collectionbackend.h"
|
|
||||||
#include "collection/collectionquery.h"
|
|
||||||
#include "collection/collectionview.h"
|
|
||||||
#include "collection/collectionviewcontainer.h"
|
|
||||||
#include "covermanager/albumcoverchoicecontroller.h"
|
|
||||||
#include "covermanager/albumcoverloader.h"
|
|
||||||
#include "covermanager/currentartloader.h"
|
|
||||||
#include "statusview.h"
|
|
||||||
|
|
||||||
const char *StatusView::kSettingsGroup = "StatusView";
|
|
||||||
|
|
||||||
StatusView::StatusView(CollectionViewContainer *collectionviewcontainer, QWidget *parent) :
|
|
||||||
|
|
||||||
QWidget(parent),
|
|
||||||
layout_(new QVBoxLayout),
|
|
||||||
scrollarea_(new QScrollArea),
|
|
||||||
container_layout_(new QVBoxLayout),
|
|
||||||
container_widget_(new QWidget),
|
|
||||||
|
|
||||||
widget_stopped_ (nullptr),
|
|
||||||
widget_playing_ (nullptr),
|
|
||||||
layout_playing_(nullptr),
|
|
||||||
layout_stopped_(nullptr),
|
|
||||||
label_stopped_top_ (nullptr),
|
|
||||||
label_stopped_logo_(nullptr),
|
|
||||||
label_stopped_text_(nullptr),
|
|
||||||
label_playing_top_(nullptr),
|
|
||||||
label_playing_album_(nullptr),
|
|
||||||
label_playing_text_(nullptr),
|
|
||||||
|
|
||||||
album_cover_choice_controller_(new AlbumCoverChoiceController(this)),
|
|
||||||
fade_animation_(new QTimeLine(1000, this)),
|
|
||||||
image_blank_(""),
|
|
||||||
image_nosong_(":/pictures/strawberry.png"),
|
|
||||||
widgetstate_(None),
|
|
||||||
menu_(new QMenu(this))
|
|
||||||
{
|
|
||||||
|
|
||||||
collectionview_ = collectionviewcontainer->view();
|
|
||||||
connect(collectionview_, SIGNAL(TotalSongCountUpdated_()), this, SLOT(UpdateNoSong()));
|
|
||||||
connect(collectionview_, SIGNAL(TotalArtistCountUpdated_()), this, SLOT(UpdateNoSong()));
|
|
||||||
connect(collectionview_, SIGNAL(TotalAlbumCountUpdated_()), this, SLOT(UpdateNoSong()));
|
|
||||||
|
|
||||||
connect(fade_animation_, SIGNAL(valueChanged(qreal)), SLOT(FadePreviousTrack(qreal)));
|
|
||||||
fade_animation_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0
|
|
||||||
|
|
||||||
cover_loader_options_.desired_height_ = 300;
|
|
||||||
cover_loader_options_.pad_output_image_ = true;
|
|
||||||
cover_loader_options_.scale_output_image_ = true;
|
|
||||||
pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_blank_));
|
|
||||||
|
|
||||||
CreateWidget();
|
|
||||||
NoSongWidget();
|
|
||||||
NoSong();
|
|
||||||
AddActions();
|
|
||||||
|
|
||||||
// Load settings
|
|
||||||
QSettings s;
|
|
||||||
s.beginGroup(kSettingsGroup);
|
|
||||||
album_cover_choice_controller_->search_cover_auto_action()->setChecked(s.value("search_for_cover_auto", true).toBool());
|
|
||||||
s.endGroup();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusView::~StatusView() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::AddActions() {
|
|
||||||
|
|
||||||
QList<QAction*> actions = album_cover_choice_controller_->GetAllActions();
|
|
||||||
|
|
||||||
// Here we add the search automatically action, too!
|
|
||||||
actions.append(album_cover_choice_controller_->search_cover_auto_action());
|
|
||||||
|
|
||||||
connect(album_cover_choice_controller_->cover_from_file_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromFile()));
|
|
||||||
connect(album_cover_choice_controller_->cover_to_file_action(), SIGNAL(triggered()), this, SLOT(SaveCoverToFile()));
|
|
||||||
connect(album_cover_choice_controller_->cover_from_url_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromURL()));
|
|
||||||
connect(album_cover_choice_controller_->search_for_cover_action(), SIGNAL(triggered()), this, SLOT(SearchForCover()));
|
|
||||||
connect(album_cover_choice_controller_->unset_cover_action(), SIGNAL(triggered()), this, SLOT(UnsetCover()));
|
|
||||||
connect(album_cover_choice_controller_->show_cover_action(), SIGNAL(triggered()), this, SLOT(ShowCover()));
|
|
||||||
connect(album_cover_choice_controller_->search_cover_auto_action(), SIGNAL(triggered()), this, SLOT(SearchCoverAutomatically()));
|
|
||||||
|
|
||||||
menu_->addActions(actions);
|
|
||||||
menu_->addSeparator();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::CreateWidget() {
|
|
||||||
|
|
||||||
setLayout(layout_);
|
|
||||||
|
|
||||||
layout_->setSizeConstraint(QLayout::SetMinAndMaxSize);
|
|
||||||
layout_->setContentsMargins(0, 0, 0, 0);
|
|
||||||
layout_->setSpacing(6);
|
|
||||||
layout_->addWidget(scrollarea_);
|
|
||||||
|
|
||||||
scrollarea_->setWidget(container_widget_);
|
|
||||||
scrollarea_->setWidgetResizable(true);
|
|
||||||
scrollarea_->setStyleSheet("background-color: white;");
|
|
||||||
scrollarea_->setVisible(true);
|
|
||||||
|
|
||||||
container_widget_->setLayout(container_layout_);
|
|
||||||
container_widget_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
|
||||||
container_widget_->setBackgroundRole(QPalette::Base);
|
|
||||||
|
|
||||||
container_layout_->setSizeConstraint(QLayout::SetMinAndMaxSize);
|
|
||||||
container_layout_->setContentsMargins(0, 0, 0, 0);
|
|
||||||
container_layout_->setSpacing(6);
|
|
||||||
container_layout_->addStretch();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::SetApplication(Application *app) {
|
|
||||||
|
|
||||||
app_ = app;
|
|
||||||
|
|
||||||
album_cover_choice_controller_->SetApplication(app_);
|
|
||||||
connect(app_->current_art_loader(), SIGNAL(ArtLoaded(Song, QString, QImage)), SLOT(AlbumArtLoaded(Song, QString, QImage)));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::NoSongWidget() {
|
|
||||||
|
|
||||||
if (widgetstate_ == Playing) {
|
|
||||||
container_layout_->removeWidget(widget_playing_);
|
|
||||||
widget_playing_->setVisible(false);
|
|
||||||
delete label_playing_top_;
|
|
||||||
delete label_playing_album_;
|
|
||||||
delete label_playing_text_;
|
|
||||||
delete layout_playing_;
|
|
||||||
delete widget_playing_;
|
|
||||||
}
|
|
||||||
widget_stopped_ = new QWidget;
|
|
||||||
layout_stopped_ = new QVBoxLayout;
|
|
||||||
label_stopped_top_ = new QLabel;
|
|
||||||
label_stopped_logo_ = new QLabel;
|
|
||||||
label_stopped_text_ = new QLabel;
|
|
||||||
|
|
||||||
layout_stopped_->addWidget(label_stopped_top_);
|
|
||||||
layout_stopped_->addWidget(label_stopped_logo_);
|
|
||||||
layout_stopped_->addWidget(label_stopped_text_);
|
|
||||||
layout_stopped_->addStretch();
|
|
||||||
|
|
||||||
label_stopped_top_->setFixedHeight(40);
|
|
||||||
label_stopped_top_->setFixedWidth(300);
|
|
||||||
label_stopped_top_->setAlignment(Qt::AlignTop|Qt::AlignLeft);
|
|
||||||
|
|
||||||
widget_stopped_->setLayout(layout_stopped_);
|
|
||||||
container_layout_->insertWidget(0, widget_stopped_);
|
|
||||||
widget_stopped_->setVisible(true);
|
|
||||||
|
|
||||||
widgetstate_ = Stopped;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::SongWidget() {
|
|
||||||
|
|
||||||
if (widgetstate_ == Stopped) {
|
|
||||||
container_layout_->removeWidget(widget_stopped_);
|
|
||||||
widget_stopped_->setVisible(false);
|
|
||||||
delete label_stopped_top_ ;
|
|
||||||
delete label_stopped_logo_;
|
|
||||||
delete label_stopped_text_;
|
|
||||||
delete layout_stopped_;
|
|
||||||
delete widget_stopped_;
|
|
||||||
}
|
|
||||||
widget_playing_ = new QWidget;
|
|
||||||
widget_playing_ = new QWidget;
|
|
||||||
layout_playing_ = new QVBoxLayout;
|
|
||||||
label_playing_top_ = new QLabel;
|
|
||||||
label_playing_album_ = new QLabel;
|
|
||||||
label_playing_text_ = new QLabel;
|
|
||||||
|
|
||||||
layout_playing_->addWidget(label_playing_top_);
|
|
||||||
layout_playing_->addWidget(label_playing_album_);
|
|
||||||
layout_playing_->addWidget(label_playing_text_);
|
|
||||||
layout_playing_->addStretch();
|
|
||||||
|
|
||||||
label_playing_top_->setAlignment(Qt::AlignTop|Qt::AlignLeft);
|
|
||||||
label_playing_top_->setFixedHeight(40);
|
|
||||||
label_playing_top_->setFixedWidth(300);
|
|
||||||
label_playing_top_->setWordWrap(true);
|
|
||||||
|
|
||||||
label_playing_text_->setAlignment(Qt::AlignTop|Qt::AlignLeft);
|
|
||||||
label_playing_text_->setFixedWidth(300);
|
|
||||||
label_playing_text_->setWordWrap(true);
|
|
||||||
|
|
||||||
label_playing_album_->setFixedHeight(300);
|
|
||||||
label_playing_album_->setFixedWidth(300);
|
|
||||||
label_playing_album_->setStyleSheet("background-color: transparent;");
|
|
||||||
label_playing_album_->installEventFilter(this);
|
|
||||||
|
|
||||||
widget_playing_->setLayout(layout_playing_);
|
|
||||||
container_layout_->insertWidget(0, widget_playing_);
|
|
||||||
|
|
||||||
QFile stylesheet(":/style/statusview.css");
|
|
||||||
if (stylesheet.open(QIODevice::ReadOnly)) {
|
|
||||||
setStyleSheet(QString::fromLatin1(stylesheet.readAll()));
|
|
||||||
label_playing_text_->setStyleSheet(QString::fromLatin1(stylesheet.readAll()));
|
|
||||||
}
|
|
||||||
|
|
||||||
widget_playing_->setVisible(true);
|
|
||||||
|
|
||||||
widgetstate_ = Playing;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::SwitchWidgets(WidgetState state) {
|
|
||||||
|
|
||||||
if (widgetstate_ == None) NoSongWidget();
|
|
||||||
|
|
||||||
if ((state == Stopped) && (widgetstate_ != Stopped)) {
|
|
||||||
NoSongWidget();
|
|
||||||
}
|
|
||||||
if ((widgetstate_ != Playing) && (state == Playing)) {
|
|
||||||
SongWidget();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::UpdateSong() {
|
|
||||||
|
|
||||||
SwitchWidgets(Playing);
|
|
||||||
|
|
||||||
const QueryOptions opt;
|
|
||||||
CollectionBackend::AlbumList albumlist;
|
|
||||||
|
|
||||||
label_playing_top_->setText("");
|
|
||||||
label_playing_text_->setText("");
|
|
||||||
|
|
||||||
QString html;
|
|
||||||
QString html_albums;
|
|
||||||
html += QString("<b>%1 - %2</b><br/>%3").arg(metadata_.PrettyTitle().toHtmlEscaped(), metadata_.artist().toHtmlEscaped(), metadata_.album().toHtmlEscaped());
|
|
||||||
label_playing_top_->setText(html);
|
|
||||||
|
|
||||||
html = "";
|
|
||||||
|
|
||||||
html += QString("Filetype: %1<br />\n").arg(metadata_.TextForFiletype());
|
|
||||||
html += QString("Length: %1<br />\n").arg(Utilities::PrettyTimeNanosec(metadata_.length_nanosec()));
|
|
||||||
html += QString("Bitrate: %1 kbps<br />\n").arg(metadata_.bitrate());
|
|
||||||
html += QString("Samplerate: %1 hz / %2 bit<br />\n").arg(metadata_.samplerate()).arg(metadata_.bitdepth());
|
|
||||||
|
|
||||||
if (app_->player()->engine() && app_->player()->engine()->type() != Engine::EngineType::None) {
|
|
||||||
html += QString("<br />");
|
|
||||||
html += QString("Engine: %1<br />").arg(EngineDescription(app_->player()->engine()->type()));
|
|
||||||
}
|
|
||||||
|
|
||||||
html += QString("<br />");
|
|
||||||
|
|
||||||
html_albums += QString("<b>Albums by %1:</b>").arg( metadata_.artist().toHtmlEscaped() );
|
|
||||||
|
|
||||||
albumlist = app_->collection_backend()->GetAlbumsByArtist(metadata_.artist(), opt);
|
|
||||||
|
|
||||||
html_albums += QString("<ul>");
|
|
||||||
int i=0;
|
|
||||||
for (CollectionBackend::Album album : albumlist) {
|
|
||||||
i++;
|
|
||||||
html_albums += QString("<li>%1</li>\n").arg(album.album_name.toHtmlEscaped());
|
|
||||||
}
|
|
||||||
|
|
||||||
html_albums += QString("</ul>");
|
|
||||||
html_albums += QString("");
|
|
||||||
|
|
||||||
if (i > 1) html += html_albums;
|
|
||||||
|
|
||||||
label_playing_text_->setText(html);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::NoSong() {
|
|
||||||
|
|
||||||
QString html;
|
|
||||||
QImage image_logo(":/pictures/strawberry.png");
|
|
||||||
QImage image_logo_scaled = image_logo.scaled(300, 300, Qt::KeepAspectRatio);
|
|
||||||
QPixmap pixmap_logo(QPixmap::fromImage(image_logo_scaled));
|
|
||||||
|
|
||||||
SwitchWidgets(Stopped);
|
|
||||||
|
|
||||||
label_stopped_top_->setText("<b>No Track Playing</b>");
|
|
||||||
label_stopped_logo_->setPixmap(pixmap_logo);
|
|
||||||
|
|
||||||
html += QString(
|
|
||||||
"<html>\n"
|
|
||||||
"<head>\n"
|
|
||||||
"<style type=\"text/css\">:/style/statusview.css</style>\n"
|
|
||||||
"</head>\n"
|
|
||||||
"<body>\n"
|
|
||||||
"%1 songs<br />\n"
|
|
||||||
"%2 artists<br />\n"
|
|
||||||
"%3 albums<br />\n"
|
|
||||||
"</body>\n"
|
|
||||||
"</html>\n"
|
|
||||||
)
|
|
||||||
.arg(collectionview_->TotalSongs())
|
|
||||||
.arg(collectionview_->TotalArtists())
|
|
||||||
.arg(collectionview_->TotalAlbums())
|
|
||||||
;
|
|
||||||
|
|
||||||
label_stopped_text_->setText(html);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::SongChanged(const Song &song) {
|
|
||||||
|
|
||||||
stopped_ = false;
|
|
||||||
metadata_ = song;
|
|
||||||
|
|
||||||
UpdateSong();
|
|
||||||
|
|
||||||
update();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::SongFinished() {
|
|
||||||
|
|
||||||
stopped_ = true;
|
|
||||||
SetImage(image_blank_);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool StatusView::eventFilter(QObject *object, QEvent *event) {
|
|
||||||
|
|
||||||
switch(event->type()) {
|
|
||||||
case QEvent::Paint:{
|
|
||||||
handlePaintEvent(object, event);
|
|
||||||
}
|
|
||||||
default:{
|
|
||||||
return QObject::eventFilter(object, event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return(true);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::handlePaintEvent(QObject *object, QEvent *event) {
|
|
||||||
|
|
||||||
if (object == label_playing_album_) {
|
|
||||||
paintEvent_album(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::paintEvent_album(QEvent *event) {
|
|
||||||
|
|
||||||
QPainter p(label_playing_album_);
|
|
||||||
|
|
||||||
DrawImage(&p);
|
|
||||||
|
|
||||||
// Draw the previous track's image if we're fading
|
|
||||||
if (!pixmap_previous_.isNull()) {
|
|
||||||
p.setOpacity(pixmap_previous_opacity_);
|
|
||||||
p.drawPixmap(0, 0, pixmap_previous_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::DrawImage(QPainter *p) {
|
|
||||||
|
|
||||||
p->drawPixmap(0, 0, 300, 300, pixmap_current_);
|
|
||||||
if ((downloading_covers_) && (spinner_animation_ != nullptr)) {
|
|
||||||
p->drawPixmap(50, 50, 16, 16, spinner_animation_->currentPixmap());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::FadePreviousTrack(qreal value) {
|
|
||||||
|
|
||||||
pixmap_previous_opacity_ = value;
|
|
||||||
if (qFuzzyCompare(pixmap_previous_opacity_, qreal(0.0))) {
|
|
||||||
pixmap_previous_ = QPixmap();
|
|
||||||
}
|
|
||||||
|
|
||||||
update();
|
|
||||||
|
|
||||||
if ((value == 0) && (stopped_ == true)) {
|
|
||||||
SwitchWidgets(Stopped);
|
|
||||||
NoSong();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::contextMenuEvent(QContextMenuEvent *e) {
|
|
||||||
|
|
||||||
// show the menu
|
|
||||||
if (menu_ && widgetstate_ == Playing) menu_->popup(mapToGlobal(e->pos()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::mouseReleaseEvent(QMouseEvent *) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::dragEnterEvent(QDragEnterEvent *e) {
|
|
||||||
|
|
||||||
QWidget::dragEnterEvent(e);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::dropEvent(QDropEvent *e) {
|
|
||||||
|
|
||||||
QWidget::dropEvent(e);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::ScaleCover() {
|
|
||||||
|
|
||||||
pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, original_));
|
|
||||||
update();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::AlbumArtLoaded(const Song &metadata, const QString&, const QImage &image) {
|
|
||||||
|
|
||||||
SwitchWidgets(Playing);
|
|
||||||
|
|
||||||
label_playing_album_->clear();
|
|
||||||
|
|
||||||
metadata_ = metadata;
|
|
||||||
downloading_covers_ = false;
|
|
||||||
|
|
||||||
SetImage(image);
|
|
||||||
|
|
||||||
// Search for cover automatically?
|
|
||||||
GetCoverAutomatically();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::SetImage(const QImage &image) {
|
|
||||||
|
|
||||||
// Cache the current pixmap so we can fade between them
|
|
||||||
pixmap_previous_ = QPixmap(size());
|
|
||||||
pixmap_previous_.fill(palette().background().color());
|
|
||||||
pixmap_previous_opacity_ = 1.0;
|
|
||||||
|
|
||||||
QPainter p(&pixmap_previous_);
|
|
||||||
DrawImage(&p);
|
|
||||||
p.end();
|
|
||||||
|
|
||||||
original_ = image;
|
|
||||||
|
|
||||||
ScaleCover();
|
|
||||||
|
|
||||||
// Were we waiting for this cover to load before we started fading?
|
|
||||||
if (!pixmap_previous_.isNull() && fade_animation_) {
|
|
||||||
fade_animation_->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool StatusView::GetCoverAutomatically() {
|
|
||||||
|
|
||||||
SwitchWidgets(Playing);
|
|
||||||
|
|
||||||
// Search for cover automatically?
|
|
||||||
bool search =
|
|
||||||
!metadata_.has_manually_unset_cover() &&
|
|
||||||
metadata_.art_automatic().isEmpty() &&
|
|
||||||
metadata_.art_manual().isEmpty() &&
|
|
||||||
!metadata_.artist().isEmpty() &&
|
|
||||||
!metadata_.album().isEmpty();
|
|
||||||
|
|
||||||
if (search) {
|
|
||||||
qLog(Debug) << "GetCoverAutomatically";
|
|
||||||
downloading_covers_ = true;
|
|
||||||
album_cover_choice_controller_->SearchCoverAutomatically(metadata_);
|
|
||||||
|
|
||||||
// Show a spinner animation
|
|
||||||
spinner_animation_.reset(new QMovie(":/pictures/spinner.gif", QByteArray(), this));
|
|
||||||
connect(spinner_animation_.get(), SIGNAL(updated(const QRect&)), SLOT(update()));
|
|
||||||
spinner_animation_->start();
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
return search;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::AutomaticCoverSearchDone() {
|
|
||||||
|
|
||||||
downloading_covers_ = false;
|
|
||||||
spinner_animation_.reset();
|
|
||||||
update();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::UpdateNoSong() {
|
|
||||||
|
|
||||||
if (widgetstate_ == Playing) return;
|
|
||||||
|
|
||||||
NoSong();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::LoadCoverFromFile() {
|
|
||||||
album_cover_choice_controller_->LoadCoverFromFile(&metadata_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::LoadCoverFromURL() {
|
|
||||||
album_cover_choice_controller_->LoadCoverFromURL(&metadata_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::SearchForCover() {
|
|
||||||
album_cover_choice_controller_->SearchForCover(&metadata_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::SaveCoverToFile() {
|
|
||||||
album_cover_choice_controller_->SaveCoverToFile(metadata_, original_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::UnsetCover() {
|
|
||||||
album_cover_choice_controller_->UnsetCover(&metadata_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::ShowCover() {
|
|
||||||
album_cover_choice_controller_->ShowCover(metadata_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusView::SearchCoverAutomatically() {
|
|
||||||
|
|
||||||
QSettings s;
|
|
||||||
s.beginGroup(kSettingsGroup);
|
|
||||||
s.setValue("search_for_cover_auto", album_cover_choice_controller_->search_cover_auto_action()->isChecked());
|
|
||||||
|
|
||||||
GetCoverAutomatically();
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,177 +0,0 @@
|
||||||
/*
|
|
||||||
* Strawberry Music Player
|
|
||||||
* This file was part of Clementine.
|
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
|
||||||
*
|
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Strawberry is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QWidget>
|
|
||||||
#include <QString>
|
|
||||||
#include <QFont>
|
|
||||||
#include <QFontMetrics>
|
|
||||||
#include <QImage>
|
|
||||||
#include <QPixmap>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QPalette>
|
|
||||||
#include <QColor>
|
|
||||||
#include <QBrush>
|
|
||||||
#include <QRect>
|
|
||||||
#include <QSize>
|
|
||||||
#include <QTimeLine>
|
|
||||||
#include <QtEvents>
|
|
||||||
|
|
||||||
#include "core/qt_blurimage.h"
|
|
||||||
#include "widgetfadehelper.h"
|
|
||||||
|
|
||||||
const int WidgetFadeHelper::kLoadingPadding = 9;
|
|
||||||
const int WidgetFadeHelper::kLoadingBorderRadius = 10;
|
|
||||||
|
|
||||||
WidgetFadeHelper::WidgetFadeHelper(QWidget* parent, int msec)
|
|
||||||
: QWidget(parent),
|
|
||||||
parent_(parent),
|
|
||||||
blur_timeline_(new QTimeLine(msec, this)),
|
|
||||||
fade_timeline_(new QTimeLine(msec, this))
|
|
||||||
{
|
|
||||||
parent->installEventFilter(this);
|
|
||||||
|
|
||||||
connect(blur_timeline_, SIGNAL(valueChanged(qreal)), SLOT(update()));
|
|
||||||
connect(fade_timeline_, SIGNAL(valueChanged(qreal)), SLOT(update()));
|
|
||||||
connect(fade_timeline_, SIGNAL(finished()), SLOT(FadeFinished()));
|
|
||||||
|
|
||||||
hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WidgetFadeHelper::eventFilter(QObject* obj, QEvent* event) {
|
|
||||||
// We're only interested in our parent's resize events
|
|
||||||
if (obj != parent_ || event->type() != QEvent::Resize) return false;
|
|
||||||
|
|
||||||
// Don't care if we're hidden
|
|
||||||
if (!isVisible()) return false;
|
|
||||||
|
|
||||||
QResizeEvent* re = static_cast<QResizeEvent*>(event);
|
|
||||||
if (re->oldSize() == re->size()) {
|
|
||||||
// Ignore phoney resize events
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a new capture of the parent
|
|
||||||
hide();
|
|
||||||
CaptureParent();
|
|
||||||
show();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WidgetFadeHelper::StartBlur() {
|
|
||||||
CaptureParent();
|
|
||||||
|
|
||||||
// Cover the parent
|
|
||||||
raise();
|
|
||||||
show();
|
|
||||||
|
|
||||||
// Start the timeline
|
|
||||||
blur_timeline_->stop();
|
|
||||||
blur_timeline_->start();
|
|
||||||
|
|
||||||
setAttribute(Qt::WA_TransparentForMouseEvents, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WidgetFadeHelper::CaptureParent() {
|
|
||||||
// Take a "screenshot" of the window
|
|
||||||
original_pixmap_ = QPixmap::grabWidget(parent_);
|
|
||||||
QImage original_image = original_pixmap_.toImage();
|
|
||||||
|
|
||||||
// Blur it
|
|
||||||
QImage blurred(original_image.size(), QImage::Format_ARGB32_Premultiplied);
|
|
||||||
blurred.fill(Qt::transparent);
|
|
||||||
|
|
||||||
QPainter blur_painter(&blurred);
|
|
||||||
blur_painter.save();
|
|
||||||
qt_blurImage(&blur_painter, original_image, 10.0, true, false);
|
|
||||||
blur_painter.restore();
|
|
||||||
|
|
||||||
// Draw some loading text over the top
|
|
||||||
QFont loading_font(font());
|
|
||||||
loading_font.setBold(true);
|
|
||||||
QFontMetrics loading_font_metrics(loading_font);
|
|
||||||
|
|
||||||
const QString loading_text = tr("Loading...");
|
|
||||||
const QSize loading_size(kLoadingPadding*2 + loading_font_metrics.width(loading_text), kLoadingPadding*2 + loading_font_metrics.height());
|
|
||||||
const QRect loading_rect((blurred.width() - loading_size.width()) / 2, 100, loading_size.width(), loading_size.height());
|
|
||||||
|
|
||||||
blur_painter.setRenderHint(QPainter::Antialiasing);
|
|
||||||
blur_painter.setRenderHint(QPainter::HighQualityAntialiasing);
|
|
||||||
|
|
||||||
blur_painter.translate(0.5, 0.5);
|
|
||||||
blur_painter.setPen(QColor(200, 200, 200, 255));
|
|
||||||
blur_painter.setBrush(QColor(200, 200, 200, 192));
|
|
||||||
blur_painter.drawRoundedRect(loading_rect, kLoadingBorderRadius, kLoadingBorderRadius);
|
|
||||||
|
|
||||||
blur_painter.setPen(palette().brush(QPalette::Text).color());
|
|
||||||
blur_painter.setFont(loading_font);
|
|
||||||
blur_painter.drawText(loading_rect.translated(-1, -1), Qt::AlignCenter, loading_text);
|
|
||||||
blur_painter.translate(-0.5, -0.5);
|
|
||||||
|
|
||||||
blur_painter.end();
|
|
||||||
|
|
||||||
blurred_pixmap_ = QPixmap::fromImage(blurred);
|
|
||||||
|
|
||||||
resize(parent_->size());
|
|
||||||
}
|
|
||||||
|
|
||||||
void WidgetFadeHelper::StartFade() {
|
|
||||||
if (blur_timeline_->state() == QTimeLine::Running) {
|
|
||||||
// Blur timeline is still running, so we need render the current state into a new pixmap.
|
|
||||||
QPixmap pixmap(original_pixmap_);
|
|
||||||
QPainter painter(&pixmap);
|
|
||||||
painter.setOpacity(blur_timeline_->currentValue());
|
|
||||||
painter.drawPixmap(0, 0, blurred_pixmap_);
|
|
||||||
painter.end();
|
|
||||||
blurred_pixmap_ = pixmap;
|
|
||||||
}
|
|
||||||
blur_timeline_->stop();
|
|
||||||
original_pixmap_ = QPixmap();
|
|
||||||
|
|
||||||
// Start the timeline
|
|
||||||
fade_timeline_->stop();
|
|
||||||
fade_timeline_->start();
|
|
||||||
|
|
||||||
setAttribute(Qt::WA_TransparentForMouseEvents, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WidgetFadeHelper::paintEvent(QPaintEvent* ) {
|
|
||||||
QPainter p(this);
|
|
||||||
|
|
||||||
if (fade_timeline_->state() != QTimeLine::Running) {
|
|
||||||
// We're fading in the blur
|
|
||||||
p.drawPixmap(0, 0, original_pixmap_);
|
|
||||||
p.setOpacity(blur_timeline_->currentValue());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Fading out the blur into the new image
|
|
||||||
p.setOpacity(1.0 - fade_timeline_->currentValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
p.drawPixmap(0, 0, blurred_pixmap_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WidgetFadeHelper::FadeFinished() {
|
|
||||||
hide();
|
|
||||||
original_pixmap_ = QPixmap();
|
|
||||||
blurred_pixmap_ = QPixmap();
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
/*
|
|
||||||
* Strawberry Music Player
|
|
||||||
* This file was part of Clementine.
|
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
|
||||||
*
|
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Strawberry is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef WIDGETFADEHELPER_H
|
|
||||||
#define WIDGETFADEHELPER_H
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QWidget>
|
|
||||||
#include <QString>
|
|
||||||
#include <QPixmap>
|
|
||||||
#include <QTimeLine>
|
|
||||||
#include <QtEvents>
|
|
||||||
|
|
||||||
class QEvent;
|
|
||||||
class QPaintEvent;
|
|
||||||
|
|
||||||
class WidgetFadeHelper : public QWidget {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
WidgetFadeHelper(QWidget* parent, int msec = 500);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void StartBlur();
|
|
||||||
void StartFade();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void paintEvent(QPaintEvent*);
|
|
||||||
bool eventFilter(QObject* obj, QEvent* event);
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void FadeFinished();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void CaptureParent();
|
|
||||||
|
|
||||||
private:
|
|
||||||
static const int kLoadingPadding;
|
|
||||||
static const int kLoadingBorderRadius;
|
|
||||||
|
|
||||||
QWidget* parent_;
|
|
||||||
QTimeLine* blur_timeline_;
|
|
||||||
QTimeLine* fade_timeline_;
|
|
||||||
|
|
||||||
QPixmap original_pixmap_;
|
|
||||||
QPixmap blurred_pixmap_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // WIDGETFADEHELPER_H
|
|