mirror of
https://github.com/clementine-player/Clementine
synced 2024-12-17 03:45:56 +01:00
Create and render moodbars in background threads to avoid blocking the UI
This commit is contained in:
parent
638a4b9739
commit
a2feaa61e7
@ -25,6 +25,12 @@
|
||||
|
||||
#include <QApplication>
|
||||
#include <QPainter>
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
MoodbarItemDelegate::Data::Data()
|
||||
: state_(State_None)
|
||||
{
|
||||
}
|
||||
|
||||
MoodbarItemDelegate::MoodbarItemDelegate(Application* app, QObject* parent)
|
||||
: QItemDelegate(parent),
|
||||
@ -36,73 +42,154 @@ void MoodbarItemDelegate::paint(
|
||||
QPainter* painter, const QStyleOptionViewItem& option,
|
||||
const QModelIndex& index) const {
|
||||
QPixmap pixmap = const_cast<MoodbarItemDelegate*>(this)->PixmapForIndex(
|
||||
index, option.rect.size(), option.palette);
|
||||
index, option.rect.size());
|
||||
if (!pixmap.isNull()) {
|
||||
painter->drawPixmap(option.rect, pixmap);
|
||||
}
|
||||
}
|
||||
|
||||
QPixmap MoodbarItemDelegate::PixmapForIndex(
|
||||
const QModelIndex& index, const QSize& size, const QPalette& palette) {
|
||||
// Do we have a pixmap already that's the right size?
|
||||
QPixmap pixmap = index.data(Playlist::Role_MoodbarPixmap).value<QPixmap>();
|
||||
if (!pixmap.isNull() && pixmap.size() == size) {
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
// Do we have colors?
|
||||
ColorVector colors = index.data(Playlist::Role_MoodbarColors).value<ColorVector>();
|
||||
if (colors.isEmpty()) {
|
||||
// Nope - we need to load a mood file for this song and generate some colors
|
||||
// from it.
|
||||
const QModelIndex& index, const QSize& size) {
|
||||
// Pixmaps are keyed off URL.
|
||||
const QUrl url(index.sibling(index.row(), Playlist::Column_Filename).data().toUrl());
|
||||
|
||||
QByteArray data;
|
||||
Data* data = data_[url];
|
||||
if (!data) {
|
||||
data = new Data;
|
||||
data_.insert(url, data);
|
||||
}
|
||||
|
||||
data->indexes_.insert(index);
|
||||
data->desired_size_ = size;
|
||||
|
||||
switch (data->state_) {
|
||||
case Data::State_CannotLoad:
|
||||
case Data::State_LoadingData:
|
||||
case Data::State_LoadingColors:
|
||||
case Data::State_LoadingImage:
|
||||
return data->pixmap_;
|
||||
|
||||
case Data::State_Loaded:
|
||||
// Is the pixmap the right size?
|
||||
if (data->pixmap_.size() != size) {
|
||||
StartLoadingImage(url, data);
|
||||
}
|
||||
|
||||
return data->pixmap_;
|
||||
|
||||
case Data::State_None:
|
||||
break;
|
||||
}
|
||||
|
||||
// We have to start loading the data from scratch.
|
||||
data->state_ = Data::State_LoadingData;
|
||||
|
||||
// Load a mood file for this song and generate some colors from it
|
||||
QByteArray bytes;
|
||||
MoodbarPipeline* pipeline = NULL;
|
||||
switch (app_->moodbar_loader()->Load(url, &data, &pipeline)) {
|
||||
switch (app_->moodbar_loader()->Load(url, &bytes, &pipeline)) {
|
||||
case MoodbarLoader::CannotLoad:
|
||||
return QPixmap();
|
||||
data->state_ = Data::State_CannotLoad;
|
||||
break;
|
||||
|
||||
case MoodbarLoader::Loaded:
|
||||
// Aww yeah
|
||||
colors = MoodbarRenderer::Colors(data, MoodbarRenderer::Style_Normal, palette);
|
||||
// We got the data immediately.
|
||||
StartLoadingColors(url, bytes, data);
|
||||
break;
|
||||
|
||||
case MoodbarLoader::WillLoadAsync:
|
||||
// Maybe in a little while.
|
||||
qLog(Debug) << "Loading" << pipeline;
|
||||
NewClosure(pipeline, SIGNAL(Finished(bool)),
|
||||
this, SLOT(RequestFinished(MoodbarPipeline*,QModelIndex,QUrl)),
|
||||
pipeline, index, url);
|
||||
this, SLOT(DataLoaded(QUrl,MoodbarPipeline*)),
|
||||
url, pipeline);
|
||||
break;
|
||||
}
|
||||
|
||||
return QPixmap();
|
||||
}
|
||||
}
|
||||
|
||||
// We've got colors, let's make a pixmap.
|
||||
pixmap = QPixmap(size);
|
||||
QPainter p(&pixmap);
|
||||
MoodbarRenderer::Render(colors, &p, QRect(QPoint(0, 0), size));
|
||||
p.end();
|
||||
|
||||
// Set these on the item so we don't have to look them up again.
|
||||
QAbstractItemModel* model = const_cast<QAbstractItemModel*>(index.model());
|
||||
model->setData(index, QVariant::fromValue(colors), Playlist::Role_MoodbarColors);
|
||||
model->setData(index, pixmap, Playlist::Role_MoodbarPixmap);
|
||||
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
void MoodbarItemDelegate::RequestFinished(
|
||||
MoodbarPipeline* pipeline, const QModelIndex& index, const QUrl& url) {
|
||||
qLog(Debug) << "Finished" << pipeline;
|
||||
// Is this index still valid, and does it still point to the same URL?
|
||||
if (!index.isValid() || index.sibling(index.row(), Playlist::Column_Filename).data().toUrl() != url) {
|
||||
void MoodbarItemDelegate::DataLoaded( const QUrl& url, MoodbarPipeline* pipeline) {
|
||||
Data* data = data_[url];
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// It's good. Create the color list and set them on the item.
|
||||
ColorVector colors = MoodbarRenderer::Colors(
|
||||
pipeline->data(), MoodbarRenderer::Style_Normal, qApp->palette());
|
||||
QAbstractItemModel* model = const_cast<QAbstractItemModel*>(index.model());
|
||||
model->setData(index, QVariant::fromValue(colors), Playlist::Role_MoodbarColors);
|
||||
if (!pipeline->success()) {
|
||||
data->state_ = Data::State_CannotLoad;
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the colors next.
|
||||
StartLoadingColors(url, pipeline->data(), data);
|
||||
}
|
||||
|
||||
void MoodbarItemDelegate::StartLoadingColors(
|
||||
const QUrl& url, const QByteArray& bytes, Data* data) {
|
||||
data->state_ = Data::State_LoadingColors;
|
||||
|
||||
QFutureWatcher<ColorVector>* watcher = new QFutureWatcher<ColorVector>();
|
||||
NewClosure(watcher, SIGNAL(finished()),
|
||||
this, SLOT(ColorsLoaded(QUrl,QFutureWatcher<ColorVector>*)),
|
||||
url, watcher);
|
||||
|
||||
QFuture<ColorVector> future = QtConcurrent::run(MoodbarRenderer::Colors,
|
||||
bytes, MoodbarRenderer::Style_Normal, qApp->palette());
|
||||
watcher->setFuture(future);
|
||||
}
|
||||
|
||||
void MoodbarItemDelegate::ColorsLoaded(
|
||||
const QUrl& url, QFutureWatcher<ColorVector>* watcher) {
|
||||
watcher->deleteLater();
|
||||
|
||||
Data* data = data_[url];
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
data->colors_ = watcher->result();
|
||||
|
||||
// Load the image next.
|
||||
StartLoadingImage(url, data);
|
||||
}
|
||||
|
||||
void MoodbarItemDelegate::StartLoadingImage(const QUrl& url, Data* data) {
|
||||
data->state_ = Data::State_LoadingImage;
|
||||
|
||||
QFutureWatcher<QImage>* watcher = new QFutureWatcher<QImage>();
|
||||
NewClosure(watcher, SIGNAL(finished()),
|
||||
this, SLOT(ImageLoaded(QUrl,QFutureWatcher<QImage>*)),
|
||||
url, watcher);
|
||||
|
||||
QFuture<QImage> future = QtConcurrent::run(MoodbarRenderer::RenderToImage,
|
||||
data->colors_, data->desired_size_);
|
||||
watcher->setFuture(future);
|
||||
}
|
||||
|
||||
void MoodbarItemDelegate::ImageLoaded(const QUrl& url, QFutureWatcher<QImage>* watcher) {
|
||||
watcher->deleteLater();
|
||||
|
||||
Data* data = data_[url];
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
QImage image(watcher->result());
|
||||
|
||||
// If the desired size changed then don't even bother converting the image
|
||||
// to a pixmap, just reload it at the new size.
|
||||
if (!image.isNull() && data->desired_size_ != image.size()) {
|
||||
StartLoadingImage(url, data);
|
||||
return;
|
||||
}
|
||||
|
||||
data->pixmap_ = QPixmap::fromImage(image);
|
||||
data->state_ = Data::State_Loaded;
|
||||
|
||||
// Update all the indices with the new pixmap.
|
||||
foreach (const QPersistentModelIndex& index, data->indexes_) {
|
||||
if (index.isValid() && index.sibling(index.row(), Playlist::Column_Filename).data().toUrl() == url) {
|
||||
const_cast<Playlist*>(reinterpret_cast<const Playlist*>(index.model()))
|
||||
->MoodbarUpdated(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,12 @@
|
||||
#ifndef MOODBARITEMDELEGATE_H
|
||||
#define MOODBARITEMDELEGATE_H
|
||||
|
||||
#include "moodbarrenderer.h"
|
||||
|
||||
#include <QCache>
|
||||
#include <QItemDelegate>
|
||||
#include <QFutureWatcher>
|
||||
#include <QUrl>
|
||||
|
||||
class Application;
|
||||
class MoodbarPipeline;
|
||||
@ -35,15 +40,39 @@ public:
|
||||
const QModelIndex& index) const;
|
||||
|
||||
private slots:
|
||||
void RequestFinished(MoodbarPipeline* pipeline, const QModelIndex& index,
|
||||
const QUrl& url);
|
||||
void DataLoaded(const QUrl& url, MoodbarPipeline* pipeline);
|
||||
void ColorsLoaded(const QUrl& url, QFutureWatcher<ColorVector>* watcher);
|
||||
void ImageLoaded(const QUrl& url, QFutureWatcher<QImage>* watcher);
|
||||
|
||||
private:
|
||||
QPixmap PixmapForIndex(const QModelIndex& index, const QSize& size,
|
||||
const QPalette& palette);
|
||||
struct Data {
|
||||
Data();
|
||||
|
||||
enum State {
|
||||
State_None,
|
||||
State_CannotLoad,
|
||||
State_LoadingData,
|
||||
State_LoadingColors,
|
||||
State_LoadingImage,
|
||||
State_Loaded
|
||||
};
|
||||
|
||||
QSet<QPersistentModelIndex> indexes_;
|
||||
|
||||
State state_;
|
||||
ColorVector colors_;
|
||||
QSize desired_size_;
|
||||
QPixmap pixmap_;
|
||||
};
|
||||
|
||||
private:
|
||||
QPixmap PixmapForIndex(const QModelIndex& index, const QSize& size);
|
||||
void StartLoadingColors(const QUrl& url, const QByteArray& bytes, Data* data);
|
||||
void StartLoadingImage(const QUrl& url, Data* data);
|
||||
|
||||
private:
|
||||
Application* app_;
|
||||
QCache<QUrl, Data> data_;
|
||||
};
|
||||
|
||||
#endif // MOODBARITEMDELEGATE_H
|
||||
|
@ -32,7 +32,7 @@
|
||||
MoodbarLoader::MoodbarLoader(QObject* parent)
|
||||
: QObject(parent),
|
||||
cache_(new QNetworkDiskCache(this)),
|
||||
kMaxActiveRequests(QThread::idealThreadCount()),
|
||||
kMaxActiveRequests(QThread::idealThreadCount() / 2 + 1),
|
||||
save_alongside_originals_(false)
|
||||
{
|
||||
cache_->setCacheDirectory(Utilities::GetConfigPath(Utilities::Path_MoodbarCache));
|
||||
@ -62,10 +62,13 @@ MoodbarLoader::Result MoodbarLoader::Load(
|
||||
}
|
||||
|
||||
// Are we in the middle of loading this moodbar already?
|
||||
if (active_requests_.contains(url)) {
|
||||
*async_pipeline = active_requests_[url];
|
||||
{
|
||||
QMutexLocker l(&mutex_);
|
||||
if (requests_.contains(url)) {
|
||||
*async_pipeline = requests_[url];
|
||||
return WillLoadAsync;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a mood file exists for this file already
|
||||
const QString filename(url.toLocalFile());
|
||||
@ -93,29 +96,30 @@ MoodbarLoader::Result MoodbarLoader::Load(
|
||||
this, SLOT(RequestFinished(MoodbarPipeline*,QUrl)),
|
||||
pipeline, url);
|
||||
|
||||
active_requests_[url] = pipeline;
|
||||
|
||||
if (active_requests_.count() > kMaxActiveRequests) {
|
||||
// Just queue this request now, start it later when another request
|
||||
// finishes.
|
||||
{
|
||||
QMutexLocker l(&mutex_);
|
||||
requests_[url] = pipeline;
|
||||
queued_requests_ << url;
|
||||
} else if (!StartQueuedRequest(url)) {
|
||||
return CannotLoad;
|
||||
}
|
||||
|
||||
QMetaObject::invokeMethod(this, "MaybeTakeNextRequest", Qt::QueuedConnection);
|
||||
|
||||
*async_pipeline = pipeline;
|
||||
return WillLoadAsync;
|
||||
}
|
||||
|
||||
bool MoodbarLoader::StartQueuedRequest(const QUrl& url) {
|
||||
if (!active_requests_[url]->Start()) {
|
||||
delete active_requests_.take(url);
|
||||
return false;
|
||||
void MoodbarLoader::MaybeTakeNextRequest() {
|
||||
QMutexLocker l(&mutex_);
|
||||
if (active_requests_.count() > kMaxActiveRequests ||
|
||||
queued_requests_.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
qLog(Info) << "Creating moodbar data for" << url.toLocalFile();
|
||||
const QUrl url = queued_requests_.takeFirst();
|
||||
active_requests_ << url;
|
||||
|
||||
return true;
|
||||
qLog(Info) << "Creating moodbar data for" << url.toLocalFile();
|
||||
requests_[url]->Start();
|
||||
}
|
||||
|
||||
void MoodbarLoader::RequestFinished(MoodbarPipeline* request, const QUrl& url) {
|
||||
@ -145,10 +149,12 @@ void MoodbarLoader::RequestFinished(MoodbarPipeline* request, const QUrl& url) {
|
||||
qLog(Debug) << "Deleting" << request;
|
||||
|
||||
// Remove the request from the active list and delete it
|
||||
active_requests_.take(url);
|
||||
{
|
||||
QMutexLocker l(&mutex_);
|
||||
requests_.remove(url);
|
||||
active_requests_.remove(url);
|
||||
}
|
||||
QTimer::singleShot(10, request, SLOT(deleteLater()));
|
||||
|
||||
if (!queued_requests_.isEmpty()) {
|
||||
StartQueuedRequest(queued_requests_.takeFirst());
|
||||
}
|
||||
QMetaObject::invokeMethod(this, "MaybeTakeNextRequest", Qt::QueuedConnection);
|
||||
}
|
||||
|
@ -19,8 +19,9 @@
|
||||
#define MOODBARLOADER_H
|
||||
|
||||
#include <QMap>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QSet>
|
||||
|
||||
class QNetworkDiskCache;
|
||||
class QUrl;
|
||||
@ -51,18 +52,20 @@ public:
|
||||
|
||||
private slots:
|
||||
void RequestFinished(MoodbarPipeline* request, const QUrl& filename);
|
||||
void MaybeTakeNextRequest();
|
||||
|
||||
private:
|
||||
static QStringList MoodFilenames(const QString& song_filename);
|
||||
bool StartQueuedRequest(const QUrl& url);
|
||||
|
||||
private:
|
||||
QNetworkDiskCache* cache_;
|
||||
|
||||
const int kMaxActiveRequests;
|
||||
|
||||
QMap<QUrl, MoodbarPipeline*> active_requests_;
|
||||
QMutex mutex_;
|
||||
QMap<QUrl, MoodbarPipeline*> requests_;
|
||||
QList<QUrl> queued_requests_;
|
||||
QSet<QUrl> active_requests_;
|
||||
|
||||
bool save_alongside_originals_;
|
||||
};
|
||||
|
@ -32,7 +32,6 @@ MoodbarPipeline::MoodbarPipeline(const QString& local_filename)
|
||||
}
|
||||
|
||||
MoodbarPipeline::~MoodbarPipeline() {
|
||||
qLog(Debug) << "Actually deleting" << this;
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
@ -84,6 +83,7 @@ bool MoodbarPipeline::Start() {
|
||||
|
||||
if (!filesrc || !convert_element_ || !fftwspectrum || !moodbar || !appsink) {
|
||||
pipeline_ = NULL;
|
||||
emit Finished(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -139,3 +139,11 @@ void MoodbarRenderer::Render(const ColorVector& colors, QPainter* p, const QRect
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QImage MoodbarRenderer::RenderToImage(const ColorVector& colors, const QSize& size) {
|
||||
QImage image(size, QImage::Format_ARGB32_Premultiplied);
|
||||
QPainter p(&image);
|
||||
Render(colors, &p, image.rect());
|
||||
p.end();
|
||||
return image;
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ public:
|
||||
static ColorVector Colors(const QByteArray& data, MoodbarStyle style,
|
||||
const QPalette& palette);
|
||||
static void Render(const ColorVector& colors, QPainter* p, const QRect& rect);
|
||||
static QImage RenderToImage(const ColorVector& colors, const QSize& size);
|
||||
|
||||
private:
|
||||
MoodbarRenderer();
|
||||
|
@ -43,7 +43,6 @@
|
||||
#include "library/librarybackend.h"
|
||||
#include "library/librarymodel.h"
|
||||
#include "library/libraryplaylistitem.h"
|
||||
#include "moodbar/moodbarrenderer.h"
|
||||
#include "smartplaylists/generator.h"
|
||||
#include "smartplaylists/generatorinserter.h"
|
||||
#include "smartplaylists/generatormimedata.h"
|
||||
@ -244,12 +243,6 @@ QVariant Playlist::data(const QModelIndex& index, int role) const {
|
||||
items_[index.row()]->IsLocalLibraryItem() &&
|
||||
items_[index.row()]->Metadata().id() != -1;
|
||||
|
||||
case Role_MoodbarColors:
|
||||
return QVariant::fromValue(items_[index.row()]->MoodbarColors());
|
||||
|
||||
case Role_MoodbarPixmap:
|
||||
return items_[index.row()]->MoodbarPixmap();
|
||||
|
||||
case Qt::EditRole:
|
||||
case Qt::ToolTipRole:
|
||||
case Qt::DisplayRole: {
|
||||
@ -316,21 +309,16 @@ QVariant Playlist::data(const QModelIndex& index, int role) const {
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::MoodbarUpdated(const QModelIndex& index) {
|
||||
emit dataChanged(index.sibling(index.row(), Column_Mood),
|
||||
index.sibling(index.row(), Column_Mood));
|
||||
}
|
||||
|
||||
bool Playlist::setData(const QModelIndex& index, const QVariant& value, int role) {
|
||||
int row = index.row();
|
||||
PlaylistItemPtr item = item_at(row);
|
||||
Song song = item->Metadata();
|
||||
|
||||
if (role == Role_MoodbarColors) {
|
||||
item->SetMoodbarColors(value.value<QVector<QColor> >());
|
||||
emit dataChanged(index.sibling(index.row(), Column_Mood),
|
||||
index.sibling(index.row(), Column_Mood));
|
||||
return true;
|
||||
} else if (role == Role_MoodbarPixmap) {
|
||||
item->SetMoodbarPixmap(value.value<QPixmap>());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (index.data() == value)
|
||||
return false;
|
||||
|
||||
|
@ -122,8 +122,6 @@ class Playlist : public QAbstractListModel {
|
||||
Role_StopAfter,
|
||||
Role_QueuePosition,
|
||||
Role_CanSetRating,
|
||||
Role_MoodbarColors,
|
||||
Role_MoodbarPixmap,
|
||||
};
|
||||
|
||||
enum LastFMStatus {
|
||||
@ -246,6 +244,9 @@ class Playlist : public QAbstractListModel {
|
||||
// Unregisters a SongInsertVetoListener object.
|
||||
void RemoveSongInsertVetoListener(SongInsertVetoListener* listener);
|
||||
|
||||
// Just emits the dataChanged() signal so the mood column is repainted.
|
||||
void MoodbarUpdated(const QModelIndex& index);
|
||||
|
||||
// QAbstractListModel
|
||||
int rowCount(const QModelIndex& = QModelIndex()) const { return items_.count(); }
|
||||
int columnCount(const QModelIndex& = QModelIndex()) const { return ColumnCount; }
|
||||
|
@ -33,7 +33,6 @@
|
||||
|
||||
|
||||
PlaylistItem::~PlaylistItem() {
|
||||
delete moodbar_pixmap_;
|
||||
}
|
||||
|
||||
PlaylistItem* PlaylistItem::NewFromType(const QString& type) {
|
||||
@ -124,17 +123,3 @@ QColor PlaylistItem::GetCurrentForegroundColor() const {
|
||||
bool PlaylistItem::HasCurrentForegroundColor() const {
|
||||
return !foreground_colors_.isEmpty();
|
||||
}
|
||||
|
||||
QPixmap PlaylistItem::MoodbarPixmap() const {
|
||||
if (!moodbar_pixmap_) {
|
||||
return QPixmap();
|
||||
}
|
||||
return *moodbar_pixmap_;
|
||||
}
|
||||
|
||||
void PlaylistItem::SetMoodbarPixmap(const QPixmap& pixmap) {
|
||||
if (!moodbar_pixmap_) {
|
||||
moodbar_pixmap_ = new QPixmap;
|
||||
}
|
||||
*moodbar_pixmap_ = pixmap;
|
||||
}
|
||||
|
@ -33,8 +33,7 @@ class SqlRow;
|
||||
class PlaylistItem : public boost::enable_shared_from_this<PlaylistItem> {
|
||||
public:
|
||||
PlaylistItem(const QString& type)
|
||||
: type_(type),
|
||||
moodbar_pixmap_(NULL) {}
|
||||
: type_(type) {}
|
||||
virtual ~PlaylistItem();
|
||||
|
||||
static PlaylistItem* NewFromType(const QString& type);
|
||||
@ -93,12 +92,6 @@ class PlaylistItem : public boost::enable_shared_from_this<PlaylistItem> {
|
||||
// before actually using it.
|
||||
virtual bool IsLocalLibraryItem() const { return false; }
|
||||
|
||||
// Moodbar accessors. These are lazy-loaded by MoodbarItemDelegate.
|
||||
const QVector<QColor>& MoodbarColors() const { return moodbar_colors_; }
|
||||
void SetMoodbarColors(const QVector<QColor>& colors) { moodbar_colors_ = colors; }
|
||||
QPixmap MoodbarPixmap() const;
|
||||
void SetMoodbarPixmap(const QPixmap& pixmap);
|
||||
|
||||
protected:
|
||||
enum DatabaseColumn {
|
||||
Column_LibraryId,
|
||||
@ -115,10 +108,6 @@ class PlaylistItem : public boost::enable_shared_from_this<PlaylistItem> {
|
||||
|
||||
QMap<short, QColor> background_colors_;
|
||||
QMap<short, QColor> foreground_colors_;
|
||||
|
||||
private:
|
||||
QVector<QColor> moodbar_colors_;
|
||||
QPixmap* moodbar_pixmap_;
|
||||
};
|
||||
typedef boost::shared_ptr<PlaylistItem> PlaylistItemPtr;
|
||||
typedef QList<PlaylistItemPtr> PlaylistItemList;
|
||||
|
Loading…
Reference in New Issue
Block a user