diff --git a/Changelog b/Changelog index 8b4456523..c3909f475 100644 --- a/Changelog +++ b/Changelog @@ -2,6 +2,17 @@ Strawberry Music Player ======================= 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: * Fixed crash with newer Qt diff --git a/data/data.qrc b/data/data.qrc index e5780faa4..2a80823ad 100644 --- a/data/data.qrc +++ b/data/data.qrc @@ -3,8 +3,7 @@ schema/schema.sql schema/schema-1.sql schema/device-schema.sql - style/mainwindow.css - style/statusview.css + style/strawberry.css misc/playing_tooltip.txt pictures/strawberry.png pictures/strawbs-transparent.png @@ -98,8 +97,6 @@ icons/128x128/speaker.png icons/128x128/star-grey.png icons/128x128/star.png - icons/128x128/strawberry-panel-grey.png - icons/128x128/strawberry-panel.png icons/128x128/strawberry.png icons/128x128/strawberry.svg icons/128x128/tools-wizard.png @@ -188,8 +185,6 @@ icons/64x64/speaker.png icons/64x64/star-grey.png icons/64x64/star.png - icons/64x64/strawberry-panel-grey.png - icons/64x64/strawberry-panel.png icons/64x64/strawberry.png icons/64x64/tools-wizard.png icons/64x64/view-choose.png @@ -280,8 +275,6 @@ icons/48x48/speaker.png icons/48x48/star-grey.png icons/48x48/star.png - icons/48x48/strawberry-panel-grey.png - icons/48x48/strawberry-panel.png icons/48x48/strawberry.png icons/48x48/tools-wizard.png icons/48x48/view-choose.png @@ -372,8 +365,6 @@ icons/32x32/speaker.png icons/32x32/star-grey.png icons/32x32/star.png - icons/32x32/strawberry-panel-grey.png - icons/32x32/strawberry-panel.png icons/32x32/strawberry.png icons/32x32/strawberry.svg icons/32x32/tools-wizard.png @@ -465,8 +456,6 @@ icons/22x22/speaker.png icons/22x22/star-grey.png icons/22x22/star.png - icons/22x22/strawberry-panel-grey.png - icons/22x22/strawberry-panel.png icons/22x22/strawberry.png icons/22x22/strawberry.svg icons/22x22/tools-wizard.png @@ -481,6 +470,7 @@ icons/22x22/xine.png icons/22x22/zoom-in.png icons/22x22/zoom-out.png - icons/22x22/tidal.png + icons/22x22/tidal.png + fonts/HumongousofEternitySt.ttf diff --git a/data/fonts/HumongousofEternitySt.ttf b/data/fonts/HumongousofEternitySt.ttf new file mode 100644 index 000000000..73e818b52 Binary files /dev/null and b/data/fonts/HumongousofEternitySt.ttf differ diff --git a/data/icons/128x128/strawberry-panel-grey.png b/data/icons/128x128/strawberry-panel-grey.png deleted file mode 100644 index cac3b2622..000000000 Binary files a/data/icons/128x128/strawberry-panel-grey.png and /dev/null differ diff --git a/data/icons/128x128/strawberry-panel.png b/data/icons/128x128/strawberry-panel.png deleted file mode 100644 index 4d575b1ad..000000000 Binary files a/data/icons/128x128/strawberry-panel.png and /dev/null differ diff --git a/data/icons/22x22/strawberry-panel-grey.png b/data/icons/22x22/strawberry-panel-grey.png deleted file mode 100644 index 8ad482b4f..000000000 Binary files a/data/icons/22x22/strawberry-panel-grey.png and /dev/null differ diff --git a/data/icons/22x22/strawberry-panel.png b/data/icons/22x22/strawberry-panel.png deleted file mode 100644 index 004df73e4..000000000 Binary files a/data/icons/22x22/strawberry-panel.png and /dev/null differ diff --git a/data/icons/32x32/strawberry-panel-grey.png b/data/icons/32x32/strawberry-panel-grey.png deleted file mode 100644 index 5787faf97..000000000 Binary files a/data/icons/32x32/strawberry-panel-grey.png and /dev/null differ diff --git a/data/icons/32x32/strawberry-panel.png b/data/icons/32x32/strawberry-panel.png deleted file mode 100644 index 961d0cd69..000000000 Binary files a/data/icons/32x32/strawberry-panel.png and /dev/null differ diff --git a/data/icons/48x48/strawberry-panel-grey.png b/data/icons/48x48/strawberry-panel-grey.png deleted file mode 100644 index 8d11609a0..000000000 Binary files a/data/icons/48x48/strawberry-panel-grey.png and /dev/null differ diff --git a/data/icons/48x48/strawberry-panel.png b/data/icons/48x48/strawberry-panel.png deleted file mode 100644 index fb87d0a1b..000000000 Binary files a/data/icons/48x48/strawberry-panel.png and /dev/null differ diff --git a/data/icons/64x64/strawberry-panel-grey.png b/data/icons/64x64/strawberry-panel-grey.png deleted file mode 100644 index e14770991..000000000 Binary files a/data/icons/64x64/strawberry-panel-grey.png and /dev/null differ diff --git a/data/icons/64x64/strawberry-panel.png b/data/icons/64x64/strawberry-panel.png deleted file mode 100644 index cabce0397..000000000 Binary files a/data/icons/64x64/strawberry-panel.png and /dev/null differ diff --git a/data/icons/full/strawberry-panel-grey.png b/data/icons/full/strawberry-panel-grey.png deleted file mode 100644 index 75d8a1e5c..000000000 Binary files a/data/icons/full/strawberry-panel-grey.png and /dev/null differ diff --git a/data/icons/full/strawberry-panel.png b/data/icons/full/strawberry-panel.png deleted file mode 100644 index e651e1b33..000000000 Binary files a/data/icons/full/strawberry-panel.png and /dev/null differ diff --git a/data/style/statusview.css b/data/style/statusview.css deleted file mode 100644 index 6203a1005..000000000 --- a/data/style/statusview.css +++ /dev/null @@ -1,11 +0,0 @@ -StatusView { - background: white; - background-color: white; -} -QVBoxLayout { - background: white; - background-color: white; -} -QScrollArea { - background: qpalette(base); -} diff --git a/data/style/mainwindow.css b/data/style/strawberry.css similarity index 79% rename from data/style/mainwindow.css rename to data/style/strawberry.css index cbc6f45f0..619710fbe 100644 --- a/data/style/mainwindow.css +++ b/data/style/strawberry.css @@ -52,3 +52,20 @@ darwin { darwin QMenu { 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; +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d1cd748d3..e5c9cce5e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -107,7 +107,6 @@ set(SOURCES core/urlhandler.cpp core/utilities.cpp core/scangiomodulepath.cpp - core/flowlayout.cpp core/iconloader.cpp core/qtsystemtrayicon.cpp core/standarditemiconloader.cpp @@ -129,6 +128,10 @@ set(SOURCES equalizer/equalizer.cpp equalizer/equalizerslider.cpp + context/contextview.cpp + context/contextalbumsmodel.cpp + context/contextalbumsview.cpp + collection/collection.cpp collection/collectionmodel.cpp collection/collectionbackend.cpp @@ -218,8 +221,6 @@ set(SOURCES widgets/autoexpandingtreeview.cpp widgets/busyindicator.cpp widgets/clickablelabel.cpp - widgets/didyoumean.cpp - widgets/elidedlabel.cpp widgets/fancytabwidget.cpp widgets/favoritewidget.cpp widgets/fileview.cpp @@ -230,14 +231,9 @@ set(SOURCES widgets/lineedit.cpp widgets/linetextedit.cpp widgets/multiloadingindicator.cpp - widgets/statusview.cpp widgets/playingwidget.cpp widgets/osd.cpp widgets/osdpretty.cpp - widgets/prettyimage.cpp - widgets/prettyimageview.cpp - widgets/progressitemdelegate.cpp - widgets/ratingwidget.cpp widgets/renametablineedit.cpp widgets/sliderwidget.cpp widgets/stickyslider.cpp @@ -246,7 +242,6 @@ set(SOURCES widgets/trackslider.cpp widgets/tracksliderpopup.cpp widgets/tracksliderslider.cpp - widgets/widgetfadehelper.cpp widgets/loginstatewidget.cpp musicbrainz/acoustidclient.cpp @@ -271,6 +266,7 @@ set(SOURCES internet/internetmodel.cpp internet/internetservice.cpp internet/internetplaylistitem.cpp + tidal/tidalservice.cpp tidal/tidalsearch.cpp tidal/tidalsearchview.cpp @@ -278,6 +274,13 @@ set(SOURCES tidal/tidalsearchsortmodel.cpp tidal/tidalsearchitemdelegate.cpp + lyrics/lyricsproviders.cpp + lyrics/lyricsprovider.cpp + lyrics/lyricsfetcher.cpp + lyrics/lyricsfetchersearch.cpp + lyrics/auddlyricsprovider.cpp + lyrics/apiseedslyricsprovider.cpp + ) set(HEADERS @@ -309,6 +312,10 @@ set(HEADERS equalizer/equalizer.h equalizer/equalizerslider.h + + context/contextview.h + context/contextalbumsmodel.h + context/contextalbumsview.h collection/collection.h collection/collectionmodel.h @@ -391,8 +398,6 @@ set(HEADERS widgets/autoexpandingtreeview.h widgets/busyindicator.h widgets/clickablelabel.h - widgets/didyoumean.h - widgets/elidedlabel.h widgets/fancytabwidget.h widgets/favoritewidget.h widgets/fileview.h @@ -402,14 +407,9 @@ set(HEADERS widgets/lineedit.h widgets/linetextedit.h widgets/multiloadingindicator.h - widgets/statusview.h widgets/playingwidget.h widgets/osd.h widgets/osdpretty.h - widgets/prettyimage.h - widgets/prettyimageview.h - widgets/progressitemdelegate.h - widgets/ratingwidget.h widgets/renametablineedit.h widgets/sliderwidget.h widgets/stickyslider.h @@ -417,7 +417,6 @@ set(HEADERS widgets/trackslider.h widgets/tracksliderpopup.h widgets/tracksliderslider.h - widgets/widgetfadehelper.h widgets/loginstatewidget.h musicbrainz/acoustidclient.h @@ -447,6 +446,13 @@ set(HEADERS tidal/tidalsearch.h tidal/tidalsearchview.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 + context/contextviewcontainer.ui + collection/groupbydialog.ui collection/collectionfilterwidget.ui collection/collectionviewcontainer.ui diff --git a/src/collection/collection.cpp b/src/collection/collection.cpp index 8ecbd28a0..94eba5889 100644 --- a/src/collection/collection.cpp +++ b/src/collection/collection.cpp @@ -57,7 +57,6 @@ SCollection::SCollection(Application *app, QObject *parent) backend_->Init(app->database(), kSongsTable, kDirsTable, kSubdirsTable, kFtsTable); model_ = new CollectionModel(backend_, app_, this); - ReloadSettings(); diff --git a/src/collection/collectionbackend.cpp b/src/collection/collectionbackend.cpp index f0db67aaa..19ebbcad1 100644 --- a/src/collection/collectionbackend.cpp +++ b/src/collection/collectionbackend.cpp @@ -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) { - db_ = db; songs_table_ = songs_table; dirs_table_ = dirs_table; @@ -219,8 +218,6 @@ void CollectionBackend::UpdateTotalArtistCount() { q.exec(); if (db_->CheckErrors(q)) return; if (!q.next()) return; - - //qLog(Debug) << "TotalArtist: " << q.value(0).toInt(); emit TotalArtistCountUpdated(q.value(0).toInt()); @@ -236,8 +233,6 @@ void CollectionBackend::UpdateTotalAlbumCount() { q.exec(); if (db_->CheckErrors(q)) return; if (!q.next()) return; - - //qLog(Debug) << "TotalAlbum: " << 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) { - + CollectionQuery query(opt); query.SetColumnSpec("DISTINCT " + column); query.AddCompilationRequirement(false); @@ -547,6 +542,7 @@ QStringList CollectionBackend::GetAll(const QString &column, const QueryOptions } QStringList CollectionBackend::GetAllArtists(const QueryOptions &opt) { + return GetAll("artist", opt); } @@ -596,8 +592,7 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbumsByArtist(const QString return GetAlbums(artist, QString(), false, opt); } -CollectionBackend::AlbumList CollectionBackend::GetAlbumsByAlbumArtist( - const QString &album_artist, const QueryOptions &opt) { +CollectionBackend::AlbumList CollectionBackend::GetAlbumsByAlbumArtist(const QString &album_artist, const QueryOptions &opt) { return GetAlbums(QString(), album_artist, false, opt); } @@ -629,6 +624,7 @@ SongList CollectionBackend::ExecCollectionQuery(CollectionQuery *query) { ret << song; } return ret; + } Song CollectionBackend::GetSongById(int id) { @@ -638,7 +634,6 @@ Song CollectionBackend::GetSongById(int id) { } SongList CollectionBackend::GetSongsById(const QList &ids) { - QMutexLocker l(db_->Mutex()); 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) { - QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); @@ -687,7 +681,6 @@ Song CollectionBackend::GetSongById(int id, QSqlDatabase &db) { } SongList CollectionBackend::GetSongsById(const QStringList &ids, QSqlDatabase &db) { - QString in = ids.join(","); QSqlQuery q(db); @@ -705,7 +698,6 @@ SongList CollectionBackend::GetSongsById(const QStringList &ids, QSqlDatabase &d } Song CollectionBackend::GetSongByUrl(const QUrl &url, qint64 beginning) { - CollectionQuery query; query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec); query.AddWhere("filename", url.toEncoded()); @@ -719,7 +711,6 @@ Song CollectionBackend::GetSongByUrl(const QUrl &url, qint64 beginning) { } SongList CollectionBackend::GetSongsByUrl(const QUrl &url) { - CollectionQuery query; query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec); query.AddWhere("filename", url.toEncoded()); @@ -757,6 +748,7 @@ SongList CollectionBackend::GetCompilationSongs(const QString &album, const Quer ret << song; } return ret; + } 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) { - + metaObject()->invokeMethod(this, "UpdateManualAlbumArt", Qt::QueuedConnection, Q_ARG(QString, artist), Q_ARG(QString, albumartist), Q_ARG(QString, album), Q_ARG(QString, art)); } diff --git a/src/collection/collectiondirectorymodel.cpp b/src/collection/collectiondirectorymodel.cpp index 09b6418a2..4510233db 100644 --- a/src/collection/collectiondirectorymodel.cpp +++ b/src/collection/collectiondirectorymodel.cpp @@ -36,6 +36,8 @@ #include "collectionbackend.h" #include "collectiondirectorymodel.h" +using std::shared_ptr; + CollectionDirectoryModel::CollectionDirectoryModel(CollectionBackend *backend, QObject *parent) : QStandardItemModel(parent), dir_icon_(IconLoader::Load("document-open-folder")), diff --git a/src/collection/collectionfilterwidget.cpp b/src/collection/collectionfilterwidget.cpp index 6b93246aa..dd5a96927 100644 --- a/src/collection/collectionfilterwidget.cpp +++ b/src/collection/collectionfilterwidget.cpp @@ -43,6 +43,7 @@ #include "core/iconloader.h" #include "core/song.h" +#include "core/logging.h" #include "collectionmodel.h" #include "collectionquery.h" #include "savedgroupingmanager.h" @@ -58,6 +59,7 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent) filter_delay_(new QTimer(this)), filter_applies_to_model_(true), delay_behaviour_(DelayedOnLargeLibraries) { + ui_->setupUi(this); // Add the available fields to the tooltip here instead of the ui file to prevent that they get translated by mistake. diff --git a/src/collection/collectionmodel.cpp b/src/collection/collectionmodel.cpp index d23a26822..1cc6bca13 100644 --- a/src/collection/collectionmodel.cpp +++ b/src/collection/collectionmodel.cpp @@ -85,8 +85,8 @@ CollectionModel::CollectionModel(CollectionBackend *backend, Application *app, Q total_song_count_(0), total_artist_count_(0), total_album_count_(0), - artist_icon_(IconLoader::Load("guitar")), - album_icon_(IconLoader::Load("cd")), + artist_icon_(IconLoader::Load("folder-sound")), + album_icon_(IconLoader::Load("cdcase")), playlists_dir_icon_(IconLoader::Load("folder-sound")), playlist_icon_(IconLoader::Load("albums")), 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_->setMaximumCacheSize(CollectionModel::kIconCacheSize); - //QIcon nocover = IconLoader::Load("nocover"); - //QIcon nocover(":/pictures/noalbumart.png"); - //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); + QIcon nocover = IconLoader::Load("cdcase"); + 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); connect(backend_, SIGNAL(SongsDiscovered(SongList)), SLOT(SongsDiscovered(SongList))); connect(backend_, SIGNAL(SongsDeleted(SongList)), SLOT(SongsDeleted(SongList))); @@ -159,7 +158,6 @@ void CollectionModel::SaveGrouping(QString name) { } - void CollectionModel::Init(bool async) { if (async) { @@ -717,6 +715,7 @@ CollectionModel::QueryResult CollectionModel::RunQuery(CollectionItem *parent) { // Execute the query QMutexLocker l(backend_->db()->Mutex()); + if (!backend_->ExecQuery(&q)) return result; while (q.Next()) { @@ -751,7 +750,6 @@ void CollectionModel::PostQuery(CollectionItem *parent, const CollectionModel::Q } void CollectionModel::LazyPopulate(CollectionItem *parent, bool signal) { - if (parent->lazy_loaded) return; parent->lazy_loaded = true; @@ -761,7 +759,6 @@ void CollectionModel::LazyPopulate(CollectionItem *parent, bool signal) { } void CollectionModel::ResetAsync() { - QFuture future = QtConcurrent::run(this, &CollectionModel::RunQuery, root_); NewClosure(future, this, SLOT(ResetAsyncQueryFinished(QFuture)), future); @@ -1033,13 +1030,13 @@ CollectionItem *CollectionModel::ItemFromQuery(GroupBy type, bool signal, bool c item->key = QString::number(bitrate); item->sort_text = SortTextForNumber(bitrate) + " "; break; - + case GroupBy_Samplerate: samplerate = qMax(0, row.value(0).toInt()); item->key = QString::number(samplerate); item->sort_text = SortTextForNumber(samplerate) + " "; break; - + case GroupBy_Bitdepth: bitdepth = qMax(0, row.value(0).toInt()); item->key = QString::number(bitdepth); diff --git a/src/collection/collectionquery.cpp b/src/collection/collectionquery.cpp index a9470c5b5..28e75664e 100644 --- a/src/collection/collectionquery.cpp +++ b/src/collection/collectionquery.cpp @@ -31,11 +31,12 @@ #include #include "collectionquery.h" +#include "core/logging.h" #include "core/song.h" 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) { if (!options.filter().isEmpty()) { diff --git a/src/collection/collectionview.cpp b/src/collection/collectionview.cpp index 56e1421ff..bf8ead77d 100644 --- a/src/collection/collectionview.cpp +++ b/src/collection/collectionview.cpp @@ -298,7 +298,7 @@ bool CollectionView::RestoreLevelFocus(const QModelIndex &parent) { if (!last_selected_song_.url().isEmpty()) { QModelIndex index = qobject_cast(model())->mapToSource(current); SongList songs = app_->collection_model()->GetChildSongs(index); - for (const Song& song : songs) { + for (const Song &song : songs) { if (song == last_selected_song_) { setCurrentIndex(current); return true; @@ -338,9 +338,9 @@ void CollectionView::ReloadSettings() { QSettings settings; 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_show_dividers(settings.value("show_dividers", true).toBool()); } @@ -437,7 +437,7 @@ void CollectionView::paintEvent(QPaintEvent *event) { } void CollectionView::mouseReleaseEvent(QMouseEvent *e) { - + QTreeView::mouseReleaseEvent(e); if (total_song_count_ == 0) { @@ -494,7 +494,7 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) { int regular_elements = 0; int regular_editable = 0; - for (const QModelIndex& index : selected_indexes) { + for (const QModelIndex &index : selected_indexes) { regular_elements++; if(app_->collection_model()->data(index, CollectionModel::Role_Editable).toBool()) { regular_editable++; @@ -559,8 +559,7 @@ void CollectionView::ShowInVarious(bool on) { QList all_of_album = app_->collection_backend()->GetSongsByAlbum(album); QSet other_artists; for (const Song &s : all_of_album) { - if (!albums.contains(album, s.artist()) && - !other_artists.contains(s.artist())) { + if (!albums.contains(album, s.artist()) && !other_artists.contains(s.artist())) { other_artists.insert(s.artist()); } } @@ -586,7 +585,7 @@ void CollectionView::ShowInVarious(bool on) { void CollectionView::Load() { QMimeData *data = model()->mimeData(selectedIndexes()); - if (MimeData* mime_data = qobject_cast(data)) { + if (MimeData *mime_data = qobject_cast(data)) { mime_data->clear_first_ = true; } emit AddToPlaylistSignal(data); diff --git a/src/collection/collectionviewcontainer.cpp b/src/collection/collectionviewcontainer.cpp index 6bcc3cc87..6cf8f7580 100644 --- a/src/collection/collectionviewcontainer.cpp +++ b/src/collection/collectionviewcontainer.cpp @@ -43,11 +43,6 @@ CollectionViewContainer::CollectionViewContainer(QWidget *parent) : QWidget(pare } CollectionViewContainer::~CollectionViewContainer() { delete ui_; } - 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(); } diff --git a/src/collection/collectionviewcontainer.ui b/src/collection/collectionviewcontainer.ui index 824917c64..08b458b5c 100644 --- a/src/collection/collectionviewcontainer.ui +++ b/src/collection/collectionviewcontainer.ui @@ -6,7 +6,7 @@ 0 0 - 400 + 300 300 diff --git a/src/context/contextalbumsmodel.cpp b/src/context/contextalbumsmodel.cpp new file mode 100644 index 000000000..3ebd7f930 --- /dev/null +++ b/src/context/contextalbumsmodel.cpp @@ -0,0 +1,527 @@ +/* + * Strawberry Music Player + * This code was part of Clementine. + * Copyright 2010, David Sansome + * Copyright 2013, Jonas Kvinge + * + * 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 . + * + */ + +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(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(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(this)->LazyPopulate(const_cast(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 urls; + QSet 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 *urls, SongList *songs, QSet *song_ids) const { + + switch (item->type) { + case CollectionItem::Type_Container: { + const_cast(this)->LazyPopulate(item); + + QList 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 dontcare; + SongList ret; + QSet 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; + +} diff --git a/src/context/contextalbumsmodel.h b/src/context/contextalbumsmodel.h new file mode 100644 index 000000000..4faece1f7 --- /dev/null +++ b/src/context/contextalbumsmodel.h @@ -0,0 +1,143 @@ +/* + * Strawberry Music Player + * This code was part of Clementine. + * Copyright 2010, David Sansome + * Copyright 2013, Jonas Kvinge + * + * 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 . + * + */ + +#ifndef CONTEXTALBUMSMODEL_H +#define CONTEXTALBUMSMODEL_H + +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 { + 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 *urls, SongList *songs, QSet *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 song_nodes_; + QMap 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 ItemAndCacheKey; + QMap pending_art_; + QSet pending_cache_keys_; +}; + +#endif // CONTEXTALBUMSMODEL_H diff --git a/src/context/contextalbumsview.cpp b/src/context/contextalbumsview.cpp new file mode 100644 index 000000000..fad2349e7 --- /dev/null +++ b/src/context/contextalbumsview.cpp @@ -0,0 +1,532 @@ +/* + * Strawberry Music Player + * This code was part of Clementine. + * Copyright 2010, David Sansome + * Copyright 2013, Jonas Kvinge + * + * 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 . + * + */ + +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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()) { + pixmap = decoration.value(); + } + else if (decoration.canConvert()) { + pixmap = decoration.value().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(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(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(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(data)) { + mime_data->enqueue_now_ = true; + } + emit AddToPlaylistSignal(data); + +} + +void ContextAlbumsView::OpenInNewPlaylist() { + + QMimeData *data = model()->mimeData(selectedIndexes()); + if (MimeData* mime_data = qobject_cast(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 urls; + for (const Song &song : GetSelectedSongs()) { + urls << song.url(); + } + + Utilities::OpenInFileBrowser(urls); +} diff --git a/src/context/contextalbumsview.h b/src/context/contextalbumsview.h new file mode 100644 index 000000000..af25671c4 --- /dev/null +++ b/src/context/contextalbumsview.h @@ -0,0 +1,151 @@ +/* + * Strawberry Music Player + * This code was part of Clementine. + * Copyright 2010, David Sansome + * Copyright 2013, Jonas Kvinge + * + * 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 . + * + */ + +#ifndef CONTEXTALBUMSVIEW_H +#define CONTEXTALBUMSVIEW_H + +#include "config.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 organise_dialog_; +#endif + std::unique_ptr edit_tag_dialog_; + + bool is_in_keyboard_search_; + + // Save focus + Song last_selected_song_; + QString last_selected_container_; + QSet last_selected_path_; + + ContextAlbumsModel *model_; + +}; + +#endif // CONTEXTALBUMSVIEW_H + diff --git a/src/context/contextview.cpp b/src/context/contextview.cpp new file mode 100644 index 000000000..f3850d42e --- /dev/null +++ b/src/context/contextview.cpp @@ -0,0 +1,654 @@ +/* + * Strawberry Music Player + * Copyright 2013, Jonas Kvinge + * + * 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 . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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
\n" + "%2 artists
\n" + "%3 albums
\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 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("%1 - %2
%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(); + +} diff --git a/src/widgets/statusview.h b/src/context/contextview.h similarity index 63% rename from src/widgets/statusview.h rename to src/context/contextview.h index e999e6703..9b2eb7243 100644 --- a/src/widgets/statusview.h +++ b/src/context/contextview.h @@ -17,8 +17,8 @@ * */ -#ifndef STATUSVIEW_H -#define STATUSVIEW_H +#ifndef CONTEXTVIEW_H +#define CONTEXTVIEW_H #include "config.h" @@ -31,52 +31,48 @@ #include #include #include -#include #include +#include #include #include -#include #include -#include -#include +#include #include #include "core/song.h" #include "covermanager/albumcoverloaderoptions.h" -class QEvent; -class QContextMenuEvent; -class QDragEnterEvent; -class QDropEvent; -class QMouseEvent; +#include "ui_contextviewcontainer.h" + +using std::unique_ptr; class Application; class CollectionView; -class CollectionViewContainer; +class CollectionModel; class AlbumCoverChoiceController; +class Ui_ContextViewContainer; +class ContextAlbumsView; +class LyricsFetcher; -class StatusView : public QWidget { +class ContextView : public QWidget { Q_OBJECT -public: - StatusView(CollectionViewContainer *collectionviewcontainer, QWidget *parent = nullptr); - ~StatusView(); - - 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; + public: + ContextView(QWidget *parent = nullptr); + ~ContextView(); 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 SongFinished(); - void AlbumArtLoaded(const Song& metadata, const QString &uri, const QImage &image); - void FadePreviousTrack(qreal value); + void AlbumArtLoaded(const Song &song, const QString &uri, const QImage &image); void LoadCoverFromFile(); void SaveCoverToFile(); @@ -86,92 +82,83 @@ public slots: void ShowCover(); void SearchCoverAutomatically(); void AutomaticCoverSearchDone(); - -private: - QVBoxLayout *layout_; - QScrollArea *scrollarea_; - QVBoxLayout *container_layout_; - QWidget *container_widget_; + void UpdateLyrics(quint64 id, const QString lyrics); - QWidget *widget_stopped_; - 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_; + private: - QPixmap *pixmap_album_; - QPainter *painter_album_; - + enum WidgetState { + //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_; - + 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_; - - QImage original_; - - void CreateWidget(); - void NoSongWidget(); - void SongWidget(); + Song song_; + Song song_empty_; + Song song_prev_; + QImage image_original_; + QImage image_previous_; + QPixmap *pixmap_album_; + QPixmap pixmap_current_; + QPixmap pixmap_previous_; + QPainter *painter_album_; + qreal pixmap_previous_opacity_; + std::unique_ptr spinner_animation_; + + QString prev_artist_; + QString lyrics_; + + void LoadSettings(); 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 DrawImage(QPainter *p); void ScaleCover(); bool GetCoverAutomatically(); - - Application *app_; - AlbumCoverChoiceController *album_cover_choice_controller_; - QAction *fit_cover_width_action_; - - 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 spinner_animation_; - bool downloading_covers_; - bool stopped_; - bool playing_; - - enum WidgetState { - None = 0, - Playing, - Stopped - }; - WidgetState widgetstate_; - QMenu *menu_; - -protected: + protected: bool eventFilter(QObject *, QEvent *); void handlePaintEvent(QObject *object, QEvent *event); - void paintEvent_album(QEvent *event); + void PaintEventAlbum(QEvent *event); void contextMenuEvent(QContextMenuEvent *e); void mouseReleaseEvent(QMouseEvent *); void dragEnterEvent(QDragEnterEvent *e); void dropEvent(QDropEvent *e); - void UpdateSong(); - void NoSong(); - void SwitchWidgets(WidgetState state); private slots: - void UpdateNoSong(); + void ActionShowData(); + void ActionShowOutput(); + void ActionShowAlbums(); + void ActionShowLyrics(); + void FadePreviousTrack(qreal value); }; -#endif // STATUSVIEW_H +#endif // CONTEXTVIEW_H diff --git a/src/context/contextviewcontainer.ui b/src/context/contextviewcontainer.ui new file mode 100644 index 000000000..e56398882 --- /dev/null +++ b/src/context/contextviewcontainer.ui @@ -0,0 +1,567 @@ + + + ContextViewContainer + + + + 0 + 0 + 400 + 927 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + true + + + + + 0 + 0 + 396 + 923 + + + + + + + + 0 + 70 + + + + + 16777215 + 70 + + + + No song playing + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + 300 + 300 + + + + + 300 + 300 + + + + + + + :/pictures/strawberry.png + + + true + + + + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + Qt::Vertical + + + + 0 + 53 + + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + true + + + + + 0 + 0 + 396 + 923 + + + + + + + + 0 + 70 + + + + + 16777215 + 70 + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + 300 + 300 + + + + + 300 + 300 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + + + 0 + 0 + + + + Bit depth + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + Length + + + + + + + + + + + + + + + 0 + 0 + + + + Samplerate + + + + + + + + + + + + + + + 0 + 0 + + + + Filetype + + + + + + + + 0 + 0 + + + + Bitrate + + + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + + + 32 + 32 + + + + + + + + + + + + 100 + 0 + + + + + + + + + + + + 100 + 0 + + + + + + + + + + + + 32 + 32 + + + + + + + + + + + + 60 + 16777215 + + + + Engine + + + + + + + + 60 + 16777215 + + + + Device + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + 0 + 20 + + + + + 300 + 16777215 + + + + + + + true + + + 6 + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 0 + 20 + + + + + + + + + + + true + + + + + + + Qt::Vertical + + + + 0 + 20 + + + + + + + + + + + + + + + + + + + ContextAlbumsView + QWidget +
context/contextalbumsview.h
+
+
+ + + + +
diff --git a/src/core/application.cpp b/src/core/application.cpp index 801bf8774..2c03ab6d2 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -52,6 +52,11 @@ #include "covermanager/discogscoverprovider.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 "tidal/tidalsearch.h" @@ -60,18 +65,18 @@ bool Application::kIsPortable = false; class ApplicationImpl { public: ApplicationImpl(Application *app) : - tag_reader_client_([=]() { + tag_reader_client_([=]() { TagReaderClient *client = new TagReaderClient(app); app->MoveToNewThread(client); client->Start(); return client; }), - database_([=]() { + database_([=]() { Database *db = new Database(app, app); app->MoveToNewThread(db); DoInAMinuteOrSo(db, SLOT(DoBackup())); return db; - }), + }), appearance_([=]() { return new Appearance(app); }), task_manager_([=]() { return new TaskManager(app); }), player_([=]() { return new Player(app, app); }), @@ -88,10 +93,10 @@ class ApplicationImpl { CoverProviders *cover_providers = new CoverProviders(app); // Initialize the repository of cover providers. #ifdef HAVE_LIBLASTFM - cover_providers->AddProvider(new LastFmCoverProvider(app)); + cover_providers->AddProvider(new LastFmCoverProvider(app)); #endif cover_providers->AddProvider(new AmazonCoverProvider(app)); - cover_providers->AddProvider(new DiscogsCoverProvider(app)); + cover_providers->AddProvider(new DiscogsCoverProvider(app)); cover_providers->AddProvider(new MusicbrainzCoverProvider(app)); return cover_providers; }), @@ -102,7 +107,13 @@ class ApplicationImpl { }), current_art_loader_([=]() { return new CurrentArtLoader(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 tag_reader_client_; @@ -120,6 +131,7 @@ class ApplicationImpl { Lazy current_art_loader_; Lazy internet_model_; Lazy tidal_search_; + Lazy lyrics_providers_; }; @@ -227,3 +239,7 @@ InternetModel* Application::internet_model() const { TidalSearch* Application::tidal_search() const { return p_->tidal_search_.get(); } + +LyricsProviders *Application::lyrics_providers() const { + return p_->lyrics_providers_.get(); +} diff --git a/src/core/application.h b/src/core/application.h index 7ad8cc684..b81703035 100644 --- a/src/core/application.h +++ b/src/core/application.h @@ -51,6 +51,7 @@ class AlbumCoverLoader; class CurrentArtLoader; class InternetModel; class TidalSearch; +class LyricsProviders; class Application : public QObject { Q_OBJECT @@ -84,6 +85,8 @@ class Application : public QObject { InternetModel *internet_model() const; TidalSearch *tidal_search() const; + LyricsProviders *lyrics_providers() const; + void MoveToNewThread(QObject *object); void MoveToThread(QObject *object, QThread *thread); diff --git a/src/core/database.cpp b/src/core/database.cpp index 4c221886d..8b22e330c 100644 --- a/src/core/database.cpp +++ b/src/core/database.cpp @@ -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) { - + for (const QString &command : commands) { // 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. if (command.contains(kMagicAllSongsTables)) { for (const QString &table : song_tables) { // Another horrible hack: device songs tables don't have matching _fts tables, so if this command tries to touch one, ignore it. - if (table.startsWith("device_") && - command.contains(QString(kMagicAllSongsTables) + "_fts")) { + if (table.startsWith("device_") && command.contains(QString(kMagicAllSongsTables) + "_fts")) { continue; } @@ -526,7 +525,8 @@ void Database::ExecSongTablesCommands(QSqlDatabase &db, const QStringList &song_ if (CheckErrors(query)) qFatal("Unable to update music collection database"); } - } else { + } + else { QSqlQuery query(db.exec(command)); if (CheckErrors(query)) qFatal("Unable to update music collection database"); } diff --git a/src/core/flowlayout.cpp b/src/core/flowlayout.cpp deleted file mode 100644 index 9d3bd7361..000000000 --- a/src/core/flowlayout.cpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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(parent); - return pw->style()->pixelMetric(pm, 0, pw); - } - else { - return static_cast(parent)->spacing(); - } -} -//! [12] diff --git a/src/core/flowlayout.h b/src/core/flowlayout.h deleted file mode 100644 index 30069f219..000000000 --- a/src/core/flowlayout.h +++ /dev/null @@ -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 - -#include -#include -#include -#include -#include -#include -#include - -//! [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 itemList; - int m_hSpace; - int m_vSpace; -}; -//! [0] - -#endif diff --git a/src/core/iconloader.cpp b/src/core/iconloader.cpp index 4b14fc2ca..f6c326a1b 100644 --- a/src/core/iconloader.cpp +++ b/src/core/iconloader.cpp @@ -31,34 +31,24 @@ #include "core/logging.h" #include "iconloader.h" -QList IconLoader::sizes_; -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 IconLoader::Load(const QString &name, const int size) { QIcon ret; + QList sizes; + sizes.clear(); + if (size == 0) { sizes << 22 << 32 << 48 << 64; } + else sizes << size; + if (name.isEmpty()) { - qLog(Warning) << "Icon name is empty!"; - ret.addFile(IconDefault, QSize(64, 64)); + qLog(Error) << "Icon name is empty!"; return ret; } const QString path(":icons/%1x%2/%3.png"); - for (int size : sizes_) { - QString filename(path.arg(size).arg(size).arg(name)); - if (QFile::exists(filename)) ret.addFile(filename, QSize(size, size)); + for (int s : sizes) { + QString filename(path.arg(s).arg(s).arg(name)); + if (QFile::exists(filename)) ret.addFile(filename, QSize(s, s)); } // 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; } - if (ret.isNull()) { - ret.addFile(IconDefault, QSize(64, 64)); - } - return ret; } diff --git a/src/core/iconloader.h b/src/core/iconloader.h index e209cc76c..c1ad9f509 100644 --- a/src/core/iconloader.h +++ b/src/core/iconloader.h @@ -27,14 +27,10 @@ class IconLoader { public: - - static void Init(); - static QIcon Load(const QString &name); - + static QIcon Load(const QString &name, const int size = 0); private: IconLoader() {} - - static QList sizes_; }; #endif // ICONLOADER_H + diff --git a/src/core/main.cpp b/src/core/main.cpp index a37fc4ce0..17ccb6c6a 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -200,9 +200,6 @@ int main(int argc, char* argv[]) { // Resources Q_INIT_RESOURCE(data); - - // Icons - IconLoader::Init(); Application app; diff --git a/src/core/mainwindow.cpp b/src/core/mainwindow.cpp index 37d58fd82..03a401cf9 100644 --- a/src/core/mainwindow.cpp +++ b/src/core/mainwindow.cpp @@ -89,12 +89,13 @@ #include "widgets/fancytabwidget.h" #include "widgets/playingwidget.h" #include "widgets/sliderwidget.h" -#include "widgets/statusview.h" #include "widgets/fileview.h" #include "widgets/multiloadingindicator.h" #include "widgets/osd.h" #include "widgets/stylehelper.h" #include "widgets/trackslider.h" +#include "context/contextview.h" +#include "collection/collectionview.h" #include "collection/collection.h" #include "collection/collectionbackend.h" #include "collection/collectiondirectorymodel.h" @@ -158,12 +159,12 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co osd_(osd), edit_tag_dialog_(std::bind(&MainWindow::CreateEditTagDialog, this)), global_shortcuts_(new GlobalShortcuts(this)), + context_view_(new ContextView(this)), collection_view_(new CollectionViewContainer(this)), - status_view_(new StatusView(collection_view_, this)), file_view_(new FileView(this)), - playlist_list_(new PlaylistListContainer(this)), device_view_container_(new DeviceViewContainer(this)), device_view_(device_view_container_->view()), + playlist_list_(new PlaylistListContainer(this)), settings_dialog_(std::bind(&MainWindow::CreateSettingsDialog, this)), cover_manager_([=]() { 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)), track_position_timer_(new QTimer(this)), track_slider_timer_(new QTimer(this)), - was_maximized_(false), + initialised_(false), + was_maximized_(true), saved_playback_position_(0), saved_playback_state_(Engine::Empty), doubleclick_addmode_(AddBehaviour_Append), @@ -214,7 +216,8 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co #endif 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_); 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()); // 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(file_view_, IconLoader::Load("document-open"), tr("Files")); 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_->track_slider, SIGNAL(ValueChangedSeconds(int)), app_->player(), SLOT(SeekTo(int))); + + // Context connections + + connect(context_view_->albums(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*))); // Collection connections 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(SaveGeometry())); - // Status - ConnectStatusView(status_view_); + // Context + 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 //ui_->analyzer->SetEngine(app_->player()->engine()); @@ -611,8 +622,12 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co // Playing widget qLog(Debug) << "Creating playing widget"; 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(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())); PlayingWidgetPositionChanged(); @@ -622,7 +637,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co const_cast(Appearance::kDefaultPalette) = QApplication::palette(); app_->appearance()->LoadUserTheme(); StyleSheetLoader *css_loader = new StyleSheetLoader(this); - css_loader->SetStyleSheet(this, ":style/mainwindow.css"); + css_loader->SetStyleSheet(this, ":/style/strawberry.css"); RefreshStyleSheet(); // 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 window state only if the window was last maximized - was_maximized_ = settings_.value("maximized", false).toBool(); - restoreGeometry(settings_.value("geometry").toByteArray()); - if (was_maximized_) { - setWindowState(windowState() | Qt::WindowMaximized); - } + was_maximized_ = settings_.value("maximized", true).toBool(); + + if (was_maximized_) setWindowState(windowState() | Qt::WindowMaximized); + else restoreGeometry(settings_.value("geometry").toByteArray()); if (!ui_->splitter->restoreState(settings_.value("splitter_state").toByteArray())) { - ui_->splitter->setSizes(QList() << 300 << width() - 300); + ui_->splitter->setSizes(QList() << 250 << width() - 250); } + ui_->tabs->setCurrentIndex(settings_.value("current_tab", 1 /* Collection tab */ ).toInt()); FancyTabWidget::Mode default_mode = FancyTabWidget::Mode_LargeSidebar; 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"; RefreshStyleSheet(); + + initialised_ = true; } @@ -733,8 +750,6 @@ void MainWindow::ReloadSettings() { bool showtrayicon = settings.value("showtrayicon", true).toBool(); settings.endGroup(); - //qLog(Debug) << "showtrayicon" << showtrayicon; - tray_icon_->SetVisible(showtrayicon); if (!showtrayicon && !isVisible()) show(); #endif @@ -766,7 +781,7 @@ void MainWindow::RefreshStyleSheet() { setStyleSheet(styleSheet()); } void MainWindow::MediaStopped() { - + setWindowTitle("Strawberry Music Player"); ui_->action_stop->setEnabled(false); @@ -828,7 +843,6 @@ void MainWindow::VolumeChanged(int volume) { void MainWindow::SongChanged(const Song &song) { - //setWindowTitle(song.PrettyTitleWithArtist() + " --- Strawberry Music Player"); setWindowTitle(song.PrettyTitleWithArtist()); 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() { @@ -870,10 +891,8 @@ void MainWindow::SaveGeometry() { was_maximized_ = isMaximized(); settings_.setValue("maximized", was_maximized_); - // Save the geometry only when mainwindow is not in maximized state - if (!was_maximized_) { - settings_.setValue("geometry", saveGeometry()); - } + if (was_maximized_) settings_.remove("geometry"); + else settings_.setValue("geometry", saveGeometry()); settings_.setValue("splitter_state", ui_->splitter->saveState()); settings_.setValue("current_tab", ui_->tabs->currentIndex()); settings_.setValue("tab_mode", ui_->tabs->mode()); @@ -2104,34 +2123,6 @@ void MainWindow::ShowQueueManager() { 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() { connect(ui_->playlist->view()->selectionModel(),SIGNAL(currentChanged(QModelIndex, QModelIndex)), SLOT(PlaylistCurrentChanged(QModelIndex))); diff --git a/src/core/mainwindow.h b/src/core/mainwindow.h index 920a8c1ed..9df6f89bf 100644 --- a/src/core/mainwindow.h +++ b/src/core/mainwindow.h @@ -59,6 +59,7 @@ class About; class AlbumCoverManager;; class Application; +class ContextView; class CollectionViewContainer; class CommandlineOptions; class DeviceView; @@ -73,7 +74,6 @@ class OrganiseDialog; class PlaylistListContainer; class QueueManager; class Song; -class StatusView; class SystemTrayIcon; #if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT) class TagFetcher; @@ -129,6 +129,7 @@ class MainWindow : public QMainWindow, public PlatformInterface { protected: void keyPressEvent(QKeyEvent *event); + void changeEvent(QEvent *event); void resizeEvent(QResizeEvent *event); void closeEvent(QCloseEvent *event); @@ -270,7 +271,6 @@ signals: void SearchForAlbum(); private: - void ConnectStatusView(StatusView *statusview); void ApplyAddBehaviour(AddBehaviour b, MimeData *data) const; void ApplyPlayBehaviour(PlayBehaviour b, MimeData *data) const; @@ -292,12 +292,12 @@ signals: GlobalShortcuts *global_shortcuts_; + ContextView *context_view_; CollectionViewContainer *collection_view_; - StatusView *status_view_; FileView *file_view_; - PlaylistListContainer *playlist_list_; DeviceViewContainer *device_view_container_; DeviceView *device_view_; + PlaylistListContainer *playlist_list_; Lazy settings_dialog_; Lazy cover_manager_; @@ -354,6 +354,7 @@ signals: QTimer *track_slider_timer_; QSettings settings_; + bool initialised_; bool was_maximized_; int saved_playback_position_; Engine::State saved_playback_state_; diff --git a/src/core/player.cpp b/src/core/player.cpp index ce11ef579..7169fc1e9 100644 --- a/src/core/player.cpp +++ b/src/core/player.cpp @@ -431,6 +431,7 @@ void Player::EngineStateChanged(Engine::State state) { emit Playing(); break; case Engine::Error: + emit Error(); case Engine::Empty: case Engine::Idle: emit Stopped(); diff --git a/src/core/player.h b/src/core/player.h index ee86f3a32..f38bc656b 100644 --- a/src/core/player.h +++ b/src/core/player.h @@ -95,10 +95,11 @@ class PlayerInterface : public QObject { virtual void Play() = 0; virtual void ShowOSD() = 0; -signals: + signals: void Playing(); void Paused(); void Stopped(); + void Error(); void PlaylistFinished(); void VolumeChanged(int volume); void Error(const QString &message); diff --git a/src/core/qtsystemtrayicon.cpp b/src/core/qtsystemtrayicon.cpp index 333681ca7..d571fd275 100644 --- a/src/core/qtsystemtrayicon.cpp +++ b/src/core/qtsystemtrayicon.cpp @@ -47,12 +47,12 @@ QtSystemTrayIcon::QtSystemTrayIcon(QObject *parent) action_mute_(nullptr) { - QIcon theme_icon = IconLoader::Load("strawberry-panel"); - QIcon theme_icon_grey = IconLoader::Load("strawberry-panel-grey"); + QIcon theme_icon = IconLoader::Load("strawberry", 48); + QIcon theme_icon_grey = IconLoader::Load("strawberry-grey", 48); if (theme_icon.isNull() || theme_icon_grey.isNull()) { // Load the default icon - QIcon icon(":/icons/64x64/strawberry-panel.png"); + QIcon icon(":/icons/48x48/strawberry.png"); normal_icon_ = icon.pixmap(48, QIcon::Normal); grey_icon_ = icon.pixmap(48, QIcon::Disabled); } diff --git a/src/core/song.cpp b/src/core/song.cpp index 456fa3cee..b4cd97b8a 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -693,7 +693,7 @@ void Song::InitFromFilePartial(const QString &filename) { TagLib::FileRef fileref(filename.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 { d->valid_ = false; qLog(Error) << "File" << filename << "is not recognized by TagLib as a valid audio file."; diff --git a/src/core/songloader.cpp b/src/core/songloader.cpp index ad4b03efb..6f9633765 100644 --- a/src/core/songloader.cpp +++ b/src/core/songloader.cpp @@ -118,9 +118,10 @@ SongLoader::Result SongLoader::Load(const QUrl &url) { 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, - // 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; } diff --git a/src/covermanager/albumcoverchoicecontroller.cpp b/src/covermanager/albumcoverchoicecontroller.cpp index b3f48efa3..9dfd34a7a 100644 --- a/src/covermanager/albumcoverchoicecontroller.cpp +++ b/src/covermanager/albumcoverchoicecontroller.cpp @@ -79,7 +79,7 @@ AlbumCoverChoiceController::AlbumCoverChoiceController(QWidget *parent) : unset_cover_ = new QAction(IconLoader::Load("list-remove"), tr("Unset cover"), 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_->setChecked(false); diff --git a/src/covermanager/albumcoverfetchersearch.h b/src/covermanager/albumcoverfetchersearch.h index eaf457344..7d5e19eb3 100644 --- a/src/covermanager/albumcoverfetchersearch.h +++ b/src/covermanager/albumcoverfetchersearch.h @@ -58,26 +58,26 @@ class AlbumCoverFetcherSearch : public QObject { CoverSearchStatistics statistics() const { return statistics_; } -signals: + signals: // 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. void AlbumCoverFetched(quint64, const QImage &cover); -private slots: + private slots: void ProviderSearchFinished(int id, const QList &results); void ProviderCoverFetchFinished(RedirectFollower *reply); void TerminateSearch(); -private: + private: void AllProvidersFinished(); void FetchMoreImages(); float ScoreImage(const QImage &image) const; void SendBestImage(); -private: + private: static const int kSearchTimeoutMs; static const int kImageLoadTimeoutMs; static const int kTargetSize; diff --git a/src/covermanager/albumcoverloader.cpp b/src/covermanager/albumcoverloader.cpp index 37db4af3f..8c4cd9634 100644 --- a/src/covermanager/albumcoverloader.cpp +++ b/src/covermanager/albumcoverloader.cpp @@ -226,6 +226,7 @@ void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply) { } NextState(&task); + } QImage AlbumCoverLoader::ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image) { diff --git a/src/covermanager/albumcovermanager.cpp b/src/covermanager/albumcovermanager.cpp index d99454201..f5090aee7 100644 --- a/src/covermanager/albumcovermanager.cpp +++ b/src/covermanager/albumcovermanager.cpp @@ -92,9 +92,8 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec cover_searcher_(nullptr), cover_export_(nullptr), cover_exporter_(new AlbumCoverExporter(this)), - artist_icon_(IconLoader::Load("guitar" )), - all_artists_icon_(IconLoader::Load("cd" )), - //no_cover_icon_(IconLoader::Load("nocover")), + artist_icon_(IconLoader::Load("folder-sound" )), + all_artists_icon_(IconLoader::Load("vinyl" )), no_cover_icon_(":/pictures/noalbumart.png"), no_cover_image_(GenerateNoCoverImage(no_cover_icon_)), no_cover_item_icon_(QPixmap::fromImage(no_cover_image_)), diff --git a/src/covermanager/amazoncoverprovider.cpp b/src/covermanager/amazoncoverprovider.cpp index d3e69b922..2c5470a34 100644 --- a/src/covermanager/amazoncoverprovider.cpp +++ b/src/covermanager/amazoncoverprovider.cpp @@ -111,7 +111,7 @@ void AmazonCoverProvider::QueryFinished(QNetworkReply *reply, int id) { reply->deleteLater(); - QString data=(QString)reply->readAll(); + QString data(reply->readAll()); CoverSearchResults results; diff --git a/src/covermanager/lastfmcoverprovider.h b/src/covermanager/lastfmcoverprovider.h index 423fa9911..b195f2297 100644 --- a/src/covermanager/lastfmcoverprovider.h +++ b/src/covermanager/lastfmcoverprovider.h @@ -37,7 +37,7 @@ class LastFmCoverProvider : public CoverProvider { Q_OBJECT -public: + public: explicit LastFmCoverProvider(QObject *parent = nullptr); bool StartSearch(const QString &artist, const QString &album, int id); @@ -45,10 +45,10 @@ public: static const char *kApiKey; static const char *kSecret; -private slots: + private slots: void QueryFinished(QNetworkReply *reply, int id); -private: + private: QNetworkAccessManager *network_; QMap pending_queries_; diff --git a/src/device/filesystemdevice.cpp b/src/device/filesystemdevice.cpp index 36f0939d4..362ce011f 100644 --- a/src/device/filesystemdevice.cpp +++ b/src/device/filesystemdevice.cpp @@ -46,7 +46,7 @@ FilesystemDevice::FilesystemDevice(const QUrl &url, DeviceLister *lister, const watcher_->set_backend(backend_); 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(watcher_, SIGNAL(NewOrUpdatedSongs(SongList)), backend_, SLOT(AddOrUpdateSongs(SongList))); connect(watcher_, SIGNAL(SongsMTimeUpdated(SongList)), backend_, SLOT(UpdateMTimesOnly(SongList))); diff --git a/src/engine/alsadevicefinder.cpp b/src/engine/alsadevicefinder.cpp index 75483da74..20fd4aed1 100644 --- a/src/engine/alsadevicefinder.cpp +++ b/src/engine/alsadevicefinder.cpp @@ -101,6 +101,8 @@ QList AlsaDeviceFinder::ListDevices() { 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.iconname = GuessIconName(device.description); + device.card = card; + device.device = dev; ret.append(device); } diff --git a/src/engine/devicefinder.h b/src/engine/devicefinder.h index b218425b1..9ee9602c5 100644 --- a/src/engine/devicefinder.h +++ b/src/engine/devicefinder.h @@ -38,6 +38,8 @@ class DeviceFinder { QString description; QVariant value; QString iconname; + int card; + int device; }; virtual ~DeviceFinder() {} diff --git a/src/engine/enginebase.h b/src/engine/enginebase.h index 847c7e8ac..254057942 100644 --- a/src/engine/enginebase.h +++ b/src/engine/enginebase.h @@ -41,6 +41,7 @@ #include "engine_fwd.h" #include "enginetype.h" +#include "enginedevice.h" namespace Engine { @@ -119,6 +120,8 @@ public: static const int kScopeSize = 1024; + QVariant device() { return device_; } + public slots: virtual void SetEqualizerEnabled(bool) {} virtual void SetEqualizerParameters(int preamp, const QList &bandGains) {} diff --git a/src/lyrics/apiseedslyricsprovider.cpp b/src/lyrics/apiseedslyricsprovider.cpp new file mode 100644 index 000000000..ab9ea6636 --- /dev/null +++ b/src/lyrics/apiseedslyricsprovider.cpp @@ -0,0 +1,183 @@ +/* + * Strawberry Music Player + * Copyright 2018, Jonas Kvinge + * + * 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 . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 Arg; + typedef QList ArgList; + typedef QPair 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); +} diff --git a/src/lyrics/apiseedslyricsprovider.h b/src/lyrics/apiseedslyricsprovider.h new file mode 100644 index 000000000..bdbdcae6c --- /dev/null +++ b/src/lyrics/apiseedslyricsprovider.h @@ -0,0 +1,62 @@ +/* + * Strawberry Music Player + * Copyright 2018, Jonas Kvinge + * + * 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 . + * + */ + +#ifndef APISEEDSLYRICSPROVIDER_H +#define APISEEDSLYRICSPROVIDER_H + +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 + diff --git a/src/lyrics/auddlyricsprovider.cpp b/src/lyrics/auddlyricsprovider.cpp new file mode 100644 index 000000000..105f8f21e --- /dev/null +++ b/src/lyrics/auddlyricsprovider.cpp @@ -0,0 +1,215 @@ +/* + * Strawberry Music Player + * Copyright 2018, Jonas Kvinge + * + * 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 . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 Arg; + typedef QList ArgList; + + typedef QPair EncodedArg; + typedef QList 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); +} diff --git a/src/lyrics/auddlyricsprovider.h b/src/lyrics/auddlyricsprovider.h new file mode 100644 index 000000000..3be128315 --- /dev/null +++ b/src/lyrics/auddlyricsprovider.h @@ -0,0 +1,61 @@ +/* + * Strawberry Music Player + * Copyright 2018, Jonas Kvinge + * + * 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 . + * + */ + +#ifndef AUDDLYRICSPROVIDER_H +#define AUDDLYRICSPROVIDER_H + +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include + +#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 + diff --git a/src/lyrics/lyricsfetcher.cpp b/src/lyrics/lyricsfetcher.cpp new file mode 100644 index 000000000..73985a32c --- /dev/null +++ b/src/lyrics/lyricsfetcher.cpp @@ -0,0 +1,124 @@ +/* + * Strawberry Music Player + * Copyright 2018, Jonas Kvinge + * + * 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 . + * + */ + +#include "config.h" + +#include +#include +#include +#include + +#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); + +} diff --git a/src/lyrics/lyricsfetcher.h b/src/lyrics/lyricsfetcher.h new file mode 100644 index 000000000..7db0365ad --- /dev/null +++ b/src/lyrics/lyricsfetcher.h @@ -0,0 +1,95 @@ +/* + * Strawberry Music Player + * Copyright 2018, Jonas Kvinge + * + * 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 . + * + */ + +#ifndef LYRICSFETCHER_H +#define LYRICSFETCHER_H + +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +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 LyricsSearchResults; +Q_DECLARE_METATYPE(QList); + +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 queued_requests_; + QHash active_requests_; + + QTimer *request_starter_; + +}; + +#endif // LYRICSFETCHER_H diff --git a/src/lyrics/lyricsfetchersearch.cpp b/src/lyrics/lyricsfetchersearch.cpp new file mode 100644 index 000000000..7211b634b --- /dev/null +++ b/src/lyrics/lyricsfetchersearch.cpp @@ -0,0 +1,117 @@ +/* + * Strawberry Music Player + * Copyright 2018, Jonas Kvinge + * + * 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 . + * + */ + +#include "config.h" + +#include +#include + +#include +#include +#include +#include +#include + +#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)), SLOT(ProviderSearchFinished(quint64, QList))); + 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 &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(); + } + +} + diff --git a/src/lyrics/lyricsfetchersearch.h b/src/lyrics/lyricsfetchersearch.h new file mode 100644 index 000000000..4c0b1db30 --- /dev/null +++ b/src/lyrics/lyricsfetchersearch.h @@ -0,0 +1,69 @@ +/* + * Strawberry Music Player + * Copyright 2018, Jonas Kvinge + * + * 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 . + * + */ + +#ifndef LYRICSFETCHERSEARCH_H +#define LYRICSFETCHERSEARCH_H + +#include "config.h" + +#include + +#include +#include +#include +#include + +#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 &results); + void TerminateSearch(); + + private: + void AllProvidersFinished(); + + void SendBestImage(); + + private: + static const int kSearchTimeoutMs; + + LyricsSearchRequest request_; + LyricsSearchResults results_; + QMap pending_requests_; + bool cancel_requested_; + +}; + +#endif // LYRICSFETCHERSEARCH_H diff --git a/src/widgets/elidedlabel.h b/src/lyrics/lyricsprovider.cpp similarity index 59% rename from src/widgets/elidedlabel.h rename to src/lyrics/lyricsprovider.cpp index 4c8b2b4dd..a6bf54acd 100644 --- a/src/widgets/elidedlabel.h +++ b/src/lyrics/lyricsprovider.cpp @@ -1,7 +1,6 @@ /* * Strawberry Music Player - * This file was part of Clementine. - * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * 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 -#include #include -#include -#include -class QResizeEvent; +#include "lyricsprovider.h" -class ElidedLabel : public QLabel { - Q_OBJECT - - 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 +LyricsProvider::LyricsProvider(const QString &name, QObject *parent) + : QObject(parent), name_(name) {} diff --git a/src/widgets/progressitemdelegate.h b/src/lyrics/lyricsprovider.h similarity index 55% rename from src/widgets/progressitemdelegate.h rename to src/lyrics/lyricsprovider.h index 9cf37e37c..1b3dbafea 100644 --- a/src/widgets/progressitemdelegate.h +++ b/src/lyrics/lyricsprovider.h @@ -1,7 +1,6 @@ /* * Strawberry Music Player - * This file was part of Clementine. - * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,27 +17,36 @@ * */ -#ifndef PROGRESSITEMDELEGATE_H -#define PROGRESSITEMDELEGATE_H +#ifndef LYRICSPROVIDER_H +#define LYRICSPROVIDER_H #include "config.h" +#include + #include +#include #include -#include -#include -#include -#include -class QModelIndex; +struct LyricsSearchResult; -class ProgressItemDelegate : public QStyledItemDelegate { +class LyricsProvider : public QObject { 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& results); + +private: + QString name_; + }; -#endif // PROGRESSITEMDELEGATE_H - +#endif // LYRICSPROVIDER_H diff --git a/src/lyrics/lyricsproviders.cpp b/src/lyrics/lyricsproviders.cpp new file mode 100644 index 000000000..615bf5294 --- /dev/null +++ b/src/lyrics/lyricsproviders.cpp @@ -0,0 +1,76 @@ +/* + * Strawberry Music Player + * Copyright 2018, Jonas Kvinge + * + * 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 . + * + */ + +#include "config.h" + +#include +#include +#include +#include + +#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(sender()); + RemoveProvider(provider); + +} + +int LyricsProviders::NextId() { return next_id_.fetchAndAddRelaxed(1); } diff --git a/src/lyrics/lyricsproviders.h b/src/lyrics/lyricsproviders.h new file mode 100644 index 000000000..b359c4eb5 --- /dev/null +++ b/src/lyrics/lyricsproviders.h @@ -0,0 +1,60 @@ +/* + * Strawberry Music Player + * Copyright 2018, Jonas Kvinge + * + * 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 . + * + */ + +#ifndef LYRICSPROVIDERS_H +#define LYRICSPROVIDERS_H + +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +class LyricsProvider; + +class LyricsProviders : public QObject { + Q_OBJECT + + public: + explicit LyricsProviders(QObject *parent = nullptr); + void AddProvider(LyricsProvider *provider); + void RemoveProvider(LyricsProvider *provider); + QList 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 lyrics_providers_; + QMutex mutex_; + + QAtomicInt next_id_; +}; + +#endif // LYRICSPROVIDERS_H diff --git a/src/playlist/playlist.cpp b/src/playlist/playlist.cpp index 35489c136..324f280ee 100644 --- a/src/playlist/playlist.cpp +++ b/src/playlist/playlist.cpp @@ -1448,9 +1448,9 @@ void Playlist::StopAfter(int row) { } void Playlist::SetStreamMetadata(const QUrl &url, const Song &song) { - + //qLog(Debug) << "Setting metadata for" << url << "to" << song.artist() << song.title(); - + if (!current_item()) return; if (current_item()->Url() != url) return; @@ -1881,14 +1881,18 @@ bool Playlist::ApplyValidityOnCurrentSong(const QUrl &url, bool valid) { Song current_song = current->Metadata(); // 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())) { - ReloadItems(QList() << current_row()); - } + // FIXME: Why? + // 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() << current_row()); + //} // Gray out the song if it's now broken; otherwise undo the gray color if (valid) { current->RemoveForegroundColor(kInvalidSongPriority); - } else { + } + else { current->SetForegroundColor(kInvalidSongPriority, kInvalidSongColor); } } diff --git a/src/playlist/playlistlistcontainer.cpp b/src/playlist/playlistlistcontainer.cpp index d8f260325..8ce9759e1 100644 --- a/src/playlist/playlistlistcontainer.cpp +++ b/src/playlist/playlistlistcontainer.cpp @@ -394,7 +394,7 @@ void PlaylistListContainer::contextMenuEvent(QContextMenuEvent *e) { void PlaylistListContainer::ActivePlaying() { 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())); new_pixmap.fill(Qt::transparent); @@ -409,7 +409,7 @@ void PlaylistListContainer::ActivePlaying() { } void PlaylistListContainer::ActivePaused() { - UpdateActiveIcon(active_playlist_id_, QIcon(":pictures/tiny-pause.png")); + UpdateActiveIcon(active_playlist_id_, QIcon(":/pictures/tiny-pause.png")); } void PlaylistListContainer::ActiveStopped() { diff --git a/src/playlist/playlistview.cpp b/src/playlist/playlistview.cpp index 5624a2900..f8301e02c 100644 --- a/src/playlist/playlistview.cpp +++ b/src/playlist/playlistview.cpp @@ -584,7 +584,8 @@ void PlaylistView::RemoveSelected(bool deleting_from_disk) { if (!deleting_from_disk) { model()->removeRows(range.top(), range.height(), range.topLeft()); - } else { + } + else { 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_Title, 0.16); - header_->SetColumnWidth(Playlist::Column_Artist, 0.10); - header_->SetColumnWidth(Playlist::Column_Album, 0.10); + header_->SetColumnWidth(Playlist::Column_Artist, 0.12); + header_->SetColumnWidth(Playlist::Column_Album, 0.12); 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_Bitdepth, 0.07); + header_->SetColumnWidth(Playlist::Column_Bitrate, 0.07); header_->SetColumnWidth(Playlist::Column_Filetype, 0.06); + header_->SetColumnWidth(Playlist::Column_Source, 0.06); setting_initial_header_layout_ = false; } diff --git a/src/tidal/tidalsearch.cpp b/src/tidal/tidalsearch.cpp index c8326f4e3..a16a890b8 100644 --- a/src/tidal/tidalsearch.cpp +++ b/src/tidal/tidalsearch.cpp @@ -219,10 +219,6 @@ bool TidalSearch::FindCachedPixmap(const TidalSearch::Result &result, QPixmap *p 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) { 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); HandleLoadedArt(orig_id, image); + } void TidalSearch::HandleLoadedArt(int id, const QImage &image) { diff --git a/src/tidal/tidalsearch.h b/src/tidal/tidalsearch.h index 29cacfd41..6137eda64 100644 --- a/src/tidal/tidalsearch.h +++ b/src/tidal/tidalsearch.h @@ -118,7 +118,6 @@ class TidalSearch : public QObject { void HandleLoadedArt(int id, const QImage &image); bool FindCachedPixmap(const TidalSearch::Result &result, QPixmap *pixmap) const; QString PixmapCacheKey(const TidalSearch::Result &result) const; - void LoadArtAsync(int id, const Result &result); void MaybeSearchFinished(int id); void ShowConfig() {} static QImage ScaleAndPad(const QImage &image); diff --git a/src/tidal/tidalsearchmodel.cpp b/src/tidal/tidalsearchmodel.cpp index 6e9f87278..ded76c6bf 100644 --- a/src/tidal/tidalsearchmodel.cpp +++ b/src/tidal/tidalsearchmodel.cpp @@ -40,13 +40,16 @@ TidalSearchModel::TidalSearchModel(TidalSearch *engine, QObject *parent) engine_(engine), proxy_(nullptr), use_pretty_covers_(true), - artist_icon_(IconLoader::Load("guitar")) { + artist_icon_(IconLoader::Load("folder-sound")) { group_by_[0] = CollectionModel::GroupBy_Artist; group_by_[1] = CollectionModel::GroupBy_Album; 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_; } diff --git a/src/tidal/tidalsearchview.ui b/src/tidal/tidalsearchview.ui index a1cab7181..996b86019 100644 --- a/src/tidal/tidalsearchview.ui +++ b/src/tidal/tidalsearchview.ui @@ -6,7 +6,7 @@ 0 0 - 437 + 400 633 @@ -82,14 +82,14 @@ - albu&ms + a&lbums - songs + son&gs @@ -186,7 +186,7 @@ 0 0 - 435 + 398 533 @@ -195,7 +195,7 @@ 9 109 - 420 + 336 100 @@ -214,12 +214,6 @@ - - - 0 - 80 - - Enter search terms above to find music diff --git a/src/tidal/tidalservice.cpp b/src/tidal/tidalservice.cpp index b93f262ec..99c5328d6 100644 --- a/src/tidal/tidalservice.cpp +++ b/src/tidal/tidalservice.cpp @@ -741,7 +741,8 @@ Song *TidalService::ParseSong(TidalSearchContext *search_ctx, const int album_id //if (i > 1) song.set_compilation_detected(true); 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()); if (search_ctx->requests_song_.contains(id)) return search_ctx->requests_song_.value(id); diff --git a/src/widgets/autoexpandingtreeview.cpp b/src/widgets/autoexpandingtreeview.cpp index 0601a6457..37e2b08c1 100644 --- a/src/widgets/autoexpandingtreeview.cpp +++ b/src/widgets/autoexpandingtreeview.cpp @@ -34,17 +34,19 @@ const int AutoExpandingTreeView::kRowsToShow = 50; AutoExpandingTreeView::AutoExpandingTreeView(QWidget *parent) : QTreeView(parent), - auto_open_(true), - expand_on_reset_(true), + auto_open_(false), + expand_on_reset_(false), add_on_double_click_(true), ignore_next_click_(false) -{ - setExpandsOnDoubleClick(false); + { + + setExpandsOnDoubleClick(true); setAnimated(true); connect(this, SIGNAL(expanded(QModelIndex)), SLOT(ItemExpanded(QModelIndex))); connect(this, SIGNAL(clicked(QModelIndex)), SLOT(ItemClicked(QModelIndex))); connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(ItemDoubleClicked(QModelIndex))); + } void AutoExpandingTreeView::reset() { @@ -158,8 +160,7 @@ void AutoExpandingTreeView::keyPressEvent(QKeyEvent *e) { case Qt::Key_Left: // Set focus on the root of the current branch - if (index.isValid() && index.parent() != rootIndex() && - (!isExpanded(index) || model()->rowCount(index) == 0)) { + if (index.isValid() && index.parent() != rootIndex() && (!isExpanded(index) || model()->rowCount(index) == 0)) { setCurrentIndex(index.parent()); setFocus(); e->accept(); diff --git a/src/widgets/didyoumean.cpp b/src/widgets/didyoumean.cpp deleted file mode 100644 index b7670458f..000000000 --- a/src/widgets/didyoumean.cpp +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Strawberry Music Player - * This file was part of Clementine. - * Copyright 2010, David Sansome - * - * 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 . - * - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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(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(); -} - diff --git a/src/widgets/didyoumean.h b/src/widgets/didyoumean.h deleted file mode 100644 index df42ad16a..000000000 --- a/src/widgets/didyoumean.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Strawberry Music Player - * This file was part of Clementine. - * Copyright 2010, David Sansome - * - * 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 . - * - */ - -#ifndef DIDYOUMEAN_H -#define DIDYOUMEAN_H - -#include "config.h" - -#include - -#include -#include -#include -#include -#include -#include - -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 diff --git a/src/widgets/elidedlabel.cpp b/src/widgets/elidedlabel.cpp deleted file mode 100644 index a8911705b..000000000 --- a/src/widgets/elidedlabel.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Strawberry Music Player - * This file was part of Clementine. - * Copyright 2010, David Sansome - * - * 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 . - * - */ - -#include "config.h" - -#include -#include -#include -#include -#include - -#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)); -} diff --git a/src/widgets/fancytabwidget.cpp b/src/widgets/fancytabwidget.cpp index 7452512f9..3a56beb3d 100644 --- a/src/widgets/fancytabwidget.cpp +++ b/src/widgets/fancytabwidget.cpp @@ -126,7 +126,8 @@ void FancyTabProxyStyle::drawControl(ControlElement element, const QStyleOption* if (vertical_tabs) { m = QTransform::fromTranslate(rect.left(), rect.bottom()); m.rotate(-90); - } else { + } + else { m = QTransform::fromTranslate(rect.left(), rect.top()); } @@ -314,8 +315,7 @@ QSize FancyTabBar::tabSizeHint(bool minimum) const { return QSize(width, iconHeight + spacing + fm.height()); } -void FancyTabBar::paintEvent(QPaintEvent *event) -{ +void FancyTabBar::paintEvent(QPaintEvent *event) { Q_UNUSED(event) QPainter p(this); @@ -328,8 +328,7 @@ void FancyTabBar::paintEvent(QPaintEvent *event) paintTab(&p, currentIndex()); } -bool FancyTab::event(QEvent* event) -{ +bool FancyTab::event(QEvent* event) { if (event->type() == QEvent::ToolTip) { QFontMetrics metrics (font()); int text_width = metrics.width(text); @@ -338,7 +337,8 @@ bool FancyTab::event(QEvent* event) // The text is elided: show the tooltip QHelpEvent* he = static_cast(event); QToolTip::showText(he->globalPos(), text); - } else { + } + else { QToolTip::hideText(); } return true; @@ -346,30 +346,25 @@ bool FancyTab::event(QEvent* event) return QWidget::event(event); } -void FancyTab::enterEvent(QEvent*) -{ +void FancyTab::enterEvent(QEvent*) { fadeIn(); } -void FancyTab::leaveEvent(QEvent*) -{ +void FancyTab::leaveEvent(QEvent*) { fadeOut(); } -QSize FancyTabBar::sizeHint() const -{ +QSize FancyTabBar::sizeHint() const { QSize sh = tabSizeHint(); return QSize(sh.width(), sh.height() * m_tabs.count()); } -QSize FancyTabBar::minimumSizeHint() const -{ +QSize FancyTabBar::minimumSizeHint() const { QSize sh = tabSizeHint(true); 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(); } @@ -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 -void FancyTabBar::emitCurrentIndex() -{ +void FancyTabBar::emitCurrentIndex() { emit currentChanged(m_currentIndex); } -void FancyTabBar::mousePressEvent(QMouseEvent *e) -{ +void FancyTabBar::mousePressEvent(QMouseEvent *e) { e->accept(); for (int index = 0; index < m_tabs.count(); ++index) { if (tabRect(index).contains(e->pos())) { @@ -415,8 +408,7 @@ void FancyTabBar::addSpacer(int size) { 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)) { qWarning("invalid index"); return; @@ -427,7 +419,7 @@ void FancyTabBar::paintTab(QPainter *painter, int tabIndex) const bool selected = (tabIndex == m_currentIndex); if (selected) { - //background + //background painter->save(); QLinearGradient grad(rect.topLeft(), rect.topRight()); 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->restore(); - //shadows + //shadows 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->setPen(QColor(0, 0, 0, 40)); painter->drawLine(rect.topLeft(), rect.bottomLeft()); - //highlights + //highlights painter->setPen(QColor(255, 255, 255, 50)); - 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.topLeft() + QPoint(0, -2), rect.topRight() - QPoint(0,2)); + painter->drawLine(rect.bottomLeft() + QPoint(0, 1), rect.bottomRight() + QPoint(0,1)); painter->setPen(QColor(255, 255, 255, 40)); painter->drawLine(rect.topLeft() + QPoint(0, 0), rect.topRight()); - 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.topRight() + 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())); @@ -567,6 +559,7 @@ void FancyTabWidget::SetBackgroundPixmap(const QPixmap& pixmap) { } void FancyTabWidget::paintEvent(QPaintEvent*) { + if (!use_background_) return; QPainter painter(this); @@ -602,9 +595,11 @@ int FancyTabWidget::currentIndex() const { void FancyTabWidget::setCurrentIndex(int index) { if (FancyTabBar* bar = qobject_cast(tab_bar_)) { bar->setCurrentIndex(index); - } else if (QTabBar* bar = qobject_cast(tab_bar_)) { + } + else if (QTabBar* bar = qobject_cast(tab_bar_)) { bar->setCurrentIndex(index); - } else { + } + else { stack_->setCurrentIndex(index); } } @@ -709,8 +704,7 @@ void FancyTabWidget::AddMenuItem(QSignalMapper* mapper, QActionGroup* group, con if (mode == mode_) action->setChecked(true); } -void FancyTabWidget::MakeTabBar(QTabBar::Shape shape, bool text, bool icons, - bool fancy) { +void FancyTabWidget::MakeTabBar(QTabBar::Shape shape, bool text, bool icons, bool fancy) { QTabBar* bar = new QTabBar(this); bar->setShape(shape); bar->setDocumentMode(true); diff --git a/src/widgets/playingwidget.cpp b/src/widgets/playingwidget.cpp index 6879ca88a..4411eeed1 100644 --- a/src/widgets/playingwidget.cpp +++ b/src/widgets/playingwidget.cpp @@ -49,6 +49,8 @@ #include "covermanager/currentartloader.h" #include "playingwidget.h" +using std::unique_ptr; + const char *PlayingWidget::kSettingsGroup = "PlayingWidget"; // 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::kGradientTail = 20; -// Maximum height of the cover in large mode, and offset between the -// bottom of the cover and bottom of the widget +// Maximum height of the cover in large mode, and offset between the bottom of the cover and bottom of the widget const int PlayingWidget::kMaxCoverSize = 260; const int PlayingWidget::kBottomOffset = 0; // Border for large mode const int PlayingWidget::kTopBorder = 4; - PlayingWidget::PlayingWidget(QWidget *parent) : QWidget(parent), app_(nullptr), album_cover_choice_controller_(new AlbumCoverChoiceController(this)), - mode_(SmallSongDetails), + mode_(LargeSongDetails), menu_(new QMenu(this)), fit_cover_width_action_(nullptr), enabled_(false), @@ -79,12 +79,14 @@ PlayingWidget::PlayingWidget(QWidget *parent) active_(false), small_ideal_height_(0), fit_width_(false), - show_hide_animation_(new QTimeLine(500, this)), - fade_animation_(new QTimeLine(1000, this)), + timeline_show_hide_(new QTimeLine(500, this)), + timeline_fade_(new QTimeLine(1000, this)), details_(new QTextDocument(this)), - previous_track_opacity_(0.0), + pixmap_previous_track_opacity_(0.0), downloading_covers_(false) { + SetHeight(0); + // Load settings QSettings s; s.beginGroup(kSettingsGroup); @@ -129,26 +131,20 @@ PlayingWidget::PlayingWidget(QWidget *parent) menu_->addSeparator(); // Animations - connect(show_hide_animation_, SIGNAL(frameChanged(int)), SLOT(SetHeight(int))); - setMaximumHeight(0); - - connect(fade_animation_, SIGNAL(valueChanged(qreal)), SLOT(FadePreviousTrack(qreal))); - fade_animation_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0 + connect(timeline_show_hide_, SIGNAL(frameChanged(int)), SLOT(SetHeight(int))); + connect(timeline_fade_, SIGNAL(valueChanged(qreal)), SLOT(FadePreviousTrack(qreal))); + timeline_fade_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0 // add placeholder text to get the correct height if (mode_ == LargeSongDetails) { - details_->setDefaultStyleSheet( - "p {" - " font-size: small;" - " color: black;" - "}"); + details_->setDefaultStyleSheet("p { font-size: small; font-weight: bold; }"); details_->setHtml(QString("



")); } UpdateHeight(); connect(album_cover_choice_controller_, SIGNAL(AutomaticCoverSearchDone()), this, SLOT(AutomaticCoverSearchDone())); - + } 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); - action->setCheckable(true); - mapper->setMapping(action, mode); - connect(action, SIGNAL(triggered()), mapper, SLOT(map())); +void PlayingWidget::SetEnabled() { + enabled_ = true; + if (!visible_ && active_) SetVisible(true); +} - 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 { - 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_) { - 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; - } + QAction *action = new QAction(text, group); + action->setCheckable(true); + mapper->setMapping(action, mode); + connect(action, SIGNAL(triggered()), mapper, SLOT(map())); - // Update the animation settings and resize the widget now if we're visible - show_hide_animation_->setFrameRange(0, total_height_); - if (visible_ && show_hide_animation_->state() != QTimeLine::Running) setMaximumHeight(total_height_); + if (mode == mode_) action->setChecked(true); - // 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 += "

"; - 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 += "

"; - break; - } - - // TODO: Make this configurable - html += QString("%1
%2
%3").arg(metadata_.PrettyTitle().toHtmlEscaped(), metadata_.artist().toHtmlEscaped(), metadata_.album().toHtmlEscaped()); - - html += "

"; - 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) { - + mode_ = Mode(mode); 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 += "

"; + break; + case LargeSongDetails: + details_->setTextWidth(cover_loader_options_.desired_height_); + html += "

"; + break; + } + + html += QString("%1
%2
%3").arg(song_.PrettyTitle().toHtmlEscaped(), song_.artist().toHtmlEscaped(), song_.album().toHtmlEscaped()); + + html += "

"; + 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) { - if (visible_ && e->oldSize() != e->size()) { + //if (visible_ && e->oldSize() != e->size()) { + if (e->oldSize() != e->size()) { if (mode_ == LargeSongDetails) { UpdateHeight(); 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) { if (AlbumCoverChoiceController::CanAcceptDrag(e)) { @@ -492,7 +480,7 @@ void PlayingWidget::dragEnterEvent(QDragEnterEvent *e) { void PlayingWidget::dropEvent(QDropEvent *e) { - album_cover_choice_controller_->SaveCover(&metadata_, e); + album_cover_choice_controller_->SaveCover(&song_, e); QWidget::dropEvent(e); @@ -503,14 +491,13 @@ bool PlayingWidget::GetCoverAutomatically() { // Search for cover automatically? bool search = album_cover_choice_controller_->search_cover_auto_action()->isChecked() && - !metadata_.has_manually_unset_cover() && - metadata_.art_automatic().isEmpty() && metadata_.art_manual().isEmpty() && - !metadata_.artist().isEmpty() && !metadata_.album().isEmpty(); + !song_.has_manually_unset_cover() && + song_.art_automatic().isEmpty() && song_.art_manual().isEmpty() && + !song_.artist().isEmpty() && !song_.album().isEmpty(); if (search) { - //qLog(Debug) << "GetCoverAutomatically"; downloading_covers_ = true; - album_cover_choice_controller_->SearchCoverAutomatically(metadata_); + album_cover_choice_controller_->SearchCoverAutomatically(song_); // Show a spinner animation 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; - - if ((active_ == true) && (visible_ == false)) SetVisible(true); + QSettings s; + s.beginGroup(kSettingsGroup); + s.setValue("search_for_cover_auto", album_cover_choice_controller_->search_cover_auto_action()->isChecked()); + s.endGroup(); - enabled_ = true; + GetCoverAutomatically(); } -void PlayingWidget::SetDisabled() { - - if (enabled_ == false) return; - - if (visible_ == true) SetVisible(false); - - enabled_ = false; - +void PlayingWidget::LoadCoverFromFile() { + album_cover_choice_controller_->LoadCoverFromFile(&song_); } + +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_); +} + diff --git a/src/widgets/playingwidget.h b/src/widgets/playingwidget.h index d98bc7407..ed1bdd244 100644 --- a/src/widgets/playingwidget.h +++ b/src/widgets/playingwidget.h @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2013, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -45,12 +47,13 @@ #include "core/song.h" #include "covermanager/albumcoverloaderoptions.h" +using std::unique_ptr; + class QContextMenuEvent; class QDragEnterEvent; class QDropEvent; class QMouseEvent; class QPaintEvent; -class QPainter; class QResizeEvent; class AlbumCoverChoiceController; @@ -63,35 +66,22 @@ class PlayingWidget : public QWidget { PlayingWidget(QWidget *parent = nullptr); ~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 SetEnabled(); void SetDisabled(); - void set_ideal_height(int height); - QSize sizeHint() const; -signals: + signals: void ShowAboveStatusBarChanged(bool above); -public slots: + public slots: + void Playing(); void Stopped(); + void Error(); + void SongChanged(const Song &song); -protected: + protected: void paintEvent(QPaintEvent *e); void resizeEvent(QResizeEvent*); void contextMenuEvent(QContextMenuEvent *e); @@ -99,17 +89,11 @@ protected: void dragEnterEvent(QDragEnterEvent *e); void dropEvent(QDropEvent *e); -private slots: + private slots: + void SetMode(int mode); 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 SaveCoverToFile(); void LoadCoverFromURL(); @@ -119,49 +103,58 @@ private slots: void SearchCoverAutomatically(); void AutomaticCoverSearchDone(); - private: - void CreateModeAction(Mode mode, const QString &text, QActionGroup *group, QSignalMapper *mapper); - void UpdateDetailsText(); - void UpdateHeight(); - void DrawContents(QPainter *p); - void SetImage(const QImage &image); - void ScaleCover(); - bool GetCoverAutomatically(); + void AlbumArtLoaded(const Song &song, const QString &uri, const QImage &image); + void SetHeight(int height); + void FadePreviousTrack(qreal value); + + private: + + enum Mode { + 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_; AlbumCoverChoiceController *album_cover_choice_controller_; - Mode mode_; - QMenu *menu_; - QAction *fit_cover_width_action_; - bool enabled_; bool visible_; bool active_; - int small_ideal_height_; AlbumCoverLoaderOptions cover_loader_options_; int total_height_; bool fit_width_; - QTimeLine *show_hide_animation_; - QTimeLine *fade_animation_; - - // Information about the current track - Song metadata_; - QPixmap cover_; - // A copy of the original, unscaled album cover. - QImage original_; + QTimeLine *timeline_show_hide_; + QTimeLine *timeline_fade_; QTextDocument *details_; - - // Holds the last track while we're fading to the new track - QPixmap previous_track_; - qreal previous_track_opacity_; - - std::unique_ptr spinner_animation_; + qreal pixmap_previous_track_opacity_; bool downloading_covers_; + + Song song_; + QImage image_original_; + QPixmap pixmap_cover_; + QPixmap pixmap_previous_track_; + std::unique_ptr 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 diff --git a/src/widgets/prettyimage.cpp b/src/widgets/prettyimage.cpp deleted file mode 100644 index fdafb5489..000000000 --- a/src/widgets/prettyimage.cpp +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Strawberry Music Player - * This file was part of Clementine. - * Copyright 2010, David Sansome - * - * 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 . - * - */ - -#include "config.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 future = QtConcurrent::run(image_, &QImage::scaled, image_size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); - NewClosure(future, this, SLOT(ImageScaled(QFuture)), future); - } -} - -void PrettyImage::ImageScaled(QFuture 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); -} - diff --git a/src/widgets/prettyimage.h b/src/widgets/prettyimage.h deleted file mode 100644 index e31fbd6fa..000000000 --- a/src/widgets/prettyimage.h +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Strawberry Music Player - * This file was part of Clementine. - * Copyright 2010, David Sansome - * - * 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 . - * - */ - -#ifndef PRETTYIMAGE_H -#define PRETTYIMAGE_H - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 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 - diff --git a/src/widgets/prettyimageview.cpp b/src/widgets/prettyimageview.cpp deleted file mode 100644 index 9ef9a8ed2..000000000 --- a/src/widgets/prettyimageview.cpp +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Strawberry Music Player - * This file was part of Clementine. - * Copyright 2010, David Sansome - * - * 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 . - * - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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(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_indexcount() - 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); -} - diff --git a/src/widgets/prettyimageview.h b/src/widgets/prettyimageview.h deleted file mode 100644 index 2b2290cfe..000000000 --- a/src/widgets/prettyimageview.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Strawberry Music Player - * This file was part of Clementine. - * Copyright 2010, David Sansome - * - * 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 . - * - */ - -#ifndef PRETTYIMAGEVIEW_H -#define PRETTYIMAGEVIEW_H - -#include "config.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 - diff --git a/src/widgets/progressitemdelegate.cpp b/src/widgets/progressitemdelegate.cpp deleted file mode 100644 index a35a7100e..000000000 --- a/src/widgets/progressitemdelegate.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Strawberry Music Player - * This file was part of Clementine. - * Copyright 2010, David Sansome - * - * 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 . - * - */ - -#include "config.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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); - } - -} - diff --git a/src/widgets/ratingwidget.cpp b/src/widgets/ratingwidget.cpp deleted file mode 100644 index 269633c1b..000000000 --- a/src/widgets/ratingwidget.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Strawberry Music Player - * This file was part of Clementine. - * Copyright 2010, David Sansome - * - * 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 . - * - */ - -#include "config.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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(); -} - diff --git a/src/widgets/ratingwidget.h b/src/widgets/ratingwidget.h deleted file mode 100644 index 0e8e0c64c..000000000 --- a/src/widgets/ratingwidget.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Strawberry Music Player - * This file was part of Clementine. - * Copyright 2010, David Sansome - * - * 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 . - * - */ - -#ifndef RATINGWIDGET_H -#define RATINGWIDGET_H - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 - diff --git a/src/widgets/statusview.cpp b/src/widgets/statusview.cpp deleted file mode 100644 index cc3dc7352..000000000 --- a/src/widgets/statusview.cpp +++ /dev/null @@ -1,582 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2013, Jonas Kvinge - * - * 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 . - * - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 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("%1 - %2
%3").arg(metadata_.PrettyTitle().toHtmlEscaped(), metadata_.artist().toHtmlEscaped(), metadata_.album().toHtmlEscaped()); - label_playing_top_->setText(html); - - html = ""; - - html += QString("Filetype: %1
\n").arg(metadata_.TextForFiletype()); - html += QString("Length: %1
\n").arg(Utilities::PrettyTimeNanosec(metadata_.length_nanosec())); - html += QString("Bitrate: %1 kbps
\n").arg(metadata_.bitrate()); - html += QString("Samplerate: %1 hz / %2 bit
\n").arg(metadata_.samplerate()).arg(metadata_.bitdepth()); - - if (app_->player()->engine() && app_->player()->engine()->type() != Engine::EngineType::None) { - html += QString("
"); - html += QString("Engine: %1
").arg(EngineDescription(app_->player()->engine()->type())); - } - - html += QString("
"); - - html_albums += QString("Albums by %1:").arg( metadata_.artist().toHtmlEscaped() ); - - albumlist = app_->collection_backend()->GetAlbumsByArtist(metadata_.artist(), opt); - - html_albums += QString("
    "); - int i=0; - for (CollectionBackend::Album album : albumlist) { - i++; - html_albums += QString("
  • %1
  • \n").arg(album.album_name.toHtmlEscaped()); - } - - html_albums += QString("
"); - 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("No Track Playing"); - label_stopped_logo_->setPixmap(pixmap_logo); - - html += QString( - "\n" - "\n" - "\n" - "\n" - "\n" - "%1 songs
\n" - "%2 artists
\n" - "%3 albums
\n" - "\n" - "\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(); - -} diff --git a/src/widgets/widgetfadehelper.cpp b/src/widgets/widgetfadehelper.cpp deleted file mode 100644 index cca808351..000000000 --- a/src/widgets/widgetfadehelper.cpp +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Strawberry Music Player - * This file was part of Clementine. - * Copyright 2010, David Sansome - * - * 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 . - * - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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(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(); -} diff --git a/src/widgets/widgetfadehelper.h b/src/widgets/widgetfadehelper.h deleted file mode 100644 index 85fc0b02d..000000000 --- a/src/widgets/widgetfadehelper.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Strawberry Music Player - * This file was part of Clementine. - * Copyright 2010, David Sansome - * - * 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 . - * - */ - -#ifndef WIDGETFADEHELPER_H -#define WIDGETFADEHELPER_H - -#include "config.h" - -#include - -#include -#include -#include -#include -#include -#include -#include - -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