New context with albums and lyrics +++ much more
* Added new lyrics provider with lyrics from AudD and API Seeds * New improved context widget with albums and lyrics * Fixed playing and context widget getting stuck in play mode when there was an error * Changed icons for artists in collection, tidal and cover manager * Removed "search" icon from "Search automatically" checkbox (right click) that looked ugly * Removed some unused widgets from the src/widgets directory * Fixed initial size of window and side panel * Fixed saving window size correctly
11
Changelog
@ -2,6 +2,17 @@ Strawberry Music Player
|
||||
=======================
|
||||
ChangeLog
|
||||
|
||||
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
|
||||
|
@ -3,8 +3,7 @@
|
||||
<file>schema/schema.sql</file>
|
||||
<file>schema/schema-1.sql</file>
|
||||
<file>schema/device-schema.sql</file>
|
||||
<file>style/mainwindow.css</file>
|
||||
<file>style/statusview.css</file>
|
||||
<file>style/strawberry.css</file>
|
||||
<file>misc/playing_tooltip.txt</file>
|
||||
<file>pictures/strawberry.png</file>
|
||||
<file>pictures/strawbs-transparent.png</file>
|
||||
@ -98,8 +97,6 @@
|
||||
<file>icons/128x128/speaker.png</file>
|
||||
<file>icons/128x128/star-grey.png</file>
|
||||
<file>icons/128x128/star.png</file>
|
||||
<file>icons/128x128/strawberry-panel-grey.png</file>
|
||||
<file>icons/128x128/strawberry-panel.png</file>
|
||||
<file>icons/128x128/strawberry.png</file>
|
||||
<file>icons/128x128/strawberry.svg</file>
|
||||
<file>icons/128x128/tools-wizard.png</file>
|
||||
@ -188,8 +185,6 @@
|
||||
<file>icons/64x64/speaker.png</file>
|
||||
<file>icons/64x64/star-grey.png</file>
|
||||
<file>icons/64x64/star.png</file>
|
||||
<file>icons/64x64/strawberry-panel-grey.png</file>
|
||||
<file>icons/64x64/strawberry-panel.png</file>
|
||||
<file>icons/64x64/strawberry.png</file>
|
||||
<file>icons/64x64/tools-wizard.png</file>
|
||||
<file>icons/64x64/view-choose.png</file>
|
||||
@ -280,8 +275,6 @@
|
||||
<file>icons/48x48/speaker.png</file>
|
||||
<file>icons/48x48/star-grey.png</file>
|
||||
<file>icons/48x48/star.png</file>
|
||||
<file>icons/48x48/strawberry-panel-grey.png</file>
|
||||
<file>icons/48x48/strawberry-panel.png</file>
|
||||
<file>icons/48x48/strawberry.png</file>
|
||||
<file>icons/48x48/tools-wizard.png</file>
|
||||
<file>icons/48x48/view-choose.png</file>
|
||||
@ -372,8 +365,6 @@
|
||||
<file>icons/32x32/speaker.png</file>
|
||||
<file>icons/32x32/star-grey.png</file>
|
||||
<file>icons/32x32/star.png</file>
|
||||
<file>icons/32x32/strawberry-panel-grey.png</file>
|
||||
<file>icons/32x32/strawberry-panel.png</file>
|
||||
<file>icons/32x32/strawberry.png</file>
|
||||
<file>icons/32x32/strawberry.svg</file>
|
||||
<file>icons/32x32/tools-wizard.png</file>
|
||||
@ -465,8 +456,6 @@
|
||||
<file>icons/22x22/speaker.png</file>
|
||||
<file>icons/22x22/star-grey.png</file>
|
||||
<file>icons/22x22/star.png</file>
|
||||
<file>icons/22x22/strawberry-panel-grey.png</file>
|
||||
<file>icons/22x22/strawberry-panel.png</file>
|
||||
<file>icons/22x22/strawberry.png</file>
|
||||
<file>icons/22x22/strawberry.svg</file>
|
||||
<file>icons/22x22/tools-wizard.png</file>
|
||||
@ -481,6 +470,7 @@
|
||||
<file>icons/22x22/xine.png</file>
|
||||
<file>icons/22x22/zoom-in.png</file>
|
||||
<file>icons/22x22/zoom-out.png</file>
|
||||
<file>icons/22x22/tidal.png</file>
|
||||
<file>icons/22x22/tidal.png</file>
|
||||
<file>fonts/HumongousofEternitySt.ttf</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
BIN
data/fonts/HumongousofEternitySt.ttf
Normal file
Before Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 363 KiB |
Before Width: | Height: | Size: 628 KiB |
@ -1,11 +0,0 @@
|
||||
StatusView {
|
||||
background: white;
|
||||
background-color: white;
|
||||
}
|
||||
QVBoxLayout {
|
||||
background: white;
|
||||
background-color: white;
|
||||
}
|
||||
QScrollArea {
|
||||
background: qpalette(base);
|
||||
}
|
@ -52,3 +52,20 @@ darwin {
|
||||
darwin QMenu {
|
||||
font-size: 13pt;
|
||||
}
|
||||
|
||||
#scrollarea_play {
|
||||
background-color: white;
|
||||
font: 11pt;
|
||||
}
|
||||
|
||||
#scrollarea_stop {
|
||||
background-color: white;
|
||||
font: 11pt;
|
||||
}
|
||||
|
||||
#scrollAreaWidgetContents_stop {
|
||||
background-color: white;
|
||||
}
|
||||
#scrollAreaWidgetContents_play {
|
||||
background-color: white;
|
||||
}
|
@ -107,7 +107,6 @@ set(SOURCES
|
||||
core/urlhandler.cpp
|
||||
core/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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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<int> &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));
|
||||
|
||||
}
|
||||
|
@ -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")),
|
||||
|
@ -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.
|
||||
|
@ -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<CollectionModel::QueryResult> future = QtConcurrent::run(this, &CollectionModel::RunQuery, root_);
|
||||
NewClosure(future, this, SLOT(ResetAsyncQueryFinished(QFuture<CollectionModel::QueryResult>)), future);
|
||||
|
||||
@ -1033,13 +1030,13 @@ CollectionItem *CollectionModel::ItemFromQuery(GroupBy type, bool signal, bool c
|
||||
item->key = QString::number(bitrate);
|
||||
item->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);
|
||||
|
@ -31,11 +31,12 @@
|
||||
#include <QSqlQuery>
|
||||
|
||||
#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()) {
|
||||
|
@ -298,7 +298,7 @@ bool CollectionView::RestoreLevelFocus(const QModelIndex &parent) {
|
||||
if (!last_selected_song_.url().isEmpty()) {
|
||||
QModelIndex index = qobject_cast<QSortFilterProxyModel*>(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<Song> all_of_album = app_->collection_backend()->GetSongsByAlbum(album);
|
||||
QSet<QString> 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<MimeData*>(data)) {
|
||||
if (MimeData *mime_data = qobject_cast<MimeData*>(data)) {
|
||||
mime_data->clear_first_ = true;
|
||||
}
|
||||
emit AddToPlaylistSignal(data);
|
||||
|
@ -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(); }
|
||||
|
@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<width>300</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
|
527
src/context/contextalbumsmodel.cpp
Normal file
@ -0,0 +1,527 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QObject>
|
||||
#include <QtGlobal>
|
||||
#include <QtAlgorithms>
|
||||
#include <QMutex>
|
||||
#include <QMimeData>
|
||||
#include <QVariant>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
#include <QRegExp>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QImage>
|
||||
#include <QPixmapCache>
|
||||
#include <QSettings>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/database.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/logging.h"
|
||||
#include "collection/collectionquery.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectionitem.h"
|
||||
#include "collection/sqlrow.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
#include "playlist/songmimedata.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
|
||||
#include "contextalbumsmodel.h"
|
||||
|
||||
using std::placeholders::_1;
|
||||
using std::placeholders::_2;
|
||||
|
||||
const int ContextAlbumsModel::kPrettyCoverSize = 32;
|
||||
const qint64 ContextAlbumsModel::kIconCacheSize = 100000000; //~100MB
|
||||
|
||||
ContextAlbumsModel::ContextAlbumsModel(CollectionBackend *backend, Application *app, QObject *parent) :
|
||||
SimpleTreeModel<CollectionItem>(new CollectionItem(this), parent),
|
||||
backend_(backend),
|
||||
app_(app),
|
||||
artist_icon_(IconLoader::Load("folder-sound")),
|
||||
album_icon_(IconLoader::Load("cdcase")),
|
||||
playlists_dir_icon_(IconLoader::Load("folder-sound")),
|
||||
playlist_icon_(IconLoader::Load("albums")),
|
||||
use_pretty_covers_(true)
|
||||
{
|
||||
|
||||
root_->lazy_loaded = true;
|
||||
|
||||
cover_loader_options_.desired_height_ = kPrettyCoverSize;
|
||||
cover_loader_options_.pad_output_image_ = true;
|
||||
cover_loader_options_.scale_output_image_ = true;
|
||||
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(AlbumArtLoaded(quint64, QImage)));
|
||||
|
||||
QIcon nocover = IconLoader::Load("cdcase");
|
||||
no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
}
|
||||
|
||||
ContextAlbumsModel::~ContextAlbumsModel() { delete root_; }
|
||||
|
||||
void ContextAlbumsModel::set_pretty_covers(bool use_pretty_covers) {
|
||||
|
||||
if (use_pretty_covers != use_pretty_covers_) {
|
||||
use_pretty_covers_ = use_pretty_covers;
|
||||
Reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ContextAlbumsModel::AddSongs(const SongList &songs) {
|
||||
|
||||
for (const Song &song : songs) {
|
||||
if (song_nodes_.contains(song.id())) continue;
|
||||
|
||||
// Before we can add each song we need to make sure the required container items already exist in the tree.
|
||||
|
||||
// Find parent containers in the tree
|
||||
CollectionItem *container = root_;
|
||||
|
||||
// Does it exist already?
|
||||
if (!container_nodes_.contains(song.album())) {
|
||||
// Create the container
|
||||
container_nodes_[song.album()] = ItemFromSong(CollectionItem::Type_Container, true, container, song, 0);
|
||||
}
|
||||
container = container_nodes_[song.album()];
|
||||
if (!container->lazy_loaded) continue;
|
||||
|
||||
// We've gone all the way down to the deepest level and everything was already lazy loaded, so now we have to create the song in the container.
|
||||
song_nodes_[song.id()] = ItemFromSong(CollectionItem::Type_Song, true, container, song, -1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString ContextAlbumsModel::AlbumIconPixmapCacheKey(const QModelIndex &index) const {
|
||||
|
||||
QStringList path;
|
||||
QModelIndex index_copy(index);
|
||||
while (index_copy.isValid()) {
|
||||
path.prepend(index_copy.data().toString());
|
||||
index_copy = index_copy.parent();
|
||||
}
|
||||
return "contextalbumsart:" + path.join("/");
|
||||
|
||||
}
|
||||
|
||||
QVariant ContextAlbumsModel::AlbumIcon(const QModelIndex &index) {
|
||||
|
||||
CollectionItem *item = IndexToItem(index);
|
||||
if (!item) return no_cover_icon_;
|
||||
|
||||
// Check the cache for a pixmap we already loaded.
|
||||
const QString cache_key = AlbumIconPixmapCacheKey(index);
|
||||
|
||||
QPixmap cached_pixmap;
|
||||
if (QPixmapCache::find(cache_key, &cached_pixmap)) {
|
||||
return cached_pixmap;
|
||||
}
|
||||
|
||||
// Maybe we're loading a pixmap already?
|
||||
if (pending_cache_keys_.contains(cache_key)) {
|
||||
return no_cover_icon_;
|
||||
}
|
||||
|
||||
// No art is cached and we're not loading it already. Load art for the first song in the album.
|
||||
SongList songs = GetChildSongs(index);
|
||||
if (!songs.isEmpty()) {
|
||||
const quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, songs.first());
|
||||
pending_art_[id] = ItemAndCacheKey(item, cache_key);
|
||||
pending_cache_keys_.insert(cache_key);
|
||||
}
|
||||
|
||||
return no_cover_icon_;
|
||||
|
||||
}
|
||||
|
||||
void ContextAlbumsModel::AlbumArtLoaded(quint64 id, const QImage &image) {
|
||||
|
||||
ItemAndCacheKey item_and_cache_key = pending_art_.take(id);
|
||||
CollectionItem *item = item_and_cache_key.first;
|
||||
const QString &cache_key = item_and_cache_key.second;
|
||||
|
||||
if (!item) return;
|
||||
|
||||
pending_cache_keys_.remove(cache_key);
|
||||
|
||||
// Insert this image in the cache.
|
||||
if (image.isNull()) {
|
||||
// Set the no_cover image so we don't continually try to load art.
|
||||
QPixmapCache::insert(cache_key, no_cover_icon_);
|
||||
}
|
||||
else {
|
||||
//qLog(Debug) << cache_key;
|
||||
QPixmap image_pixmap;
|
||||
image_pixmap = QPixmap::fromImage(image);
|
||||
QPixmapCache::insert(cache_key, image_pixmap);
|
||||
}
|
||||
|
||||
const QModelIndex index = ItemToIndex(item);
|
||||
emit dataChanged(index, index);
|
||||
|
||||
}
|
||||
|
||||
QVariant ContextAlbumsModel::data(const QModelIndex &index, int role) const {
|
||||
|
||||
const CollectionItem *item = IndexToItem(index);
|
||||
|
||||
// Handle a special case for returning album artwork instead of a generic CD icon.
|
||||
// This is here instead of in the other data() function to let us use the QModelIndex& version of GetChildSongs,
|
||||
// which satisfies const-ness, instead of the CollectionItem *version, which doesn't.
|
||||
if (use_pretty_covers_) {
|
||||
bool is_album_node = false;
|
||||
if (role == Qt::DecorationRole && item->type == CollectionItem::Type_Container) {
|
||||
is_album_node = (item->container_level == 0);
|
||||
}
|
||||
if (is_album_node) {
|
||||
// It has const behaviour some of the time - that's ok right?
|
||||
return const_cast<ContextAlbumsModel*>(this)->AlbumIcon(index);
|
||||
}
|
||||
}
|
||||
|
||||
return data(item, role);
|
||||
|
||||
}
|
||||
|
||||
QVariant ContextAlbumsModel::data(const CollectionItem *item, int role) const {
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case Qt::ToolTipRole:
|
||||
return item->DisplayText();
|
||||
|
||||
case Qt::DecorationRole:
|
||||
switch (item->type) {
|
||||
case CollectionItem::Type_PlaylistContainer:
|
||||
return playlists_dir_icon_;
|
||||
case CollectionItem::Type_Container:
|
||||
if (item->type == CollectionItem::Type_Container && item->container_level == 0) { return album_icon_; }
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Role_Type:
|
||||
return item->type;
|
||||
|
||||
case Role_IsDivider:
|
||||
return item->type == CollectionItem::Type_Divider;
|
||||
|
||||
case Role_ContainerType:
|
||||
return item->type;
|
||||
|
||||
case Role_Key:
|
||||
return item->key;
|
||||
|
||||
case Role_Artist:
|
||||
return item->metadata.artist();
|
||||
|
||||
case Role_Editable:
|
||||
if (!item->lazy_loaded) {
|
||||
const_cast<ContextAlbumsModel*>(this)->LazyPopulate(const_cast<CollectionItem*>(item), true);
|
||||
}
|
||||
|
||||
if (item->type == CollectionItem::Type_Container) {
|
||||
// if we have even one non editable item as a child, we ourselves are not available for edit
|
||||
if (!item->children.isEmpty()) {
|
||||
for (CollectionItem *child : item->children) {
|
||||
if (!data(child, role).toBool()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (item->type == CollectionItem::Type_Song) {
|
||||
return item->metadata.IsEditable();
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
case Role_SortText:
|
||||
return item->SortText();
|
||||
}
|
||||
return QVariant();
|
||||
|
||||
}
|
||||
|
||||
ContextAlbumsModel::QueryResult ContextAlbumsModel::RunQuery(CollectionItem *parent) {
|
||||
|
||||
QueryResult result;
|
||||
CollectionQuery q(query_options_);
|
||||
q.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
|
||||
|
||||
// Walk up through the item's parents adding filters as necessary
|
||||
CollectionItem *p = parent;
|
||||
while (p && p->type == CollectionItem::Type_Container) {
|
||||
if (p->container_level == 0) {
|
||||
q.AddWhere("album", p->key);
|
||||
}
|
||||
p = p->parent;
|
||||
}
|
||||
|
||||
// Execute the query
|
||||
QMutexLocker l(backend_->db()->Mutex());
|
||||
|
||||
if (!backend_->ExecQuery(&q)) return result;
|
||||
|
||||
while (q.Next()) {
|
||||
result.rows << SqlRow(q);
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
void ContextAlbumsModel::PostQuery(CollectionItem *parent, const ContextAlbumsModel::QueryResult &result, bool signal) {
|
||||
|
||||
int child_level = (parent == root_ ? 0 : parent->container_level + 1);
|
||||
|
||||
for (const SqlRow &row : result.rows) {
|
||||
|
||||
CollectionItem::Type item_type = (parent == root_ ? CollectionItem::Type_Container : CollectionItem::Type_Song);
|
||||
|
||||
if (signal) beginInsertRows(ItemToIndex(parent), parent->children.count(), parent->children.count());
|
||||
|
||||
CollectionItem *item = new CollectionItem(item_type, parent);
|
||||
item->container_level = child_level;
|
||||
item->metadata.InitFromQuery(row, true);
|
||||
item->key = item->metadata.title();
|
||||
item->display_text = item->metadata.TitleWithCompilationArtist();
|
||||
item->sort_text = SortTextForSong(item->metadata);
|
||||
if (parent != root_) item->lazy_loaded = true;
|
||||
|
||||
if (signal) endInsertRows();
|
||||
|
||||
if (parent == root_) container_nodes_[item->key] = item;
|
||||
else song_nodes_[item->metadata.id()] = item;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ContextAlbumsModel::LazyPopulate(CollectionItem *parent, bool signal) {
|
||||
|
||||
if (parent->lazy_loaded) return;
|
||||
parent->lazy_loaded = true;
|
||||
|
||||
QueryResult result = RunQuery(parent);
|
||||
PostQuery(parent, result, signal);
|
||||
|
||||
}
|
||||
|
||||
void ContextAlbumsModel::Reset() {
|
||||
|
||||
beginResetModel();
|
||||
delete root_;
|
||||
song_nodes_.clear();
|
||||
container_nodes_.clear();
|
||||
pending_art_.clear();
|
||||
|
||||
root_ = new CollectionItem(this);
|
||||
root_->lazy_loaded = false;
|
||||
endResetModel();
|
||||
|
||||
}
|
||||
|
||||
CollectionItem *ContextAlbumsModel::ItemFromSong(CollectionItem::Type item_type, bool signal, CollectionItem *parent, const Song &s, int container_level) {
|
||||
|
||||
if (signal) beginInsertRows(ItemToIndex(parent), parent->children.count(), parent->children.count());
|
||||
|
||||
CollectionItem *item = new CollectionItem(item_type, parent);
|
||||
item->container_level = container_level;
|
||||
|
||||
if (item->key.isNull()) item->key = s.album();
|
||||
//if (item->key.isNull()) item->key = s.effective_albumartist();
|
||||
item->display_text = TextOrUnknown(item->key);
|
||||
item->sort_text = SortTextForArtist(item->key);
|
||||
|
||||
if (item_type == CollectionItem::Type_Song) item->lazy_loaded = true;
|
||||
if (signal) endInsertRows();
|
||||
|
||||
return item;
|
||||
|
||||
}
|
||||
|
||||
QString ContextAlbumsModel::TextOrUnknown(const QString &text) {
|
||||
|
||||
if (text.isEmpty()) return tr("Unknown");
|
||||
return text;
|
||||
|
||||
}
|
||||
|
||||
QString ContextAlbumsModel::SortText(QString text) {
|
||||
|
||||
if (text.isEmpty()) {
|
||||
text = " unknown";
|
||||
}
|
||||
else {
|
||||
text = text.toLower();
|
||||
}
|
||||
text = text.remove(QRegExp("[^\\w ]"));
|
||||
|
||||
return text;
|
||||
|
||||
}
|
||||
|
||||
QString ContextAlbumsModel::SortTextForArtist(QString artist) {
|
||||
|
||||
artist = SortText(artist);
|
||||
|
||||
if (artist.startsWith("the ")) {
|
||||
artist = artist.right(artist.length() - 4) + ", the";
|
||||
}
|
||||
else if (artist.startsWith("a ")) {
|
||||
artist = artist.right(artist.length() - 2) + ", a";
|
||||
}
|
||||
else if (artist.startsWith("an ")) {
|
||||
artist = artist.right(artist.length() - 3) + ", an";
|
||||
}
|
||||
|
||||
return artist;
|
||||
|
||||
}
|
||||
|
||||
QString ContextAlbumsModel::SortTextForSong(const Song &song) {
|
||||
|
||||
QString ret = QString::number(qMax(0, song.disc()) * 1000 + qMax(0, song.track()));
|
||||
ret.prepend(QString("0").repeated(6 - ret.length()));
|
||||
ret.append(song.url().toString());
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
Qt::ItemFlags ContextAlbumsModel::flags(const QModelIndex &index) const {
|
||||
|
||||
switch (IndexToItem(index)->type) {
|
||||
case CollectionItem::Type_Song:
|
||||
case CollectionItem::Type_Container:
|
||||
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled;
|
||||
case CollectionItem::Type_Divider:
|
||||
case CollectionItem::Type_Root:
|
||||
case CollectionItem::Type_LoadingIndicator:
|
||||
default:
|
||||
return Qt::ItemIsEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QStringList ContextAlbumsModel::mimeTypes() const {
|
||||
return QStringList() << "text/uri-list";
|
||||
}
|
||||
|
||||
QMimeData *ContextAlbumsModel::mimeData(const QModelIndexList &indexes) const {
|
||||
|
||||
if (indexes.isEmpty()) return nullptr;
|
||||
|
||||
SongMimeData *data = new SongMimeData;
|
||||
QList<QUrl> urls;
|
||||
QSet<int> song_ids;
|
||||
|
||||
data->backend = backend_;
|
||||
|
||||
for (const QModelIndex &index : indexes) {
|
||||
GetChildSongs(IndexToItem(index), &urls, &data->songs, &song_ids);
|
||||
}
|
||||
|
||||
data->setUrls(urls);
|
||||
data->name_for_new_playlist_ = PlaylistManager::GetNameForNewPlaylist(data->songs);
|
||||
|
||||
return data;
|
||||
|
||||
}
|
||||
|
||||
bool ContextAlbumsModel::CompareItems(const CollectionItem *a, const CollectionItem *b) const {
|
||||
|
||||
QVariant left(data(a, ContextAlbumsModel::Role_SortText));
|
||||
QVariant right(data(b, ContextAlbumsModel::Role_SortText));
|
||||
|
||||
if (left.type() == QVariant::Int) return left.toInt() < right.toInt();
|
||||
return left.toString() < right.toString();
|
||||
|
||||
}
|
||||
|
||||
void ContextAlbumsModel::GetChildSongs(CollectionItem *item, QList<QUrl> *urls, SongList *songs, QSet<int> *song_ids) const {
|
||||
|
||||
switch (item->type) {
|
||||
case CollectionItem::Type_Container: {
|
||||
const_cast<ContextAlbumsModel*>(this)->LazyPopulate(item);
|
||||
|
||||
QList<CollectionItem*> children = item->children;
|
||||
qSort(children.begin(), children.end(), std::bind(&ContextAlbumsModel::CompareItems, this, _1, _2));
|
||||
|
||||
for (CollectionItem *child : children)
|
||||
GetChildSongs(child, urls, songs, song_ids);
|
||||
break;
|
||||
}
|
||||
|
||||
case CollectionItem::Type_Song:
|
||||
urls->append(item->metadata.url());
|
||||
if (!song_ids->contains(item->metadata.id())) {
|
||||
songs->append(item->metadata);
|
||||
song_ids->insert(item->metadata.id());
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SongList ContextAlbumsModel::GetChildSongs(const QModelIndexList &indexes) const {
|
||||
|
||||
QList<QUrl> dontcare;
|
||||
SongList ret;
|
||||
QSet<int> song_ids;
|
||||
|
||||
for (const QModelIndex &index : indexes) {
|
||||
GetChildSongs(IndexToItem(index), &dontcare, &ret, &song_ids);
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
SongList ContextAlbumsModel::GetChildSongs(const QModelIndex &index) const {
|
||||
return GetChildSongs(QModelIndexList() << index);
|
||||
}
|
||||
|
||||
bool ContextAlbumsModel::canFetchMore(const QModelIndex &parent) const {
|
||||
|
||||
if (!parent.isValid()) return false;
|
||||
|
||||
CollectionItem *item = IndexToItem(parent);
|
||||
return !item->lazy_loaded;
|
||||
|
||||
}
|
143
src/context/contextalbumsmodel.h
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CONTEXTALBUMSMODEL_H
|
||||
#define CONTEXTALBUMSMODEL_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QAbstractItemModel>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QPair>
|
||||
#include <QSet>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QMimeData>
|
||||
#include <QImage>
|
||||
#include <QIcon>
|
||||
#include <QPixmap>
|
||||
#include <QNetworkDiskCache>
|
||||
#include <QSettings>
|
||||
|
||||
#include "core/simpletreemodel.h"
|
||||
#include "core/song.h"
|
||||
#include "collection/collectionquery.h"
|
||||
#include "collection/collectionitem.h"
|
||||
#include "collection/sqlrow.h"
|
||||
#include "covermanager/albumcoverloaderoptions.h"
|
||||
|
||||
class Application;
|
||||
class CollectionBackend;
|
||||
class CollectionItem;
|
||||
|
||||
class ContextAlbumsModel : public SimpleTreeModel<CollectionItem> {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ContextAlbumsModel(CollectionBackend *backend, Application *app, QObject *parent = nullptr);
|
||||
~ContextAlbumsModel();
|
||||
|
||||
static const int kPrettyCoverSize;
|
||||
static const qint64 kIconCacheSize;
|
||||
|
||||
enum Role {
|
||||
Role_Type = Qt::UserRole + 1,
|
||||
Role_ContainerType,
|
||||
Role_SortText,
|
||||
Role_Key,
|
||||
Role_Artist,
|
||||
Role_IsDivider,
|
||||
Role_Editable,
|
||||
LastRole
|
||||
};
|
||||
|
||||
struct QueryResult {
|
||||
QueryResult() {}
|
||||
SqlRowList rows;
|
||||
};
|
||||
|
||||
CollectionBackend *backend() const { return backend_; }
|
||||
|
||||
void GetChildSongs(CollectionItem *item, QList<QUrl> *urls, SongList *songs, QSet<int> *song_ids) const;
|
||||
SongList GetChildSongs(const QModelIndex &index) const;
|
||||
SongList GetChildSongs(const QModelIndexList &indexes) const;
|
||||
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
QStringList mimeTypes() const;
|
||||
QMimeData *mimeData(const QModelIndexList &indexes) const;
|
||||
bool canFetchMore(const QModelIndex &parent) const;
|
||||
|
||||
void set_pretty_covers(bool use_pretty_covers);
|
||||
bool use_pretty_covers() const { return use_pretty_covers_; }
|
||||
|
||||
static QString TextOrUnknown(const QString &text);
|
||||
static QString SortText(QString text);
|
||||
static QString SortTextForArtist(QString artist);
|
||||
static QString SortTextForSong(const Song &song);
|
||||
|
||||
void Reset();
|
||||
void AddSongs(const SongList &songs);
|
||||
|
||||
protected:
|
||||
void LazyPopulate(CollectionItem *item) { LazyPopulate(item, true); }
|
||||
void LazyPopulate(CollectionItem *item, bool signal);
|
||||
|
||||
private slots:
|
||||
void AlbumArtLoaded(quint64 id, const QImage &image);
|
||||
|
||||
private:
|
||||
QueryResult RunQuery(CollectionItem *parent);
|
||||
void PostQuery(CollectionItem *parent, const QueryResult &result, bool signal);
|
||||
CollectionItem *ItemFromSong(CollectionItem::Type item_type, bool signal, CollectionItem *parent, const Song &s, int container_level);
|
||||
|
||||
QString AlbumIconPixmapCacheKey(const QModelIndex &index) const;
|
||||
QVariant AlbumIcon(const QModelIndex &index);
|
||||
QVariant data(const CollectionItem *item, int role) const;
|
||||
bool CompareItems(const CollectionItem *a, const CollectionItem *b) const;
|
||||
|
||||
private:
|
||||
CollectionBackend *backend_;
|
||||
Application *app_;
|
||||
QueryOptions query_options_;
|
||||
QMap<int, CollectionItem*> song_nodes_;
|
||||
QMap<QString, CollectionItem*> container_nodes_;
|
||||
QIcon artist_icon_;
|
||||
QIcon album_icon_;
|
||||
QPixmap no_cover_icon_;
|
||||
QIcon playlists_dir_icon_;
|
||||
QIcon playlist_icon_;
|
||||
QNetworkDiskCache *icon_cache_;
|
||||
bool use_pretty_covers_;
|
||||
AlbumCoverLoaderOptions cover_loader_options_;
|
||||
typedef QPair<CollectionItem*, QString> ItemAndCacheKey;
|
||||
QMap<quint64, ItemAndCacheKey> pending_art_;
|
||||
QSet<QString> pending_cache_keys_;
|
||||
};
|
||||
|
||||
#endif // CONTEXTALBUMSMODEL_H
|
532
src/context/contextalbumsview.cpp
Normal file
@ -0,0 +1,532 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <qcoreevent.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QWidget>
|
||||
#include <QItemSelectionModel>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QAbstractItemView>
|
||||
#include <QStyleOptionViewItem>
|
||||
#include <QAction>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QList>
|
||||
#include <QLocale>
|
||||
#include <QMap>
|
||||
#include <QMessageBox>
|
||||
#include <QMenu>
|
||||
#include <QMimeData>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
#include <QPen>
|
||||
#include <QPoint>
|
||||
#include <QRect>
|
||||
#include <QSet>
|
||||
#include <QSize>
|
||||
#include <QToolTip>
|
||||
#include <QTreeView>
|
||||
#include <QHeaderView>
|
||||
#include <QWhatsThis>
|
||||
#include <QBrush>
|
||||
#include <QColor>
|
||||
#include <QFont>
|
||||
#include <QFontMetrics>
|
||||
#include <QPixmap>
|
||||
#include <QIcon>
|
||||
#include <QLinearGradient>
|
||||
#include <QSettings>
|
||||
#include <QtEvents>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/mimedata.h"
|
||||
#include "core/utilities.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectiondirectorymodel.h"
|
||||
#include "collection/collectionitem.h"
|
||||
#include "device/devicemanager.h"
|
||||
#include "device/devicestatefiltermodel.h"
|
||||
#include "dialogs/edittagdialog.h"
|
||||
#ifdef HAVE_GSTREAMER
|
||||
#include "dialogs/organisedialog.h"
|
||||
#endif
|
||||
#include "settings/collectionsettingspage.h"
|
||||
|
||||
#include "contextview.h"
|
||||
#include "contextalbumsmodel.h"
|
||||
#include "contextalbumsview.h"
|
||||
|
||||
ContextItemDelegate::ContextItemDelegate(QObject *parent) : QStyledItemDelegate(parent) {}
|
||||
|
||||
void ContextItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const {
|
||||
|
||||
const bool is_divider = index.data(ContextAlbumsModel::Role_IsDivider).toBool();
|
||||
|
||||
if (is_divider) {
|
||||
QString text(index.data().toString());
|
||||
|
||||
painter->save();
|
||||
|
||||
QRect text_rect(opt.rect);
|
||||
|
||||
// Does this item have an icon?
|
||||
QPixmap pixmap;
|
||||
QVariant decoration = index.data(Qt::DecorationRole);
|
||||
if (!decoration.isNull()) {
|
||||
if (decoration.canConvert<QPixmap>()) {
|
||||
pixmap = decoration.value<QPixmap>();
|
||||
}
|
||||
else if (decoration.canConvert<QIcon>()) {
|
||||
pixmap = decoration.value<QIcon>().pixmap(opt.decorationSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the icon at the left of the text rectangle
|
||||
if (!pixmap.isNull()) {
|
||||
QRect icon_rect(text_rect.topLeft(), opt.decorationSize);
|
||||
const int padding = (text_rect.height() - icon_rect.height()) / 2;
|
||||
icon_rect.adjust(padding, padding, padding, padding);
|
||||
text_rect.moveLeft(icon_rect.right() + padding + 6);
|
||||
|
||||
if (pixmap.size() != opt.decorationSize) {
|
||||
pixmap = pixmap.scaled(opt.decorationSize, Qt::KeepAspectRatio);
|
||||
}
|
||||
|
||||
painter->drawPixmap(icon_rect, pixmap);
|
||||
}
|
||||
else {
|
||||
text_rect.setLeft(text_rect.left() + 30);
|
||||
}
|
||||
|
||||
// Draw the text
|
||||
QFont bold_font(opt.font);
|
||||
bold_font.setBold(true);
|
||||
|
||||
painter->setPen(opt.palette.color(QPalette::Text));
|
||||
painter->setFont(bold_font);
|
||||
painter->drawText(text_rect, text);
|
||||
|
||||
// Draw the line under the item
|
||||
QColor line_color = opt.palette.color(QPalette::Text);
|
||||
QLinearGradient grad_color(opt.rect.bottomLeft(), opt.rect.bottomRight());
|
||||
const double fade_start_end = (opt.rect.width()/3.0)/opt.rect.width();
|
||||
line_color.setAlphaF(0.0);
|
||||
grad_color.setColorAt(0, line_color);
|
||||
line_color.setAlphaF(0.5);
|
||||
grad_color.setColorAt(fade_start_end, line_color);
|
||||
grad_color.setColorAt(1.0 - fade_start_end, line_color);
|
||||
line_color.setAlphaF(0.0);
|
||||
grad_color.setColorAt(1, line_color);
|
||||
painter->setPen(QPen(grad_color, 1));
|
||||
painter->drawLine(opt.rect.bottomLeft(), opt.rect.bottomRight());
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
else {
|
||||
if (!is_divider) QStyledItemDelegate::paint(painter, opt, index);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool ContextItemDelegate::helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) {
|
||||
|
||||
return true;
|
||||
|
||||
Q_UNUSED(option);
|
||||
|
||||
if (!event || !view) return false;
|
||||
|
||||
QHelpEvent *he = static_cast<QHelpEvent*>(event);
|
||||
QString text = displayText(index.data(), QLocale::system());
|
||||
|
||||
if (text.isEmpty() || !he) return false;
|
||||
|
||||
switch (event->type()) {
|
||||
case QEvent::ToolTip: {
|
||||
QRect displayed_text;
|
||||
QSize real_text;
|
||||
bool is_elided = false;
|
||||
|
||||
real_text = sizeHint(option, index);
|
||||
displayed_text = view->visualRect(index);
|
||||
is_elided = displayed_text.width() < real_text.width();
|
||||
|
||||
if (is_elided) {
|
||||
QToolTip::showText(he->globalPos(), text, view);
|
||||
}
|
||||
else if (index.data(Qt::ToolTipRole).isValid()) {
|
||||
// If the item has a tooltip text, display it
|
||||
QString tooltip_text = index.data(Qt::ToolTipRole).toString();
|
||||
QToolTip::showText(he->globalPos(), tooltip_text, view);
|
||||
}
|
||||
else {
|
||||
// in case that another text was previously displayed
|
||||
QToolTip::hideText();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::QueryWhatsThis:
|
||||
return true;
|
||||
|
||||
case QEvent::WhatsThis:
|
||||
QWhatsThis::showText(he->globalPos(), text, view);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
ContextAlbumsView::ContextAlbumsView(QWidget *parent)
|
||||
: AutoExpandingTreeView(parent),
|
||||
app_(nullptr),
|
||||
context_menu_(nullptr),
|
||||
is_in_keyboard_search_(false),
|
||||
model_(nullptr)
|
||||
{
|
||||
|
||||
setStyleSheet("border: none;");
|
||||
|
||||
setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
|
||||
setItemDelegate(new ContextItemDelegate(this));
|
||||
setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
setHeaderHidden(true);
|
||||
setAllColumnsShowFocus(true);
|
||||
setDragEnabled(true);
|
||||
setDragDropMode(QAbstractItemView::DragOnly);
|
||||
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
SetAddOnDoubleClick(false);
|
||||
|
||||
}
|
||||
|
||||
ContextAlbumsView::~ContextAlbumsView() {}
|
||||
|
||||
void ContextAlbumsView::SaveFocus() {
|
||||
|
||||
QModelIndex current = currentIndex();
|
||||
QVariant type = model()->data(current, ContextAlbumsModel::Role_Type);
|
||||
if (!type.isValid() || !(type.toInt() == CollectionItem::Type_Song || type.toInt() == CollectionItem::Type_Container || type.toInt() == CollectionItem::Type_Divider)) {
|
||||
return;
|
||||
}
|
||||
|
||||
last_selected_path_.clear();
|
||||
last_selected_song_ = Song();
|
||||
last_selected_container_ = QString();
|
||||
|
||||
switch (type.toInt()) {
|
||||
case CollectionItem::Type_Song: {
|
||||
QModelIndex index = current;
|
||||
SongList songs = model_->GetChildSongs(index);
|
||||
if (!songs.isEmpty()) {
|
||||
last_selected_song_ = songs.last();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CollectionItem::Type_Container:
|
||||
case CollectionItem::Type_Divider: {
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
SaveContainerPath(current);
|
||||
|
||||
}
|
||||
|
||||
void ContextAlbumsView::SaveContainerPath(const QModelIndex &child) {
|
||||
|
||||
|
||||
// return;
|
||||
|
||||
QModelIndex current = model()->parent(child);
|
||||
QVariant type = model()->data(current, ContextAlbumsModel::Role_Type);
|
||||
if (!type.isValid() || !(type.toInt() == CollectionItem::Type_Container || type.toInt() == CollectionItem::Type_Divider)) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString text = model()->data(current, ContextAlbumsModel::Role_SortText).toString();
|
||||
last_selected_path_ << text;
|
||||
SaveContainerPath(current);
|
||||
|
||||
}
|
||||
|
||||
void ContextAlbumsView::RestoreFocus() {
|
||||
|
||||
if (last_selected_container_.isEmpty() && last_selected_song_.url().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
RestoreLevelFocus();
|
||||
|
||||
}
|
||||
|
||||
bool ContextAlbumsView::RestoreLevelFocus(const QModelIndex &parent) {
|
||||
|
||||
if (model()->canFetchMore(parent)) {
|
||||
model()->fetchMore(parent);
|
||||
}
|
||||
int rows = model()->rowCount(parent);
|
||||
for (int i = 0; i < rows; i++) {
|
||||
QModelIndex current = model()->index(i, 0, parent);
|
||||
QVariant type = model()->data(current, ContextAlbumsModel::Role_Type);
|
||||
switch (type.toInt()) {
|
||||
case CollectionItem::Type_Song:
|
||||
if (!last_selected_song_.url().isEmpty()) {
|
||||
QModelIndex index = qobject_cast<QSortFilterProxyModel*>(model())->mapToSource(current);
|
||||
SongList songs = model_->GetChildSongs(index);
|
||||
for (const Song &song : songs) {
|
||||
if (song == last_selected_song_) {
|
||||
setCurrentIndex(current);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
void ContextAlbumsView::ReloadSettings() {
|
||||
|
||||
QSettings settings;
|
||||
|
||||
settings.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
||||
SetAutoOpen(settings.value("auto_open", true).toBool());
|
||||
|
||||
if (app_ && model_) {
|
||||
model_->set_pretty_covers(settings.value("pretty_covers", true).toBool());
|
||||
}
|
||||
|
||||
settings.endGroup();
|
||||
|
||||
}
|
||||
|
||||
void ContextAlbumsView::SetApplication(Application *app) {
|
||||
|
||||
app_ = app;
|
||||
|
||||
model_ = new ContextAlbumsModel(app_->collection_backend(), app_, this);
|
||||
model_->Reset();
|
||||
|
||||
setModel(model_);
|
||||
|
||||
connect(model_, SIGNAL(modelAboutToBeReset()), this, SLOT(SaveFocus()));
|
||||
connect(model_, SIGNAL(modelReset()), this, SLOT(RestoreFocus()));
|
||||
|
||||
ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
void ContextAlbumsView::paintEvent(QPaintEvent *event) {
|
||||
QTreeView::paintEvent(event);
|
||||
}
|
||||
|
||||
void ContextAlbumsView::mouseReleaseEvent(QMouseEvent *e) {
|
||||
QTreeView::mouseReleaseEvent(e);
|
||||
}
|
||||
|
||||
void ContextAlbumsView::contextMenuEvent(QContextMenuEvent *e) {
|
||||
|
||||
if (!context_menu_) {
|
||||
context_menu_ = new QMenu(this);
|
||||
//context_menu_->setStyleSheet("background-color: #3DADE8;");
|
||||
|
||||
add_to_playlist_ = context_menu_->addAction(IconLoader::Load("media-play"), tr("Append to current playlist"), this, SLOT(AddToPlaylist()));
|
||||
load_ = context_menu_->addAction(IconLoader::Load("media-play"), tr("Replace current playlist"), this, SLOT(Load()));
|
||||
open_in_new_playlist_ = context_menu_->addAction(IconLoader::Load("document-new"), tr("Open in new playlist"), this, SLOT(OpenInNewPlaylist()));
|
||||
|
||||
context_menu_->addSeparator();
|
||||
add_to_playlist_enqueue_ = context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue track"), this, SLOT(AddToPlaylistEnqueue()));
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
context_menu_->addSeparator();
|
||||
organise_ = context_menu_->addAction(IconLoader::Load("edit-copy"), tr("Organise files..."), this, SLOT(Organise()));
|
||||
copy_to_device_ = context_menu_->addAction(IconLoader::Load("device"), tr("Copy to device..."), this, SLOT(CopyToDevice()));
|
||||
#endif
|
||||
|
||||
context_menu_->addSeparator();
|
||||
edit_track_ = context_menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit track information..."), this, SLOT(EditTracks()));
|
||||
edit_tracks_ = context_menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit tracks information..."), this, SLOT(EditTracks()));
|
||||
show_in_browser_ = context_menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, SLOT(ShowInBrowser()));
|
||||
|
||||
context_menu_->addSeparator();
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
copy_to_device_->setDisabled(app_->device_manager()->connected_devices_model()->rowCount() == 0);
|
||||
connect(app_->device_manager()->connected_devices_model(), SIGNAL(IsEmptyChanged(bool)), copy_to_device_, SLOT(setDisabled(bool)));
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
context_menu_index_ = indexAt(e->pos());
|
||||
if (!context_menu_index_.isValid()) return;
|
||||
QModelIndexList selected_indexes = selectionModel()->selectedRows();
|
||||
|
||||
int regular_elements = 0;
|
||||
int regular_editable = 0;
|
||||
|
||||
for (const QModelIndex &index : selected_indexes) {
|
||||
regular_elements++;
|
||||
if(model_->data(index, ContextAlbumsModel::Role_Editable).toBool()) {
|
||||
regular_editable++;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: check if custom plugin actions should be enabled / visible
|
||||
const int songs_selected = regular_elements;
|
||||
const bool regular_elements_only = songs_selected == regular_elements && regular_elements > 0;
|
||||
|
||||
// in all modes
|
||||
load_->setEnabled(songs_selected);
|
||||
add_to_playlist_->setEnabled(songs_selected);
|
||||
open_in_new_playlist_->setEnabled(songs_selected);
|
||||
add_to_playlist_enqueue_->setEnabled(songs_selected);
|
||||
|
||||
// if neither edit_track not edit_tracks are available, we show disabled edit_track element
|
||||
edit_track_->setVisible(regular_editable <= 1);
|
||||
edit_track_->setEnabled(regular_editable == 1);
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
organise_->setVisible(regular_elements_only);
|
||||
copy_to_device_->setVisible(regular_elements_only);
|
||||
#endif
|
||||
|
||||
// only when all selected items are editable
|
||||
#ifdef HAVE_GSTREAMER
|
||||
organise_->setEnabled(regular_elements == regular_editable);
|
||||
copy_to_device_->setEnabled(regular_elements == regular_editable);
|
||||
#endif
|
||||
|
||||
context_menu_->popup(e->globalPos());
|
||||
|
||||
}
|
||||
|
||||
void ContextAlbumsView::Load() {
|
||||
|
||||
QMimeData *data = model()->mimeData(selectedIndexes());
|
||||
if (MimeData *mime_data = qobject_cast<MimeData*>(data)) {
|
||||
mime_data->clear_first_ = true;
|
||||
}
|
||||
emit AddToPlaylistSignal(data);
|
||||
|
||||
}
|
||||
|
||||
void ContextAlbumsView::AddToPlaylist() {
|
||||
|
||||
emit AddToPlaylistSignal(model()->mimeData(selectedIndexes()));
|
||||
|
||||
}
|
||||
|
||||
void ContextAlbumsView::AddToPlaylistEnqueue() {
|
||||
|
||||
QMimeData *data = model()->mimeData(selectedIndexes());
|
||||
if (MimeData* mime_data = qobject_cast<MimeData*>(data)) {
|
||||
mime_data->enqueue_now_ = true;
|
||||
}
|
||||
emit AddToPlaylistSignal(data);
|
||||
|
||||
}
|
||||
|
||||
void ContextAlbumsView::OpenInNewPlaylist() {
|
||||
|
||||
QMimeData *data = model()->mimeData(selectedIndexes());
|
||||
if (MimeData* mime_data = qobject_cast<MimeData*>(data)) {
|
||||
mime_data->open_in_new_playlist_ = true;
|
||||
}
|
||||
emit AddToPlaylistSignal(data);
|
||||
|
||||
}
|
||||
|
||||
void ContextAlbumsView::scrollTo(const QModelIndex &index, ScrollHint hint) {
|
||||
|
||||
if (is_in_keyboard_search_)
|
||||
QTreeView::scrollTo(index, QAbstractItemView::PositionAtTop);
|
||||
else
|
||||
QTreeView::scrollTo(index, hint);
|
||||
|
||||
}
|
||||
|
||||
SongList ContextAlbumsView::GetSelectedSongs() const {
|
||||
QModelIndexList selected_indexes = selectionModel()->selectedRows();
|
||||
return model_->GetChildSongs(selected_indexes);
|
||||
}
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
void ContextAlbumsView::Organise() {
|
||||
|
||||
if (!organise_dialog_)
|
||||
organise_dialog_.reset(new OrganiseDialog(app_->task_manager()));
|
||||
|
||||
organise_dialog_->SetDestinationModel(app_->collection_model()->directory_model());
|
||||
organise_dialog_->SetCopy(false);
|
||||
if (organise_dialog_->SetSongs(GetSelectedSongs()))
|
||||
organise_dialog_->show();
|
||||
else {
|
||||
QMessageBox::warning(this, tr("Error"), tr("None of the selected songs were suitable for copying to a device"));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void ContextAlbumsView::EditTracks() {
|
||||
|
||||
if (!edit_tag_dialog_) {
|
||||
edit_tag_dialog_.reset(new EditTagDialog(app_, this));
|
||||
}
|
||||
edit_tag_dialog_->SetSongs(GetSelectedSongs());
|
||||
edit_tag_dialog_->show();
|
||||
|
||||
}
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
void ContextAlbumsView::CopyToDevice() {
|
||||
|
||||
if (!organise_dialog_)
|
||||
organise_dialog_.reset(new OrganiseDialog(app_->task_manager()));
|
||||
|
||||
organise_dialog_->SetDestinationModel(app_->device_manager()->connected_devices_model(), true);
|
||||
organise_dialog_->SetCopy(true);
|
||||
organise_dialog_->SetSongs(GetSelectedSongs());
|
||||
organise_dialog_->show();
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
void ContextAlbumsView::ShowInBrowser() {
|
||||
|
||||
QList<QUrl> urls;
|
||||
for (const Song &song : GetSelectedSongs()) {
|
||||
urls << song.url();
|
||||
}
|
||||
|
||||
Utilities::OpenInFileBrowser(urls);
|
||||
}
|
151
src/context/contextalbumsview.h
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CONTEXTALBUMSVIEW_H
|
||||
#define CONTEXTALBUMSVIEW_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <memory>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QAbstractItemModel>
|
||||
#include <QAbstractItemView>
|
||||
#include <QString>
|
||||
#include <QPixmap>
|
||||
#include <QPainter>
|
||||
#include <QSet>
|
||||
#include <QStyleOption>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QStyleOptionViewItem>
|
||||
#include <QAction>
|
||||
#include <QMenu>
|
||||
#include <QtEvents>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "widgets/autoexpandingtreeview.h"
|
||||
|
||||
class Application;
|
||||
class EditTagDialog;
|
||||
#ifdef HAVE_GSTREAMER
|
||||
class OrganiseDialog;
|
||||
#endif
|
||||
class ContextAlbumsModel;
|
||||
|
||||
class ContextItemDelegate : public QStyledItemDelegate {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ContextItemDelegate(QObject *parent);
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
|
||||
|
||||
public slots:
|
||||
bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index);
|
||||
};
|
||||
|
||||
class ContextAlbumsView : public AutoExpandingTreeView {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ContextAlbumsView(QWidget *parent = nullptr);
|
||||
~ContextAlbumsView();
|
||||
|
||||
// Returns Songs currently selected in the collection view.
|
||||
// Please note that the selection is recursive meaning that if for example an album is selected this will return all of it's songs.
|
||||
SongList GetSelectedSongs() const;
|
||||
|
||||
void SetApplication(Application *app);
|
||||
|
||||
// QTreeView
|
||||
void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible);
|
||||
|
||||
ContextAlbumsModel *albums_model() { return model_; }
|
||||
|
||||
public slots:
|
||||
void ReloadSettings();
|
||||
|
||||
void SaveFocus();
|
||||
void RestoreFocus();
|
||||
|
||||
signals:
|
||||
void ShowConfigDialog();
|
||||
|
||||
protected:
|
||||
// QWidget
|
||||
void paintEvent(QPaintEvent *event);
|
||||
void mouseReleaseEvent(QMouseEvent *e);
|
||||
void contextMenuEvent(QContextMenuEvent *e);
|
||||
|
||||
private slots:
|
||||
void Load();
|
||||
void AddToPlaylist();
|
||||
void AddToPlaylistEnqueue();
|
||||
void OpenInNewPlaylist();
|
||||
#ifdef HAVE_GSTREAMER
|
||||
void Organise();
|
||||
void CopyToDevice();
|
||||
#endif
|
||||
void EditTracks();
|
||||
void ShowInBrowser();
|
||||
|
||||
private:
|
||||
void RecheckIsEmpty();
|
||||
bool RestoreLevelFocus(const QModelIndex &parent = QModelIndex());
|
||||
void SaveContainerPath(const QModelIndex &child);
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
|
||||
QMenu *context_menu_;
|
||||
QModelIndex context_menu_index_;
|
||||
QAction *load_;
|
||||
QAction *add_to_playlist_;
|
||||
QAction *add_to_playlist_enqueue_;
|
||||
QAction *open_in_new_playlist_;
|
||||
#ifdef HAVE_GSTREAMER
|
||||
QAction *organise_;
|
||||
QAction *copy_to_device_;
|
||||
#endif
|
||||
QAction *delete_;
|
||||
QAction *edit_track_;
|
||||
QAction *edit_tracks_;
|
||||
QAction *show_in_browser_;
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
std::unique_ptr<OrganiseDialog> organise_dialog_;
|
||||
#endif
|
||||
std::unique_ptr<EditTagDialog> edit_tag_dialog_;
|
||||
|
||||
bool is_in_keyboard_search_;
|
||||
|
||||
// Save focus
|
||||
Song last_selected_song_;
|
||||
QString last_selected_container_;
|
||||
QSet<QString> last_selected_path_;
|
||||
|
||||
ContextAlbumsModel *model_;
|
||||
|
||||
};
|
||||
|
||||
#endif // CONTEXTALBUMSVIEW_H
|
||||
|
654
src/context/contextview.cpp
Normal file
@ -0,0 +1,654 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QIODevice>
|
||||
#include <QFile>
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
#include <QPixmap>
|
||||
#include <QMovie>
|
||||
#include <QAction>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
#include <QBrush>
|
||||
#include <QMenu>
|
||||
#include <QSettings>
|
||||
#include <QSizePolicy>
|
||||
#include <QTimeLine>
|
||||
#include <QtEvents>
|
||||
#include <QFontDatabase>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/player.h"
|
||||
#include "core/song.h"
|
||||
#include "core/utilities.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "engine/engine_fwd.h"
|
||||
#include "engine/enginebase.h"
|
||||
#include "engine/enginetype.h"
|
||||
#include "engine/enginedevice.h"
|
||||
#include "engine/devicefinder.h"
|
||||
#include "collection/collection.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectionquery.h"
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "collection/collectionview.h"
|
||||
#include "covermanager/albumcoverchoicecontroller.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "covermanager/currentartloader.h"
|
||||
#include "lyrics/lyricsfetcher.h"
|
||||
|
||||
#include "contextview.h"
|
||||
#include "contextalbumsmodel.h"
|
||||
#include "ui_contextviewcontainer.h"
|
||||
|
||||
using std::unique_ptr;
|
||||
|
||||
const char *ContextView::kSettingsGroup = "ContextView";
|
||||
|
||||
ContextView::ContextView(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
app_(nullptr),
|
||||
ui_(new Ui_ContextViewContainer),
|
||||
collectionview_(nullptr),
|
||||
menu_(new QMenu(this)),
|
||||
timeline_fade_(new QTimeLine(1000, this)),
|
||||
image_strawberry_(":/pictures/strawberry.png"),
|
||||
album_cover_choice_controller_(new AlbumCoverChoiceController(this)),
|
||||
lyrics_fetcher_(nullptr),
|
||||
active_(false),
|
||||
downloading_covers_(false)
|
||||
{
|
||||
|
||||
ui_->setupUi(this);
|
||||
ui_->widget_stacked->setCurrentWidget(ui_->widget_stop);
|
||||
ui_->label_play_album->installEventFilter(this);
|
||||
|
||||
QFontDatabase::addApplicationFont(":/fonts/HumongousofEternitySt.ttf");
|
||||
|
||||
connect(timeline_fade_, SIGNAL(valueChanged(qreal)), SLOT(FadePreviousTrack(qreal)));
|
||||
timeline_fade_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0
|
||||
|
||||
cover_loader_options_.desired_height_ = 300;
|
||||
cover_loader_options_.pad_output_image_ = true;
|
||||
cover_loader_options_.scale_output_image_ = true;
|
||||
pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_strawberry_));
|
||||
|
||||
AddActions();
|
||||
LoadSettings();
|
||||
|
||||
}
|
||||
|
||||
ContextView::~ContextView() { delete ui_; }
|
||||
|
||||
void ContextView::LoadSettings() {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
|
||||
action_show_data_->setChecked(s.value("show_data", true).toBool());
|
||||
action_show_output_->setChecked(s.value("show_output", true).toBool());
|
||||
action_show_albums_->setChecked(s.value("show_albums", true).toBool());
|
||||
action_show_lyrics_->setChecked(s.value("show_lyrics", false).toBool());
|
||||
album_cover_choice_controller_->search_cover_auto_action()->setChecked(s.value("search_for_cover_auto", true).toBool());
|
||||
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
void ContextView::SetApplication(Application *app) {
|
||||
|
||||
app_ = app;
|
||||
|
||||
connect(app_->current_art_loader(), SIGNAL(ArtLoaded(Song, QString, QImage)), SLOT(AlbumArtLoaded(Song, QString, QImage)));
|
||||
|
||||
album_cover_choice_controller_->SetApplication(app_);
|
||||
connect(album_cover_choice_controller_, SIGNAL(AutomaticCoverSearchDone()), this, SLOT(AutomaticCoverSearchDone()));
|
||||
connect(album_cover_choice_controller_->cover_from_file_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromFile()));
|
||||
connect(album_cover_choice_controller_->cover_to_file_action(), SIGNAL(triggered()), this, SLOT(SaveCoverToFile()));
|
||||
connect(album_cover_choice_controller_->cover_from_url_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromURL()));
|
||||
connect(album_cover_choice_controller_->search_for_cover_action(), SIGNAL(triggered()), this, SLOT(SearchForCover()));
|
||||
connect(album_cover_choice_controller_->unset_cover_action(), SIGNAL(triggered()), this, SLOT(UnsetCover()));
|
||||
connect(album_cover_choice_controller_->show_cover_action(), SIGNAL(triggered()), this, SLOT(ShowCover()));
|
||||
connect(album_cover_choice_controller_->search_cover_auto_action(), SIGNAL(triggered()), this, SLOT(SearchCoverAutomatically()));
|
||||
|
||||
ui_->widget_play_albums->SetApplication(app_);
|
||||
|
||||
lyrics_fetcher_ = new LyricsFetcher(app_->lyrics_providers(), this);
|
||||
connect(lyrics_fetcher_, SIGNAL(LyricsFetched(quint64, const QString)), this, SLOT(UpdateLyrics(quint64, const QString)));
|
||||
|
||||
}
|
||||
|
||||
void ContextView::SetCollectionView(CollectionView *collectionview) {
|
||||
collectionview_ = collectionview;
|
||||
connect(collectionview_, SIGNAL(TotalSongCountUpdated_()), this, SLOT(UpdateNoSong()));
|
||||
connect(collectionview_, SIGNAL(TotalArtistCountUpdated_()), this, SLOT(UpdateNoSong()));
|
||||
connect(collectionview_, SIGNAL(TotalAlbumCountUpdated_()), this, SLOT(UpdateNoSong()));
|
||||
}
|
||||
|
||||
void ContextView::AddActions() {
|
||||
|
||||
action_show_data_ = new QAction(tr("Show song technical data"), this);
|
||||
action_show_data_->setCheckable(true);
|
||||
action_show_data_->setChecked(true);
|
||||
|
||||
action_show_output_ = new QAction(tr("Show engine and device"), this);
|
||||
action_show_output_->setCheckable(true);
|
||||
action_show_output_->setChecked(true);
|
||||
|
||||
action_show_albums_ = new QAction(tr("Show albums by artist"), this);
|
||||
action_show_albums_->setCheckable(true);
|
||||
action_show_albums_->setChecked(true);
|
||||
|
||||
action_show_lyrics_ = new QAction(tr("Show song lyrics"), this);
|
||||
action_show_lyrics_->setCheckable(true);
|
||||
action_show_lyrics_->setChecked(false);
|
||||
|
||||
menu_->addAction(action_show_data_);
|
||||
menu_->addAction(action_show_output_);
|
||||
menu_->addAction(action_show_albums_);
|
||||
menu_->addAction(action_show_lyrics_);
|
||||
menu_->addSeparator();
|
||||
|
||||
connect(action_show_data_, SIGNAL(triggered()), this, SLOT(ActionShowData()));
|
||||
connect(action_show_output_, SIGNAL(triggered()), this, SLOT(ActionShowOutput()));
|
||||
connect(action_show_albums_, SIGNAL(triggered()), this, SLOT(ActionShowAlbums()));
|
||||
connect(action_show_lyrics_, SIGNAL(triggered()), this, SLOT(ActionShowLyrics()));
|
||||
|
||||
QList<QAction*> cover_actions = album_cover_choice_controller_->GetAllActions();
|
||||
cover_actions.append(album_cover_choice_controller_->search_cover_auto_action());
|
||||
menu_->addActions(cover_actions);
|
||||
menu_->addSeparator();
|
||||
|
||||
}
|
||||
|
||||
void ContextView::Playing() {
|
||||
}
|
||||
|
||||
void ContextView::Stopped() {
|
||||
|
||||
active_ = false;
|
||||
song_ = song_empty_;
|
||||
downloading_covers_ = false;
|
||||
prev_artist_ = QString();
|
||||
lyrics_ = QString();
|
||||
SetImage(image_strawberry_);
|
||||
|
||||
}
|
||||
|
||||
void ContextView::Error() {
|
||||
}
|
||||
|
||||
void ContextView::UpdateNoSong() {
|
||||
NoSong();
|
||||
}
|
||||
|
||||
void ContextView::SongChanged(const Song &song) {
|
||||
|
||||
image_previous_ = image_original_;
|
||||
prev_artist_ = song_.artist();
|
||||
lyrics_ = QString();
|
||||
song_ = song;
|
||||
UpdateSong();
|
||||
update();
|
||||
if (action_show_lyrics_->isChecked()) lyrics_fetcher_->Search(song.artist(), song.album(), song.title());
|
||||
|
||||
}
|
||||
|
||||
void ContextView::SetText(QLabel *label, int value, const QString &suffix, const QString &def) {
|
||||
label->setText(value <= 0 ? def : (QString::number(value) + " " + suffix));
|
||||
}
|
||||
|
||||
void ContextView::NoSong() {
|
||||
|
||||
ui_->label_stop_top->setStyleSheet(
|
||||
"font: 20pt \"Humongous of Eternity St\";"
|
||||
"font-weight: Regular;"
|
||||
);
|
||||
|
||||
ui_->label_stop_top->setText("No song playing");
|
||||
|
||||
QString html = QString(
|
||||
"%1 songs<br />\n"
|
||||
"%2 artists<br />\n"
|
||||
"%3 albums<br />\n"
|
||||
)
|
||||
.arg(collectionview_->TotalSongs())
|
||||
.arg(collectionview_->TotalArtists())
|
||||
.arg(collectionview_->TotalAlbums())
|
||||
;
|
||||
|
||||
ui_->label_stop_summary->setStyleSheet(
|
||||
"font: 12pt;"
|
||||
"font-weight: regular;"
|
||||
);
|
||||
ui_->label_stop_summary->setText(html);
|
||||
|
||||
}
|
||||
|
||||
void ContextView::UpdateSong() {
|
||||
|
||||
QList <QLabel *> labels_play_data;
|
||||
|
||||
labels_play_data << ui_->label_filetype
|
||||
<< ui_->filetype
|
||||
<< ui_->label_length
|
||||
<< ui_->length
|
||||
<< ui_->label_samplerate
|
||||
<< ui_->samplerate
|
||||
<< ui_->label_bitdepth
|
||||
<< ui_->bitdepth
|
||||
<< ui_->label_bitrate
|
||||
<< ui_->bitrate;
|
||||
|
||||
ui_->label_play_top->setStyleSheet(
|
||||
"font: 11pt;"
|
||||
"font-weight: regular;"
|
||||
);
|
||||
ui_->label_play_top->setText( QString("<b>%1 - %2</b><br/>%3").arg(song_.PrettyTitle().toHtmlEscaped(), song_.artist().toHtmlEscaped(), song_.album().toHtmlEscaped()));
|
||||
|
||||
if (action_show_data_->isChecked()) {
|
||||
for (QLabel *l : labels_play_data) {
|
||||
l->setEnabled(true);
|
||||
l->setVisible(true);
|
||||
l->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
|
||||
}
|
||||
ui_->layout_play_data->setEnabled(true);
|
||||
ui_->filetype->setText(song_.TextForFiletype());
|
||||
ui_->length->setText(Utilities::PrettyTimeNanosec(song_.length_nanosec()));
|
||||
SetText(ui_->samplerate, song_.samplerate(), "Hz");
|
||||
SetText(ui_->bitdepth, song_.bitdepth(), "Bit");
|
||||
SetText(ui_->bitrate, song_.bitrate(), tr("kbps"));
|
||||
ui_->spacer_play_data->changeSize(20, 20, QSizePolicy::Fixed);
|
||||
}
|
||||
else {
|
||||
for (QLabel *l : labels_play_data) {
|
||||
l->setEnabled(false);
|
||||
l->setVisible(false);
|
||||
l->setMaximumSize(0, 0);
|
||||
}
|
||||
ui_->layout_play_data->setEnabled(false);
|
||||
ui_->filetype->clear();
|
||||
ui_->length->clear();
|
||||
ui_->samplerate->clear();
|
||||
ui_->bitdepth->clear();
|
||||
ui_->bitrate->clear();
|
||||
ui_->spacer_play_data->changeSize(0, 0, QSizePolicy::Fixed);
|
||||
}
|
||||
|
||||
if (action_show_output_->isChecked()) {
|
||||
Engine::EngineType enginetype(Engine::None);
|
||||
if (app_->player()->engine()) enginetype = app_->player()->engine()->type();
|
||||
QIcon icon_engine = IconLoader::Load(EngineName(enginetype), 32);
|
||||
|
||||
ui_->label_engine->setVisible(true);
|
||||
ui_->label_engine->setMaximumSize(60, QWIDGETSIZE_MAX);
|
||||
ui_->label_engine_icon->setVisible(true);
|
||||
ui_->label_engine_icon->setPixmap(icon_engine.pixmap(icon_engine.availableSizes().last()));
|
||||
ui_->label_engine_icon->setMinimumSize(32, 32);
|
||||
ui_->label_engine_icon->setMaximumSize(32, 32);
|
||||
ui_->engine->setVisible(true);
|
||||
ui_->engine->setText(EngineDescription(enginetype));
|
||||
ui_->engine->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
|
||||
ui_->spacer_play_output->changeSize(20, 20, QSizePolicy::Fixed);
|
||||
|
||||
DeviceFinder::Device device;
|
||||
for (DeviceFinder *f : app_->enginedevice()->device_finders_) {
|
||||
for (const DeviceFinder::Device &d : f->ListDevices()) {
|
||||
if (d.value != app_->player()->engine()->device()) continue;
|
||||
device = d;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (device.value.isValid()) {
|
||||
ui_->label_device->setVisible(true);
|
||||
ui_->label_device->setMaximumSize(60, QWIDGETSIZE_MAX);
|
||||
ui_->label_device_icon->setVisible(true);
|
||||
ui_->label_device_icon->setMinimumSize(32, 32);
|
||||
ui_->label_device_icon->setMaximumSize(32, 32);
|
||||
ui_->device->setVisible(true);
|
||||
ui_->device->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
|
||||
QIcon icon_device = IconLoader::Load(device.iconname, 32);
|
||||
ui_->label_device_icon->setPixmap(icon_device.pixmap(icon_device.availableSizes().last()));
|
||||
ui_->device->setText(device.description);
|
||||
}
|
||||
else {
|
||||
ui_->label_device->setVisible(false);
|
||||
ui_->label_device->setMaximumSize(0, 0);
|
||||
ui_->label_device_icon->setVisible(false);
|
||||
ui_->label_device_icon->setMinimumSize(0, 0);
|
||||
ui_->label_device_icon->setMaximumSize(0, 0);
|
||||
ui_->label_device_icon->clear();
|
||||
ui_->device->clear();
|
||||
ui_->device->setVisible(false);
|
||||
ui_->device->setMaximumSize(0, 0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ui_->label_engine->setVisible(false);
|
||||
ui_->label_engine->setMaximumSize(0, 0);
|
||||
ui_->label_engine_icon->clear();
|
||||
ui_->label_engine_icon->setVisible(false);
|
||||
ui_->label_engine_icon->setMinimumSize(0, 0);
|
||||
ui_->label_engine_icon->setMaximumSize(0, 0);
|
||||
ui_->engine->clear();
|
||||
ui_->engine->setVisible(false);
|
||||
ui_->engine->setMaximumSize(0, 0);
|
||||
ui_->spacer_play_output->changeSize(0, 0, QSizePolicy::Fixed);
|
||||
ui_->label_device->setVisible(false);
|
||||
ui_->label_device->setMaximumSize(0, 0);
|
||||
ui_->label_device_icon->setVisible(false);
|
||||
ui_->label_device_icon->setMinimumSize(0, 0);
|
||||
ui_->label_device_icon->setMaximumSize(0, 0);
|
||||
ui_->label_device_icon->clear();
|
||||
ui_->device->clear();
|
||||
ui_->device->setVisible(false);
|
||||
ui_->device->setMaximumSize(0, 0);
|
||||
}
|
||||
|
||||
if (action_show_albums_->isChecked() && prev_artist_ != song_.artist()) {
|
||||
const QueryOptions opt;
|
||||
CollectionBackend::AlbumList albumlist;
|
||||
ui_->widget_play_albums->albums_model()->Reset();
|
||||
albumlist = app_->collection_backend()->GetAlbumsByArtist(song_.artist(), opt);
|
||||
if (albumlist.count() > 1) {
|
||||
ui_->label_play_albums->setVisible(true);
|
||||
ui_->label_play_albums->setMinimumSize(0, 20);
|
||||
ui_->label_play_albums->setText(QString("Albums by %1").arg( song_.artist().toHtmlEscaped()));
|
||||
ui_->label_play_albums->setStyleSheet("background-color: #3DADE8; color: rgb(255, 255, 255); font: 11pt;");
|
||||
for (CollectionBackend::Album album : albumlist) {
|
||||
SongList songs = app_->collection_backend()->GetSongs(song_.artist(), album.album_name, opt);
|
||||
ui_->widget_play_albums->albums_model()->AddSongs(songs);
|
||||
}
|
||||
ui_->widget_play_albums->setEnabled(true);
|
||||
ui_->widget_play_albums->setVisible(true);
|
||||
ui_->spacer_play_albums1->changeSize(20, 10, QSizePolicy::Fixed);
|
||||
ui_->spacer_play_albums2->changeSize(20, 20, QSizePolicy::Fixed);
|
||||
}
|
||||
else {
|
||||
ui_->label_play_albums->clear();
|
||||
ui_->label_play_albums->setVisible(false);
|
||||
ui_->label_play_albums->setMinimumSize(0, 0);
|
||||
ui_->widget_play_albums->setEnabled(false);
|
||||
ui_->widget_play_albums->setVisible(false);
|
||||
ui_->spacer_play_albums1->changeSize(0, 0, QSizePolicy::Fixed);
|
||||
ui_->spacer_play_albums2->changeSize(0, 0, QSizePolicy::Fixed);
|
||||
}
|
||||
}
|
||||
else if (!action_show_albums_->isChecked()) {
|
||||
ui_->label_play_albums->clear();
|
||||
ui_->label_play_albums->setVisible(false);
|
||||
ui_->label_play_albums->setMinimumSize(0, 0);
|
||||
ui_->widget_play_albums->albums_model()->Reset();
|
||||
ui_->widget_play_albums->setEnabled(false);
|
||||
ui_->widget_play_albums->setVisible(false);
|
||||
ui_->spacer_play_albums1->changeSize(0, 0, QSizePolicy::Fixed);
|
||||
ui_->spacer_play_albums2->changeSize(0, 0, QSizePolicy::Fixed);
|
||||
}
|
||||
|
||||
if (action_show_lyrics_->isChecked()) {
|
||||
ui_->label_play_lyrics->setText(lyrics_);
|
||||
}
|
||||
else {
|
||||
ui_->label_play_lyrics->clear();
|
||||
}
|
||||
|
||||
ui_->widget_stacked->setCurrentWidget(ui_->widget_play);
|
||||
|
||||
}
|
||||
|
||||
void ContextView::UpdateLyrics(quint64 id, const QString lyrics) {
|
||||
|
||||
lyrics_ = lyrics;
|
||||
if (action_show_lyrics_->isChecked()) {
|
||||
ui_->label_play_lyrics->setText(lyrics);
|
||||
}
|
||||
else ui_->label_play_lyrics->clear();
|
||||
|
||||
}
|
||||
|
||||
bool ContextView::eventFilter(QObject *object, QEvent *event) {
|
||||
|
||||
switch(event->type()) {
|
||||
case QEvent::Paint:{
|
||||
handlePaintEvent(object, event);
|
||||
}
|
||||
default:{
|
||||
return QObject::eventFilter(object, event);
|
||||
}
|
||||
}
|
||||
|
||||
return(true);
|
||||
|
||||
}
|
||||
|
||||
void ContextView::handlePaintEvent(QObject *object, QEvent *event) {
|
||||
|
||||
if (object == ui_->label_play_album) {
|
||||
PaintEventAlbum(event);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
void ContextView::PaintEventAlbum(QEvent *event) {
|
||||
|
||||
QPainter p(ui_->label_play_album);
|
||||
|
||||
DrawImage(&p);
|
||||
|
||||
// Draw the previous track's image if we're fading
|
||||
if (!pixmap_previous_.isNull()) {
|
||||
p.setOpacity(pixmap_previous_opacity_);
|
||||
p.drawPixmap(0, 0, pixmap_previous_);
|
||||
}
|
||||
}
|
||||
|
||||
void ContextView::DrawImage(QPainter *p) {
|
||||
|
||||
p->drawPixmap(0, 0, 300, 300, pixmap_current_);
|
||||
if ((downloading_covers_) && (spinner_animation_)) {
|
||||
p->drawPixmap(50, 50, 16, 16, spinner_animation_->currentPixmap());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ContextView::FadePreviousTrack(qreal value) {
|
||||
|
||||
pixmap_previous_opacity_ = value;
|
||||
if (qFuzzyCompare(pixmap_previous_opacity_, qreal(0.0))) {
|
||||
image_previous_ = QImage();
|
||||
pixmap_previous_ = QPixmap();
|
||||
}
|
||||
update();
|
||||
|
||||
if (value == 0 && !active_) {
|
||||
ui_->widget_stacked->setCurrentWidget(ui_->widget_stop);
|
||||
NoSong();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ContextView::contextMenuEvent(QContextMenuEvent *e) {
|
||||
if (menu_ && ui_->widget_stacked->currentWidget() == ui_->widget_play) menu_->popup(mapToGlobal(e->pos()));
|
||||
}
|
||||
|
||||
void ContextView::mouseReleaseEvent(QMouseEvent *) {
|
||||
}
|
||||
|
||||
void ContextView::dragEnterEvent(QDragEnterEvent *e) {
|
||||
QWidget::dragEnterEvent(e);
|
||||
}
|
||||
|
||||
void ContextView::dropEvent(QDropEvent *e) {
|
||||
QWidget::dropEvent(e);
|
||||
}
|
||||
|
||||
void ContextView::ScaleCover() {
|
||||
|
||||
pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_original_));
|
||||
update();
|
||||
|
||||
}
|
||||
|
||||
void ContextView::AlbumArtLoaded(const Song &song, const QString&, const QImage &image) {
|
||||
|
||||
if (song == song_) {}
|
||||
else {
|
||||
qLog(Error) << __PRETTY_FUNCTION__ << "Ignoring" << song.title() << "because current song is" << song_.title();
|
||||
return;
|
||||
}
|
||||
|
||||
active_ = true;
|
||||
downloading_covers_ = false;
|
||||
SetImage(image);
|
||||
GetCoverAutomatically();
|
||||
|
||||
}
|
||||
|
||||
void ContextView::SetImage(const QImage &image) {
|
||||
|
||||
// Cache the current pixmap so we can fade between them
|
||||
pixmap_previous_ = QPixmap(size());
|
||||
pixmap_previous_.fill(palette().background().color());
|
||||
pixmap_previous_opacity_ = 1.0;
|
||||
|
||||
QPainter p(&pixmap_previous_);
|
||||
DrawImage(&p);
|
||||
p.end();
|
||||
|
||||
image_original_ = image;
|
||||
|
||||
ScaleCover();
|
||||
|
||||
// Were we waiting for this cover to load before we started fading?
|
||||
if (!pixmap_previous_.isNull() && timeline_fade_) {
|
||||
timeline_fade_->stop();
|
||||
timeline_fade_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0
|
||||
timeline_fade_->start();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool ContextView::GetCoverAutomatically() {
|
||||
|
||||
// Search for cover automatically?
|
||||
bool search = !song_.has_manually_unset_cover() && song_.art_automatic().isEmpty() && song_.art_manual().isEmpty() && !song_.artist().isEmpty() && !song_.album().isEmpty();
|
||||
|
||||
if (search) {
|
||||
downloading_covers_ = true;
|
||||
album_cover_choice_controller_->SearchCoverAutomatically(song_);
|
||||
|
||||
// Show a spinner animation
|
||||
spinner_animation_.reset(new QMovie(":/pictures/spinner.gif", QByteArray(), this));
|
||||
connect(spinner_animation_.get(), SIGNAL(updated(const QRect&)), SLOT(update()));
|
||||
spinner_animation_->start();
|
||||
update();
|
||||
}
|
||||
|
||||
return search;
|
||||
|
||||
}
|
||||
|
||||
void ContextView::AutomaticCoverSearchDone() {
|
||||
|
||||
downloading_covers_ = false;
|
||||
spinner_animation_.reset();
|
||||
update();
|
||||
|
||||
}
|
||||
|
||||
void ContextView::ActionShowData() {
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
s.setValue("show_data", action_show_data_->isChecked());
|
||||
s.endGroup();
|
||||
UpdateSong();
|
||||
}
|
||||
|
||||
void ContextView::ActionShowOutput() {
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
s.setValue("show_output", action_show_output_->isChecked());
|
||||
s.endGroup();
|
||||
UpdateSong();
|
||||
}
|
||||
|
||||
void ContextView::ActionShowAlbums() {
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
s.setValue("show_albums", action_show_albums_->isChecked());
|
||||
s.endGroup();
|
||||
prev_artist_ = QString();
|
||||
UpdateSong();
|
||||
}
|
||||
|
||||
void ContextView::ActionShowLyrics() {
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
s.setValue("show_lyrics", action_show_lyrics_->isChecked());
|
||||
s.endGroup();
|
||||
UpdateSong();
|
||||
if (lyrics_.isEmpty() && action_show_lyrics_->isChecked()) lyrics_fetcher_->Search(song_.artist(), song_.album(), song_.title());
|
||||
}
|
||||
|
||||
void ContextView::LoadCoverFromFile() {
|
||||
album_cover_choice_controller_->LoadCoverFromFile(&song_);
|
||||
}
|
||||
|
||||
void ContextView::LoadCoverFromURL() {
|
||||
album_cover_choice_controller_->LoadCoverFromURL(&song_);
|
||||
}
|
||||
|
||||
void ContextView::SearchForCover() {
|
||||
album_cover_choice_controller_->SearchForCover(&song_);
|
||||
}
|
||||
|
||||
void ContextView::SaveCoverToFile() {
|
||||
album_cover_choice_controller_->SaveCoverToFile(song_, image_original_);
|
||||
}
|
||||
|
||||
void ContextView::UnsetCover() {
|
||||
album_cover_choice_controller_->UnsetCover(&song_);
|
||||
}
|
||||
|
||||
void ContextView::ShowCover() {
|
||||
album_cover_choice_controller_->ShowCover(song_);
|
||||
}
|
||||
|
||||
void ContextView::SearchCoverAutomatically() {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
s.setValue("search_for_cover_auto", album_cover_choice_controller_->search_cover_auto_action()->isChecked());
|
||||
s.endGroup();
|
||||
|
||||
GetCoverAutomatically();
|
||||
|
||||
}
|
@ -17,8 +17,8 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef STATUSVIEW_H
|
||||
#define STATUSVIEW_H
|
||||
#ifndef CONTEXTVIEW_H
|
||||
#define CONTEXTVIEW_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
@ -31,52 +31,48 @@
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
#include <QPixmap>
|
||||
#include <QMovie>
|
||||
#include <QPainter>
|
||||
#include <QMovie>
|
||||
#include <QTimeLine>
|
||||
#include <QAction>
|
||||
#include <QLabel>
|
||||
#include <QMenu>
|
||||
#include <QScrollArea>
|
||||
#include <QBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QtEvents>
|
||||
|
||||
#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<QMovie> 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<QMovie> 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
|
||||
|
567
src/context/contextviewcontainer.ui
Normal file
@ -0,0 +1,567 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ContextViewContainer</class>
|
||||
<widget class="QWidget" name="ContextViewContainer">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>927</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="layout_container">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="widget_stacked">
|
||||
<widget class="QWidget" name="widget_stop">
|
||||
<layout class="QVBoxLayout" name="widget_layout_stop">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="layout_stop">
|
||||
<item>
|
||||
<widget class="QScrollArea" name="scrollarea_stop">
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents_stop">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>396</width>
|
||||
<height>923</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_stop_top">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>70</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>70</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>No song playing</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_stop_logo">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>300</width>
|
||||
<height>300</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>300</width>
|
||||
<height>300</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="../../data/data.qrc">:/pictures/strawberry.png</pixmap>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_stop_summary">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_stop_bottom">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>53</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="widget_play">
|
||||
<layout class="QVBoxLayout" name="widget_layout_play">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="layout_play">
|
||||
<item>
|
||||
<widget class="QScrollArea" name="scrollarea_play">
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents_play">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>396</width>
|
||||
<height>923</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_play_top">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>70</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>70</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_play_album">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>300</width>
|
||||
<height>300</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>300</width>
|
||||
<height>300</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_play">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="layout_play_data">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_bitdepth">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Bit depth</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="samplerate">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="length">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_length">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Length</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="filetype">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_samplerate">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Samplerate</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="bitdepth">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_filetype">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Filetype</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_bitrate">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Bitrate</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLabel" name="bitrate">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_play_data">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="layout_play_grid">
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="label_device_icon">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="device">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="engine">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label_engine_icon">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_engine">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Engine</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_device">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Device</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_play_output">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_play_albums">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>300</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="indent">
|
||||
<number>6</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_play_albums1">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ContextAlbumsView" name="widget_play_albums" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_play_albums2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_play_lyrics">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_play_bottom">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ContextAlbumsView</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>context/contextalbumsview.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
@ -52,6 +52,11 @@
|
||||
#include "covermanager/discogscoverprovider.h"
|
||||
#include "covermanager/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<TagReaderClient> tag_reader_client_;
|
||||
@ -120,6 +131,7 @@ class ApplicationImpl {
|
||||
Lazy<CurrentArtLoader> current_art_loader_;
|
||||
Lazy<InternetModel> internet_model_;
|
||||
Lazy<TidalSearch> tidal_search_;
|
||||
Lazy<LyricsProviders> 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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -1,193 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QWidget>
|
||||
#include <QObject>
|
||||
#include <QLayout>
|
||||
#include <QLayoutItem>
|
||||
#include <QStyle>
|
||||
#include <QSize>
|
||||
#include <QPoint>
|
||||
#include <QRect>
|
||||
#include <QSizePolicy>
|
||||
|
||||
#include "flowlayout.h"
|
||||
|
||||
//! [1]
|
||||
FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing)
|
||||
: QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing)
|
||||
{
|
||||
setContentsMargins(margin, margin, margin, margin);
|
||||
}
|
||||
|
||||
FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing)
|
||||
: m_hSpace(hSpacing), m_vSpace(vSpacing) {
|
||||
setContentsMargins(margin, margin, margin, margin);
|
||||
}
|
||||
//! [1]
|
||||
|
||||
//! [2]
|
||||
FlowLayout::~FlowLayout() {
|
||||
QLayoutItem* item;
|
||||
while ((item = takeAt(0))) delete item;
|
||||
}
|
||||
//! [2]
|
||||
|
||||
//! [3]
|
||||
void FlowLayout::addItem(QLayoutItem* item) { itemList.append(item); }
|
||||
//! [3]
|
||||
|
||||
//! [4]
|
||||
int FlowLayout::horizontalSpacing() const {
|
||||
if (m_hSpace >= 0) {
|
||||
return m_hSpace;
|
||||
} else {
|
||||
return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
|
||||
}
|
||||
}
|
||||
|
||||
int FlowLayout::verticalSpacing() const {
|
||||
if (m_vSpace >= 0) {
|
||||
return m_vSpace;
|
||||
} else {
|
||||
return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
|
||||
}
|
||||
}
|
||||
//! [4]
|
||||
|
||||
//! [5]
|
||||
int FlowLayout::count() const { return itemList.size(); }
|
||||
|
||||
QLayoutItem* FlowLayout::itemAt(int index) const {
|
||||
return itemList.value(index);
|
||||
}
|
||||
|
||||
QLayoutItem* FlowLayout::takeAt(int index) {
|
||||
if (index >= 0 && index < itemList.size())
|
||||
return itemList.takeAt(index);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
//! [5]
|
||||
|
||||
//! [6]
|
||||
Qt::Orientations FlowLayout::expandingDirections() const { return 0; }
|
||||
//! [6]
|
||||
|
||||
//! [7]
|
||||
bool FlowLayout::hasHeightForWidth() const { return true; }
|
||||
|
||||
int FlowLayout::heightForWidth(int width) const {
|
||||
int height = doLayout(QRect(0, 0, width, 0), true);
|
||||
return height;
|
||||
}
|
||||
//! [7]
|
||||
|
||||
//! [8]
|
||||
void FlowLayout::setGeometry(const QRect& rect) {
|
||||
QLayout::setGeometry(rect);
|
||||
doLayout(rect, false);
|
||||
}
|
||||
|
||||
QSize FlowLayout::sizeHint() const { return minimumSize(); }
|
||||
|
||||
QSize FlowLayout::minimumSize() const {
|
||||
QSize size;
|
||||
for (QLayoutItem* item : itemList)
|
||||
size = size.expandedTo(item->minimumSize());
|
||||
|
||||
size += QSize(2 * margin(), 2 * margin());
|
||||
return size;
|
||||
}
|
||||
//! [8]
|
||||
|
||||
//! [9]
|
||||
int FlowLayout::doLayout(const QRect& rect, bool testOnly) const {
|
||||
int left, top, right, bottom;
|
||||
getContentsMargins(&left, &top, &right, &bottom);
|
||||
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
|
||||
int x = effectiveRect.x();
|
||||
int y = effectiveRect.y();
|
||||
int lineHeight = 0;
|
||||
//! [9]
|
||||
|
||||
//! [10]
|
||||
for (QLayoutItem* item : itemList) {
|
||||
QWidget* wid = item->widget();
|
||||
int spaceX = horizontalSpacing();
|
||||
if (spaceX == -1)
|
||||
spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
|
||||
int spaceY = verticalSpacing();
|
||||
if (spaceY == -1)
|
||||
spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
|
||||
//! [10]
|
||||
//! [11]
|
||||
int nextX = x + item->sizeHint().width() + spaceX;
|
||||
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
|
||||
x = effectiveRect.x();
|
||||
y = y + lineHeight + spaceY;
|
||||
nextX = x + item->sizeHint().width() + spaceX;
|
||||
lineHeight = 0;
|
||||
}
|
||||
|
||||
if (!testOnly) item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
|
||||
|
||||
x = nextX;
|
||||
lineHeight = qMax(lineHeight, item->sizeHint().height());
|
||||
}
|
||||
return y + lineHeight - rect.y() + bottom;
|
||||
}
|
||||
//! [11]
|
||||
//! [12]
|
||||
int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const {
|
||||
QObject* parent = this->parent();
|
||||
if (!parent) {
|
||||
return -1;
|
||||
}
|
||||
else if (parent->isWidgetType()) {
|
||||
QWidget *pw = static_cast<QWidget *>(parent);
|
||||
return pw->style()->pixelMetric(pm, 0, pw);
|
||||
}
|
||||
else {
|
||||
return static_cast<QLayout *>(parent)->spacing();
|
||||
}
|
||||
}
|
||||
//! [12]
|
@ -1,84 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef FLOWLAYOUT_H
|
||||
#define FLOWLAYOUT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <QLayout>
|
||||
#include <QWidget>
|
||||
#include <QStyle>
|
||||
#include <QList>
|
||||
#include <QSize>
|
||||
#include <QLayoutItem>
|
||||
#include <QRect>
|
||||
|
||||
//! [0]
|
||||
class FlowLayout : public QLayout {
|
||||
public:
|
||||
FlowLayout(QWidget* parent, int margin = -1, int hSpacing = -1, int vSpacing = -1);
|
||||
FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1);
|
||||
~FlowLayout();
|
||||
|
||||
void addItem(QLayoutItem *item);
|
||||
int horizontalSpacing() const;
|
||||
int verticalSpacing() const;
|
||||
Qt::Orientations expandingDirections() const;
|
||||
bool hasHeightForWidth() const;
|
||||
int heightForWidth(int) const;
|
||||
int count() const;
|
||||
QLayoutItem *itemAt(int index) const;
|
||||
QSize minimumSize() const;
|
||||
void setGeometry(const QRect &rect);
|
||||
QSize sizeHint() const;
|
||||
QLayoutItem *takeAt(int index);
|
||||
|
||||
private:
|
||||
int doLayout(const QRect &rect, bool testOnly) const;
|
||||
int smartSpacing(QStyle::PixelMetric pm) const;
|
||||
|
||||
QList<QLayoutItem *> itemList;
|
||||
int m_hSpace;
|
||||
int m_vSpace;
|
||||
};
|
||||
//! [0]
|
||||
|
||||
#endif
|
@ -31,34 +31,24 @@
|
||||
#include "core/logging.h"
|
||||
#include "iconloader.h"
|
||||
|
||||
QList<int> 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<int> 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;
|
||||
|
||||
}
|
||||
|
@ -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<int> sizes_;
|
||||
};
|
||||
|
||||
#endif // ICONLOADER_H
|
||||
|
||||
|
@ -200,9 +200,6 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
// Resources
|
||||
Q_INIT_RESOURCE(data);
|
||||
|
||||
// Icons
|
||||
IconLoader::Init();
|
||||
|
||||
Application app;
|
||||
|
||||
|
@ -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<QPalette&>(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<int>() << 300 << width() - 300);
|
||||
ui_->splitter->setSizes(QList<int>() << 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)));
|
||||
|
@ -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<SettingsDialog> settings_dialog_;
|
||||
Lazy<AlbumCoverManager> 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_;
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.";
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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<CoverSearchResult> &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;
|
||||
|
@ -226,6 +226,7 @@ void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply) {
|
||||
}
|
||||
|
||||
NextState(&task);
|
||||
|
||||
}
|
||||
|
||||
QImage AlbumCoverLoader::ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image) {
|
||||
|
@ -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_)),
|
||||
|
@ -111,7 +111,7 @@ void AmazonCoverProvider::QueryFinished(QNetworkReply *reply, int id) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
QString data=(QString)reply->readAll();
|
||||
QString data(reply->readAll());
|
||||
|
||||
CoverSearchResults results;
|
||||
|
||||
|
@ -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 <QNetworkReply *, int> pending_queries_;
|
||||
|
||||
|
@ -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)));
|
||||
|
@ -101,6 +101,8 @@ QList<DeviceFinder::Device> AlsaDeviceFinder::ListDevices() {
|
||||
device.description = QString("%1 %2").arg(snd_ctl_card_info_get_name(cardinfo)).arg(snd_pcm_info_get_name(pcminfo));
|
||||
device.value = QString("hw:%1,%2").arg(card).arg(dev);
|
||||
device.iconname = GuessIconName(device.description);
|
||||
device.card = card;
|
||||
device.device = dev;
|
||||
ret.append(device);
|
||||
|
||||
}
|
||||
|
@ -38,6 +38,8 @@ class DeviceFinder {
|
||||
QString description;
|
||||
QVariant value;
|
||||
QString iconname;
|
||||
int card;
|
||||
int device;
|
||||
};
|
||||
|
||||
virtual ~DeviceFinder() {}
|
||||
|
@ -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<int> &bandGains) {}
|
||||
|
183
src/lyrics/apiseedslyricsprovider.cpp
Normal file
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QPair>
|
||||
#include <QMap>
|
||||
#include <QSet>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QStringBuilder>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
#include "core/utilities.h"
|
||||
#include "lyricsprovider.h"
|
||||
#include "lyricsfetcher.h"
|
||||
#include "apiseedslyricsprovider.h"
|
||||
|
||||
const char *APISeedsLyricsProvider::kUrlSearch = "https://orion.apiseeds.com/api/music/lyric";
|
||||
const char *APISeedsLyricsProvider::kAPIKeyB64 = "REdWenJhR245Qm03cnE5NlhoS1pTd0V5UVNCNjBtTWVEZlp0ZEttVXhKZTRRdnZSbTRYcmlaUVlaMlM3c0JQUw==";
|
||||
|
||||
APISeedsLyricsProvider::APISeedsLyricsProvider(QObject *parent) : LyricsProvider("APISeeds", parent), network_(new NetworkAccessManager(this)) {}
|
||||
|
||||
bool APISeedsLyricsProvider::StartSearch(const QString &artist, const QString &album, const QString &title, quint64 id) {
|
||||
|
||||
typedef QPair<QString, QString> Arg;
|
||||
typedef QList<Arg> ArgList;
|
||||
typedef QPair<QByteArray, QByteArray> EncodedArg;
|
||||
|
||||
ArgList args = ArgList();
|
||||
args.append(Arg("apikey", QByteArray::fromBase64(kAPIKeyB64)));
|
||||
|
||||
QUrlQuery url_query;
|
||||
for (const Arg &arg : args) {
|
||||
EncodedArg encoded_arg(QUrl::toPercentEncoding(arg.first), QUrl::toPercentEncoding(arg.second));
|
||||
url_query.addQueryItem(encoded_arg.first, encoded_arg.second);
|
||||
}
|
||||
|
||||
QUrl url(QString("%1/%2/%3").arg(kUrlSearch).arg(artist).arg(title));
|
||||
url.setQuery(url_query);
|
||||
QNetworkReply *reply = network_->get(QNetworkRequest(url));
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleSearchReply(QNetworkReply*, quint64, QString, QString)), reply, id, artist, title);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
void APISeedsLyricsProvider::CancelSearch(quint64 id) {
|
||||
}
|
||||
|
||||
void APISeedsLyricsProvider::HandleSearchReply(QNetworkReply *reply, quint64 id, const QString artist, const QString title) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
QJsonObject json_obj = ExtractResult(reply, id);
|
||||
if (json_obj.isEmpty()) return;
|
||||
|
||||
if (!json_obj.contains("artist") || !json_obj.contains("track")) {
|
||||
Error(id, "APISeedsLyrics: Invalid Json reply, result is missing artist or track.", json_obj);
|
||||
return;
|
||||
}
|
||||
QJsonObject json_artist(json_obj["artist"].toObject());
|
||||
QJsonObject json_track(json_obj["track"].toObject());
|
||||
if (!json_track.contains("text")) {
|
||||
Error(id, "APISeedsLyrics: Invalid Json reply, track is missing text.", json_obj);
|
||||
return;
|
||||
}
|
||||
|
||||
LyricsSearchResults results;
|
||||
LyricsSearchResult result;
|
||||
result.artist = json_artist["name"].toString();
|
||||
result.title = json_track["name"].toString();
|
||||
result.lyrics = json_track["text"].toString();
|
||||
result.score = 0.0;
|
||||
if (result.artist.toLower() == artist.toLower()) result.score += 1.0;
|
||||
if (result.title.toLower() == title.toLower()) result.score += 1.0;
|
||||
|
||||
results << result;
|
||||
|
||||
emit SearchFinished(id, results);
|
||||
|
||||
}
|
||||
|
||||
QJsonObject APISeedsLyricsProvider::ExtractJsonObj(QNetworkReply *reply, quint64 id) {
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
QString failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
Error(id, failure_reason);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QByteArray data(reply->readAll());
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
Error(id, "Reply from server missing Json data.");
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (json_doc.isNull() || json_doc.isEmpty()) {
|
||||
Error(id, "Received empty Json document.");
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (!json_doc.isObject()) {
|
||||
Error(id, "Json document is not an object.");
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.isEmpty()) {
|
||||
Error(id, "Received empty Json object.");
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
return json_obj;
|
||||
|
||||
}
|
||||
|
||||
QJsonObject APISeedsLyricsProvider::ExtractResult(QNetworkReply *reply, quint64 id) {
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(reply, id);
|
||||
if (json_obj.isEmpty()) return QJsonObject();
|
||||
|
||||
if (json_obj.contains("error")) {
|
||||
Error(id, json_obj["error"].toString(), json_obj);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (!json_obj.contains("result")) {
|
||||
Error(id, "Json reply is missing result.", json_obj);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QJsonObject json_result = json_obj["result"].toObject();
|
||||
if (json_result.isEmpty()) {
|
||||
Error(id, "Json result object is empty.");
|
||||
return QJsonObject();
|
||||
}
|
||||
return json_result;
|
||||
|
||||
}
|
||||
|
||||
void APISeedsLyricsProvider::Error(quint64 id, QString error, QVariant debug) {
|
||||
LyricsSearchResults results;
|
||||
qLog(Error) << "APISeedsLyrics:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
emit SearchFinished(id, results);
|
||||
}
|
62
src/lyrics/apiseedslyricsprovider.h
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef APISEEDSLYRICSPROVIDER_H
|
||||
#define APISEEDSLYRICSPROVIDER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QHash>
|
||||
#include <QMetaType>
|
||||
#include <QString>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "lyricsprovider.h"
|
||||
#include "lyricsfetcher.h"
|
||||
|
||||
class APISeedsLyricsProvider : public LyricsProvider {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit APISeedsLyricsProvider(QObject *parent = nullptr);
|
||||
|
||||
bool StartSearch(const QString &artist, const QString &album, const QString &title, quint64 id);
|
||||
void CancelSearch(quint64 id);
|
||||
|
||||
private slots:
|
||||
void HandleSearchReply(QNetworkReply *reply, quint64 id, const QString artist, const QString title);
|
||||
|
||||
private:
|
||||
static const char *kUrlSearch;
|
||||
static const char *kAPIKeyB64;
|
||||
QNetworkAccessManager *network_;
|
||||
void Error(quint64 id, QString error, QVariant debug = QVariant());
|
||||
|
||||
QJsonObject ExtractJsonObj(QNetworkReply *reply, quint64 id);
|
||||
QJsonObject ExtractResult(QNetworkReply *reply, quint64 id);
|
||||
|
||||
};
|
||||
|
||||
#endif // APISEEDSLYRICSPROVIDER_H
|
||||
|
215
src/lyrics/auddlyricsprovider.cpp
Normal file
@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QPair>
|
||||
#include <QMap>
|
||||
#include <QSet>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QStringBuilder>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
#include "core/utilities.h"
|
||||
#include "lyricsprovider.h"
|
||||
#include "lyricsfetcher.h"
|
||||
#include "auddlyricsprovider.h"
|
||||
|
||||
const char *AuddLyricsProvider::kUrlSearch = "https://api.audd.io/findLyrics/";
|
||||
const char *AuddLyricsProvider::kAPITokenB64 = "ZjA0NjQ4YjgyNDM3ZTc1MjY3YjJlZDI5ZDBlMzQxZjk=";
|
||||
|
||||
AuddLyricsProvider::AuddLyricsProvider(QObject *parent) : LyricsProvider("AudD", parent), network_(new NetworkAccessManager(this)) {}
|
||||
|
||||
bool AuddLyricsProvider::StartSearch(const QString &artist, const QString &album, const QString &title, quint64 id) {
|
||||
|
||||
QString search(artist + " " + title);
|
||||
|
||||
typedef QPair<QString, QString> Arg;
|
||||
typedef QList<Arg> ArgList;
|
||||
|
||||
typedef QPair<QByteArray, QByteArray> EncodedArg;
|
||||
typedef QList<EncodedArg> EncodedArgList;
|
||||
|
||||
ArgList args = ArgList();
|
||||
args.append(Arg("api_token", QByteArray::fromBase64(kAPITokenB64)));
|
||||
args.append(Arg("q", search));
|
||||
|
||||
QUrlQuery url_query;
|
||||
QUrl url(kUrlSearch);
|
||||
|
||||
for (const Arg &arg : args) {
|
||||
EncodedArg encoded_arg(QUrl::toPercentEncoding(arg.first), QUrl::toPercentEncoding(arg.second));
|
||||
url_query.addQueryItem(encoded_arg.first, encoded_arg.second);
|
||||
}
|
||||
|
||||
url.setQuery(url_query);
|
||||
QNetworkReply *reply = network_->get(QNetworkRequest(url));
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleSearchReply(QNetworkReply*, quint64, QString, QString)), reply, id, artist, title);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
void AuddLyricsProvider::CancelSearch(quint64 id) {
|
||||
}
|
||||
|
||||
void AuddLyricsProvider::HandleSearchReply(QNetworkReply *reply, quint64 id, const QString artist, const QString title) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
QJsonArray json_result = ExtractResult(reply, id);
|
||||
if (json_result.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LyricsSearchResults results;
|
||||
for (const QJsonValue &value : json_result) {
|
||||
if (!value.isObject()) {
|
||||
qLog(Error) << "AuddLyrics: Invalid Json reply, result is not an object.";
|
||||
qLog(Debug) << value;
|
||||
continue;
|
||||
}
|
||||
QJsonObject json_obj = value.toObject();
|
||||
if (
|
||||
!json_obj.contains("song_id") ||
|
||||
!json_obj.contains("artist_id") ||
|
||||
!json_obj.contains("title") ||
|
||||
!json_obj.contains("artist") ||
|
||||
!json_obj.contains("lyrics")
|
||||
) {
|
||||
qLog(Error) << "AuddLyrics: Invalid Json reply, result is missing data.";
|
||||
qLog(Debug) << value;
|
||||
continue;
|
||||
}
|
||||
LyricsSearchResult result;
|
||||
result.artist = json_obj["artist"].toString();
|
||||
result.title = json_obj["title"].toString();
|
||||
result.lyrics = json_obj["lyrics"].toString();
|
||||
result.score = 0.0;
|
||||
if (result.artist.toLower() == artist.toLower()) result.score += 1.0;
|
||||
if (result.title.toLower() == title.toLower()) result.score += 1.0;
|
||||
|
||||
results << result;
|
||||
}
|
||||
|
||||
emit SearchFinished(id, results);
|
||||
|
||||
}
|
||||
|
||||
QJsonObject AuddLyricsProvider::ExtractJsonObj(QNetworkReply *reply, quint64 id) {
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
QString failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
Error(id, failure_reason);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QByteArray data(reply->readAll());
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
Error(id, "Reply from server missing Json data.");
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (json_doc.isNull() || json_doc.isEmpty()) {
|
||||
Error(id, "Received empty Json document.");
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (!json_doc.isObject()) {
|
||||
Error(id, "Json document is not an object.");
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.isEmpty()) {
|
||||
Error(id, "Received empty Json object.");
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
return json_obj;
|
||||
|
||||
}
|
||||
|
||||
QJsonArray AuddLyricsProvider::ExtractResult(QNetworkReply *reply, quint64 id) {
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(reply, id);
|
||||
if (json_obj.isEmpty()) return QJsonArray();
|
||||
|
||||
if (!json_obj.contains("status")) {
|
||||
Error(id, "Json reply is missing status.", json_obj);
|
||||
return QJsonArray();
|
||||
}
|
||||
|
||||
if (json_obj["status"].toString() == "error") {
|
||||
if (!json_obj.contains("error")) {
|
||||
Error(id, "Json reply is missing error status.", json_obj);
|
||||
return QJsonArray();
|
||||
}
|
||||
QJsonObject json_error = json_obj["error"].toObject();
|
||||
if (!json_error.contains("error_code") || !json_error.contains("error_message")) {
|
||||
Error(id, "Json reply is missing error code or message.", json_error);
|
||||
return QJsonArray();
|
||||
}
|
||||
QString error_code(json_error["error_code"].toString());
|
||||
QString error_message(json_error["error_message"].toString());
|
||||
Error(id, error_message);
|
||||
return QJsonArray();
|
||||
}
|
||||
|
||||
if (!json_obj.contains("result")) {
|
||||
Error(id, "Json reply is missing result.", json_obj);
|
||||
return QJsonArray();
|
||||
}
|
||||
|
||||
QJsonArray json_result = json_obj["result"].toArray();
|
||||
if (json_result.isEmpty()) {
|
||||
Error(id, "No match.");
|
||||
return QJsonArray();
|
||||
}
|
||||
|
||||
return json_result;
|
||||
|
||||
}
|
||||
|
||||
void AuddLyricsProvider::Error(quint64 id, QString error, QVariant debug) {
|
||||
qLog(Error) << "AuddLyrics:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
LyricsSearchResults results;
|
||||
emit SearchFinished(id, results);
|
||||
}
|
61
src/lyrics/auddlyricsprovider.h
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AUDDLYRICSPROVIDER_H
|
||||
#define AUDDLYRICSPROVIDER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QHash>
|
||||
#include <QMetaType>
|
||||
#include <QString>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "lyricsprovider.h"
|
||||
#include "lyricsfetcher.h"
|
||||
|
||||
class AuddLyricsProvider : public LyricsProvider {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AuddLyricsProvider(QObject *parent = nullptr);
|
||||
|
||||
bool StartSearch(const QString &artist, const QString &album, const QString &title, quint64 id);
|
||||
void CancelSearch(quint64 id);
|
||||
|
||||
private slots:
|
||||
void HandleSearchReply(QNetworkReply *reply, quint64 id, const QString artist, const QString title);
|
||||
|
||||
private:
|
||||
static const char *kUrlSearch;
|
||||
static const char *kAPITokenB64;
|
||||
QNetworkAccessManager *network_;
|
||||
void Error(quint64 id, QString error, QVariant debug = QVariant());
|
||||
|
||||
QJsonObject ExtractJsonObj(QNetworkReply *reply, quint64 id);
|
||||
QJsonArray ExtractResult(QNetworkReply *reply, quint64 id);
|
||||
|
||||
};
|
||||
|
||||
#endif // AUDDLYRICSPROVIDER_H
|
||||
|
124
src/lyrics/lyricsfetcher.cpp
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
#include <QString>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "lyricsfetcher.h"
|
||||
#include "lyricsfetchersearch.h"
|
||||
|
||||
const int LyricsFetcher::kMaxConcurrentRequests = 5;
|
||||
const QRegExp LyricsFetcher::kRemoveNonAlpha("[^a-zA-Z0-9\\d\\s]");
|
||||
const QRegExp LyricsFetcher::kRemoveFromTitle(" ?-? ((\\(|\\[)?)(Remastered|Live) ?((\\)|\\])?)$");
|
||||
|
||||
LyricsFetcher::LyricsFetcher(LyricsProviders *lyrics_providers, QObject *parent)
|
||||
: QObject(parent),
|
||||
lyrics_providers_(lyrics_providers),
|
||||
next_id_(0),
|
||||
request_starter_(new QTimer(this))
|
||||
{
|
||||
|
||||
request_starter_->setInterval(500);
|
||||
connect(request_starter_, SIGNAL(timeout()), SLOT(StartRequests()));
|
||||
|
||||
}
|
||||
|
||||
quint64 LyricsFetcher::Search(const QString &artist, const QString &album, const QString &title) {
|
||||
|
||||
LyricsSearchRequest request;
|
||||
request.artist = artist;
|
||||
request.album = album;
|
||||
request.album.remove(kRemoveNonAlpha);
|
||||
request.album.remove(kRemoveFromTitle);
|
||||
request.title = title;
|
||||
request.title.remove(kRemoveNonAlpha);
|
||||
request.title.remove(kRemoveFromTitle);
|
||||
request.id = next_id_++;
|
||||
AddRequest(request);
|
||||
|
||||
return request.id;
|
||||
|
||||
}
|
||||
|
||||
void LyricsFetcher::AddRequest(const LyricsSearchRequest &req) {
|
||||
|
||||
queued_requests_.enqueue(req);
|
||||
|
||||
if (!request_starter_->isActive()) request_starter_->start();
|
||||
|
||||
if (active_requests_.size() < kMaxConcurrentRequests) StartRequests();
|
||||
|
||||
}
|
||||
|
||||
void LyricsFetcher::Clear() {
|
||||
|
||||
queued_requests_.clear();
|
||||
|
||||
for (LyricsFetcherSearch *search : active_requests_.values()) {
|
||||
search->Cancel();
|
||||
search->deleteLater();
|
||||
}
|
||||
active_requests_.clear();
|
||||
|
||||
}
|
||||
|
||||
void LyricsFetcher::StartRequests() {
|
||||
|
||||
if (queued_requests_.isEmpty()) {
|
||||
request_starter_->stop();
|
||||
return;
|
||||
}
|
||||
|
||||
while (!queued_requests_.isEmpty() && active_requests_.size() < kMaxConcurrentRequests) {
|
||||
|
||||
LyricsSearchRequest request = queued_requests_.dequeue();
|
||||
|
||||
LyricsFetcherSearch *search = new LyricsFetcherSearch(request, this);
|
||||
active_requests_.insert(request.id, search);
|
||||
|
||||
connect(search, SIGNAL(SearchFinished(quint64, LyricsSearchResults)), SLOT(SingleSearchFinished(quint64, LyricsSearchResults)));
|
||||
connect(search, SIGNAL(LyricsFetched(quint64, const QString&)), SLOT(SingleLyricsFetched(quint64, const QString&)));
|
||||
|
||||
search->Start(lyrics_providers_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void LyricsFetcher::SingleSearchFinished(quint64 request_id, LyricsSearchResults results) {
|
||||
|
||||
LyricsFetcherSearch *search = active_requests_.take(request_id);
|
||||
if (!search) return;
|
||||
search->deleteLater();
|
||||
emit SearchFinished(request_id, results);
|
||||
|
||||
}
|
||||
|
||||
void LyricsFetcher::SingleLyricsFetched(quint64 request_id, const QString &lyrics) {
|
||||
|
||||
LyricsFetcherSearch *search = active_requests_.take(request_id);
|
||||
if (!search) return;
|
||||
search->deleteLater();
|
||||
emit LyricsFetched(request_id, lyrics);
|
||||
|
||||
}
|
95
src/lyrics/lyricsfetcher.h
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LYRICSFETCHER_H
|
||||
#define LYRICSFETCHER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QQueue>
|
||||
#include <QTimer>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
class LyricsProviders;
|
||||
class LyricsFetcherSearch;
|
||||
|
||||
struct LyricsSearchRequest {
|
||||
quint64 id;
|
||||
QString artist;
|
||||
QString album;
|
||||
QString title;
|
||||
};
|
||||
|
||||
struct LyricsSearchResult {
|
||||
QString provider;
|
||||
QString artist;
|
||||
QString album;
|
||||
QString title;
|
||||
QString lyrics;
|
||||
float score;
|
||||
};
|
||||
Q_DECLARE_METATYPE(LyricsSearchResult);
|
||||
|
||||
typedef QList<LyricsSearchResult> LyricsSearchResults;
|
||||
Q_DECLARE_METATYPE(QList<LyricsSearchResult>);
|
||||
|
||||
class LyricsFetcher : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
LyricsFetcher(LyricsProviders *lyrics_providers, QObject *parent = nullptr);
|
||||
virtual ~LyricsFetcher() {}
|
||||
|
||||
static const int kMaxConcurrentRequests;
|
||||
static const QRegExp kRemoveNonAlpha;
|
||||
static const QRegExp kRemoveFromTitle;
|
||||
|
||||
quint64 Search(const QString &artist, const QString &album, const QString &title);
|
||||
void Clear();
|
||||
|
||||
signals:
|
||||
void LyricsFetched(quint64, const QString &lyrics);
|
||||
void SearchFinished(quint64, const LyricsSearchResults &results);
|
||||
|
||||
private slots:
|
||||
void SingleSearchFinished(quint64, LyricsSearchResults results);
|
||||
void SingleLyricsFetched(quint64, const QString &lyrics);
|
||||
void StartRequests();
|
||||
|
||||
private:
|
||||
void AddRequest(const LyricsSearchRequest &req);
|
||||
|
||||
LyricsProviders *lyrics_providers_;
|
||||
quint64 next_id_;
|
||||
|
||||
QQueue<LyricsSearchRequest> queued_requests_;
|
||||
QHash<quint64, LyricsFetcherSearch*> active_requests_;
|
||||
|
||||
QTimer *request_starter_;
|
||||
|
||||
};
|
||||
|
||||
#endif // LYRICSFETCHER_H
|
117
src/lyrics/lyricsfetchersearch.cpp
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include <QObject>
|
||||
#include <QtAlgorithms>
|
||||
#include <QTimer>
|
||||
#include <QList>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "lyricsfetcher.h"
|
||||
#include "lyricsfetchersearch.h"
|
||||
#include "lyricsprovider.h"
|
||||
#include "lyricsproviders.h"
|
||||
|
||||
const int LyricsFetcherSearch::kSearchTimeoutMs = 6000;
|
||||
|
||||
LyricsFetcherSearch::LyricsFetcherSearch(
|
||||
const LyricsSearchRequest &request, QObject *parent)
|
||||
: QObject(parent),
|
||||
request_(request),
|
||||
cancel_requested_(false) {
|
||||
|
||||
QTimer::singleShot(kSearchTimeoutMs, this, SLOT(TerminateSearch()));
|
||||
|
||||
}
|
||||
|
||||
void LyricsFetcherSearch::TerminateSearch() {
|
||||
|
||||
for (int id : pending_requests_.keys()) {
|
||||
pending_requests_.take(id)->CancelSearch(id);
|
||||
}
|
||||
AllProvidersFinished();
|
||||
|
||||
}
|
||||
|
||||
void LyricsFetcherSearch::Start(LyricsProviders *lyrics_providers) {
|
||||
|
||||
for (LyricsProvider *provider : lyrics_providers->List()) {
|
||||
connect(provider, SIGNAL(SearchFinished(quint64, QList<LyricsSearchResult>)), SLOT(ProviderSearchFinished(quint64, QList<LyricsSearchResult>)));
|
||||
const int id = lyrics_providers->NextId();
|
||||
const bool success = provider->StartSearch(request_.artist, request_.album, request_.title, id);
|
||||
if (success) pending_requests_[id] = provider;
|
||||
}
|
||||
|
||||
if (pending_requests_.isEmpty()) TerminateSearch();
|
||||
|
||||
}
|
||||
|
||||
void LyricsFetcherSearch::ProviderSearchFinished(quint64 id, const QList<LyricsSearchResult> &results) {
|
||||
|
||||
if (!pending_requests_.contains(id)) return;
|
||||
LyricsProvider *provider = pending_requests_.take(id);
|
||||
|
||||
LyricsSearchResults results_copy(results);
|
||||
for (int i = 0; i < results_copy.count(); ++i) {
|
||||
results_copy[i].provider = provider->name();
|
||||
}
|
||||
|
||||
results_.append(results_copy);
|
||||
|
||||
if (!pending_requests_.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AllProvidersFinished();
|
||||
|
||||
}
|
||||
|
||||
void LyricsFetcherSearch::AllProvidersFinished() {
|
||||
|
||||
if (cancel_requested_) return;
|
||||
|
||||
if (!results_.isEmpty()) {
|
||||
LyricsSearchResult result_use;
|
||||
result_use.score = 0.0;
|
||||
for (LyricsSearchResult result : results_) {
|
||||
if (result_use.lyrics.isEmpty() || result.score > result_use.score) result_use = result;
|
||||
}
|
||||
emit LyricsFetched(request_.id, result_use.lyrics);
|
||||
}
|
||||
emit SearchFinished(request_.id, results_);
|
||||
|
||||
}
|
||||
|
||||
void LyricsFetcherSearch::Cancel() {
|
||||
|
||||
cancel_requested_ = true;
|
||||
|
||||
if (!pending_requests_.isEmpty()) {
|
||||
TerminateSearch();
|
||||
}
|
||||
|
||||
}
|
||||
|
69
src/lyrics/lyricsfetchersearch.h
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LYRICSFETCHERSEARCH_H
|
||||
#define LYRICSFETCHERSEARCH_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
|
||||
#include "lyricsfetcher.h"
|
||||
|
||||
class LyricsProvider;
|
||||
class LyricsProviders;
|
||||
|
||||
class LyricsFetcherSearch : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
LyricsFetcherSearch(const LyricsSearchRequest &request, QObject *parent);
|
||||
|
||||
void Start(LyricsProviders *cover_providers);
|
||||
void Cancel();
|
||||
|
||||
signals:
|
||||
void SearchFinished(quint64, const LyricsSearchResults &results);
|
||||
void LyricsFetched(quint64, const QString &lyrics);
|
||||
|
||||
private slots:
|
||||
void ProviderSearchFinished(quint64 id, const QList<LyricsSearchResult> &results);
|
||||
void TerminateSearch();
|
||||
|
||||
private:
|
||||
void AllProvidersFinished();
|
||||
|
||||
void SendBestImage();
|
||||
|
||||
private:
|
||||
static const int kSearchTimeoutMs;
|
||||
|
||||
LyricsSearchRequest request_;
|
||||
LyricsSearchResults results_;
|
||||
QMap<int, LyricsProvider*> pending_requests_;
|
||||
bool cancel_requested_;
|
||||
|
||||
};
|
||||
|
||||
#endif // LYRICSFETCHERSEARCH_H
|
@ -1,7 +1,6 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -18,36 +17,12 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ELIDEDLABEL_H
|
||||
#define ELIDEDLABEL_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QString>
|
||||
#include <QLabel>
|
||||
#include <QtEvents>
|
||||
|
||||
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) {}
|
@ -1,7 +1,6 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -18,27 +17,36 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PROGRESSITEMDELEGATE_H
|
||||
#define PROGRESSITEMDELEGATE_H
|
||||
#ifndef LYRICSPROVIDER_H
|
||||
#define LYRICSPROVIDER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QPainter>
|
||||
#include <QStyleOption>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QStyleOptionViewItem>
|
||||
|
||||
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<LyricsSearchResult>& results);
|
||||
|
||||
private:
|
||||
QString name_;
|
||||
|
||||
};
|
||||
|
||||
#endif // PROGRESSITEMDELEGATE_H
|
||||
|
||||
#endif // LYRICSPROVIDER_H
|
76
src/lyrics/lyricsproviders.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
#include <QString>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "lyricsprovider.h"
|
||||
#include "lyricsproviders.h"
|
||||
#include "lyricsfetcher.h"
|
||||
|
||||
LyricsProviders::LyricsProviders(QObject *parent) : QObject(parent) {}
|
||||
|
||||
void LyricsProviders::AddProvider(LyricsProvider *provider) {
|
||||
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
lyrics_providers_.insert(provider, provider->name());
|
||||
connect(provider, SIGNAL(destroyed()), SLOT(ProviderDestroyed()));
|
||||
}
|
||||
|
||||
qLog(Debug) << "Registered lyrics provider" << provider->name();
|
||||
|
||||
}
|
||||
|
||||
void LyricsProviders::RemoveProvider(LyricsProvider *provider) {
|
||||
|
||||
if (!provider) return;
|
||||
|
||||
// It's not safe to dereference provider at this point because it might have already been destroyed.
|
||||
|
||||
QString name;
|
||||
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
name = lyrics_providers_.take(provider);
|
||||
}
|
||||
|
||||
if (name.isNull()) {
|
||||
qLog(Debug) << "Tried to remove a lyrics provider that was not registered";
|
||||
}
|
||||
else {
|
||||
qLog(Debug) << "Unregistered lyrics provider" << name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void LyricsProviders::ProviderDestroyed() {
|
||||
|
||||
LyricsProvider *provider = static_cast<LyricsProvider*>(sender());
|
||||
RemoveProvider(provider);
|
||||
|
||||
}
|
||||
|
||||
int LyricsProviders::NextId() { return next_id_.fetchAndAddRelaxed(1); }
|
60
src/lyrics/lyricsproviders.h
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LYRICSPROVIDERS_H
|
||||
#define LYRICSPROVIDERS_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QAtomicInt>
|
||||
|
||||
class LyricsProvider;
|
||||
|
||||
class LyricsProviders : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LyricsProviders(QObject *parent = nullptr);
|
||||
void AddProvider(LyricsProvider *provider);
|
||||
void RemoveProvider(LyricsProvider *provider);
|
||||
QList<LyricsProvider*> List() const { return lyrics_providers_.keys(); }
|
||||
bool HasAnyProviders() const { return !lyrics_providers_.isEmpty(); }
|
||||
int NextId();
|
||||
|
||||
private slots:
|
||||
void ProviderDestroyed();
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(LyricsProviders);
|
||||
|
||||
QMap<LyricsProvider *, QString> lyrics_providers_;
|
||||
QMutex mutex_;
|
||||
|
||||
QAtomicInt next_id_;
|
||||
};
|
||||
|
||||
#endif // LYRICSPROVIDERS_H
|
@ -1448,9 +1448,9 @@ void Playlist::StopAfter(int row) {
|
||||
}
|
||||
|
||||
void Playlist::SetStreamMetadata(const QUrl &url, const Song &song) {
|
||||
|
||||
|
||||
//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<int>() << 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<int>() << 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);
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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_;
|
||||
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>437</width>
|
||||
<width>400</width>
|
||||
<height>633</height>
|
||||
</rect>
|
||||
</property>
|
||||
@ -82,14 +82,14 @@
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radiobutton_searchbyalbums">
|
||||
<property name="text">
|
||||
<string>albu&ms</string>
|
||||
<string>a&lbums</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radiobutton_searchbysongs">
|
||||
<property name="text">
|
||||
<string>songs</string>
|
||||
<string>son&gs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -186,7 +186,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>435</width>
|
||||
<width>398</width>
|
||||
<height>533</height>
|
||||
</rect>
|
||||
</property>
|
||||
@ -195,7 +195,7 @@
|
||||
<rect>
|
||||
<x>9</x>
|
||||
<y>109</y>
|
||||
<width>420</width>
|
||||
<width>336</width>
|
||||
<height>100</height>
|
||||
</rect>
|
||||
</property>
|
||||
@ -214,12 +214,6 @@
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_helptext">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>80</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enter search terms above to find music</string>
|
||||
</property>
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -1,184 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QFlags>
|
||||
#include <QFontMetrics>
|
||||
#include <QString>
|
||||
#include <QStringBuilder>
|
||||
#include <QIcon>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
#include <QColor>
|
||||
#include <QRect>
|
||||
#include <QSize>
|
||||
#include <QToolButton>
|
||||
#include <QtEvents>
|
||||
|
||||
#include "didyoumean.h"
|
||||
|
||||
const int DidYouMean::kPadding = 3;
|
||||
|
||||
DidYouMean::DidYouMean(QWidget *buddy, QWidget *parent)
|
||||
: QWidget(parent, Qt::ToolTip),
|
||||
buddy_(buddy),
|
||||
close_(new QToolButton(this)),
|
||||
normal_font_(font()),
|
||||
correction_font_(font()),
|
||||
press_enter_font_(font()) {
|
||||
|
||||
// Close icon
|
||||
close_->setToolTip(tr("Close"));
|
||||
close_->setIcon(QIcon(":/qt-project.org/styles/macstyle/images/closedock-16.png"));
|
||||
close_->setIconSize(QSize(16, 16));
|
||||
connect(close_, SIGNAL(clicked()), SLOT(hide()));
|
||||
|
||||
// Cursors
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
close_->setCursor(Qt::ArrowCursor);
|
||||
|
||||
// Fonts
|
||||
correction_font_.setBold(true);
|
||||
press_enter_font_.setBold(true);
|
||||
press_enter_font_.setPointSizeF(7.5);
|
||||
|
||||
hide();
|
||||
buddy_->installEventFilter(this);
|
||||
|
||||
// Texts
|
||||
did_you_mean_ = tr("Did you mean") + ": ";
|
||||
press_enter_ = "(" + tr("press enter") + ")";
|
||||
|
||||
// Texts' sizes
|
||||
did_you_mean_size_ = QFontMetrics(normal_font_).width(did_you_mean_);
|
||||
press_enter_size_ = QFontMetrics(press_enter_font_).width(press_enter_);
|
||||
|
||||
}
|
||||
|
||||
bool DidYouMean::eventFilter(QObject *object, QEvent *event) {
|
||||
|
||||
if (object != buddy_) {
|
||||
return QObject::eventFilter(object, event);
|
||||
}
|
||||
|
||||
switch (event->type()) {
|
||||
case QEvent::Move:
|
||||
case QEvent::Resize:
|
||||
if (isVisible()) {
|
||||
UpdateGeometry();
|
||||
}
|
||||
break;
|
||||
|
||||
case QEvent::KeyPress:
|
||||
if (!isVisible()) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (static_cast<QKeyEvent*>(event)->key()) {
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Enter:
|
||||
emit Accepted(correction_);
|
||||
// fallthrough
|
||||
case Qt::Key_Escape:
|
||||
hide();
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case QEvent::FocusOut:
|
||||
case QEvent::WindowDeactivate:
|
||||
hide();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return QObject::eventFilter(object, event);
|
||||
}
|
||||
|
||||
void DidYouMean::showEvent(QShowEvent*) {
|
||||
UpdateGeometry();
|
||||
}
|
||||
|
||||
void DidYouMean::UpdateGeometry() {
|
||||
const int text_height = fontMetrics().height();
|
||||
const int height = text_height + kPadding * 2;
|
||||
|
||||
move(buddy_->mapToGlobal(buddy_->rect().bottomLeft()));
|
||||
// Resize to len(text to display) + total number of padding added + size(close button), so the "Did you mean" widget is always fully displayed
|
||||
|
||||
resize(QSize(did_you_mean_size_ + QFontMetrics(correction_font_).width(correction_ + " ") + press_enter_size_ + kPadding * 6 + close_->width(), height));
|
||||
|
||||
close_->move(kPadding, kPadding);
|
||||
close_->resize(text_height, text_height);
|
||||
}
|
||||
|
||||
void DidYouMean::paintEvent(QPaintEvent*) {
|
||||
QPainter p(this);
|
||||
|
||||
// Draw the background
|
||||
QColor bg(palette().color(QPalette::Inactive, QPalette::ToolTipBase));
|
||||
p.fillRect(0, 0, width()-1, height()-1, bg);
|
||||
|
||||
// Border
|
||||
p.setPen(Qt::black);
|
||||
p.drawRect(0, 0, width()-1, height()-1);
|
||||
|
||||
// Text rectangle
|
||||
QRect text_rect(kPadding + close_->width() + kPadding, kPadding, rect().width() - kPadding, rect().height() - kPadding);
|
||||
|
||||
// Text
|
||||
p.setFont(normal_font_);
|
||||
p.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, did_you_mean_);
|
||||
text_rect.setLeft(text_rect.left() + p.fontMetrics().width(did_you_mean_));
|
||||
|
||||
p.setFont(correction_font_);
|
||||
p.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, correction_);
|
||||
text_rect.setLeft(text_rect.left() + p.fontMetrics().width(correction_ + " "));
|
||||
|
||||
p.setPen(palette().color(QPalette::Disabled, QPalette::Text));
|
||||
p.setFont(press_enter_font_);
|
||||
p.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, press_enter_);
|
||||
}
|
||||
|
||||
void DidYouMean::SetCorrection(const QString &correction) {
|
||||
correction_ = correction;
|
||||
UpdateGeometry();
|
||||
update();
|
||||
}
|
||||
|
||||
void DidYouMean::Show(const QString &correction) {
|
||||
SetCorrection(correction);
|
||||
show();
|
||||
}
|
||||
|
||||
void DidYouMean::mouseReleaseEvent(QMouseEvent *e) {
|
||||
emit Accepted(correction_);
|
||||
hide();
|
||||
}
|
||||
|
@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DIDYOUMEAN_H
|
||||
#define DIDYOUMEAN_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QFont>
|
||||
#include <QString>
|
||||
#include <QToolButton>
|
||||
#include <QtEvents>
|
||||
|
||||
class QEvent;
|
||||
class QMouseEvent;
|
||||
class QPaintEvent;
|
||||
class QShowEvent;
|
||||
|
||||
class DidYouMean : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DidYouMean(QWidget *buddy, QWidget *parent);
|
||||
|
||||
static const int kPadding;
|
||||
|
||||
public slots:
|
||||
void SetCorrection(const QString& correction);
|
||||
void Show(const QString& correction);
|
||||
|
||||
signals:
|
||||
void Accepted(const QString& correction);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent*);
|
||||
void showEvent(QShowEvent*);
|
||||
void mouseReleaseEvent(QMouseEvent *e);
|
||||
bool eventFilter(QObject *object, QEvent *event);
|
||||
|
||||
private:
|
||||
void UpdateGeometry();
|
||||
|
||||
private:
|
||||
QWidget *buddy_;
|
||||
QString correction_;
|
||||
|
||||
QToolButton *close_;
|
||||
|
||||
QFont normal_font_;
|
||||
QFont correction_font_;
|
||||
QFont press_enter_font_;
|
||||
|
||||
QString did_you_mean_;
|
||||
QString press_enter_;
|
||||
|
||||
// Size of the text to display, according to QFonts above.
|
||||
// Stored here to avoid to recompute them each time
|
||||
int did_you_mean_size_;
|
||||
int press_enter_size_;
|
||||
};
|
||||
|
||||
#endif // DIDYOUMEAN_H
|
@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QString>
|
||||
#include <QLabel>
|
||||
#include <QFontMetrics>
|
||||
#include <QtEvents>
|
||||
|
||||
#include "elidedlabel.h"
|
||||
|
||||
class QResizeEvent;
|
||||
|
||||
ElidedLabel::ElidedLabel(QWidget *parent) : QLabel(parent) {}
|
||||
|
||||
void ElidedLabel::SetText(const QString& text) {
|
||||
text_ = text;
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
void ElidedLabel::resizeEvent(QResizeEvent *) {
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
void ElidedLabel::UpdateText() {
|
||||
setText(fontMetrics().elidedText(text_, Qt::ElideRight, width() - 5));
|
||||
}
|
@ -126,7 +126,8 @@ void FancyTabProxyStyle::drawControl(ControlElement element, const QStyleOption*
|
||||
if (vertical_tabs) {
|
||||
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<QHelpEvent*>(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<FancyTabBar*>(tab_bar_)) {
|
||||
bar->setCurrentIndex(index);
|
||||
} else if (QTabBar* bar = qobject_cast<QTabBar*>(tab_bar_)) {
|
||||
}
|
||||
else if (QTabBar* bar = qobject_cast<QTabBar*>(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);
|
||||
|
@ -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("<p align=center><i></i><br/><br/></p>"));
|
||||
}
|
||||
|
||||
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 += "<p>";
|
||||
break;
|
||||
case LargeSongDetails:
|
||||
details_->setTextWidth(cover_loader_options_.desired_height_);
|
||||
if (fit_width_) {
|
||||
details_->setDefaultStyleSheet(
|
||||
"p {"
|
||||
" font-size: small;"
|
||||
"}");
|
||||
}
|
||||
else {
|
||||
details_->setDefaultStyleSheet(
|
||||
"p {"
|
||||
" font-size: small;"
|
||||
" color: black;"
|
||||
"}");
|
||||
}
|
||||
html += "<p align=center>";
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Make this configurable
|
||||
html += QString("<i>%1</i><br/>%2<br/>%3").arg(metadata_.PrettyTitle().toHtmlEscaped(), metadata_.artist().toHtmlEscaped(), metadata_.album().toHtmlEscaped());
|
||||
|
||||
html += "</p>";
|
||||
details_->setHtml(html);
|
||||
|
||||
// if something spans multiple lines the height needs to change
|
||||
if (mode_ == LargeSongDetails) UpdateHeight();
|
||||
|
||||
}
|
||||
|
||||
void PlayingWidget::ScaleCover() {
|
||||
|
||||
cover_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, original_));
|
||||
update();
|
||||
|
||||
}
|
||||
|
||||
void PlayingWidget::AlbumArtLoaded(const Song &metadata, const QString &, const QImage &image) {
|
||||
|
||||
active_ = true;
|
||||
|
||||
metadata_ = metadata;
|
||||
downloading_covers_ = false;
|
||||
|
||||
SetImage(image);
|
||||
|
||||
// Search for cover automatically?
|
||||
GetCoverAutomatically();
|
||||
|
||||
}
|
||||
|
||||
void PlayingWidget::SetImage(const QImage &image) {
|
||||
|
||||
active_ = true;
|
||||
|
||||
if (visible_) {
|
||||
// Cache the current pixmap so we can fade between them
|
||||
previous_track_ = QPixmap(size());
|
||||
previous_track_.fill(palette().background().color());
|
||||
previous_track_opacity_ = 1.0;
|
||||
QPainter p(&previous_track_);
|
||||
DrawContents(&p);
|
||||
p.end();
|
||||
}
|
||||
|
||||
original_ = image;
|
||||
|
||||
UpdateDetailsText();
|
||||
ScaleCover();
|
||||
|
||||
if (enabled_ == true) SetVisible(true);
|
||||
|
||||
// Were we waiting for this cover to load before we started fading?
|
||||
if (!previous_track_.isNull()) {
|
||||
fade_animation_->start();
|
||||
}
|
||||
}
|
||||
|
||||
void PlayingWidget::SetHeight(int height) {
|
||||
|
||||
setMaximumHeight(height);
|
||||
|
||||
}
|
||||
|
||||
void PlayingWidget::SetVisible(bool visible) {
|
||||
|
||||
if (visible == visible_) return;
|
||||
visible_ = visible;
|
||||
|
||||
show_hide_animation_->setDirection(visible ? QTimeLine::Forward : QTimeLine::Backward);
|
||||
show_hide_animation_->start();
|
||||
|
||||
}
|
||||
|
||||
void PlayingWidget::paintEvent(QPaintEvent *e) {
|
||||
|
||||
QPainter p(this);
|
||||
|
||||
DrawContents(&p);
|
||||
|
||||
// Draw the previous track's image if we're fading
|
||||
if (!previous_track_.isNull()) {
|
||||
p.setOpacity(previous_track_opacity_);
|
||||
p.drawPixmap(0, 0, previous_track_);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayingWidget::DrawContents(QPainter *p) {
|
||||
|
||||
switch (mode_) {
|
||||
case SmallSongDetails:
|
||||
// Draw the cover
|
||||
p->drawPixmap(0, 0, small_ideal_height_, small_ideal_height_, cover_);
|
||||
if (downloading_covers_) {
|
||||
p->drawPixmap(small_ideal_height_ - 18, 6, 16, 16, spinner_animation_->currentPixmap());
|
||||
}
|
||||
|
||||
// Draw the details
|
||||
p->translate(small_ideal_height_ + kPadding, 0);
|
||||
details_->drawContents(p);
|
||||
p->translate(-small_ideal_height_ - kPadding, 0);
|
||||
break;
|
||||
|
||||
case LargeSongDetails:
|
||||
// Work out how high the text is going to be
|
||||
const int text_height = details_->size().height();
|
||||
const int cover_size = fit_width_ ? width() : qMin(kMaxCoverSize, width());
|
||||
const int x_offset = (width() - cover_loader_options_.desired_height_) / 2;
|
||||
|
||||
if (!fit_width_) {
|
||||
// Draw the black background
|
||||
//p->fillRect(QRect(0, kTopBorder, width(), height() - kTopBorder), Qt::black);
|
||||
}
|
||||
|
||||
// Draw the cover
|
||||
p->drawPixmap(x_offset, kTopBorder, cover_size, cover_size, cover_);
|
||||
if (downloading_covers_) {
|
||||
p->drawPixmap(x_offset + 45, 35, 16, 16, spinner_animation_->currentPixmap());
|
||||
}
|
||||
|
||||
// Draw the text below
|
||||
p->translate(x_offset, height() - text_height);
|
||||
details_->drawContents(p);
|
||||
p->translate(-x_offset, -height() + text_height);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PlayingWidget::FadePreviousTrack(qreal value) {
|
||||
|
||||
previous_track_opacity_ = value;
|
||||
if (qFuzzyCompare(previous_track_opacity_, qreal(0.0))) {
|
||||
previous_track_ = QPixmap();
|
||||
}
|
||||
|
||||
update();
|
||||
|
||||
}
|
||||
|
||||
void PlayingWidget::SetMode(int mode) {
|
||||
|
||||
|
||||
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 += "<p>";
|
||||
break;
|
||||
case LargeSongDetails:
|
||||
details_->setTextWidth(cover_loader_options_.desired_height_);
|
||||
html += "<p align=center>";
|
||||
break;
|
||||
}
|
||||
|
||||
html += QString("%1<br/>%2<br/>%3").arg(song_.PrettyTitle().toHtmlEscaped(), song_.artist().toHtmlEscaped(), song_.album().toHtmlEscaped());
|
||||
|
||||
html += "</p>";
|
||||
details_->setHtml(html);
|
||||
|
||||
// if something spans multiple lines the height needs to change
|
||||
if (mode_ == LargeSongDetails) UpdateHeight();
|
||||
|
||||
}
|
||||
|
||||
void PlayingWidget::paintEvent(QPaintEvent *e) {
|
||||
|
||||
QPainter p(this);
|
||||
|
||||
DrawContents(&p);
|
||||
|
||||
// Draw the previous track's image if we're fading
|
||||
if (!pixmap_previous_track_.isNull()) {
|
||||
p.setOpacity(pixmap_previous_track_opacity_);
|
||||
p.drawPixmap(0, 0, pixmap_previous_track_);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayingWidget::DrawContents(QPainter *p) {
|
||||
|
||||
switch (mode_) {
|
||||
case SmallSongDetails:
|
||||
// Draw the cover
|
||||
p->drawPixmap(0, 0, small_ideal_height_, small_ideal_height_, pixmap_cover_);
|
||||
if (downloading_covers_) {
|
||||
p->drawPixmap(small_ideal_height_ - 18, 6, 16, 16, spinner_animation_->currentPixmap());
|
||||
}
|
||||
|
||||
// Draw the details
|
||||
p->translate(small_ideal_height_ + kPadding, 0);
|
||||
details_->drawContents(p);
|
||||
p->translate(-small_ideal_height_ - kPadding, 0);
|
||||
break;
|
||||
|
||||
case LargeSongDetails:
|
||||
// Work out how high the text is going to be
|
||||
const int text_height = details_->size().height();
|
||||
const int cover_size = fit_width_ ? width() : qMin(kMaxCoverSize, width());
|
||||
const int x_offset = (width() - cover_loader_options_.desired_height_) / 2;
|
||||
|
||||
// Draw the cover
|
||||
p->drawPixmap(x_offset, kTopBorder, cover_size, cover_size, pixmap_cover_);
|
||||
if (downloading_covers_) {
|
||||
p->drawPixmap(x_offset + 45, 35, 16, 16, spinner_animation_->currentPixmap());
|
||||
}
|
||||
|
||||
// Draw the text below
|
||||
p->translate(x_offset, height() - text_height);
|
||||
details_->drawContents(p);
|
||||
p->translate(-x_offset, -height() + text_height);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PlayingWidget::FadePreviousTrack(qreal value) {
|
||||
|
||||
if (!visible_) return;
|
||||
|
||||
pixmap_previous_track_opacity_ = value;
|
||||
if (qFuzzyCompare(pixmap_previous_track_opacity_, qreal(0.0))) {
|
||||
pixmap_previous_track_ = QPixmap();
|
||||
}
|
||||
|
||||
update();
|
||||
|
||||
}
|
||||
|
||||
void PlayingWidget::resizeEvent(QResizeEvent* e) {
|
||||
|
||||
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_);
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -32,6 +33,7 @@
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
#include <QPixmap>
|
||||
#include <QPainter>
|
||||
#include <QSize>
|
||||
#include <QSignalMapper>
|
||||
#include <QTextDocument>
|
||||
@ -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<QMovie> 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<QMovie> spinner_animation_;
|
||||
|
||||
void SetVisible(bool visible);
|
||||
void CreateModeAction(Mode mode, const QString &text, QActionGroup *group, QSignalMapper *mapper);
|
||||
void UpdateDetailsText();
|
||||
void UpdateHeight();
|
||||
void SetImage(const QImage &image);
|
||||
void DrawContents(QPainter *p);
|
||||
void ScaleCover();
|
||||
bool GetCoverAutomatically();
|
||||
|
||||
};
|
||||
|
||||
#endif // PLAYINGWIDGET_H
|
||||
|
@ -1,268 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QWidget>
|
||||
#include <QApplication>
|
||||
#include <QtConcurrentRun>
|
||||
#include <QDir>
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QStringBuilder>
|
||||
#include <QUrl>
|
||||
#include <QImage>
|
||||
#include <QPixmap>
|
||||
#include <QFrame>
|
||||
#include <QFuture>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
#include <QColor>
|
||||
#include <QBrush>
|
||||
#include <QPoint>
|
||||
#include <QRect>
|
||||
#include <QLabel>
|
||||
#include <QMenu>
|
||||
#include <QScrollArea>
|
||||
#include <QSizePolicy>
|
||||
#include <QTransform>
|
||||
#include <QDesktopWidget>
|
||||
#include <QLinearGradient>
|
||||
#include <QSettings>
|
||||
#include <QFlags>
|
||||
#include <QtDebug>
|
||||
#include <QtEvents>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "core/closure.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
#include "prettyimage.h"
|
||||
|
||||
const int PrettyImage::kTotalHeight = 200;
|
||||
const int PrettyImage::kReflectionHeight = 40;
|
||||
const int PrettyImage::kImageHeight = PrettyImage::kTotalHeight - PrettyImage::kReflectionHeight;
|
||||
|
||||
const int PrettyImage::kMaxImageWidth = 300;
|
||||
|
||||
const char *PrettyImage::kSettingsGroup = "PrettyImageView";
|
||||
|
||||
PrettyImage::PrettyImage(const QUrl &url, QNetworkAccessManager* network, QWidget* parent)
|
||||
: QWidget(parent),
|
||||
network_(network),
|
||||
state_(State_WaitingForLazyLoad),
|
||||
url_(url),
|
||||
menu_(nullptr) {
|
||||
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
|
||||
LazyLoad();
|
||||
}
|
||||
|
||||
void PrettyImage::LazyLoad() {
|
||||
|
||||
if (state_ != State_WaitingForLazyLoad) return;
|
||||
|
||||
// Start fetching the image
|
||||
QNetworkReply* reply = network_->get(QNetworkRequest(url_));
|
||||
RedirectFollower* follower = new RedirectFollower(reply);
|
||||
state_ = State_Fetching;
|
||||
NewClosure(follower, SIGNAL(finished()), this, SLOT(ImageFetched(RedirectFollower*)), follower);
|
||||
}
|
||||
|
||||
QSize PrettyImage::image_size() const {
|
||||
|
||||
if (state_ != State_Finished) return QSize(kImageHeight * 1.6, kImageHeight);
|
||||
|
||||
QSize ret = image_.size();
|
||||
ret.scale(kMaxImageWidth, kImageHeight, Qt::KeepAspectRatio);
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
QSize PrettyImage::sizeHint() const {
|
||||
return QSize(image_size().width(), kTotalHeight);
|
||||
}
|
||||
|
||||
void PrettyImage::ImageFetched(RedirectFollower* follower) {
|
||||
|
||||
follower->deleteLater();
|
||||
QNetworkReply* reply = follower->reply();
|
||||
reply->deleteLater();
|
||||
|
||||
QImage image = QImage::fromData(reply->readAll());
|
||||
if (image.isNull()) {
|
||||
qLog(Debug) << "Image failed to load" << reply->request().url() << reply->error();
|
||||
deleteLater();
|
||||
}
|
||||
else {
|
||||
state_ = State_CreatingThumbnail;
|
||||
image_ = image;
|
||||
|
||||
QFuture<QImage> future = QtConcurrent::run(image_, &QImage::scaled, image_size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
NewClosure(future, this, SLOT(ImageScaled(QFuture<QImage>)), future);
|
||||
}
|
||||
}
|
||||
|
||||
void PrettyImage::ImageScaled(QFuture<QImage> future) {
|
||||
|
||||
thumbnail_ = QPixmap::fromImage(future.result());
|
||||
state_ = State_Finished;
|
||||
|
||||
updateGeometry();
|
||||
update();
|
||||
emit Loaded();
|
||||
}
|
||||
|
||||
void PrettyImage::paintEvent(QPaintEvent* ) {
|
||||
|
||||
// Draw at the bottom of our area
|
||||
QRect image_rect(QPoint(0, 0), image_size());
|
||||
image_rect.moveBottom(kImageHeight);
|
||||
|
||||
QPainter p(this);
|
||||
|
||||
// Draw the main image
|
||||
DrawThumbnail(&p, image_rect);
|
||||
|
||||
// Draw the reflection
|
||||
// Figure out where to draw it
|
||||
QRect reflection_rect(image_rect);
|
||||
reflection_rect.moveTop(image_rect.bottom());
|
||||
|
||||
// Create the reflected pixmap
|
||||
QImage reflection(reflection_rect.size(), QImage::Format_ARGB32_Premultiplied);
|
||||
reflection.fill(palette().color(QPalette::Base).rgba());
|
||||
QPainter reflection_painter(&reflection);
|
||||
|
||||
// Set up the transformation
|
||||
QTransform transform;
|
||||
transform.scale(1.0, -1.0);
|
||||
transform.translate(0.0, -reflection_rect.height());
|
||||
reflection_painter.setTransform(transform);
|
||||
|
||||
QRect fade_rect(reflection.rect().bottomLeft() - QPoint(0, kReflectionHeight), reflection.rect().bottomRight());
|
||||
|
||||
// Draw the reflection into the buffer
|
||||
DrawThumbnail(&reflection_painter, reflection.rect());
|
||||
|
||||
// Make it fade out towards the bottom
|
||||
QLinearGradient fade_gradient(fade_rect.topLeft(), fade_rect.bottomLeft());
|
||||
fade_gradient.setColorAt(0.0, QColor(0, 0, 0, 0));
|
||||
fade_gradient.setColorAt(1.0, QColor(0, 0, 0, 128));
|
||||
|
||||
reflection_painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
|
||||
reflection_painter.fillRect(fade_rect, fade_gradient);
|
||||
|
||||
reflection_painter.end();
|
||||
|
||||
// Draw the reflection on the image
|
||||
p.drawImage(reflection_rect, reflection);
|
||||
|
||||
}
|
||||
|
||||
void PrettyImage::DrawThumbnail(QPainter* p, const QRect& rect) {
|
||||
|
||||
switch (state_) {
|
||||
case State_WaitingForLazyLoad:
|
||||
case State_Fetching:
|
||||
case State_CreatingThumbnail:
|
||||
p->setPen(palette().color(QPalette::Disabled, QPalette::Text));
|
||||
p->drawText(rect, Qt::AlignHCenter | Qt::AlignBottom, tr("Loading..."));
|
||||
break;
|
||||
|
||||
case State_Finished:
|
||||
p->drawPixmap(rect, thumbnail_);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PrettyImage::contextMenuEvent(QContextMenuEvent* e) {
|
||||
|
||||
if (e->pos().y() >= kImageHeight) return;
|
||||
|
||||
if (!menu_) {
|
||||
menu_ = new QMenu(this);
|
||||
menu_->addAction(IconLoader::Load("zoom-in"), tr("Show fullsize..."), this, SLOT(ShowFullsize()));
|
||||
menu_->addAction(IconLoader::Load("document-save"), tr("Save image") + "...", this, SLOT(SaveAs()));
|
||||
}
|
||||
|
||||
menu_->popup(e->globalPos());
|
||||
|
||||
}
|
||||
|
||||
void PrettyImage::ShowFullsize() {
|
||||
|
||||
// Work out how large to make the window, based on the size of the screen
|
||||
QRect desktop_rect(QApplication::desktop()->availableGeometry(this));
|
||||
QSize window_size(qMin(desktop_rect.width() - 20, image_.width()), qMin(desktop_rect.height() - 20, image_.height()));
|
||||
|
||||
// Create the window
|
||||
QScrollArea* window = new QScrollArea;
|
||||
window->setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
window->setWindowTitle(tr("Strawberry image viewer"));
|
||||
window->resize(window_size);
|
||||
|
||||
// Create the label that displays the image
|
||||
QLabel* label = new QLabel(window);
|
||||
label->setPixmap(QPixmap::fromImage(image_));
|
||||
|
||||
// Show the label in the window
|
||||
window->setWidget(label);
|
||||
window->setFrameShape(QFrame::NoFrame);
|
||||
window->show();
|
||||
|
||||
}
|
||||
|
||||
void PrettyImage::SaveAs() {
|
||||
|
||||
QString filename = QFileInfo(url_.path()).fileName();
|
||||
|
||||
if (filename.isEmpty()) filename = "artwork.jpg";
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
QString last_save_dir = s.value("last_save_dir", QDir::homePath()).toString();
|
||||
|
||||
QString path = last_save_dir.isEmpty() ? QDir::homePath() : last_save_dir;
|
||||
QFileInfo path_info(path);
|
||||
if (path_info.isDir()) {
|
||||
path += "/" + filename;
|
||||
}
|
||||
else {
|
||||
path = path_info.path() + "/" + filename;
|
||||
}
|
||||
|
||||
filename = QFileDialog::getSaveFileName(this, tr("Save image"), path);
|
||||
if (filename.isEmpty()) return;
|
||||
|
||||
image_.save(filename);
|
||||
|
||||
s.setValue("last_save_dir", last_save_dir);
|
||||
}
|
||||
|
@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PRETTYIMAGE_H
|
||||
#define PRETTYIMAGE_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QFuture>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QImage>
|
||||
#include <QPixmap>
|
||||
#include <QPainter>
|
||||
#include <QRect>
|
||||
#include <QSize>
|
||||
#include <QMenu>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QtEvents>
|
||||
|
||||
class QContextMenuEvent;
|
||||
class QPaintEvent;
|
||||
|
||||
class RedirectFollower;
|
||||
|
||||
class PrettyImage : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PrettyImage(const QUrl &url, QNetworkAccessManager *network, QWidget *parent = nullptr);
|
||||
|
||||
static const int kTotalHeight;
|
||||
static const int kReflectionHeight;
|
||||
static const int kImageHeight;
|
||||
|
||||
static const int kMaxImageWidth;
|
||||
|
||||
static const char *kSettingsGroup;
|
||||
|
||||
QSize sizeHint() const;
|
||||
QSize image_size() const;
|
||||
|
||||
signals:
|
||||
void Loaded();
|
||||
|
||||
public slots:
|
||||
void LazyLoad();
|
||||
void SaveAs();
|
||||
void ShowFullsize();
|
||||
|
||||
protected:
|
||||
void contextMenuEvent(QContextMenuEvent*);
|
||||
void paintEvent(QPaintEvent*);
|
||||
|
||||
private slots:
|
||||
void ImageFetched(RedirectFollower *reply);
|
||||
void ImageScaled(QFuture<QImage> future);
|
||||
|
||||
private:
|
||||
enum State {
|
||||
State_WaitingForLazyLoad,
|
||||
State_Fetching,
|
||||
State_CreatingThumbnail,
|
||||
State_Finished,
|
||||
};
|
||||
|
||||
void DrawThumbnail(QPainter *p, const QRect &rect);
|
||||
|
||||
private:
|
||||
QNetworkAccessManager *network_;
|
||||
State state_;
|
||||
QUrl url_;
|
||||
|
||||
QImage image_;
|
||||
QPixmap thumbnail_;
|
||||
|
||||
QMenu *menu_;
|
||||
QString last_save_dir_;
|
||||
};
|
||||
|
||||
#endif // PRETTYIMAGE_H
|
||||
|
@ -1,191 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QUrl>
|
||||
#include <QFrame>
|
||||
#include <QPoint>
|
||||
#include <QRect>
|
||||
#include <QAbstractSlider>
|
||||
#include <QEasingCurve>
|
||||
#include <QLayout>
|
||||
#include <QLayoutItem>
|
||||
#include <QBoxLayout>
|
||||
#include <QScrollBar>
|
||||
#include <QSizePolicy>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QScrollArea>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QtEvents>
|
||||
|
||||
#include "prettyimage.h"
|
||||
#include "prettyimageview.h"
|
||||
|
||||
PrettyImageView::PrettyImageView(QNetworkAccessManager* network, QWidget* parent)
|
||||
: QScrollArea(parent),
|
||||
network_(network),
|
||||
container_(new QWidget(this)),
|
||||
layout_(new QHBoxLayout(container_)),
|
||||
current_index_(-1),
|
||||
scroll_animation_(new QPropertyAnimation(horizontalScrollBar(), "value", this)),
|
||||
recursion_filter_(false)
|
||||
{
|
||||
|
||||
setWidget(container_);
|
||||
setWidgetResizable(true);
|
||||
setMinimumHeight(PrettyImage::kTotalHeight + 10);
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
setFrameShape(QFrame::NoFrame);
|
||||
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
|
||||
scroll_animation_->setDuration(250);
|
||||
scroll_animation_->setEasingCurve(QEasingCurve::InOutCubic);
|
||||
connect(horizontalScrollBar(), SIGNAL(sliderReleased()), SLOT(ScrollBarReleased()));
|
||||
connect(horizontalScrollBar(), SIGNAL(actionTriggered(int)), SLOT(ScrollBarAction(int)));
|
||||
|
||||
layout_->setSizeConstraint(QLayout::SetMinAndMaxSize);
|
||||
layout_->setContentsMargins(6, 6, 6, 6);
|
||||
layout_->setSpacing(6);
|
||||
layout_->addSpacing(200);
|
||||
layout_->addSpacing(200);
|
||||
|
||||
container_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
|
||||
}
|
||||
|
||||
bool PrettyImageView::eventFilter(QObject* obj, QEvent* event) {
|
||||
|
||||
// Work around infinite recursion in QScrollArea resizes.
|
||||
if (recursion_filter_) {
|
||||
return false;
|
||||
}
|
||||
recursion_filter_ = true;
|
||||
bool ret = QScrollArea::eventFilter(obj, event);
|
||||
recursion_filter_ = false;
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
void PrettyImageView::AddImage(const QUrl &url) {
|
||||
|
||||
PrettyImage *image = new PrettyImage(url, network_, container_);
|
||||
connect(image, SIGNAL(destroyed()), SLOT(ScrollToCurrent()));
|
||||
connect(image, SIGNAL(Loaded()), SLOT(ScrollToCurrent()));
|
||||
|
||||
layout_->insertWidget(layout_->count() - 1, image);
|
||||
if (current_index_ == -1) ScrollTo(0);
|
||||
|
||||
}
|
||||
|
||||
void PrettyImageView::mouseReleaseEvent(QMouseEvent* e) {
|
||||
|
||||
// Find the image that was clicked on
|
||||
QWidget* widget = container_->childAt(container_->mapFrom(this, e->pos()));
|
||||
if (!widget) return;
|
||||
|
||||
// Get the index of that image
|
||||
const int index = layout_->indexOf(widget) - 1;
|
||||
if (index == -1) return;
|
||||
|
||||
if (index == current_index_) {
|
||||
// Show the image fullsize
|
||||
PrettyImage* pretty_image = qobject_cast<PrettyImage*>(widget);
|
||||
if (pretty_image) {
|
||||
pretty_image->ShowFullsize();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Scroll to the image
|
||||
ScrollTo(index);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PrettyImageView::ScrollTo(int index, bool smooth) {
|
||||
|
||||
current_index_ = qBound(0, index, layout_->count() - 3);
|
||||
const int layout_index = current_index_ + 1;
|
||||
|
||||
const QWidget* target_widget = layout_->itemAt(layout_index)->widget();
|
||||
if (!target_widget) return;
|
||||
|
||||
const int current_x = horizontalScrollBar()->value();
|
||||
const int target_x = target_widget->geometry().center().x() - width() / 2;
|
||||
|
||||
if (current_x == target_x) return;
|
||||
|
||||
if (smooth) {
|
||||
scroll_animation_->setStartValue(current_x);
|
||||
scroll_animation_->setEndValue(target_x);
|
||||
scroll_animation_->start();
|
||||
}
|
||||
else {
|
||||
scroll_animation_->stop();
|
||||
horizontalScrollBar()->setValue(target_x);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PrettyImageView::ScrollToCurrent() {
|
||||
ScrollTo(current_index_);
|
||||
}
|
||||
|
||||
void PrettyImageView::ScrollBarReleased() {
|
||||
// Find the nearest widget to where the scroll bar was released
|
||||
const int current_x = horizontalScrollBar()->value() + width() / 2;
|
||||
int layout_index = 1;
|
||||
for (; layout_index<layout_->count() - 1 ; ++layout_index) {
|
||||
const QWidget* widget = layout_->itemAt(layout_index)->widget();
|
||||
if (widget && widget->geometry().right() > current_x) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ScrollTo(layout_index - 1);
|
||||
}
|
||||
|
||||
void PrettyImageView::ScrollBarAction(int action) {
|
||||
switch (action) {
|
||||
case QAbstractSlider::SliderSingleStepAdd:
|
||||
case QAbstractSlider::SliderPageStepAdd:
|
||||
ScrollTo(current_index_ + 1);
|
||||
break;
|
||||
|
||||
case QAbstractSlider::SliderSingleStepSub:
|
||||
case QAbstractSlider::SliderPageStepSub:
|
||||
ScrollTo(current_index_ - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PrettyImageView::resizeEvent(QResizeEvent* e) {
|
||||
QScrollArea::resizeEvent(e);
|
||||
ScrollTo(current_index_, false);
|
||||
}
|
||||
|
||||
void PrettyImageView::wheelEvent(QWheelEvent* e) {
|
||||
const int d = e->delta() > 0 ? -1 : 1;
|
||||
ScrollTo(current_index_ + d, true);
|
||||
}
|
||||
|
@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PRETTYIMAGEVIEW_H
|
||||
#define PRETTYIMAGEVIEW_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QScrollArea>
|
||||
#include <QBoxLayout>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QtEvents>
|
||||
|
||||
class QEvent;
|
||||
class QMouseEvent;
|
||||
class QResizeEvent;
|
||||
class QWheelEvent;
|
||||
|
||||
class PrettyImageView : public QScrollArea {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PrettyImageView(QNetworkAccessManager *network, QWidget *parent = nullptr);
|
||||
|
||||
static const char *kSettingsGroup;
|
||||
|
||||
public slots:
|
||||
void AddImage(const QUrl& url);
|
||||
|
||||
protected:
|
||||
void mouseReleaseEvent(QMouseEvent*);
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
void wheelEvent(QWheelEvent *e);
|
||||
|
||||
private slots:
|
||||
void ScrollBarReleased();
|
||||
void ScrollBarAction(int action);
|
||||
void ScrollTo(int index, bool smooth = true);
|
||||
void ScrollToCurrent();
|
||||
|
||||
private:
|
||||
bool eventFilter(QObject*, QEvent*);
|
||||
|
||||
QNetworkAccessManager *network_;
|
||||
|
||||
QWidget *container_;
|
||||
QHBoxLayout *layout_;
|
||||
|
||||
int current_index_;
|
||||
QPropertyAnimation *scroll_animation_;
|
||||
|
||||
bool recursion_filter_;
|
||||
};
|
||||
|
||||
#endif // PRETTYIMAGEVIEW_H
|
||||
|
@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QAbstractItemModel>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QApplication>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QStringBuilder>
|
||||
#include <QPainter>
|
||||
#include <QStyle>
|
||||
#include <QRect>
|
||||
#include <QStyleOptionProgressBar>
|
||||
|
||||
#include "progressitemdelegate.h"
|
||||
|
||||
ProgressItemDelegate::ProgressItemDelegate(QObject *parent)
|
||||
: QStyledItemDelegate(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void ProgressItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||
|
||||
bool ok = false;
|
||||
int progress = index.data().toInt(&ok);
|
||||
|
||||
if (ok) {
|
||||
QStyleOptionProgressBar opt;
|
||||
opt.rect = option.rect;
|
||||
opt.minimum = 0;
|
||||
opt.maximum = 100;
|
||||
opt.progress = progress;
|
||||
opt.text = QString::number(progress) + "%";
|
||||
opt.textVisible = true;
|
||||
|
||||
QApplication::style()->drawControl(QStyle::CE_ProgressBar, &opt, painter);
|
||||
}
|
||||
else {
|
||||
QStyledItemDelegate::paint(painter, option, index);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,165 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QWidget>
|
||||
#include <QPixmap>
|
||||
#include <QFrame>
|
||||
#include <QPainter>
|
||||
#include <QRect>
|
||||
#include <QPoint>
|
||||
#include <QSize>
|
||||
#include <QSizePolicy>
|
||||
#include <QStyle>
|
||||
#include <QStyleOption>
|
||||
#include <QStylePainter>
|
||||
#include <QtEvents>
|
||||
|
||||
#include "ratingwidget.h"
|
||||
|
||||
const int RatingPainter::kStarCount;
|
||||
const int RatingPainter::kStarSize;
|
||||
|
||||
RatingPainter::RatingPainter() {
|
||||
|
||||
// Load the base pixmaps
|
||||
QPixmap on(":/icons/64x64/star.png");
|
||||
QPixmap off(":/icons/64x64/star-grey.png");
|
||||
|
||||
// Generate the 10 states, better to do it now than on the fly
|
||||
for (int i = 0; i < kStarCount * 2 + 1; ++i) {
|
||||
const float rating = float(i) / 2.0;
|
||||
|
||||
// Clear the pixmap
|
||||
stars_[i] = QPixmap(kStarSize * kStarCount, kStarSize);
|
||||
stars_[i].fill(Qt::transparent);
|
||||
QPainter p(&stars_[i]);
|
||||
|
||||
// Draw the stars
|
||||
int x = 0;
|
||||
for (int i = 0; i < kStarCount; ++i, x += kStarSize) {
|
||||
const QRect rect(x, 0, kStarSize, kStarSize);
|
||||
|
||||
if (rating - 0.25 <= i) {
|
||||
// Totally empty
|
||||
p.drawPixmap(rect, off);
|
||||
} else if (rating - 0.75 <= i) {
|
||||
// Half full
|
||||
const QRect target_left(rect.x(), rect.y(), kStarSize/2, kStarSize);
|
||||
const QRect target_right(rect.x() + kStarSize/2, rect.y(), kStarSize/2, kStarSize);
|
||||
const QRect source_left(0, 0, kStarSize/2, kStarSize);
|
||||
const QRect source_right(kStarSize/2, 0, kStarSize/2, kStarSize);
|
||||
p.drawPixmap(target_left, on, source_left);
|
||||
p.drawPixmap(target_right, off, source_right);
|
||||
}
|
||||
else {
|
||||
// Totally full
|
||||
p.drawPixmap(rect, on);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QRect RatingPainter::Contents(const QRect& rect) {
|
||||
const int width = kStarSize * kStarCount;
|
||||
const int x = rect.x() + (rect.width() - width) / 2;
|
||||
|
||||
return QRect(x, rect.y(), width, rect.height());
|
||||
}
|
||||
|
||||
double RatingPainter::RatingForPos(const QPoint& pos, const QRect& rect) {
|
||||
const QRect contents = Contents(rect);
|
||||
const double raw = double(pos.x() - contents.left()) / contents.width();
|
||||
|
||||
// Round to the nearest 0.1
|
||||
return double(int(raw * kStarCount * 2 + 0.5)) / (kStarCount * 2);
|
||||
}
|
||||
|
||||
void RatingPainter::Paint(QPainter* painter, const QRect& rect, float rating) const {
|
||||
|
||||
QSize size(qMin(kStarSize*kStarCount, rect.width()), qMin(kStarSize, rect.height()));
|
||||
QPoint pos(rect.center() - QPoint(size.width() / 2, size.height() / 2));
|
||||
|
||||
rating *= kStarCount;
|
||||
|
||||
// Draw the stars
|
||||
const int star = qBound(0, int(rating*2.0 + 0.5), kStarCount*2);
|
||||
painter->drawPixmap(QRect(pos, size), stars_[star], QRect(QPoint(0,0), size));
|
||||
|
||||
}
|
||||
|
||||
|
||||
RatingWidget::RatingWidget(QWidget* parent)
|
||||
: QWidget(parent),
|
||||
rating_(0.0),
|
||||
hover_rating_(-1.0)
|
||||
{
|
||||
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
QSize RatingWidget::sizeHint() const {
|
||||
const int frame_width = 1 + style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
|
||||
return QSize(RatingPainter::kStarSize * (RatingPainter::kStarCount+2) + frame_width*2, RatingPainter::kStarSize + frame_width*2);
|
||||
}
|
||||
|
||||
void RatingWidget::set_rating(float rating) {
|
||||
rating_ = rating;
|
||||
update();
|
||||
}
|
||||
|
||||
void RatingWidget::paintEvent(QPaintEvent* e) {
|
||||
|
||||
QStylePainter p(this);
|
||||
|
||||
// Draw the background
|
||||
QStyleOptionFrameV3 opt;
|
||||
opt.initFrom(this);
|
||||
opt.state |= QStyle::State_Sunken;
|
||||
opt.frameShape = QFrame::StyledPanel;
|
||||
opt.lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this);
|
||||
opt.midLineWidth = 0;
|
||||
|
||||
p.drawPrimitive(QStyle::PE_PanelLineEdit, opt);
|
||||
|
||||
// Draw the stars
|
||||
painter_.Paint(&p, rect(), hover_rating_ == -1.0 ? rating_ : hover_rating_);
|
||||
|
||||
}
|
||||
|
||||
void RatingWidget::mousePressEvent(QMouseEvent* e) {
|
||||
rating_ = RatingPainter::RatingForPos(e->pos(), rect());
|
||||
emit RatingChanged(rating_);
|
||||
}
|
||||
|
||||
void RatingWidget::mouseMoveEvent(QMouseEvent* e) {
|
||||
hover_rating_ = RatingPainter::RatingForPos(e->pos(), rect());
|
||||
update();
|
||||
}
|
||||
|
||||
void RatingWidget::leaveEvent(QEvent*) {
|
||||
hover_rating_ = -1.0;
|
||||
update();
|
||||
}
|
||||
|
@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef RATINGWIDGET_H
|
||||
#define RATINGWIDGET_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QString>
|
||||
#include <QPixmap>
|
||||
#include <QPainter>
|
||||
#include <QRect>
|
||||
#include <QSize>
|
||||
#include <QPoint>
|
||||
#include <QtEvents>
|
||||
|
||||
class QEvent;
|
||||
class QMouseEvent;
|
||||
class QPaintEvent;
|
||||
|
||||
class RatingPainter {
|
||||
public:
|
||||
RatingPainter();
|
||||
|
||||
static const int kStarCount = 5;
|
||||
static const int kStarSize = 15;
|
||||
static QRect Contents(const QRect& rect);
|
||||
static double RatingForPos(const QPoint& pos, const QRect& rect);
|
||||
|
||||
void Paint(QPainter *painter, const QRect& rect, float rating) const;
|
||||
|
||||
private:
|
||||
QPixmap stars_[kStarCount*2+1];
|
||||
};
|
||||
|
||||
class RatingWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(float rating READ rating WRITE set_rating);
|
||||
|
||||
public:
|
||||
RatingWidget(QWidget *parent = nullptr);
|
||||
|
||||
QSize sizeHint() const;
|
||||
|
||||
float rating() const { return rating_; }
|
||||
void set_rating(float rating);
|
||||
|
||||
signals:
|
||||
void RatingChanged(float rating);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent*);
|
||||
void mousePressEvent(QMouseEvent *e);
|
||||
void mouseMoveEvent(QMouseEvent *e);
|
||||
void leaveEvent(QEvent*);
|
||||
|
||||
private:
|
||||
RatingPainter painter_;
|
||||
float rating_;
|
||||
float hover_rating_;
|
||||
};
|
||||
|
||||
#endif // RATINGWIDGET_H
|
||||
|
@ -1,582 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QIODevice>
|
||||
#include <QFile>
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
#include <QPixmap>
|
||||
#include <QMovie>
|
||||
#include <QAction>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
#include <QBrush>
|
||||
#include <QLabel>
|
||||
#include <QLayout>
|
||||
#include <QMenu>
|
||||
#include <QScrollArea>
|
||||
#include <QBoxLayout>
|
||||
#include <QSettings>
|
||||
#include <QSizePolicy>
|
||||
#include <QTimeLine>
|
||||
#include <QtEvents>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/player.h"
|
||||
#include "core/song.h"
|
||||
#include "core/utilities.h"
|
||||
#include "engine/engine_fwd.h"
|
||||
#include "engine/enginebase.h"
|
||||
#include "engine/enginetype.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectionquery.h"
|
||||
#include "collection/collectionview.h"
|
||||
#include "collection/collectionviewcontainer.h"
|
||||
#include "covermanager/albumcoverchoicecontroller.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "covermanager/currentartloader.h"
|
||||
#include "statusview.h"
|
||||
|
||||
const char *StatusView::kSettingsGroup = "StatusView";
|
||||
|
||||
StatusView::StatusView(CollectionViewContainer *collectionviewcontainer, QWidget *parent) :
|
||||
|
||||
QWidget(parent),
|
||||
layout_(new QVBoxLayout),
|
||||
scrollarea_(new QScrollArea),
|
||||
container_layout_(new QVBoxLayout),
|
||||
container_widget_(new QWidget),
|
||||
|
||||
widget_stopped_ (nullptr),
|
||||
widget_playing_ (nullptr),
|
||||
layout_playing_(nullptr),
|
||||
layout_stopped_(nullptr),
|
||||
label_stopped_top_ (nullptr),
|
||||
label_stopped_logo_(nullptr),
|
||||
label_stopped_text_(nullptr),
|
||||
label_playing_top_(nullptr),
|
||||
label_playing_album_(nullptr),
|
||||
label_playing_text_(nullptr),
|
||||
|
||||
album_cover_choice_controller_(new AlbumCoverChoiceController(this)),
|
||||
fade_animation_(new QTimeLine(1000, this)),
|
||||
image_blank_(""),
|
||||
image_nosong_(":/pictures/strawberry.png"),
|
||||
widgetstate_(None),
|
||||
menu_(new QMenu(this))
|
||||
{
|
||||
|
||||
collectionview_ = collectionviewcontainer->view();
|
||||
connect(collectionview_, SIGNAL(TotalSongCountUpdated_()), this, SLOT(UpdateNoSong()));
|
||||
connect(collectionview_, SIGNAL(TotalArtistCountUpdated_()), this, SLOT(UpdateNoSong()));
|
||||
connect(collectionview_, SIGNAL(TotalAlbumCountUpdated_()), this, SLOT(UpdateNoSong()));
|
||||
|
||||
connect(fade_animation_, SIGNAL(valueChanged(qreal)), SLOT(FadePreviousTrack(qreal)));
|
||||
fade_animation_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0
|
||||
|
||||
cover_loader_options_.desired_height_ = 300;
|
||||
cover_loader_options_.pad_output_image_ = true;
|
||||
cover_loader_options_.scale_output_image_ = true;
|
||||
pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_blank_));
|
||||
|
||||
CreateWidget();
|
||||
NoSongWidget();
|
||||
NoSong();
|
||||
AddActions();
|
||||
|
||||
// Load settings
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
album_cover_choice_controller_->search_cover_auto_action()->setChecked(s.value("search_for_cover_auto", true).toBool());
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
StatusView::~StatusView() {
|
||||
}
|
||||
|
||||
void StatusView::AddActions() {
|
||||
|
||||
QList<QAction*> actions = album_cover_choice_controller_->GetAllActions();
|
||||
|
||||
// Here we add the search automatically action, too!
|
||||
actions.append(album_cover_choice_controller_->search_cover_auto_action());
|
||||
|
||||
connect(album_cover_choice_controller_->cover_from_file_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromFile()));
|
||||
connect(album_cover_choice_controller_->cover_to_file_action(), SIGNAL(triggered()), this, SLOT(SaveCoverToFile()));
|
||||
connect(album_cover_choice_controller_->cover_from_url_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromURL()));
|
||||
connect(album_cover_choice_controller_->search_for_cover_action(), SIGNAL(triggered()), this, SLOT(SearchForCover()));
|
||||
connect(album_cover_choice_controller_->unset_cover_action(), SIGNAL(triggered()), this, SLOT(UnsetCover()));
|
||||
connect(album_cover_choice_controller_->show_cover_action(), SIGNAL(triggered()), this, SLOT(ShowCover()));
|
||||
connect(album_cover_choice_controller_->search_cover_auto_action(), SIGNAL(triggered()), this, SLOT(SearchCoverAutomatically()));
|
||||
|
||||
menu_->addActions(actions);
|
||||
menu_->addSeparator();
|
||||
|
||||
}
|
||||
|
||||
void StatusView::CreateWidget() {
|
||||
|
||||
setLayout(layout_);
|
||||
|
||||
layout_->setSizeConstraint(QLayout::SetMinAndMaxSize);
|
||||
layout_->setContentsMargins(0, 0, 0, 0);
|
||||
layout_->setSpacing(6);
|
||||
layout_->addWidget(scrollarea_);
|
||||
|
||||
scrollarea_->setWidget(container_widget_);
|
||||
scrollarea_->setWidgetResizable(true);
|
||||
scrollarea_->setStyleSheet("background-color: white;");
|
||||
scrollarea_->setVisible(true);
|
||||
|
||||
container_widget_->setLayout(container_layout_);
|
||||
container_widget_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
container_widget_->setBackgroundRole(QPalette::Base);
|
||||
|
||||
container_layout_->setSizeConstraint(QLayout::SetMinAndMaxSize);
|
||||
container_layout_->setContentsMargins(0, 0, 0, 0);
|
||||
container_layout_->setSpacing(6);
|
||||
container_layout_->addStretch();
|
||||
|
||||
}
|
||||
|
||||
void StatusView::SetApplication(Application *app) {
|
||||
|
||||
app_ = app;
|
||||
|
||||
album_cover_choice_controller_->SetApplication(app_);
|
||||
connect(app_->current_art_loader(), SIGNAL(ArtLoaded(Song, QString, QImage)), SLOT(AlbumArtLoaded(Song, QString, QImage)));
|
||||
|
||||
}
|
||||
|
||||
void StatusView::NoSongWidget() {
|
||||
|
||||
if (widgetstate_ == Playing) {
|
||||
container_layout_->removeWidget(widget_playing_);
|
||||
widget_playing_->setVisible(false);
|
||||
delete label_playing_top_;
|
||||
delete label_playing_album_;
|
||||
delete label_playing_text_;
|
||||
delete layout_playing_;
|
||||
delete widget_playing_;
|
||||
}
|
||||
widget_stopped_ = new QWidget;
|
||||
layout_stopped_ = new QVBoxLayout;
|
||||
label_stopped_top_ = new QLabel;
|
||||
label_stopped_logo_ = new QLabel;
|
||||
label_stopped_text_ = new QLabel;
|
||||
|
||||
layout_stopped_->addWidget(label_stopped_top_);
|
||||
layout_stopped_->addWidget(label_stopped_logo_);
|
||||
layout_stopped_->addWidget(label_stopped_text_);
|
||||
layout_stopped_->addStretch();
|
||||
|
||||
label_stopped_top_->setFixedHeight(40);
|
||||
label_stopped_top_->setFixedWidth(300);
|
||||
label_stopped_top_->setAlignment(Qt::AlignTop|Qt::AlignLeft);
|
||||
|
||||
widget_stopped_->setLayout(layout_stopped_);
|
||||
container_layout_->insertWidget(0, widget_stopped_);
|
||||
widget_stopped_->setVisible(true);
|
||||
|
||||
widgetstate_ = Stopped;
|
||||
|
||||
}
|
||||
|
||||
void StatusView::SongWidget() {
|
||||
|
||||
if (widgetstate_ == Stopped) {
|
||||
container_layout_->removeWidget(widget_stopped_);
|
||||
widget_stopped_->setVisible(false);
|
||||
delete label_stopped_top_ ;
|
||||
delete label_stopped_logo_;
|
||||
delete label_stopped_text_;
|
||||
delete layout_stopped_;
|
||||
delete widget_stopped_;
|
||||
}
|
||||
widget_playing_ = new QWidget;
|
||||
widget_playing_ = new QWidget;
|
||||
layout_playing_ = new QVBoxLayout;
|
||||
label_playing_top_ = new QLabel;
|
||||
label_playing_album_ = new QLabel;
|
||||
label_playing_text_ = new QLabel;
|
||||
|
||||
layout_playing_->addWidget(label_playing_top_);
|
||||
layout_playing_->addWidget(label_playing_album_);
|
||||
layout_playing_->addWidget(label_playing_text_);
|
||||
layout_playing_->addStretch();
|
||||
|
||||
label_playing_top_->setAlignment(Qt::AlignTop|Qt::AlignLeft);
|
||||
label_playing_top_->setFixedHeight(40);
|
||||
label_playing_top_->setFixedWidth(300);
|
||||
label_playing_top_->setWordWrap(true);
|
||||
|
||||
label_playing_text_->setAlignment(Qt::AlignTop|Qt::AlignLeft);
|
||||
label_playing_text_->setFixedWidth(300);
|
||||
label_playing_text_->setWordWrap(true);
|
||||
|
||||
label_playing_album_->setFixedHeight(300);
|
||||
label_playing_album_->setFixedWidth(300);
|
||||
label_playing_album_->setStyleSheet("background-color: transparent;");
|
||||
label_playing_album_->installEventFilter(this);
|
||||
|
||||
widget_playing_->setLayout(layout_playing_);
|
||||
container_layout_->insertWidget(0, widget_playing_);
|
||||
|
||||
QFile stylesheet(":/style/statusview.css");
|
||||
if (stylesheet.open(QIODevice::ReadOnly)) {
|
||||
setStyleSheet(QString::fromLatin1(stylesheet.readAll()));
|
||||
label_playing_text_->setStyleSheet(QString::fromLatin1(stylesheet.readAll()));
|
||||
}
|
||||
|
||||
widget_playing_->setVisible(true);
|
||||
|
||||
widgetstate_ = Playing;
|
||||
|
||||
}
|
||||
|
||||
void StatusView::SwitchWidgets(WidgetState state) {
|
||||
|
||||
if (widgetstate_ == None) NoSongWidget();
|
||||
|
||||
if ((state == Stopped) && (widgetstate_ != Stopped)) {
|
||||
NoSongWidget();
|
||||
}
|
||||
if ((widgetstate_ != Playing) && (state == Playing)) {
|
||||
SongWidget();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StatusView::UpdateSong() {
|
||||
|
||||
SwitchWidgets(Playing);
|
||||
|
||||
const QueryOptions opt;
|
||||
CollectionBackend::AlbumList albumlist;
|
||||
|
||||
label_playing_top_->setText("");
|
||||
label_playing_text_->setText("");
|
||||
|
||||
QString html;
|
||||
QString html_albums;
|
||||
html += QString("<b>%1 - %2</b><br/>%3").arg(metadata_.PrettyTitle().toHtmlEscaped(), metadata_.artist().toHtmlEscaped(), metadata_.album().toHtmlEscaped());
|
||||
label_playing_top_->setText(html);
|
||||
|
||||
html = "";
|
||||
|
||||
html += QString("Filetype: %1<br />\n").arg(metadata_.TextForFiletype());
|
||||
html += QString("Length: %1<br />\n").arg(Utilities::PrettyTimeNanosec(metadata_.length_nanosec()));
|
||||
html += QString("Bitrate: %1 kbps<br />\n").arg(metadata_.bitrate());
|
||||
html += QString("Samplerate: %1 hz / %2 bit<br />\n").arg(metadata_.samplerate()).arg(metadata_.bitdepth());
|
||||
|
||||
if (app_->player()->engine() && app_->player()->engine()->type() != Engine::EngineType::None) {
|
||||
html += QString("<br />");
|
||||
html += QString("Engine: %1<br />").arg(EngineDescription(app_->player()->engine()->type()));
|
||||
}
|
||||
|
||||
html += QString("<br />");
|
||||
|
||||
html_albums += QString("<b>Albums by %1:</b>").arg( metadata_.artist().toHtmlEscaped() );
|
||||
|
||||
albumlist = app_->collection_backend()->GetAlbumsByArtist(metadata_.artist(), opt);
|
||||
|
||||
html_albums += QString("<ul>");
|
||||
int i=0;
|
||||
for (CollectionBackend::Album album : albumlist) {
|
||||
i++;
|
||||
html_albums += QString("<li>%1</li>\n").arg(album.album_name.toHtmlEscaped());
|
||||
}
|
||||
|
||||
html_albums += QString("</ul>");
|
||||
html_albums += QString("");
|
||||
|
||||
if (i > 1) html += html_albums;
|
||||
|
||||
label_playing_text_->setText(html);
|
||||
|
||||
}
|
||||
|
||||
void StatusView::NoSong() {
|
||||
|
||||
QString html;
|
||||
QImage image_logo(":/pictures/strawberry.png");
|
||||
QImage image_logo_scaled = image_logo.scaled(300, 300, Qt::KeepAspectRatio);
|
||||
QPixmap pixmap_logo(QPixmap::fromImage(image_logo_scaled));
|
||||
|
||||
SwitchWidgets(Stopped);
|
||||
|
||||
label_stopped_top_->setText("<b>No Track Playing</b>");
|
||||
label_stopped_logo_->setPixmap(pixmap_logo);
|
||||
|
||||
html += QString(
|
||||
"<html>\n"
|
||||
"<head>\n"
|
||||
"<style type=\"text/css\">:/style/statusview.css</style>\n"
|
||||
"</head>\n"
|
||||
"<body>\n"
|
||||
"%1 songs<br />\n"
|
||||
"%2 artists<br />\n"
|
||||
"%3 albums<br />\n"
|
||||
"</body>\n"
|
||||
"</html>\n"
|
||||
)
|
||||
.arg(collectionview_->TotalSongs())
|
||||
.arg(collectionview_->TotalArtists())
|
||||
.arg(collectionview_->TotalAlbums())
|
||||
;
|
||||
|
||||
label_stopped_text_->setText(html);
|
||||
|
||||
}
|
||||
|
||||
void StatusView::SongChanged(const Song &song) {
|
||||
|
||||
stopped_ = false;
|
||||
metadata_ = song;
|
||||
|
||||
UpdateSong();
|
||||
|
||||
update();
|
||||
|
||||
}
|
||||
|
||||
void StatusView::SongFinished() {
|
||||
|
||||
stopped_ = true;
|
||||
SetImage(image_blank_);
|
||||
|
||||
}
|
||||
|
||||
bool StatusView::eventFilter(QObject *object, QEvent *event) {
|
||||
|
||||
switch(event->type()) {
|
||||
case QEvent::Paint:{
|
||||
handlePaintEvent(object, event);
|
||||
}
|
||||
default:{
|
||||
return QObject::eventFilter(object, event);
|
||||
}
|
||||
}
|
||||
|
||||
return(true);
|
||||
|
||||
}
|
||||
|
||||
void StatusView::handlePaintEvent(QObject *object, QEvent *event) {
|
||||
|
||||
if (object == label_playing_album_) {
|
||||
paintEvent_album(event);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
void StatusView::paintEvent_album(QEvent *event) {
|
||||
|
||||
QPainter p(label_playing_album_);
|
||||
|
||||
DrawImage(&p);
|
||||
|
||||
// Draw the previous track's image if we're fading
|
||||
if (!pixmap_previous_.isNull()) {
|
||||
p.setOpacity(pixmap_previous_opacity_);
|
||||
p.drawPixmap(0, 0, pixmap_previous_);
|
||||
}
|
||||
}
|
||||
|
||||
void StatusView::DrawImage(QPainter *p) {
|
||||
|
||||
p->drawPixmap(0, 0, 300, 300, pixmap_current_);
|
||||
if ((downloading_covers_) && (spinner_animation_ != nullptr)) {
|
||||
p->drawPixmap(50, 50, 16, 16, spinner_animation_->currentPixmap());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StatusView::FadePreviousTrack(qreal value) {
|
||||
|
||||
pixmap_previous_opacity_ = value;
|
||||
if (qFuzzyCompare(pixmap_previous_opacity_, qreal(0.0))) {
|
||||
pixmap_previous_ = QPixmap();
|
||||
}
|
||||
|
||||
update();
|
||||
|
||||
if ((value == 0) && (stopped_ == true)) {
|
||||
SwitchWidgets(Stopped);
|
||||
NoSong();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StatusView::contextMenuEvent(QContextMenuEvent *e) {
|
||||
|
||||
// show the menu
|
||||
if (menu_ && widgetstate_ == Playing) menu_->popup(mapToGlobal(e->pos()));
|
||||
}
|
||||
|
||||
void StatusView::mouseReleaseEvent(QMouseEvent *) {
|
||||
}
|
||||
|
||||
void StatusView::dragEnterEvent(QDragEnterEvent *e) {
|
||||
|
||||
QWidget::dragEnterEvent(e);
|
||||
|
||||
}
|
||||
|
||||
void StatusView::dropEvent(QDropEvent *e) {
|
||||
|
||||
QWidget::dropEvent(e);
|
||||
|
||||
}
|
||||
|
||||
void StatusView::ScaleCover() {
|
||||
|
||||
pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, original_));
|
||||
update();
|
||||
|
||||
}
|
||||
|
||||
void StatusView::AlbumArtLoaded(const Song &metadata, const QString&, const QImage &image) {
|
||||
|
||||
SwitchWidgets(Playing);
|
||||
|
||||
label_playing_album_->clear();
|
||||
|
||||
metadata_ = metadata;
|
||||
downloading_covers_ = false;
|
||||
|
||||
SetImage(image);
|
||||
|
||||
// Search for cover automatically?
|
||||
GetCoverAutomatically();
|
||||
|
||||
}
|
||||
|
||||
void StatusView::SetImage(const QImage &image) {
|
||||
|
||||
// Cache the current pixmap so we can fade between them
|
||||
pixmap_previous_ = QPixmap(size());
|
||||
pixmap_previous_.fill(palette().background().color());
|
||||
pixmap_previous_opacity_ = 1.0;
|
||||
|
||||
QPainter p(&pixmap_previous_);
|
||||
DrawImage(&p);
|
||||
p.end();
|
||||
|
||||
original_ = image;
|
||||
|
||||
ScaleCover();
|
||||
|
||||
// Were we waiting for this cover to load before we started fading?
|
||||
if (!pixmap_previous_.isNull() && fade_animation_) {
|
||||
fade_animation_->start();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool StatusView::GetCoverAutomatically() {
|
||||
|
||||
SwitchWidgets(Playing);
|
||||
|
||||
// Search for cover automatically?
|
||||
bool search =
|
||||
!metadata_.has_manually_unset_cover() &&
|
||||
metadata_.art_automatic().isEmpty() &&
|
||||
metadata_.art_manual().isEmpty() &&
|
||||
!metadata_.artist().isEmpty() &&
|
||||
!metadata_.album().isEmpty();
|
||||
|
||||
if (search) {
|
||||
qLog(Debug) << "GetCoverAutomatically";
|
||||
downloading_covers_ = true;
|
||||
album_cover_choice_controller_->SearchCoverAutomatically(metadata_);
|
||||
|
||||
// Show a spinner animation
|
||||
spinner_animation_.reset(new QMovie(":/pictures/spinner.gif", QByteArray(), this));
|
||||
connect(spinner_animation_.get(), SIGNAL(updated(const QRect&)), SLOT(update()));
|
||||
spinner_animation_->start();
|
||||
update();
|
||||
}
|
||||
|
||||
return search;
|
||||
|
||||
}
|
||||
|
||||
void StatusView::AutomaticCoverSearchDone() {
|
||||
|
||||
downloading_covers_ = false;
|
||||
spinner_animation_.reset();
|
||||
update();
|
||||
|
||||
}
|
||||
|
||||
void StatusView::UpdateNoSong() {
|
||||
|
||||
if (widgetstate_ == Playing) return;
|
||||
|
||||
NoSong();
|
||||
|
||||
}
|
||||
|
||||
void StatusView::LoadCoverFromFile() {
|
||||
album_cover_choice_controller_->LoadCoverFromFile(&metadata_);
|
||||
}
|
||||
|
||||
void StatusView::LoadCoverFromURL() {
|
||||
album_cover_choice_controller_->LoadCoverFromURL(&metadata_);
|
||||
}
|
||||
|
||||
void StatusView::SearchForCover() {
|
||||
album_cover_choice_controller_->SearchForCover(&metadata_);
|
||||
}
|
||||
|
||||
void StatusView::SaveCoverToFile() {
|
||||
album_cover_choice_controller_->SaveCoverToFile(metadata_, original_);
|
||||
}
|
||||
|
||||
void StatusView::UnsetCover() {
|
||||
album_cover_choice_controller_->UnsetCover(&metadata_);
|
||||
}
|
||||
|
||||
void StatusView::ShowCover() {
|
||||
album_cover_choice_controller_->ShowCover(metadata_);
|
||||
}
|
||||
|
||||
void StatusView::SearchCoverAutomatically() {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
s.setValue("search_for_cover_auto", album_cover_choice_controller_->search_cover_auto_action()->isChecked());
|
||||
|
||||
GetCoverAutomatically();
|
||||
|
||||
}
|
@ -1,177 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QWidget>
|
||||
#include <QString>
|
||||
#include <QFont>
|
||||
#include <QFontMetrics>
|
||||
#include <QImage>
|
||||
#include <QPixmap>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
#include <QColor>
|
||||
#include <QBrush>
|
||||
#include <QRect>
|
||||
#include <QSize>
|
||||
#include <QTimeLine>
|
||||
#include <QtEvents>
|
||||
|
||||
#include "core/qt_blurimage.h"
|
||||
#include "widgetfadehelper.h"
|
||||
|
||||
const int WidgetFadeHelper::kLoadingPadding = 9;
|
||||
const int WidgetFadeHelper::kLoadingBorderRadius = 10;
|
||||
|
||||
WidgetFadeHelper::WidgetFadeHelper(QWidget* parent, int msec)
|
||||
: QWidget(parent),
|
||||
parent_(parent),
|
||||
blur_timeline_(new QTimeLine(msec, this)),
|
||||
fade_timeline_(new QTimeLine(msec, this))
|
||||
{
|
||||
parent->installEventFilter(this);
|
||||
|
||||
connect(blur_timeline_, SIGNAL(valueChanged(qreal)), SLOT(update()));
|
||||
connect(fade_timeline_, SIGNAL(valueChanged(qreal)), SLOT(update()));
|
||||
connect(fade_timeline_, SIGNAL(finished()), SLOT(FadeFinished()));
|
||||
|
||||
hide();
|
||||
}
|
||||
|
||||
bool WidgetFadeHelper::eventFilter(QObject* obj, QEvent* event) {
|
||||
// We're only interested in our parent's resize events
|
||||
if (obj != parent_ || event->type() != QEvent::Resize) return false;
|
||||
|
||||
// Don't care if we're hidden
|
||||
if (!isVisible()) return false;
|
||||
|
||||
QResizeEvent* re = static_cast<QResizeEvent*>(event);
|
||||
if (re->oldSize() == re->size()) {
|
||||
// Ignore phoney resize events
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get a new capture of the parent
|
||||
hide();
|
||||
CaptureParent();
|
||||
show();
|
||||
return false;
|
||||
}
|
||||
|
||||
void WidgetFadeHelper::StartBlur() {
|
||||
CaptureParent();
|
||||
|
||||
// Cover the parent
|
||||
raise();
|
||||
show();
|
||||
|
||||
// Start the timeline
|
||||
blur_timeline_->stop();
|
||||
blur_timeline_->start();
|
||||
|
||||
setAttribute(Qt::WA_TransparentForMouseEvents, false);
|
||||
}
|
||||
|
||||
void WidgetFadeHelper::CaptureParent() {
|
||||
// Take a "screenshot" of the window
|
||||
original_pixmap_ = QPixmap::grabWidget(parent_);
|
||||
QImage original_image = original_pixmap_.toImage();
|
||||
|
||||
// Blur it
|
||||
QImage blurred(original_image.size(), QImage::Format_ARGB32_Premultiplied);
|
||||
blurred.fill(Qt::transparent);
|
||||
|
||||
QPainter blur_painter(&blurred);
|
||||
blur_painter.save();
|
||||
qt_blurImage(&blur_painter, original_image, 10.0, true, false);
|
||||
blur_painter.restore();
|
||||
|
||||
// Draw some loading text over the top
|
||||
QFont loading_font(font());
|
||||
loading_font.setBold(true);
|
||||
QFontMetrics loading_font_metrics(loading_font);
|
||||
|
||||
const QString loading_text = tr("Loading...");
|
||||
const QSize loading_size(kLoadingPadding*2 + loading_font_metrics.width(loading_text), kLoadingPadding*2 + loading_font_metrics.height());
|
||||
const QRect loading_rect((blurred.width() - loading_size.width()) / 2, 100, loading_size.width(), loading_size.height());
|
||||
|
||||
blur_painter.setRenderHint(QPainter::Antialiasing);
|
||||
blur_painter.setRenderHint(QPainter::HighQualityAntialiasing);
|
||||
|
||||
blur_painter.translate(0.5, 0.5);
|
||||
blur_painter.setPen(QColor(200, 200, 200, 255));
|
||||
blur_painter.setBrush(QColor(200, 200, 200, 192));
|
||||
blur_painter.drawRoundedRect(loading_rect, kLoadingBorderRadius, kLoadingBorderRadius);
|
||||
|
||||
blur_painter.setPen(palette().brush(QPalette::Text).color());
|
||||
blur_painter.setFont(loading_font);
|
||||
blur_painter.drawText(loading_rect.translated(-1, -1), Qt::AlignCenter, loading_text);
|
||||
blur_painter.translate(-0.5, -0.5);
|
||||
|
||||
blur_painter.end();
|
||||
|
||||
blurred_pixmap_ = QPixmap::fromImage(blurred);
|
||||
|
||||
resize(parent_->size());
|
||||
}
|
||||
|
||||
void WidgetFadeHelper::StartFade() {
|
||||
if (blur_timeline_->state() == QTimeLine::Running) {
|
||||
// Blur timeline is still running, so we need render the current state into a new pixmap.
|
||||
QPixmap pixmap(original_pixmap_);
|
||||
QPainter painter(&pixmap);
|
||||
painter.setOpacity(blur_timeline_->currentValue());
|
||||
painter.drawPixmap(0, 0, blurred_pixmap_);
|
||||
painter.end();
|
||||
blurred_pixmap_ = pixmap;
|
||||
}
|
||||
blur_timeline_->stop();
|
||||
original_pixmap_ = QPixmap();
|
||||
|
||||
// Start the timeline
|
||||
fade_timeline_->stop();
|
||||
fade_timeline_->start();
|
||||
|
||||
setAttribute(Qt::WA_TransparentForMouseEvents, true);
|
||||
}
|
||||
|
||||
void WidgetFadeHelper::paintEvent(QPaintEvent* ) {
|
||||
QPainter p(this);
|
||||
|
||||
if (fade_timeline_->state() != QTimeLine::Running) {
|
||||
// We're fading in the blur
|
||||
p.drawPixmap(0, 0, original_pixmap_);
|
||||
p.setOpacity(blur_timeline_->currentValue());
|
||||
}
|
||||
else {
|
||||
// Fading out the blur into the new image
|
||||
p.setOpacity(1.0 - fade_timeline_->currentValue());
|
||||
}
|
||||
|
||||
p.drawPixmap(0, 0, blurred_pixmap_);
|
||||
}
|
||||
|
||||
void WidgetFadeHelper::FadeFinished() {
|
||||
hide();
|
||||
original_pixmap_ = QPixmap();
|
||||
blurred_pixmap_ = QPixmap();
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef WIDGETFADEHELPER_H
|
||||
#define WIDGETFADEHELPER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QString>
|
||||
#include <QPixmap>
|
||||
#include <QTimeLine>
|
||||
#include <QtEvents>
|
||||
|
||||
class QEvent;
|
||||
class QPaintEvent;
|
||||
|
||||
class WidgetFadeHelper : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
WidgetFadeHelper(QWidget* parent, int msec = 500);
|
||||
|
||||
public slots:
|
||||
void StartBlur();
|
||||
void StartFade();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent*);
|
||||
bool eventFilter(QObject* obj, QEvent* event);
|
||||
|
||||
private slots:
|
||||
void FadeFinished();
|
||||
|
||||
private:
|
||||
void CaptureParent();
|
||||
|
||||
private:
|
||||
static const int kLoadingPadding;
|
||||
static const int kLoadingBorderRadius;
|
||||
|
||||
QWidget* parent_;
|
||||
QTimeLine* blur_timeline_;
|
||||
QTimeLine* fade_timeline_;
|
||||
|
||||
QPixmap original_pixmap_;
|
||||
QPixmap blurred_pixmap_;
|
||||
};
|
||||
|
||||
#endif // WIDGETFADEHELPER_H
|