/* This file is part of Clementine. Copyright 2012, David Sansome Clementine 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. Clementine 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 Clementine. If not, see . */ #include "moodbaritemdelegate.h" #include "moodbarloader.h" #include "moodbarpipeline.h" #include "moodbarrenderer.h" #include "core/application.h" #include "core/closure.h" #include "core/qhash_qurl.h" #include "playlist/playlist.h" #include "playlist/playlistview.h" #include #include #include #include #include MoodbarItemDelegate::Data::Data() : state_(State_None) {} MoodbarItemDelegate::MoodbarItemDelegate(Application* app, PlaylistView* view, QObject* parent) : QItemDelegate(parent), app_(app), view_(view), style_(MoodbarRenderer::Style_Normal) { connect(app_, SIGNAL(SettingsChanged()), SLOT(ReloadSettings())); ReloadSettings(); } void MoodbarItemDelegate::ReloadSettings() { QSettings s; s.beginGroup("Moodbar"); MoodbarRenderer::MoodbarStyle new_style = static_cast( s.value("style", MoodbarRenderer::Style_Normal).toInt()); if (new_style != style_) { style_ = new_style; ReloadAllColors(); } } void MoodbarItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QPixmap pixmap = const_cast(this) ->PixmapForIndex(index, option.rect.size()); drawBackground(painter, option, index); if (!pixmap.isNull()) { // Make a little border for the moodbar const QRect moodbar_rect(option.rect.adjusted(1, 1, -1, -1)); painter->drawPixmap(moodbar_rect, pixmap); } } QPixmap MoodbarItemDelegate::PixmapForIndex(const QModelIndex& index, const QSize& size) { // Pixmaps are keyed off URL. const QUrl url( index.sibling(index.row(), Playlist::Column_Filename).data().toUrl()); 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. StartLoadingData(url, data); return QPixmap(); } void MoodbarItemDelegate::StartLoadingData(const QUrl& url, Data* data) { data->state_ = Data::State_LoadingData; // Load a mood file for this song and generate some colors from it QByteArray bytes; MoodbarPipeline* pipeline = nullptr; switch (app_->moodbar_loader()->Load(url, &bytes, &pipeline)) { case MoodbarLoader::CannotLoad: data->state_ = Data::State_CannotLoad; break; case MoodbarLoader::Loaded: // We got the data immediately. StartLoadingColors(url, bytes, data); break; case MoodbarLoader::WillLoadAsync: // Maybe in a little while. NewClosure(pipeline, SIGNAL(Finished(bool)), this, SLOT(DataLoaded(QUrl, MoodbarPipeline*)), url, pipeline); break; } } bool MoodbarItemDelegate::RemoveFromCacheIfIndexesInvalid(const QUrl& url, Data* data) { for (const QPersistentModelIndex& index : data->indexes_) { if (index.isValid()) { return false; } } data_.remove(url); return true; } void MoodbarItemDelegate::ReloadAllColors() { for (const QUrl& url : data_.keys()) { Data* data = data_[url]; if (data->state_ == Data::State_Loaded) { StartLoadingData(url, data); } } } void MoodbarItemDelegate::DataLoaded(const QUrl& url, MoodbarPipeline* pipeline) { Data* data = data_[url]; if (!data) { return; } if (RemoveFromCacheIfIndexesInvalid(url, data)) { return; } 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* watcher = new QFutureWatcher(); NewClosure(watcher, SIGNAL(finished()), this, SLOT(ColorsLoaded(QUrl, QFutureWatcher*)), url, watcher); QFuture future = QtConcurrent::run( MoodbarRenderer::Colors, bytes, style_, qApp->palette()); watcher->setFuture(future); } void MoodbarItemDelegate::ColorsLoaded(const QUrl& url, QFutureWatcher* watcher) { watcher->deleteLater(); Data* data = data_[url]; if (!data) { return; } if (RemoveFromCacheIfIndexesInvalid(url, 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* watcher = new QFutureWatcher(); NewClosure(watcher, SIGNAL(finished()), this, SLOT(ImageLoaded(QUrl, QFutureWatcher*)), url, watcher); QFuture future = QtConcurrent::run( MoodbarRenderer::RenderToImage, data->colors_, data->desired_size_); watcher->setFuture(future); } void MoodbarItemDelegate::ImageLoaded(const QUrl& url, QFutureWatcher* watcher) { watcher->deleteLater(); Data* data = data_[url]; if (!data) { return; } if (RemoveFromCacheIfIndexesInvalid(url, 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; Playlist* playlist = view_->playlist(); const QSortFilterProxyModel* filter = playlist->proxy(); // Update all the indices with the new pixmap. for (const QPersistentModelIndex& index : data->indexes_) { if (index.isValid() && index.sibling(index.row(), Playlist::Column_Filename).data().toUrl() == url) { QModelIndex source_index = index; if (index.model() == filter) { source_index = filter->mapToSource(source_index); } if (source_index.model() != playlist) { // The pixmap was for an index in a different playlist, maybe the user // switched to a different one. continue; } playlist->MoodbarUpdated(source_index); } } }