Add moodbar

This commit is contained in:
Jonas Kvinge
2019-04-18 15:03:01 +02:00
parent 37b923bea3
commit 907d18a83a
49 changed files with 3347 additions and 5 deletions

View File

@@ -879,6 +879,28 @@ optional_source(HAVE_TIDAL
settings/tidalsettingspage.ui
)
# Moodbar
optional_source(HAVE_MOODBAR
SOURCES
moodbar/moodbarbuilder.cpp
moodbar/moodbarcontroller.cpp
moodbar/moodbaritemdelegate.cpp
moodbar/moodbarloader.cpp
moodbar/moodbarpipeline.cpp
moodbar/moodbarproxystyle.cpp
moodbar/moodbarrenderer.cpp
settings/moodbarsettingspage.cpp
HEADERS
moodbar/moodbarcontroller.h
moodbar/moodbaritemdelegate.h
moodbar/moodbarloader.h
moodbar/moodbarpipeline.h
moodbar/moodbarproxystyle.h
settings/moodbarsettingspage.h
UI
settings/moodbarsettingspage.ui
)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
@@ -1003,6 +1025,10 @@ if(HAVE_LIBPULSE)
target_link_libraries(strawberry_lib ${LIBPULSE_LIBRARIES})
endif()
if(HAVE_MOODBAR)
target_link_libraries(strawberry_lib gstmoodbar)
endif()
if (APPLE)
target_link_libraries(strawberry_lib
"-framework AppKit"
@@ -1025,7 +1051,6 @@ if (WIN32)
target_link_libraries(strawberry_lib
${ZLIB_LIBRARIES}
dsound
${QT_QTGUI_LIBRARY}
)
endif (WIN32)

View File

@@ -50,6 +50,8 @@
#cmakedefine HAVE_TIDAL
#cmakedefine HAVE_MOODBAR
#cmakedefine HAVE_KEYSYMDEF_H
#cmakedefine HAVE_XF86KEYSYM_H

View File

@@ -61,6 +61,8 @@
#include "lyrics/auddlyricsprovider.h"
#include "lyrics/chartlyricsprovider.h"
#include "scrobbler/audioscrobbler.h"
#include "internet/internetservices.h"
#include "internet/internetsearch.h"
@@ -69,7 +71,10 @@
# include "covermanager/tidalcoverprovider.h"
#endif
#include "scrobbler/audioscrobbler.h"
#ifdef HAVE_MOODBAR
# include "moodbar/moodbarcontroller.h"
# include "moodbar/moodbarloader.h"
#endif
bool Application::kIsPortable = false;
@@ -136,7 +141,14 @@ class ApplicationImpl {
#ifdef HAVE_TIDAL
tidal_search_([=]() { return new InternetSearch(app, Song::Source_Tidal, app); }),
#endif
scrobbler_([=]() { return new AudioScrobbler(app, app); })
scrobbler_([=]() { return new AudioScrobbler(app, app); }),
#ifdef HAVE_MOODBAR
moodbar_loader_([=]() { return new MoodbarLoader(app, app); }),
moodbar_controller_([=]() { return new MoodbarController(app, app); }),
#endif
dummy_([=]() { return nullptr; })
{}
Lazy<TagReaderClient> tag_reader_client_;
@@ -160,6 +172,11 @@ class ApplicationImpl {
Lazy<InternetSearch> tidal_search_;
#endif
Lazy<AudioScrobbler> scrobbler_;
#ifdef HAVE_MOODBAR
Lazy<MoodbarLoader> moodbar_loader_;
Lazy<MoodbarController> moodbar_controller_;
#endif
Lazy<QVariant> dummy_;
};
@@ -231,3 +248,7 @@ InternetServices *Application::internet_services() const { return p_->internet_s
InternetSearch *Application::tidal_search() const { return p_->tidal_search_.get(); }
#endif
AudioScrobbler *Application::scrobbler() const { return p_->scrobbler_.get(); }
#ifdef HAVE_MOODBAR
MoodbarController *Application::moodbar_controller() const { return p_->moodbar_controller_.get(); }
MoodbarLoader *Application::moodbar_loader() const { return p_->moodbar_loader_.get(); }
#endif

View File

@@ -56,9 +56,13 @@ class CoverProviders;
class AlbumCoverLoader;
class CurrentArtLoader;
class LyricsProviders;
class AudioScrobbler;
class InternetServices;
class InternetSearch;
class AudioScrobbler;
#ifdef HAVE_MOODBAR
class MoodbarController;
class MoodbarLoader;
#endif
class Application : public QObject {
Q_OBJECT
@@ -92,12 +96,17 @@ class Application : public QObject {
LyricsProviders *lyrics_providers() const;
AudioScrobbler *scrobbler() const;
InternetServices *internet_services() const;
#ifdef HAVE_TIDAL
InternetSearch *tidal_search() const;
#endif
AudioScrobbler *scrobbler() const;
#ifdef HAVE_MOODBAR
MoodbarController *moodbar_controller() const;
MoodbarLoader *moodbar_loader() const;
#endif
void MoveToNewThread(QObject *object);
void MoveToThread(QObject *object, QThread *thread);

View File

@@ -151,6 +151,11 @@
# include "core/macsystemtrayicon.h"
#endif
#ifdef HAVE_MOODBAR
# include "moodbar/moodbarcontroller.h"
# include "moodbar/moodbarproxystyle.h"
#endif
using std::bind;
using std::floor;
using std::stable_sort;
@@ -667,6 +672,11 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
ui_->track_slider->SetApplication(app);
#ifdef HAVE_MOODBAR
// Moodbar connections
connect(app_->moodbar_controller(), SIGNAL(CurrentMoodbarDataChanged(QByteArray)), ui_->track_slider->moodbar_style(), SLOT(SetMoodbarData(QByteArray)));
#endif
// Playing widget
qLog(Debug) << "Creating playing widget";
ui_->widget_playing->set_ideal_height(ui_->status_bar->sizeHint().height() + ui_->player_controls->sizeHint().height());

View File

@@ -67,6 +67,10 @@
# include "ext/gstafc/gstafcsrc.h"
#endif
#ifdef HAVE_MOODBAR
# include "ext/gstmoodbar/gstmoodbarplugin.h"
#endif
#include "settings/backendsettingspage.h"
using std::shared_ptr;
@@ -118,6 +122,7 @@ bool GstEngine::Init() {
SetEnvironment();
initialising_ = QtConcurrent::run(this, &GstEngine::InitialiseGStreamer);
return true;
}
@@ -415,6 +420,10 @@ void GstEngine::InitialiseGStreamer() {
afcsrc_register_static();
#endif
#ifdef HAVE_MOODBAR
gstfastspectrum_register_static();
#endif
}
void GstEngine::SetEnvironment() {

View File

@@ -0,0 +1,199 @@
/* This file was part of Clementine.
Copyright 2014, David Sansome <me@davidsansome.com>
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cmath>
#include <QList>
#include <QByteArray>
#include "moodbarbuilder.h"
#include "core/arraysize.h"
namespace {
static const int sBarkBands[] = {
100, 200, 300, 400, 510, 630, 770, 920, 1080, 1270, 1480, 1720,
2000, 2320, 2700, 3150, 3700, 4400, 5300, 6400, 7700, 9500, 12000, 15500};
static const int sBarkBandCount = arraysize(sBarkBands);
} // namespace
MoodbarBuilder::MoodbarBuilder() : bands_(0), rate_hz_(0) {}
int MoodbarBuilder::BandFrequency(int band) const {
return ((rate_hz_ / 2) * band + rate_hz_ / 4) / bands_;
}
void MoodbarBuilder::Init(int bands, int rate_hz) {
bands_ = bands;
rate_hz_ = rate_hz;
barkband_table_.clear();
barkband_table_.reserve(bands + 1);
int barkband = 0;
for (int i = 0; i < bands + 1; ++i) {
if (barkband < sBarkBandCount - 1 && BandFrequency(i) >= sBarkBands[barkband]) {
barkband++;
}
barkband_table_.append(barkband);
}
}
void MoodbarBuilder::AddFrame(const double* magnitudes, int size) {
if (size > barkband_table_.length()) {
return;
}
// Calculate total magnitudes for different bark bands.
double bands[sBarkBandCount];
for (int i = 0; i < sBarkBandCount; ++i) {
bands[i] = 0.0;
}
for (int i = 0; i < size; ++i) {
bands[barkband_table_[i]] += magnitudes[i];
}
// Now divide the bark bands into thirds and compute their total amplitudes.
double rgb[] = {0, 0, 0};
for (int i = 0; i < sBarkBandCount; ++i) {
rgb[(i * 3) / sBarkBandCount] += bands[i] * bands[i];
}
frames_.append(Rgb(sqrt(rgb[0]), sqrt(rgb[1]), sqrt(rgb[2])));
}
void MoodbarBuilder::Normalize(QList<Rgb>* vals, double Rgb::*member) {
double mini = vals->at(0).*member;
double maxi = vals->at(0).*member;
for (int i = 1; i < vals->count(); i++) {
const double value = vals->at(i).*member;
if (value > maxi) {
maxi = value;
}
else if (value < mini) {
mini = value;
}
}
double avg = 0;
int t = 0;
for (const Rgb& rgb : *vals) {
const double value = rgb.*member;
if (value != mini && value != maxi) {
avg += value / vals->count();
t++;
}
}
double tu = 0;
double tb = 0;
double avgu = 0;
double avgb = 0;
for (const Rgb& rgb : *vals) {
const double value = rgb.*member;
if (value != mini && value != maxi) {
if (value > avg) {
avgu += value;
tu++;
}
else {
avgb += value;
tb++;
}
}
}
avgu /= tu;
avgb /= tb;
tu = 0;
tb = 0;
double avguu = 0;
double avgbb = 0;
for (const Rgb& rgb : *vals) {
const double value = rgb.*member;
if (value != mini && value != maxi) {
if (value > avgu) {
avguu += value;
tu++;
}
else if (value < avgb) {
avgbb += value;
tb++;
}
}
}
avguu /= tu;
avgbb /= tb;
mini = std::max(avg + (avgb - avg) * 2, avgbb);
maxi = std::min(avg + (avgu - avg) * 2, avguu);
double delta = maxi - mini;
if (delta == 0) {
delta = 1;
}
for (auto it = vals->begin(); it != vals->end(); ++it) {
double* value = &((*it).*member);
*value = std::isfinite(*value) ? qBound(0.0, (*value - mini) / delta, 1.0) : 0;
}
}
QByteArray MoodbarBuilder::Finish(int width) {
QByteArray ret;
ret.resize(width * 3);
char* data = ret.data();
if (frames_.count() == 0) return ret;
Normalize(&frames_, &Rgb::r);
Normalize(&frames_, &Rgb::g);
Normalize(&frames_, &Rgb::b);
for (int i = 0; i < width; ++i) {
Rgb rgb;
int start = i * frames_.count() / width;
int end = (i + 1) * frames_.count() / width;
if (start == end) {
end = start + 1;
}
for (int j = start; j < end; j++) {
const Rgb& frame = frames_[j];
rgb.r += frame.r * 255;
rgb.g += frame.g * 255;
rgb.b += frame.b * 255;
}
const int n = end - start;
*(data++) = rgb.r / n;
*(data++) = rgb.g / n;
*(data++) = rgb.b / n;
}
return ret;
}

View File

@@ -0,0 +1,50 @@
/* This file was part of Clementine.
Copyright 2014, David Sansome <me@davidsansome.com>
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MOODBARBUILDER_H
#define MOODBARBUILDER_H
#include <QList>
#include <QByteArray>
class MoodbarBuilder {
public:
MoodbarBuilder();
void Init(int bands, int rate_hz);
void AddFrame(const double* magnitudes, int size);
QByteArray Finish(int width);
private:
struct Rgb {
Rgb() : r(0), g(0), b(0) {}
Rgb(double r_, double g_, double b_) : r(r_), g(g_), b(b_) {}
double r, g, b;
};
int BandFrequency(int band) const;
static void Normalize(QList<Rgb>* vals, double Rgb::*member);
QList<uint> barkband_table_;
int bands_;
int rate_hz_;
QList<Rgb> frames_;
};
#endif // MOODBARBUILDER_H

View File

@@ -0,0 +1,91 @@
/* This file was part of Clementine.
Copyright 2012, David Sansome <me@davidsansome.com>
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QObject>
#include <QByteArray>
#include <QUrl>
#include "core/application.h"
#include "core/closure.h"
#include "core/logging.h"
#include "core/player.h"
#include "playlist/playlistmanager.h"
#include "moodbarcontroller.h"
#include "moodbarloader.h"
#include "moodbarpipeline.h"
MoodbarController::MoodbarController(Application* app, QObject* parent)
: QObject(parent),
app_(app) {
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(CurrentSongChanged(Song)));
connect(app_->player(), SIGNAL(Stopped()), SLOT(PlaybackStopped()));
}
void MoodbarController::CurrentSongChanged(const Song& song) {
QByteArray data;
MoodbarPipeline* pipeline = nullptr;
const MoodbarLoader::Result result = app_->moodbar_loader()->Load(song.url(), &data, &pipeline);
switch (result) {
case MoodbarLoader::CannotLoad:
emit CurrentMoodbarDataChanged(QByteArray());
break;
case MoodbarLoader::Loaded:
emit CurrentMoodbarDataChanged(data);
break;
case MoodbarLoader::WillLoadAsync:
// Emit an empty array for now so the GUI reverts to a normal progress
// bar. Our slot will be called when the data is actually loaded.
emit CurrentMoodbarDataChanged(QByteArray());
NewClosure(pipeline, SIGNAL(Finished(bool)), this, SLOT(AsyncLoadComplete(MoodbarPipeline*, QUrl)), pipeline, song.url());
break;
}
}
void MoodbarController::PlaybackStopped() {
emit CurrentMoodbarDataChanged(QByteArray());
}
void MoodbarController::AsyncLoadComplete(MoodbarPipeline* pipeline, const QUrl& url) {
// Is this song still playing?
PlaylistItemPtr current_item = app_->player()->GetCurrentItem();
if (current_item && current_item->Url() != url) {
return;
}
// Did we stop the song?
switch(app_->player()->GetState()) {
case Engine::Error:
case Engine::Empty:
case Engine::Idle:
return;
default:
break;
}
emit CurrentMoodbarDataChanged(pipeline->data());
}

View File

@@ -0,0 +1,47 @@
/* This file was part of Clementine.
Copyright 2012, David Sansome <me@davidsansome.com>
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MOODBARCONTROLLER_H
#define MOODBARCONTROLLER_H
#include <QObject>
#include <QByteArray>
#include <QUrl>
class Application;
class MoodbarPipeline;
class Song;
class MoodbarController : public QObject {
Q_OBJECT
public:
MoodbarController(Application* app, QObject* parent = nullptr);
signals:
void CurrentMoodbarDataChanged(const QByteArray& data);
private slots:
void CurrentSongChanged(const Song& song);
void PlaybackStopped();
void AsyncLoadComplete(MoodbarPipeline* pipeline, const QUrl& url);
private:
Application* app_;
};
#endif // MOODBARCONTROLLER_H

View File

@@ -0,0 +1,274 @@
/* This file was part of Clementine.
Copyright 2012, David Sansome <me@davidsansome.com>
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QApplication>
#include <QtConcurrentRun>
#include <QFuture>
#include <QSettings>
#include <QSortFilterProxyModel>
#include <QItemDelegate>
#include <QByteArray>
#include <QUrl>
#include <QImage>
#include <QPixmap>
#include <QPainter>
#include "core/application.h"
#include "core/closure.h"
#include "playlist/playlist.h"
#include "playlist/playlistview.h"
#include "moodbaritemdelegate.h"
#include "moodbarloader.h"
#include "moodbarpipeline.h"
#include "moodbarrenderer.h"
#include "settings/moodbarsettingspage.h"
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(MoodbarSettingsPage::kSettingsGroup);
MoodbarRenderer::MoodbarStyle new_style = static_cast<MoodbarRenderer::MoodbarStyle>(s.value("style", MoodbarRenderer::Style_Normal).toInt());
s.endGroup();
if (new_style != style_) {
style_ = new_style;
ReloadAllColors();
}
}
void MoodbarItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
QPixmap pixmap = const_cast<MoodbarItemDelegate*>(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;
QFuture<ColorVector> future = QtConcurrent::run(MoodbarRenderer::Colors, bytes, style_, qApp->palette());
NewClosure(future, this, SLOT(ColorsLoaded(QUrl, QFuture<ColorVector>)), url, future);
}
void MoodbarItemDelegate::ColorsLoaded(const QUrl& url, QFuture<ColorVector> future) {
Data* data = data_[url];
if (!data) {
return;
}
if (RemoveFromCacheIfIndexesInvalid(url, data)) {
return;
}
data->colors_ = future.result();
// Load the image next.
StartLoadingImage(url, data);
}
void MoodbarItemDelegate::StartLoadingImage(const QUrl& url, Data* data) {
data->state_ = Data::State_LoadingImage;
QFuture<QImage> future = QtConcurrent::run(MoodbarRenderer::RenderToImage, data->colors_, data->desired_size_);
NewClosure(future, this, SLOT(ImageLoaded(QUrl, QFuture<QImage>)), url, future);
}
void MoodbarItemDelegate::ImageLoaded(const QUrl& url, QFuture<QImage> future) {
Data* data = data_[url];
if (!data) {
return;
}
if (RemoveFromCacheIfIndexesInvalid(url, data)) {
return;
}
QImage image(future.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);
}
}
}

View File

@@ -0,0 +1,90 @@
/* This file is part of Clementine.
Copyright 2012, David Sansome <me@davidsansome.com>
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MOODBARITEMDELEGATE_H
#define MOODBARITEMDELEGATE_H
#include "moodbarrenderer.h"
#include <QObject>
#include <QItemDelegate>
#include <QCache>
#include <QFuture>
#include <QSet>
#include <QByteArray>
#include <QUrl>
#include <QPainter>
class Application;
class MoodbarPipeline;
class PlaylistView;
class MoodbarItemDelegate : public QItemDelegate {
Q_OBJECT
public:
MoodbarItemDelegate(Application* app, PlaylistView* view, QObject* parent = nullptr);
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
private slots:
void ReloadSettings();
void DataLoaded(const QUrl& url, MoodbarPipeline* pipeline);
void ColorsLoaded(const QUrl& url, QFuture<ColorVector> future);
void ImageLoaded(const QUrl& url, QFuture<QImage> future);
private:
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 StartLoadingData(const QUrl& url, Data* data);
void StartLoadingColors(const QUrl& url, const QByteArray& bytes, Data* data);
void StartLoadingImage(const QUrl& url, Data* data);
bool RemoveFromCacheIfIndexesInvalid(const QUrl& url, Data* data);
void ReloadAllColors();
private:
Application* app_;
PlaylistView* view_;
QCache<QUrl, Data> data_;
MoodbarRenderer::MoodbarStyle style_;
};
#endif // MOODBARITEMDELEGATE_H

View File

@@ -0,0 +1,203 @@
/* This file was part of Clementine.
Copyright 2012, David Sansome <me@davidsansome.com>
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include "moodbarloader.h"
#include <memory>
#include <QObject>
#include <QThread>
#include <QCoreApplication>
#include <QStandardPaths>
#include <QIODevice>
#include <QDir>
#include <QFileInfo>
#include <QNetworkDiskCache>
#include <QTimer>
#include <QString>
#include <QUrl>
#include "core/application.h"
#include "core/closure.h"
#include "core/logging.h"
#include "moodbarpipeline.h"
#include "settings/moodbarsettingspage.h"
#ifdef Q_OS_WIN32
# include <windows.h>
#endif
using std::unique_ptr;
MoodbarLoader::MoodbarLoader(Application* app, QObject* parent)
: QObject(parent),
cache_(new QNetworkDiskCache(this)),
thread_(new QThread(this)),
kMaxActiveRequests(qMax(1, QThread::idealThreadCount() / 2)),
enabled_(false),
save_(false) {
cache_->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/moodbar");
cache_->setMaximumCacheSize(60 * 1024 * 1024); // 60MB - enough for 20,000 moodbars
connect(app, SIGNAL(SettingsChanged()), SLOT(ReloadSettings()));
ReloadSettings();
}
MoodbarLoader::~MoodbarLoader() {
thread_->quit();
thread_->wait(1000);
}
void MoodbarLoader::ReloadSettings() {
QSettings s;
s.beginGroup(MoodbarSettingsPage::kSettingsGroup);
enabled_ = s.value("enabled", false).toBool();
save_ = s.value("save", false).toBool();
s.endGroup();
MaybeTakeNextRequest();
}
QStringList MoodbarLoader::MoodFilenames(const QString& song_filename) {
const QFileInfo file_info(song_filename);
const QString dir_path(file_info.dir().path());
const QString mood_filename = file_info.baseName() + ".mood";
return QStringList() << dir_path + "/." + mood_filename << dir_path + "/" + mood_filename;
}
MoodbarLoader::Result MoodbarLoader::Load(const QUrl& url, QByteArray* data, MoodbarPipeline** async_pipeline) {
if (url.scheme() != "file") {
return CannotLoad;
}
// Are we in the middle of loading this moodbar already?
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());
for (const QString& possible_mood_file : MoodFilenames(filename)) {
QFile f(possible_mood_file);
if (f.open(QIODevice::ReadOnly)) {
qLog(Info) << "Loading moodbar data from" << possible_mood_file;
*data = f.readAll();
return Loaded;
}
}
// Maybe it exists in the cache?
std::unique_ptr<QIODevice> cache_device(cache_->data(url));
if (cache_device) {
qLog(Info) << "Loading cached moodbar data for" << filename;
*data = cache_device->readAll();
if (!data->isEmpty()) {
return Loaded;
}
}
if (!thread_->isRunning()) thread_->start(QThread::IdlePriority);
// There was no existing file, analyze the audio file and create one.
MoodbarPipeline* pipeline = new MoodbarPipeline(url);
pipeline->moveToThread(thread_);
NewClosure(pipeline, SIGNAL(Finished(bool)), this, SLOT(RequestFinished(MoodbarPipeline*, QUrl)), pipeline, url);
requests_[url] = pipeline;
queued_requests_ << url;
MaybeTakeNextRequest();
*async_pipeline = pipeline;
return WillLoadAsync;
}
void MoodbarLoader::MaybeTakeNextRequest() {
Q_ASSERT(QThread::currentThread() == qApp->thread());
if (active_requests_.count() >= kMaxActiveRequests || queued_requests_.isEmpty() || !enabled_) {
return;
}
const QUrl url = queued_requests_.takeFirst();
active_requests_ << url;
qLog(Info) << "Creating moodbar data for" << url.toLocalFile();
QMetaObject::invokeMethod(requests_[url], "Start", Qt::QueuedConnection);
}
void MoodbarLoader::RequestFinished(MoodbarPipeline* request, const QUrl& url) {
Q_ASSERT(QThread::currentThread() == qApp->thread());
if (request->success()) {
qLog(Info) << "Moodbar data generated successfully for" << url.toLocalFile();
// Save the data in the cache
QNetworkCacheMetaData metadata;
metadata.setUrl(url);
QIODevice* cache_file = cache_->prepare(metadata);
if (cache_file) {
cache_file->write(request->data());
cache_->insert(cache_file);
}
// Save the data alongside the original as well if we're configured to.
if (save_) {
const QString mood_filename(MoodFilenames(url.toLocalFile())[0]);
QFile mood_file(mood_filename);
if (mood_file.open(QIODevice::WriteOnly)) {
mood_file.write(request->data());
#ifdef Q_OS_WIN32
if (!SetFileAttributes((LPCTSTR)mood_filename.utf16(), FILE_ATTRIBUTE_HIDDEN)) {
qLog(Warning) << "Error setting hidden attribute for file" << mood_filename;
}
#endif
}
else {
qLog(Warning) << "Error opening mood file for writing" << mood_filename;
}
}
}
// Remove the request from the active list and delete it
requests_.remove(url);
active_requests_.remove(url);
QTimer::singleShot(1000, request, SLOT(deleteLater()));
MaybeTakeNextRequest();
}

View File

@@ -0,0 +1,79 @@
/* This file was part of Clementine.
Copyright 2012, David Sansome <me@davidsansome.com>
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MOODBARLOADER_H
#define MOODBARLOADER_H
#include <QObject>
#include <QThread>
#include <QList>
#include <QMap>
#include <QSet>
#include <QByteArray>
#include <QStringList>
#include <QUrl>
#include <QNetworkDiskCache>
class Application;
class MoodbarPipeline;
class MoodbarLoader : public QObject {
Q_OBJECT
public:
MoodbarLoader(Application* app, QObject* parent = nullptr);
~MoodbarLoader();
enum Result {
// The URL isn't a local file or the moodbar plugin was not available -
// moodbar data can never be loaded.
CannotLoad,
// Moodbar data was loaded and returned.
Loaded,
// Moodbar data will be loaded in the background, a MoodbarPipeline* was
// was returned that you can connect to the Finished() signal on.
WillLoadAsync
};
Result Load(const QUrl& url, QByteArray* data, MoodbarPipeline** async_pipeline);
private slots:
void ReloadSettings();
void RequestFinished(MoodbarPipeline* request, const QUrl& filename);
void MaybeTakeNextRequest();
private:
static QStringList MoodFilenames(const QString& song_filename);
private:
QNetworkDiskCache* cache_;
QThread* thread_;
const int kMaxActiveRequests;
QMap<QUrl, MoodbarPipeline*> requests_;
QList<QUrl> queued_requests_;
QSet<QUrl> active_requests_;
bool enabled_;
bool save_;
};
#endif // MOODBARLOADER_H

View File

@@ -0,0 +1,227 @@
/* This file was part of Clementine.
Copyright 2012, David Sansome <me@davidsansome.com>
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include "moodbarpipeline.h"
#include <QObject>
#include <QCoreApplication>
#include <QThread>
#include <QString>
#include <QUrl>
#include "core/logging.h"
#include "core/signalchecker.h"
#include "core/timeconstants.h"
#include "core/utilities.h"
#include "moodbar/moodbarbuilder.h"
#include "ext/gstmoodbar/gstfastspectrum.h"
bool MoodbarPipeline::sIsAvailable = false;
const int MoodbarPipeline::kBands = 128;
MoodbarPipeline::MoodbarPipeline(const QUrl& local_filename)
: QObject(nullptr),
local_filename_(local_filename),
pipeline_(nullptr),
convert_element_(nullptr),
success_(false),
running_(false) {}
MoodbarPipeline::~MoodbarPipeline() { Cleanup(); }
bool MoodbarPipeline::IsAvailable() {
if (!sIsAvailable) {
GstElementFactory* factory = gst_element_factory_find("fftwspectrum");
if (!factory) {
return false;
}
gst_object_unref(factory);
sIsAvailable = true;
}
return sIsAvailable;
}
GstElement* MoodbarPipeline::CreateElement(const QString& factory_name) {
GstElement* ret = gst_element_factory_make(factory_name.toLatin1().constData(), nullptr);
if (ret) {
gst_bin_add(GST_BIN(pipeline_), ret);
}
else {
qLog(Warning) << "Unable to create gstreamer element" << factory_name;
}
return ret;
}
void MoodbarPipeline::Start() {
Q_ASSERT(QThread::currentThread() != qApp->thread());
Utilities::SetThreadIOPriority(Utilities::IOPRIO_CLASS_IDLE);
if (pipeline_) {
return;
}
pipeline_ = gst_pipeline_new("moodbar-pipeline");
GstElement* decodebin = CreateElement("uridecodebin");
convert_element_ = CreateElement("audioconvert");
GstElement* spectrum = CreateElement("fastspectrum");
GstElement* fakesink = CreateElement("fakesink");
if (!decodebin || !convert_element_ || !spectrum || !fakesink) {
pipeline_ = nullptr;
emit Finished(false);
return;
}
// Join them together
if (!gst_element_link(convert_element_, spectrum) || !gst_element_link(spectrum, fakesink)) {
qLog(Error) << "Failed to link elements";
pipeline_ = nullptr;
emit Finished(false);
return;
}
builder_.reset(new MoodbarBuilder);
// Set properties
g_object_set(decodebin, "uri", local_filename_.toEncoded().constData(), nullptr);
g_object_set(spectrum, "bands", kBands, nullptr);
GstFastSpectrum* fast_spectrum = GST_FASTSPECTRUM(spectrum);
fast_spectrum->output_callback = [this](double* magnitudes, int size) { builder_->AddFrame(magnitudes, size); };
// Connect signals
CHECKED_GCONNECT(decodebin, "pad-added", &NewPadCallback, this);
GstBus* bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline_));
gst_bus_set_sync_handler(bus, BusCallbackSync, this, nullptr);
gst_object_unref(bus);
// Start playing
running_ = true;
gst_element_set_state(pipeline_, GST_STATE_PLAYING);
}
void MoodbarPipeline::ReportError(GstMessage* msg) {
GError* error;
gchar* debugs;
gst_message_parse_error(msg, &error, &debugs);
QString message = QString::fromLocal8Bit(error->message);
g_error_free(error);
free(debugs);
qLog(Error) << "Error processing" << local_filename_ << ":" << message;
}
void MoodbarPipeline::NewPadCallback(GstElement*, GstPad* pad, gpointer data) {
MoodbarPipeline* self = reinterpret_cast<MoodbarPipeline*>(data);
if (!self->running_) {
qLog(Warning) << "Received gstreamer callback after pipeline has stopped.";
return;
}
GstPad* const audiopad = gst_element_get_static_pad(self->convert_element_, "sink");
if (GST_PAD_IS_LINKED(audiopad)) {
qLog(Warning) << "audiopad is already linked, unlinking old pad";
gst_pad_unlink(audiopad, GST_PAD_PEER(audiopad));
}
gst_pad_link(pad, audiopad);
gst_object_unref(audiopad);
int rate = 0;
GstCaps* caps = gst_pad_get_current_caps(pad);
GstStructure* structure = gst_caps_get_structure(caps, 0);
gst_structure_get_int(structure, "rate", &rate);
gst_caps_unref(caps);
if (self->builder_)
self->builder_->Init(kBands, rate);
else
qLog(Error) << "Builder does not exist";
}
GstBusSyncReply MoodbarPipeline::BusCallbackSync(GstBus*, GstMessage* msg, gpointer data) {
MoodbarPipeline* self = reinterpret_cast<MoodbarPipeline*>(data);
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_EOS:
self->Stop(true);
break;
case GST_MESSAGE_ERROR:
self->ReportError(msg);
self->Stop(false);
break;
default:
break;
}
return GST_BUS_PASS;
}
void MoodbarPipeline::Stop(bool success) {
success_ = success;
running_ = false;
if (builder_ != nullptr) {
data_ = builder_->Finish(1000);
builder_.reset();
}
emit Finished(success);
}
void MoodbarPipeline::Cleanup() {
Q_ASSERT(QThread::currentThread() == thread());
Q_ASSERT(QThread::currentThread() != qApp->thread());
running_ = false;
if (pipeline_) {
GstBus* bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline_));
gst_bus_set_sync_handler(bus, nullptr, nullptr, nullptr);
gst_object_unref(bus);
gst_element_set_state(pipeline_, GST_STATE_NULL);
gst_object_unref(pipeline_);
pipeline_ = nullptr;
}
}

View File

@@ -0,0 +1,79 @@
/* This file was part of Clementine.
Copyright 2012, David Sansome <me@davidsansome.com>
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MOODBARPIPELINE_H
#define MOODBARPIPELINE_H
#include <QObject>
#include <QByteArray>
#include <QString>
#include <QUrl>
#include <gst/gst.h>
#include <gst/app/gstappsink.h>
#include <memory>
class MoodbarBuilder;
// Creates moodbar data for a single local music file.
class MoodbarPipeline : public QObject {
Q_OBJECT
public:
MoodbarPipeline(const QUrl& local_filename);
~MoodbarPipeline();
static bool IsAvailable();
bool success() const { return success_; }
const QByteArray& data() const { return data_; }
public slots:
void Start();
signals:
void Finished(bool success);
private:
GstElement* CreateElement(const QString& factory_name);
void ReportError(GstMessage* message);
void Stop(bool success);
void Cleanup();
static void NewPadCallback(GstElement*, GstPad* pad, gpointer data);
static GstFlowReturn NewBufferCallback(GstAppSink* app_sink, gpointer self);
static gboolean BusCallback(GstBus*, GstMessage* msg, gpointer data);
static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage* msg, gpointer data);
private:
static bool sIsAvailable;
static const int kBands;
QUrl local_filename_;
GstElement* pipeline_;
GstElement* convert_element_;
std::unique_ptr<MoodbarBuilder> builder_;
bool success_;
bool running_;
QByteArray data_;
};
#endif // MOODBARPIPELINE_H

View File

@@ -0,0 +1,397 @@
/* This file was part of Clementine.
Copyright 2012, David Sansome <me@davidsansome.com>
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QProxyStyle>
#include <QSettings>
#include <QMenu>
#include <QPixmap>
#include <QPainter>
#include <QSlider>
#include <QStyleOptionComplex>
#include <QStyleOptionSlider>
#include <QTimeLine>
#include <QEvent>
#include <QContextMenuEvent>
#include "core/application.h"
#include "core/logging.h"
#include "moodbarproxystyle.h"
#include "settings/moodbarsettingspage.h"
const int MoodbarProxyStyle::kMarginSize = 3;
const int MoodbarProxyStyle::kBorderSize = 1;
const int MoodbarProxyStyle::kArrowWidth = 17;
const int MoodbarProxyStyle::kArrowHeight = 13;
MoodbarProxyStyle::MoodbarProxyStyle(Application* app, QSlider* slider)
: QProxyStyle(slider->style()),
app_(app),
slider_(slider),
enabled_(true),
moodbar_style_(MoodbarRenderer::Style_Normal),
state_(MoodbarOff),
fade_timeline_(new QTimeLine(1000, this)),
moodbar_colors_dirty_(true),
moodbar_pixmap_dirty_(true),
context_menu_(nullptr),
show_moodbar_action_(nullptr),
style_action_group_(nullptr) {
slider->setStyle(this);
slider->installEventFilter(this);
connect(fade_timeline_, SIGNAL(valueChanged(qreal)), SLOT(FaderValueChanged(qreal)));
connect(app, SIGNAL(SettingsChanged()), SLOT(ReloadSettings()));
ReloadSettings();
}
void MoodbarProxyStyle::ReloadSettings() {
QSettings s;
s.beginGroup(MoodbarSettingsPage::kSettingsGroup);
// Get the enabled/disabled setting, and start the timelines if there's a change.
enabled_ = s.value("show", false).toBool();
NextState();
// Get the style, and redraw if there's a change.
MoodbarRenderer::MoodbarStyle new_style = static_cast<MoodbarRenderer::MoodbarStyle>(s.value("style", MoodbarRenderer::Style_Normal).toInt());
s.endGroup();
if (new_style != moodbar_style_) {
moodbar_style_ = new_style;
moodbar_colors_dirty_ = true;
slider_->update();
}
}
void MoodbarProxyStyle::SetMoodbarData(const QByteArray& data) {
data_ = data;
moodbar_colors_dirty_ = true; // Redraw next time
NextState();
}
void MoodbarProxyStyle::SetMoodbarEnabled(bool enabled) {
enabled_ = enabled;
// Save the enabled setting.
QSettings s;
s.beginGroup(MoodbarSettingsPage::kSettingsGroup);
s.setValue("show", enabled);
s.endGroup();
app_->ReloadSettings();
}
void MoodbarProxyStyle::NextState() {
const bool visible = enabled_ && !data_.isEmpty();
// While the regular slider should stay at the standard size (Fixed),
// moodbars should use all available space (MinimumExpanding).
slider_->setSizePolicy(QSizePolicy::Expanding, visible ? QSizePolicy::MinimumExpanding : QSizePolicy::Fixed);
slider_->updateGeometry();
if (show_moodbar_action_) {
show_moodbar_action_->setChecked(enabled_);
}
if ((visible && (state_ == MoodbarOn || state_ == FadingToOn)) || (!visible && (state_ == MoodbarOff || state_ == FadingToOff))) {
return;
}
const QTimeLine::Direction direction = visible ? QTimeLine::Forward : QTimeLine::Backward;
if (state_ == MoodbarOn || state_ == MoodbarOff) {
// Start the fade from the beginning.
fade_timeline_->setDirection(direction);
fade_timeline_->start();
fade_source_ = QPixmap();
fade_target_ = QPixmap();
}
else {
// Stop an existing fade and start fading the other direction from the
// same place.
fade_timeline_->stop();
fade_timeline_->setDirection(direction);
fade_timeline_->resume();
}
state_ = visible ? FadingToOn : FadingToOff;
}
void MoodbarProxyStyle::FaderValueChanged(qreal value) { slider_->update(); }
bool MoodbarProxyStyle::eventFilter(QObject* object, QEvent* event) {
if (object == slider_) {
switch (event->type()) {
case QEvent::Resize:
// The widget was resized, we've got to render a new pixmap.
moodbar_pixmap_dirty_ = true;
break;
case QEvent::ContextMenu:
ShowContextMenu(static_cast<QContextMenuEvent*>(event)->globalPos());
return true;
default:
break;
}
}
return QProxyStyle::eventFilter(object, event);
}
void MoodbarProxyStyle::drawComplexControl(ComplexControl control, const QStyleOptionComplex* option, QPainter* painter, const QWidget* widget) const {
if (control != CC_Slider || widget != slider_) {
QProxyStyle::drawComplexControl(control, option, painter, widget);
return;
}
const_cast<MoodbarProxyStyle*>(this)->Render(control, qstyleoption_cast<const QStyleOptionSlider*>(option), painter, widget);
}
void MoodbarProxyStyle::Render(ComplexControl control, const QStyleOptionSlider* option, QPainter* painter, const QWidget* widget) {
const qreal fade_value = fade_timeline_->currentValue();
// Have we finished fading?
if (state_ == FadingToOn && fade_value == 1.0) {
state_ = MoodbarOn;
}
else if (state_ == FadingToOff && fade_value == 0.0) {
state_ = MoodbarOff;
}
switch (state_) {
case FadingToOn:
case FadingToOff:
// Update the cached pixmaps if necessary
if (fade_source_.isNull()) {
// Draw the normal slider into the fade source pixmap.
fade_source_ = QPixmap(option->rect.size());
fade_source_.fill(option->palette.color(QPalette::Active, QPalette::Background));
QPainter p(&fade_source_);
QStyleOptionSlider opt_copy(*option);
opt_copy.rect.moveTo(0, 0);
QProxyStyle::drawComplexControl(control, &opt_copy, &p, widget);
p.end();
}
if (fade_target_.isNull()) {
if (state_ == FadingToOn) {
EnsureMoodbarRendered(option);
}
fade_target_ = moodbar_pixmap_;
QPainter p(&fade_target_);
DrawArrow(option, &p);
p.end();
}
// Blend the pixmaps into each other
painter->drawPixmap(option->rect, fade_source_);
painter->setOpacity(fade_value);
painter->drawPixmap(option->rect, fade_target_);
painter->setOpacity(1.0);
break;
case MoodbarOff:
// It's a normal slider widget.
QProxyStyle::drawComplexControl(control, option, painter, widget);
break;
case MoodbarOn:
EnsureMoodbarRendered(option);
painter->drawPixmap(option->rect, moodbar_pixmap_);
DrawArrow(option, painter);
break;
}
}
void MoodbarProxyStyle::EnsureMoodbarRendered(const QStyleOptionSlider* opt) {
if (moodbar_colors_dirty_) {
moodbar_colors_ = MoodbarRenderer::Colors(data_, moodbar_style_, slider_->palette());
moodbar_colors_dirty_ = false;
moodbar_pixmap_dirty_ = true;
}
if (moodbar_pixmap_dirty_) {
moodbar_pixmap_ = MoodbarPixmap(moodbar_colors_, slider_->size(), slider_->palette(), opt);
moodbar_pixmap_dirty_ = false;
}
}
QRect MoodbarProxyStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex* opt, SubControl sc, const QWidget* widget) const {
if (cc != QStyle::CC_Slider || widget != slider_) {
return QProxyStyle::subControlRect(cc, opt, sc, widget);
}
switch (state_) {
case MoodbarOff:
case FadingToOff:
break;
case MoodbarOn:
case FadingToOn:
switch (sc) {
case SC_SliderGroove:
return opt->rect.adjusted(kMarginSize, kMarginSize, -kMarginSize, -kMarginSize);
case SC_SliderHandle: {
const QStyleOptionSlider* slider_opt = qstyleoption_cast<const QStyleOptionSlider*>(opt);
int x_offset = 0;
/* slider_opt->{maximum,minimum} can have the value 0 (their default
values), so this check avoids a division by 0. */
if (slider_opt->maximum > slider_opt->minimum) {
qint64 slider_delta = slider_opt->sliderValue - slider_opt->minimum;
qint64 slider_range = slider_opt->maximum - slider_opt->minimum;
int rectangle_effective_width = opt->rect.width() - kArrowWidth;
qint64 x = slider_delta * rectangle_effective_width / slider_range;
x_offset = static_cast<int>(x);
}
return QRect(QPoint(opt->rect.left() + x_offset, opt->rect.top()), QSize(kArrowWidth, kArrowHeight));
}
default:
break;
}
}
return QProxyStyle::subControlRect(cc, opt, sc, widget);
}
void MoodbarProxyStyle::DrawArrow(const QStyleOptionSlider* option, QPainter* painter) const {
// Get the dimensions of the arrow
const QRect rect = subControlRect(CC_Slider, option, SC_SliderHandle, slider_);
// Make a polygon
QPolygon poly;
poly << rect.topLeft() << rect.topRight() << QPoint(rect.center().x(), rect.bottom());
// Draw it
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
painter->translate(0.5, 0.5);
painter->setPen(Qt::black);
painter->setBrush(slider_->palette().brush(QPalette::Active, QPalette::Base));
painter->drawPolygon(poly);
painter->restore();
}
QPixmap MoodbarProxyStyle::MoodbarPixmap(const ColorVector& colors, const QSize& size, const QPalette& palette, const QStyleOptionSlider* opt) {
QRect rect(QPoint(0, 0), size);
QRect border_rect(rect);
border_rect.adjust(kMarginSize, kMarginSize, -kMarginSize, -kMarginSize);
QRect inner_rect(border_rect);
inner_rect.adjust(kBorderSize, kBorderSize, -kBorderSize, -kBorderSize);
QPixmap ret(size);
QPainter p(&ret);
// Draw the moodbar
MoodbarRenderer::Render(colors, &p, inner_rect);
// Draw the border
p.setPen(QPen(Qt::black, kBorderSize, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
p.drawRect(border_rect.adjusted(0, 0, -1, -1));
// Draw the outer bit
p.setPen(QPen(palette.brush(QPalette::Active, QPalette::Background), kMarginSize, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
p.drawRect(rect.adjusted(1, 1, -2, -2));
p.end();
return ret;
}
void MoodbarProxyStyle::ShowContextMenu(const QPoint& pos) {
if (!context_menu_) {
context_menu_ = new QMenu(slider_);
show_moodbar_action_ = context_menu_->addAction(tr("Show moodbar"), this, SLOT(SetMoodbarEnabled(bool)));
show_moodbar_action_->setCheckable(true);
show_moodbar_action_->setChecked(enabled_);
QMenu* styles_menu = context_menu_->addMenu(tr("Moodbar style"));
style_action_group_ = new QActionGroup(styles_menu);
for (int i = 0; i < MoodbarRenderer::StyleCount; ++i) {
const MoodbarRenderer::MoodbarStyle style = MoodbarRenderer::MoodbarStyle(i);
QAction* action = style_action_group_->addAction(MoodbarRenderer::StyleName(style));
action->setCheckable(true);
action->setData(i);
}
styles_menu->addActions(style_action_group_->actions());
connect(styles_menu, SIGNAL(triggered(QAction*)), SLOT(ChangeStyle(QAction*)));
}
// Update the currently selected style
for (QAction* action : style_action_group_->actions()) {
if (MoodbarRenderer::MoodbarStyle(action->data().toInt()) == moodbar_style_) {
action->setChecked(true);
break;
}
}
context_menu_->popup(pos);
}
void MoodbarProxyStyle::ChangeStyle(QAction* action) {
QSettings s;
s.beginGroup(MoodbarSettingsPage::kSettingsGroup);
s.setValue("style", action->data().toInt());
s.endGroup();
app_->ReloadSettings();
}

View File

@@ -0,0 +1,103 @@
/* This file was part of Clementine.
Copyright 2012, David Sansome <me@davidsansome.com>
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MOODBARPROXYSTYLE_H
#define MOODBARPROXYSTYLE_H
#include <QProxyStyle>
#include <QTimeLine>
#include <QByteArray>
#include <QPixmap>
#include <QPainter>
#include <QSlider>
#include <QMenu>
#include <QActionGroup>
#include <QStyleOptionSlider>
#include <QEvent>
#include "moodbarrenderer.h"
class Application;
class MoodbarProxyStyle : public QProxyStyle {
Q_OBJECT
public:
MoodbarProxyStyle(Application* app, QSlider* slider);
// QProxyStyle
void drawComplexControl(ComplexControl control, const QStyleOptionComplex* option, QPainter* painter, const QWidget* widget) const;
QRect subControlRect(ComplexControl cc, const QStyleOptionComplex* opt, SubControl sc, const QWidget* widget) const;
// QObject
bool eventFilter(QObject* object, QEvent* event);
public slots:
// An empty byte array means there's no moodbar, so just show a normal slider.
void SetMoodbarData(const QByteArray& data);
// If the moodbar is disabled then a normal slider will always be shown.
void SetMoodbarEnabled(bool enabled);
private:
static const int kMarginSize;
static const int kBorderSize;
static const int kArrowWidth;
static const int kArrowHeight;
enum State { MoodbarOn, MoodbarOff, FadingToOn, FadingToOff };
private:
void NextState();
void Render(ComplexControl control, const QStyleOptionSlider* option, QPainter* painter, const QWidget* widget);
void EnsureMoodbarRendered(const QStyleOptionSlider* opt);
void DrawArrow(const QStyleOptionSlider* option, QPainter* painter) const;
void ShowContextMenu(const QPoint& pos);
QPixmap MoodbarPixmap(const ColorVector& colors, const QSize& size, const QPalette& palette, const QStyleOptionSlider* opt);
private slots:
void ReloadSettings();
void FaderValueChanged(qreal value);
void ChangeStyle(QAction* action);
private:
Application* app_;
QSlider* slider_;
bool enabled_;
QByteArray data_;
MoodbarRenderer::MoodbarStyle moodbar_style_;
State state_;
QTimeLine* fade_timeline_;
QPixmap fade_source_;
QPixmap fade_target_;
bool moodbar_colors_dirty_;
bool moodbar_pixmap_dirty_;
ColorVector moodbar_colors_;
QPixmap moodbar_pixmap_;
QMenu* context_menu_;
QAction* show_moodbar_action_;
QActionGroup* style_action_group_;
};
#endif // MOODBARPROXYSTYLE_H

View File

@@ -0,0 +1,178 @@
/* This file was part of Clementine.
Copyright 2012, David Sansome <me@davidsansome.com>
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include "moodbarrenderer.h"
#include <QByteArray>
#include <QString>
#include <QImage>
#include <QPainter>
#include <QPalette>
#include <QColor>
#include "core/arraysize.h"
const int MoodbarRenderer::kNumHues = 12;
ColorVector MoodbarRenderer::Colors(const QByteArray& data, MoodbarStyle style, const QPalette& palette) {
const int samples = data.size() / 3;
// Set some parameters based on the moodbar style
StyleProperties properties;
switch (style) {
case Style_Angry:
properties = StyleProperties(samples / 360 * 9, 45, -45, 200, 100);
break;
case Style_Frozen:
properties = StyleProperties(samples / 360 * 1, 140, 160, 50, 100);
break;
case Style_Happy:
properties = StyleProperties(samples / 360 * 2, 0, 359, 150, 250);
break;
case Style_Normal:
properties = StyleProperties(samples / 360 * 3, 0, 359, 100, 100);
break;
case Style_SystemPalette:
default: {
const QColor highlight_color(palette.color(QPalette::Active, QPalette::Highlight));
properties.threshold_ = samples / 360 * 3;
properties.range_start_ = (highlight_color.hsvHue() - 20 + 360) % 360;
properties.range_delta_ = 20;
properties.sat_ = highlight_color.hsvSaturation();
properties.val_ = highlight_color.value() / 2;
}
}
const unsigned char* data_p = reinterpret_cast<const unsigned char*>(data.constData());
int hue_distribution[360];
int total = 0;
memset(hue_distribution, 0, sizeof(hue_distribution));
ColorVector colors;
// Read the colors, keeping track of some histograms
for (int i = 0; i < samples; ++i) {
QColor color;
color.setRed(int(*data_p++));
color.setGreen(int(*data_p++));
color.setBlue(int(*data_p++));
colors << color;
const int hue = qMax(0, color.hue());
if (hue_distribution[hue]++ == properties.threshold_) {
total++;
}
}
total = qMax(total, 1);
// Remap the hue values to be between rangeStart and
// rangeStart + rangeDelta. Every time we see an input hue
// above the threshold, increment the output hue by
// (1/total) * rangeDelta.
for (int i = 0, n = 0; i < 360; i++) {
hue_distribution[i] = ((hue_distribution[i] > properties.threshold_ ? n++ : n) * properties.range_delta_ / total + properties.range_start_) % 360;
}
// Now huedist is a hue mapper: huedist[h] is the new hue value
// for a bar with hue h
for (ColorVector::iterator it = colors.begin(); it != colors.end(); ++it) {
const int hue = qMax(0, it->hue());
*it = QColor::fromHsv(qBound(0, hue_distribution[hue], 359), qBound(0, it->saturation() * properties.sat_ / 100, 255), qBound(0, it->value() * properties.val_ / 100, 255));
}
return colors;
}
void MoodbarRenderer::Render(const ColorVector& colors, QPainter* p, const QRect& rect) {
// Sample the colors and map them to screen pixels.
ColorVector screen_colors;
for (int x = 0; x < rect.width(); ++x) {
int r = 0;
int g = 0;
int b = 0;
int start = x * colors.size() / rect.width();
int end = (x + 1) * colors.size() / rect.width();
if (start == end) end = qMin(start + 1, colors.size() - 1);
for (int j = start; j < end; j++) {
r += colors[j].red();
g += colors[j].green();
b += colors[j].blue();
}
const int n = qMax(1, end - start);
screen_colors.append(QColor(r / n, g / n, b / n));
}
// Draw the actual moodbar.
for (int x = 0; x < rect.width(); x++) {
int h, s, v;
screen_colors[x].getHsv(&h, &s, &v);
for (int y = 0; y <= rect.height() / 2; y++) {
float coeff = float(y) / float(rect.height() / 2);
float coeff2 = 1.0f - ((1.0f - coeff) * (1.0f - coeff));
coeff = 1.0f - (1.0f - coeff) / 2.0f;
coeff2 = 1.f - (1.f - coeff2) / 2.0f;
p->setPen(QColor::fromHsv(h, qBound(0, int(float(s) * coeff), 255), qBound(0, int(255.f - (255.f - float(v)) * coeff2), 255)));
p->drawPoint(rect.left() + x, rect.top() + y);
p->drawPoint(rect.left() + x, rect.top() + rect.height() - 1 - y);
}
}
}
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;
}
QString MoodbarRenderer::StyleName(MoodbarStyle style) {
switch (style) {
case Style_Normal:
return QObject::tr("Normal");
case Style_Angry:
return QObject::tr("Angry");
case Style_Frozen:
return QObject::tr("Frozen");
case Style_Happy:
return QObject::tr("Happy");
case Style_SystemPalette:
return QObject::tr("System colors");
default:
return QString();
}
}

View File

@@ -0,0 +1,73 @@
/* This file was part of Clementine.
Copyright 2012, David Sansome <me@davidsansome.com>
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MOODBARRENDERER_H
#define MOODBARRENDERER_H
#include <QByteArray>
#include <QImage>
#include <QPixmap>
#include <QMetaType>
#include <QVector>
#include <QColor>
#include <QPainter>
#include <QPalette>
typedef QVector<QColor> ColorVector;
class MoodbarRenderer {
public:
// These values are persisted. Remember to change moodbarsettingspage.ui when changing them.
enum MoodbarStyle {
Style_Normal = 0,
Style_Angry,
Style_Frozen,
Style_Happy,
Style_SystemPalette,
StyleCount
};
static const int kNumHues;
static QString StyleName(MoodbarStyle style);
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();
struct StyleProperties {
StyleProperties(int threshold = 0, int range_start = 0, int range_delta = 0, int sat = 0, int val = 0)
: threshold_(threshold),
range_start_(range_start),
range_delta_(range_delta),
sat_(sat),
val_(val) {}
int threshold_;
int range_start_;
int range_delta_;
int sat_;
int val_;
};
};
Q_DECLARE_METATYPE(QVector<QColor>)
#endif // MOODBARRENDERER_H

View File

@@ -365,6 +365,12 @@ QVariant Playlist::data(const QModelIndex &index, int role) const {
}
#ifdef HAVE_MOODBAR
void Playlist::MoodbarUpdated(const QModelIndex& index) {
emit dataChanged(index.sibling(index.row(), Column_Mood), index.sibling(index.row(), Column_Mood));
}
#endif
bool Playlist::setData(const QModelIndex &index, const QVariant &value, int role) {
int row = index.row();
@@ -1183,6 +1189,7 @@ QString Playlist::column_name(Column column) {
case Column_Comment: return tr("Comment");
case Column_Source: return tr("Source");
case Column_Mood: return tr("Mood");
default: qLog(Error) << "No such column" << column;;
}
return "";

View File

@@ -124,6 +124,7 @@ class Playlist : public QAbstractListModel {
Column_Comment,
Column_Grouping,
Column_Source,
Column_Mood,
ColumnCount
};
@@ -251,6 +252,11 @@ class Playlist : public QAbstractListModel {
// Unregisters a SongInsertVetoListener object.
void RemoveSongInsertVetoListener(SongInsertVetoListener *listener);
// Just emits the dataChanged() signal so the mood column is repainted.
#ifdef HAVE_MOODBAR
void MoodbarUpdated(const QModelIndex& index);
#endif
// QAbstractListModel
int rowCount(const QModelIndex& = QModelIndex()) const { return items_.count(); }
int columnCount(const QModelIndex& = QModelIndex()) const { return ColumnCount; }

View File

@@ -104,6 +104,12 @@ void PlaylistHeader::contextMenuEvent(QContextMenuEvent *e) {
void PlaylistHeader::AddColumnAction(int index) {
#ifndef HAVE_MOODBAR
if (index == Playlist::Column_Mood) {
return;
}
#endif
QString title(model()->headerData(index, Qt::Horizontal).toString());
QAction *action = menu_->addAction(title, show_mapper_, SLOT(map()));

View File

@@ -77,6 +77,10 @@
#include "settings/appearancesettingspage.h"
#include "settings/playlistsettingspage.h"
#ifdef HAVE_MOODBAR
# include "moodbar/moodbaritemdelegate.h"
#endif
using std::sort;
const int PlaylistView::kGlowIntensitySteps = 24;
@@ -244,6 +248,10 @@ void PlaylistView::SetItemDelegates(CollectionBackend *backend) {
setItemDelegateForColumn(Playlist::Column_Source, new SongSourceDelegate(this));
#ifdef HAVE_MOODBAR
setItemDelegateForColumn(Playlist::Column_Mood, new MoodbarItemDelegate(app_, this, this));
#endif
}
void PlaylistView::SetPlaylist(Playlist *playlist) {
@@ -310,6 +318,7 @@ void PlaylistView::LoadGeometry() {
header_->HideSection(Playlist::Column_LastPlayed);
header_->HideSection(Playlist::Column_Comment);
header_->HideSection(Playlist::Column_Grouping);
header_->HideSection(Playlist::Column_Mood);
header_->moveSection(header_->visualIndex(Playlist::Column_Track), 0);
setting_initial_header_layout_ = true;

View File

@@ -0,0 +1,122 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QIODevice>
#include <QFile>
#include <QByteArray>
#include <QPixmap>
#include <QPainter>
#include <QSettings>
#include "core/application.h"
#include "core/mainwindow.h"
#include "core/iconloader.h"
#include "core/logging.h"
#include "settingsdialog.h"
#ifdef HAVE_MOODBAR
# include "moodbar/moodbarrenderer.h"
#endif
#include "moodbarsettingspage.h"
#include "ui_moodbarsettingspage.h"
const char *MoodbarSettingsPage::kSettingsGroup = "Moodbar";
const int MoodbarSettingsPage::kMoodbarPreviewWidth = 150;
const int MoodbarSettingsPage::kMoodbarPreviewHeight = 18;
MoodbarSettingsPage::MoodbarSettingsPage(SettingsDialog* dialog)
: SettingsPage(dialog),
ui_(new Ui_MoodbarSettingsPage),
initialised_(false)
{
ui_->setupUi(this);
setWindowIcon(IconLoader::Load("moodbar"));
Load();
}
MoodbarSettingsPage::~MoodbarSettingsPage() { delete ui_; }
void MoodbarSettingsPage::Load() {
QSettings s;
s.beginGroup(kSettingsGroup);
ui_->moodbar_enabled->setChecked(s.value("enabled", false).toBool());
ui_->moodbar_show->setChecked(s.value("show", false).toBool());
ui_->moodbar_style->setCurrentIndex(s.value("style", 0).toInt());
ui_->moodbar_save->setChecked(s.value("save", false).toBool());
s.endGroup();
InitMoodbarPreviews();
}
void MoodbarSettingsPage::Save() {
QSettings s;
s.beginGroup(kSettingsGroup);
s.setValue("enabled", ui_->moodbar_enabled->isChecked());
s.setValue("show", ui_->moodbar_show->isChecked());
s.setValue("style", ui_->moodbar_style->currentIndex());
s.setValue("save", ui_->moodbar_save->isChecked());
s.endGroup();
}
void MoodbarSettingsPage::Cancel() {}
void MoodbarSettingsPage::InitMoodbarPreviews() {
if (initialised_) return;
initialised_ = true;
const QSize preview_size(kMoodbarPreviewWidth, kMoodbarPreviewHeight);
ui_->moodbar_style->setIconSize(preview_size);
// Read the sample data
QFile file(":/mood/sample.mood");
if (!file.open(QIODevice::ReadOnly)) {
qLog(Warning) << "Unable to open moodbar sample file";
return;
}
QByteArray data(file.readAll());
// Render and set each preview
for (int i = 0; i < MoodbarRenderer::StyleCount; ++i) {
const MoodbarRenderer::MoodbarStyle style = MoodbarRenderer::MoodbarStyle(i);
const ColorVector colors = MoodbarRenderer::Colors(data, style, palette());
QPixmap pixmap(preview_size);
QPainter p(&pixmap);
MoodbarRenderer::Render(colors, &p, pixmap.rect());
p.end();
ui_->moodbar_style->addItem(MoodbarRenderer::StyleName(style));
ui_->moodbar_style->setItemData(i, pixmap, Qt::DecorationRole);
}
}

View File

@@ -0,0 +1,54 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MOODBARSETTINGSPAGE_H
#define MOODBARSETTINGSPAGE_H
#include "settingspage.h"
#include <QObject>
class Ui_MoodbarSettingsPage;
class MoodbarSettingsPage : public SettingsPage {
Q_OBJECT
public:
MoodbarSettingsPage(SettingsDialog* dialog);
~MoodbarSettingsPage();
static const char *kSettingsGroup;
void Load();
void Save();
void Cancel();
private:
static const int kMoodbarPreviewWidth;
static const int kMoodbarPreviewHeight;
void InitMoodbarPreviews();
Ui_MoodbarSettingsPage* ui_;
bool initialised_;
};
#endif // MOODBARSETTINGSPAGE_H

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MoodbarSettingsPage</class>
<widget class="QWidget" name="MoodbarSettingsPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>596</width>
<height>666</height>
</rect>
</property>
<property name="windowTitle">
<string>Moodbar</string>
</property>
<layout class="QVBoxLayout" name="layout_moodbarsettingspage">
<item>
<widget class="QGroupBox" name="moodbar_group">
<property name="title">
<string>Moodbar</string>
</property>
<layout class="QFormLayout" name="layout_moodbar">
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="moodbar_show">
<property name="text">
<string>Show a moodbar in the track progress bar</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_moodbar_style">
<property name="text">
<string>Moodbar style</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="moodbar_style"/>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="moodbar_save">
<property name="text">
<string>Save the .mood files directly in the songs folders</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="moodbar_enabled">
<property name="text">
<string>Enabled</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -65,6 +65,9 @@
#ifdef HAVE_TIDAL
# include "tidalsettingspage.h"
#endif
#ifdef HAVE_MOODBAR
# include "moodbarsettingspage.h"
#endif
#include "ui_settingsdialog.h"
@@ -133,6 +136,10 @@ SettingsDialog::SettingsDialog(Application *app, QWidget *parent)
AddPage(Page_GlobalShortcuts, new GlobalShortcutsSettingsPage(this), iface);
#endif
#ifdef HAVE_MOODBAR
AddPage(Page_Moodbar, new MoodbarSettingsPage(this), iface);
#endif
#if defined(HAVE_TIDAL)
QTreeWidgetItem *streaming = AddCategory(tr("Streaming"));
#endif

View File

@@ -83,6 +83,7 @@ class SettingsDialog : public QDialog {
Page_Proxy,
Page_Scrobbler,
Page_Tidal,
Page_Moodbar,
};
enum Role {

View File

@@ -35,11 +35,18 @@
#include "clickablelabel.h"
#include "tracksliderslider.h"
#ifdef HAVE_MOODBAR
# include "moodbar/moodbarproxystyle.h"
#endif
const char* TrackSlider::kSettingsGroup = "MainWindow";
TrackSlider::TrackSlider(QWidget* parent)
: QWidget(parent),
ui_(new Ui_TrackSlider),
#ifdef HAVE_MOODBAR
moodbar_style_(nullptr),
#endif
setting_value_(false),
show_remaining_time_(true),
slider_maximum_value_(0)
@@ -64,6 +71,9 @@ TrackSlider::~TrackSlider() {
}
void TrackSlider::SetApplication(Application* app) {
#ifdef HAVE_MOODBAR
moodbar_style_ = new MoodbarProxyStyle(app, ui_->slider);
#endif
}
void TrackSlider::UpdateLabelWidth() {

View File

@@ -34,6 +34,9 @@
class QEvent;
class Application;
#ifdef HAVE_MOODBAR
class MoodbarProxyStyle;
#endif
class Ui_TrackSlider;
class TrackSlider : public QWidget {
@@ -51,6 +54,11 @@ class TrackSlider : public QWidget {
// QObject
bool event(QEvent *);
#ifdef HAVE_MOODBAR
MoodbarProxyStyle *moodbar_style() const { return moodbar_style_; }
#endif
static const char* kSettingsGroup;
public slots:
@@ -75,6 +83,10 @@ class TrackSlider : public QWidget {
private:
Ui_TrackSlider* ui_;
#ifdef HAVE_MOODBAR
MoodbarProxyStyle* moodbar_style_;
#endif
bool setting_value_;
bool show_remaining_time_;
int slider_maximum_value_; //we cache it to avoid unnecessary updates