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

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

164
src/context/contextview.h Normal file
View File

@@ -0,0 +1,164 @@
/*
* 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/>.
*
*/
#ifndef CONTEXTVIEW_H
#define CONTEXTVIEW_H
#include "config.h"
#include <memory>
#include <stdbool.h>
#include <QtGlobal>
#include <QObject>
#include <QWidget>
#include <QString>
#include <QImage>
#include <QPixmap>
#include <QPainter>
#include <QMovie>
#include <QTimeLine>
#include <QAction>
#include <QMenu>
#include <QLabel>
#include <QtEvents>
#include "core/song.h"
#include "covermanager/albumcoverloaderoptions.h"
#include "ui_contextviewcontainer.h"
using std::unique_ptr;
class Application;
class CollectionView;
class CollectionModel;
class AlbumCoverChoiceController;
class Ui_ContextViewContainer;
class ContextAlbumsView;
class LyricsFetcher;
class ContextView : public QWidget {
Q_OBJECT
public:
ContextView(QWidget *parent = nullptr);
~ContextView();
void SetApplication(Application *app);
void SetCollectionView(CollectionView *collectionview);
ContextAlbumsView *albums() { return ui_->widget_play_albums; }
public slots:
void UpdateNoSong();
void Playing();
void Stopped();
void Error();
void SongChanged(const Song &song);
void AlbumArtLoaded(const Song &song, const QString &uri, const QImage &image);
void LoadCoverFromFile();
void SaveCoverToFile();
void LoadCoverFromURL();
void SearchForCover();
void UnsetCover();
void ShowCover();
void SearchCoverAutomatically();
void AutomaticCoverSearchDone();
void UpdateLyrics(quint64 id, const QString lyrics);
private:
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_;
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();
protected:
bool eventFilter(QObject *, QEvent *);
void handlePaintEvent(QObject *object, QEvent *event);
void PaintEventAlbum(QEvent *event);
void contextMenuEvent(QContextMenuEvent *e);
void mouseReleaseEvent(QMouseEvent *);
void dragEnterEvent(QDragEnterEvent *e);
void dropEvent(QDropEvent *e);
private slots:
void ActionShowData();
void ActionShowOutput();
void ActionShowAlbums();
void ActionShowLyrics();
void FadePreviousTrack(qreal value);
};
#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>