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
This commit is contained in:
Jonas Kvinge 2018-08-29 21:42:24 +02:00
parent 3b30e66e87
commit ac6cac8da1
96 changed files with 4361 additions and 3135 deletions

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 363 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 628 KiB

View File

@ -1,11 +0,0 @@
StatusView {
background: white;
background-color: white;
}
QVBoxLayout {
background: white;
background-color: white;
}
QScrollArea {
background: qpalette(base);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<width>300</width>
<height>300</height>
</rect>
</property>

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

View 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

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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -200,9 +200,6 @@ int main(int argc, char* argv[]) {
// Resources
Q_INIT_RESOURCE(data);
// Icons
IconLoader::Init();
Application app;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -226,6 +226,7 @@ void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply) {
}
NextState(&task);
}
QImage AlbumCoverLoader::ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image) {

View File

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

View File

@ -111,7 +111,7 @@ void AmazonCoverProvider::QueryFinished(QNetworkReply *reply, int id) {
reply->deleteLater();
QString data=(QString)reply->readAll();
QString data(reply->readAll());
CoverSearchResults results;

View File

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

View File

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

View File

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

View File

@ -38,6 +38,8 @@ class DeviceFinder {
QString description;
QVariant value;
QString iconname;
int card;
int device;
};
virtual ~DeviceFinder() {}

View File

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

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

View 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

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

View 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

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

View 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

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

View 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

View File

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

View File

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

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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&amp;ms</string>
<string>a&amp;lbums</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radiobutton_searchbysongs">
<property name="text">
<string>songs</string>
<string>son&amp;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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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