Reformat all non-3rd-party C/C++/Objective-C++.

Command line:
find src ext -regex '.*\.\(h\|cpp\|mm\)' -exec clang-format -i
 -style='{BasedOnStyle: Google, DerivePointerBinding: false}' {} \;
This commit is contained in:
John Maguire 2014-02-07 16:34:20 +01:00
parent acfc7e6d21
commit bebd781fdf
803 changed files with 22699 additions and 22831 deletions

View File

@ -18,7 +18,6 @@
// it is used by the Spotify blob which links against libspotify and is not GPL // it is used by the Spotify blob which links against libspotify and is not GPL
// compatible. // compatible.
#include <QCoreApplication> #include <QCoreApplication>
#include <QStringList> #include <QStringList>

View File

@ -25,15 +25,13 @@
#include <cstring> #include <cstring>
MediaPipeline::MediaPipeline(int port, quint64 length_msec) MediaPipeline::MediaPipeline(int port, quint64 length_msec)
: port_(port), : port_(port),
length_msec_(length_msec), length_msec_(length_msec),
accepting_data_(true), accepting_data_(true),
pipeline_(nullptr), pipeline_(nullptr),
appsrc_(nullptr), appsrc_(nullptr),
byte_rate_(1), byte_rate_(1),
offset_bytes_(0) offset_bytes_(0) {}
{
}
MediaPipeline::~MediaPipeline() { MediaPipeline::~MediaPipeline() {
if (pipeline_) { if (pipeline_) {
@ -43,8 +41,7 @@ MediaPipeline::~MediaPipeline() {
} }
bool MediaPipeline::Init(int sample_rate, int channels) { bool MediaPipeline::Init(int sample_rate, int channels) {
if (is_initialised()) if (is_initialised()) return false;
return false;
pipeline_ = gst_pipeline_new("pipeline"); pipeline_ = gst_pipeline_new("pipeline");
@ -54,10 +51,21 @@ bool MediaPipeline::Init(int sample_rate, int channels) {
tcpsink_ = gst_element_factory_make("tcpclientsink", nullptr); tcpsink_ = gst_element_factory_make("tcpclientsink", nullptr);
if (!pipeline_ || !appsrc_ || !tcpsink_) { if (!pipeline_ || !appsrc_ || !tcpsink_) {
if (pipeline_) { gst_object_unref(GST_OBJECT(pipeline_)); pipeline_ = nullptr; } if (pipeline_) {
if (appsrc_) { gst_object_unref(GST_OBJECT(appsrc_)); appsrc_ = nullptr; } gst_object_unref(GST_OBJECT(pipeline_));
if (gdppay) { gst_object_unref(GST_OBJECT(gdppay)); } pipeline_ = nullptr;
if (tcpsink_) { gst_object_unref(GST_OBJECT(tcpsink_)); tcpsink_ = nullptr; } }
if (appsrc_) {
gst_object_unref(GST_OBJECT(appsrc_));
appsrc_ = nullptr;
}
if (gdppay) {
gst_object_unref(GST_OBJECT(gdppay));
}
if (tcpsink_) {
gst_object_unref(GST_OBJECT(tcpsink_));
tcpsink_ = nullptr;
}
return false; return false;
} }
@ -73,7 +81,8 @@ bool MediaPipeline::Init(int sample_rate, int channels) {
// Try to send 5 seconds of audio in advance to initially fill Clementine's // Try to send 5 seconds of audio in advance to initially fill Clementine's
// buffer. // buffer.
g_object_set(G_OBJECT(tcpsink_), "ts-offset", qint64(-5 * kNsecPerSec), nullptr); g_object_set(G_OBJECT(tcpsink_), "ts-offset", qint64(-5 * kNsecPerSec),
nullptr);
// We know the time of each buffer // We know the time of each buffer
g_object_set(G_OBJECT(appsrc_), "format", GST_FORMAT_TIME, nullptr); g_object_set(G_OBJECT(appsrc_), "format", GST_FORMAT_TIME, nullptr);
@ -97,14 +106,11 @@ bool MediaPipeline::Init(int sample_rate, int channels) {
#endif #endif
// Set caps // Set caps
GstCaps* caps = gst_caps_new_simple("audio/x-raw-int", GstCaps* caps = gst_caps_new_simple(
"endianness", G_TYPE_INT, endianness, "audio/x-raw-int", "endianness", G_TYPE_INT, endianness, "signed",
"signed", G_TYPE_BOOLEAN, TRUE, G_TYPE_BOOLEAN, TRUE, "width", G_TYPE_INT, 16, "depth", G_TYPE_INT, 16,
"width", G_TYPE_INT, 16, "rate", G_TYPE_INT, sample_rate, "channels", G_TYPE_INT, channels,
"depth", G_TYPE_INT, 16, nullptr);
"rate", G_TYPE_INT, sample_rate,
"channels", G_TYPE_INT, channels,
nullptr);
gst_app_src_set_caps(appsrc_, caps); gst_app_src_set_caps(appsrc_, caps);
gst_caps_unref(caps); gst_caps_unref(caps);
@ -115,12 +121,12 @@ bool MediaPipeline::Init(int sample_rate, int channels) {
gst_app_src_set_size(appsrc_, bytes); gst_app_src_set_size(appsrc_, bytes);
// Ready to go // Ready to go
return gst_element_set_state(pipeline_, GST_STATE_PLAYING) != GST_STATE_CHANGE_FAILURE; return gst_element_set_state(pipeline_, GST_STATE_PLAYING) !=
GST_STATE_CHANGE_FAILURE;
} }
void MediaPipeline::WriteData(const char* data, qint64 length) { void MediaPipeline::WriteData(const char* data, qint64 length) {
if (!is_initialised()) if (!is_initialised()) return;
return;
GstBuffer* buffer = gst_buffer_new_and_alloc(length); GstBuffer* buffer = gst_buffer_new_and_alloc(length);
@ -137,8 +143,7 @@ void MediaPipeline::WriteData(const char* data, qint64 length) {
} }
void MediaPipeline::EndStream() { void MediaPipeline::EndStream() {
if (!is_initialised()) if (!is_initialised()) return;
return;
gst_app_src_end_of_stream(appsrc_); gst_app_src_end_of_stream(appsrc_);
} }
@ -153,8 +158,9 @@ void MediaPipeline::EnoughDataCallback(GstAppSrc* src, void* data) {
me->accepting_data_ = false; me->accepting_data_ = false;
} }
gboolean MediaPipeline::SeekDataCallback(GstAppSrc* src, guint64 offset, void * data) { gboolean MediaPipeline::SeekDataCallback(GstAppSrc* src, guint64 offset,
//MediaPipeline* me = reinterpret_cast<MediaPipeline*>(data); void* data) {
// MediaPipeline* me = reinterpret_cast<MediaPipeline*>(data);
qLog(Debug) << "Gstreamer wants seek to" << offset; qLog(Debug) << "Gstreamer wants seek to" << offset;
return false; return false;

View File

@ -27,7 +27,7 @@
#include <gst/app/gstappsrc.h> #include <gst/app/gstappsrc.h>
class MediaPipeline { class MediaPipeline {
public: public:
MediaPipeline(int port, quint64 length_msec); MediaPipeline(int port, quint64 length_msec);
~MediaPipeline(); ~MediaPipeline();
@ -38,12 +38,12 @@ public:
void WriteData(const char* data, qint64 length); void WriteData(const char* data, qint64 length);
void EndStream(); void EndStream();
private: private:
static void NeedDataCallback(GstAppSrc* src, guint length, void* data); static void NeedDataCallback(GstAppSrc* src, guint length, void* data);
static void EnoughDataCallback(GstAppSrc* src, void* data); static void EnoughDataCallback(GstAppSrc* src, void* data);
static gboolean SeekDataCallback(GstAppSrc* src, guint64 offset, void* data); static gboolean SeekDataCallback(GstAppSrc* src, guint64 offset, void* data);
private: private:
Q_DISABLE_COPY(MediaPipeline) Q_DISABLE_COPY(MediaPipeline)
const int port_; const int port_;
@ -59,4 +59,4 @@ private:
quint64 offset_bytes_; quint64 offset_bytes_;
}; };
#endif // MEDIAPIPELINE_H #endif // MEDIAPIPELINE_H

View File

@ -31,7 +31,8 @@ namespace utilities {
QString GetCacheDirectory() { QString GetCacheDirectory() {
QString user_cache = GetUserDataDirectory(); QString user_cache = GetUserDataDirectory();
return user_cache + "/" + QCoreApplication::applicationName() + "/spotify-cache"; return user_cache + "/" + QCoreApplication::applicationName() +
"/spotify-cache";
} }
#ifndef Q_OS_DARWIN // See spotify_utilities.mm for Mac implementation. #ifndef Q_OS_DARWIN // See spotify_utilities.mm for Mac implementation.
@ -47,10 +48,11 @@ QString GetSettingsDirectory() {
QString ret; QString ret;
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
ret = GetUserDataDirectory() + "/" + QCoreApplication::applicationName() + "/spotify-settings"; ret = GetUserDataDirectory() + "/" + QCoreApplication::applicationName() +
"/spotify-settings";
#else #else
ret = QFileInfo(QSettings().fileName()).absolutePath() + "/spotify-settings"; ret = QFileInfo(QSettings().fileName()).absolutePath() + "/spotify-settings";
#endif // Q_OS_WIN32 #endif // Q_OS_WIN32
// Create the directory // Create the directory
QDir dir; QDir dir;
@ -59,6 +61,6 @@ QString GetSettingsDirectory() {
return ret; return ret;
} }
#endif // Q_OS_DARWIN #endif // Q_OS_DARWIN
} // namespace utilities } // namespace utilities

View File

@ -32,7 +32,6 @@ QString GetUserDataDirectory();
QString GetCacheDirectory(); QString GetCacheDirectory();
QString GetSettingsDirectory(); QString GetSettingsDirectory();
} }
#endif #endif

View File

@ -10,10 +10,8 @@ QString GetUserDataDirectory() {
NSAutoreleasePool* pool = [NSAutoreleasePool alloc]; NSAutoreleasePool* pool = [NSAutoreleasePool alloc];
[pool init]; [pool init];
NSArray* paths = NSSearchPathForDirectoriesInDomains( NSArray* paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
NSCachesDirectory, NSUserDomainMask, YES);
NSUserDomainMask,
YES);
QString ret; QString ret;
if ([paths count] > 0) { if ([paths count] > 0) {
NSString* user_path = [paths objectAtIndex:0]; NSString* user_path = [paths objectAtIndex:0];
@ -28,9 +26,7 @@ QString GetUserDataDirectory() {
QString GetSettingsDirectory() { QString GetSettingsDirectory() {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSArray* paths = NSSearchPathForDirectoriesInDomains( NSArray* paths = NSSearchPathForDirectoriesInDomains(
NSApplicationSupportDirectory, NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSUserDomainMask,
YES);
NSString* ret; NSString* ret;
if ([paths count] > 0) { if ([paths count] > 0) {
ret = [paths objectAtIndex:0]; ret = [paths objectAtIndex:0];
@ -40,15 +36,13 @@ QString GetSettingsDirectory() {
ret = [ret stringByAppendingString:@"/Clementine/spotify-settings"]; ret = [ret stringByAppendingString:@"/Clementine/spotify-settings"];
NSFileManager* file_manager = [NSFileManager defaultManager]; NSFileManager* file_manager = [NSFileManager defaultManager];
[file_manager createDirectoryAtPath: [file_manager createDirectoryAtPath:ret
ret withIntermediateDirectories:YES
withIntermediateDirectories:YES attributes:nil
attributes:nil error:nil];
error:nil];
QString path = QString::fromUtf8([ret UTF8String]); QString path = QString::fromUtf8([ret UTF8String]);
[pool drain]; [pool drain];
return path; return path;
} }
} }

View File

@ -36,18 +36,18 @@
const int SpotifyClient::kSpotifyImageIDSize = 20; const int SpotifyClient::kSpotifyImageIDSize = 20;
const int SpotifyClient::kWaveHeaderSize = 44; const int SpotifyClient::kWaveHeaderSize = 44;
SpotifyClient::SpotifyClient(QObject* parent) SpotifyClient::SpotifyClient(QObject* parent)
: AbstractMessageHandler<pb::spotify::Message>(nullptr, parent), : AbstractMessageHandler<pb::spotify::Message>(nullptr, parent),
api_key_(QByteArray::fromBase64(kSpotifyApiKey)), api_key_(QByteArray::fromBase64(kSpotifyApiKey)),
protocol_socket_(new QTcpSocket(this)), protocol_socket_(new QTcpSocket(this)),
session_(nullptr), session_(nullptr),
events_timer_(new QTimer(this)) { events_timer_(new QTimer(this)) {
SetDevice(protocol_socket_); SetDevice(protocol_socket_);
memset(&spotify_callbacks_, 0, sizeof(spotify_callbacks_)); memset(&spotify_callbacks_, 0, sizeof(spotify_callbacks_));
memset(&spotify_config_, 0, sizeof(spotify_config_)); memset(&spotify_config_, 0, sizeof(spotify_config_));
memset(&playlistcontainer_callbacks_, 0, sizeof(playlistcontainer_callbacks_)); memset(&playlistcontainer_callbacks_, 0,
sizeof(playlistcontainer_callbacks_));
memset(&get_playlists_callbacks_, 0, sizeof(get_playlists_callbacks_)); memset(&get_playlists_callbacks_, 0, sizeof(get_playlists_callbacks_));
memset(&load_playlist_callbacks_, 0, sizeof(load_playlist_callbacks_)); memset(&load_playlist_callbacks_, 0, sizeof(load_playlist_callbacks_));
@ -64,15 +64,17 @@ SpotifyClient::SpotifyClient(QObject* parent)
spotify_callbacks_.start_playback = &StartPlaybackCallback; spotify_callbacks_.start_playback = &StartPlaybackCallback;
spotify_callbacks_.stop_playback = &StopPlaybackCallback; spotify_callbacks_.stop_playback = &StopPlaybackCallback;
playlistcontainer_callbacks_.container_loaded =
playlistcontainer_callbacks_.container_loaded = &PlaylistContainerLoadedCallback; &PlaylistContainerLoadedCallback;
playlistcontainer_callbacks_.playlist_added = &PlaylistAddedCallback; playlistcontainer_callbacks_.playlist_added = &PlaylistAddedCallback;
playlistcontainer_callbacks_.playlist_moved = &PlaylistMovedCallback; playlistcontainer_callbacks_.playlist_moved = &PlaylistMovedCallback;
playlistcontainer_callbacks_.playlist_removed = &PlaylistRemovedCallback; playlistcontainer_callbacks_.playlist_removed = &PlaylistRemovedCallback;
get_playlists_callbacks_.playlist_state_changed = &PlaylistStateChangedForGetPlaylists; get_playlists_callbacks_.playlist_state_changed =
&PlaylistStateChangedForGetPlaylists;
load_playlist_callbacks_.playlist_state_changed = &PlaylistStateChangedForLoadPlaylist; load_playlist_callbacks_.playlist_state_changed =
&PlaylistStateChangedForLoadPlaylist;
QString cache = utilities::GetCacheDirectory(); QString cache = utilities::GetCacheDirectory();
qLog(Debug) << "Using:" << cache << "for Spotify cache"; qLog(Debug) << "Using:" << cache << "for Spotify cache";
@ -111,41 +113,43 @@ void SpotifyClient::Init(quint16 port) {
} }
void SpotifyClient::LoggedInCallback(sp_session* session, sp_error error) { void SpotifyClient::LoggedInCallback(sp_session* session, sp_error error) {
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session)); SpotifyClient* me =
reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
const bool success = error == SP_ERROR_OK; const bool success = error == SP_ERROR_OK;
pb::spotify::LoginResponse_Error error_code = pb::spotify::LoginResponse_Error_Other; pb::spotify::LoginResponse_Error error_code =
pb::spotify::LoginResponse_Error_Other;
if (!success) { if (!success) {
qLog(Warning) << "Failed to login" << sp_error_message(error); qLog(Warning) << "Failed to login" << sp_error_message(error);
} }
switch (error) { switch (error) {
case SP_ERROR_BAD_USERNAME_OR_PASSWORD: case SP_ERROR_BAD_USERNAME_OR_PASSWORD:
error_code = pb::spotify::LoginResponse_Error_BadUsernameOrPassword; error_code = pb::spotify::LoginResponse_Error_BadUsernameOrPassword;
break; break;
case SP_ERROR_USER_BANNED: case SP_ERROR_USER_BANNED:
error_code = pb::spotify::LoginResponse_Error_UserBanned; error_code = pb::spotify::LoginResponse_Error_UserBanned;
break; break;
case SP_ERROR_USER_NEEDS_PREMIUM : case SP_ERROR_USER_NEEDS_PREMIUM:
error_code = pb::spotify::LoginResponse_Error_UserNeedsPremium; error_code = pb::spotify::LoginResponse_Error_UserNeedsPremium;
break; break;
default: default:
error_code = pb::spotify::LoginResponse_Error_Other; error_code = pb::spotify::LoginResponse_Error_Other;
break; break;
} }
me->SendLoginCompleted(success, sp_error_message(error), error_code); me->SendLoginCompleted(success, sp_error_message(error), error_code);
if (success) { if (success) {
sp_playlistcontainer_add_callbacks( sp_playlistcontainer_add_callbacks(sp_session_playlistcontainer(session),
sp_session_playlistcontainer(session), &me->playlistcontainer_callbacks_, me);
&me->playlistcontainer_callbacks_, me);
sp_session_flush_caches(me->session_); sp_session_flush_caches(me->session_);
} }
} }
void SpotifyClient::NotifyMainThreadCallback(sp_session* session) { void SpotifyClient::NotifyMainThreadCallback(sp_session* session) {
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session)); SpotifyClient* me =
reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
QMetaObject::invokeMethod(me, "ProcessEvents", Qt::QueuedConnection); QMetaObject::invokeMethod(me, "ProcessEvents", Qt::QueuedConnection);
} }
@ -160,14 +164,11 @@ void SpotifyClient::LogMessageCallback(sp_session* session, const char* data) {
} }
void SpotifyClient::Search(const pb::spotify::SearchRequest& req) { void SpotifyClient::Search(const pb::spotify::SearchRequest& req) {
sp_search* search = sp_search_create( sp_search* search =
session_, req.query().c_str(), sp_search_create(session_, req.query().c_str(), 0, req.limit(), 0,
0, req.limit(), req.limit_album(), 0, 0, // artists
0, req.limit_album(), 0, 0, // playlists
0, 0, // artists SP_SEARCH_STANDARD, &SearchCompleteCallback, this);
0, 0, // playlists
SP_SEARCH_STANDARD,
&SearchCompleteCallback, this);
pending_searches_[search] = req; pending_searches_[search] = req;
} }
@ -184,10 +185,10 @@ void SpotifyClient::SearchCompleteCallback(sp_search* result, void* userdata) {
// we can send our response. // we can send our response.
const int count = sp_search_num_albums(result); const int count = sp_search_num_albums(result);
if (count != 0) { if (count != 0) {
for (int i=0 ; i<count ; ++i) { for (int i = 0; i < count; ++i) {
sp_album* album = sp_search_album(result, i); sp_album* album = sp_search_album(result, i);
sp_albumbrowse* browse = sp_albumbrowse* browse = sp_albumbrowse_create(
sp_albumbrowse_create(me->session_, album, &SearchAlbumBrowseComplete, me); me->session_, album, &SearchAlbumBrowseComplete, me);
me->pending_search_album_browse_responses_[browse] = result; me->pending_search_album_browse_responses_[browse] = result;
} }
@ -197,7 +198,8 @@ void SpotifyClient::SearchCompleteCallback(sp_search* result, void* userdata) {
me->SendSearchResponse(result); me->SendSearchResponse(result);
} }
void SpotifyClient::SearchAlbumBrowseComplete(sp_albumbrowse* result, void* userdata) { void SpotifyClient::SearchAlbumBrowseComplete(sp_albumbrowse* result,
void* userdata) {
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata); SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
if (!me->pending_search_album_browse_responses_.contains(result)) { if (!me->pending_search_album_browse_responses_.contains(result)) {
@ -208,7 +210,8 @@ void SpotifyClient::SearchAlbumBrowseComplete(sp_albumbrowse* result, void* user
sp_search* search = me->pending_search_album_browse_responses_.take(result); sp_search* search = me->pending_search_album_browse_responses_.take(result);
me->pending_search_album_browses_[search].append(result); me->pending_search_album_browses_[search].append(result);
if (me->pending_search_album_browses_[search].count() >= sp_search_num_albums(search)) { if (me->pending_search_album_browses_[search].count() >=
sp_search_num_albums(search)) {
me->SendSearchResponse(search); me->SendSearchResponse(search);
} }
} }
@ -235,14 +238,14 @@ void SpotifyClient::SendSearchResponse(sp_search* result) {
// Get the list of tracks from the search // Get the list of tracks from the search
int count = sp_search_num_tracks(result); int count = sp_search_num_tracks(result);
for (int i=0 ; i<count ; ++i) { for (int i = 0; i < count; ++i) {
sp_track* track = sp_search_track(result, i); sp_track* track = sp_search_track(result, i);
ConvertTrack(track, response->add_result()); ConvertTrack(track, response->add_result());
} }
// Get the albums from the search. All these should be resolved by now. // Get the albums from the search. All these should be resolved by now.
QList<sp_albumbrowse*> browses = pending_search_album_browses_.take(result); QList<sp_albumbrowse*> browses = pending_search_album_browses_.take(result);
foreach (sp_albumbrowse* browse, browses) { foreach(sp_albumbrowse * browse, browses) {
sp_album* album = sp_albumbrowse_album(browse); sp_album* album = sp_albumbrowse_album(browse);
pb::spotify::Album* msg = response->add_album(); pb::spotify::Album* msg = response->add_album();
@ -251,7 +254,7 @@ void SpotifyClient::SendSearchResponse(sp_search* result) {
// Add all tracks // Add all tracks
const int tracks = sp_albumbrowse_num_tracks(browse); const int tracks = sp_albumbrowse_num_tracks(browse);
for (int i=0 ; i<tracks ; ++i) { for (int i = 0; i < tracks; ++i) {
ConvertTrack(sp_albumbrowse_track(browse, i), msg->add_track()); ConvertTrack(sp_albumbrowse_track(browse, i), msg->add_track());
} }
@ -290,16 +293,23 @@ void SpotifyClient::MessageArrived(const pb::spotify::Message& message) {
} }
} }
void SpotifyClient::SetPlaybackSettings(const pb::spotify::PlaybackSettings& req) { void SpotifyClient::SetPlaybackSettings(
const pb::spotify::PlaybackSettings& req) {
sp_bitrate bitrate = SP_BITRATE_320k; sp_bitrate bitrate = SP_BITRATE_320k;
switch (req.bitrate()) { switch (req.bitrate()) {
case pb::spotify::Bitrate96k: bitrate = SP_BITRATE_96k; break; case pb::spotify::Bitrate96k:
case pb::spotify::Bitrate160k: bitrate = SP_BITRATE_160k; break; bitrate = SP_BITRATE_96k;
case pb::spotify::Bitrate320k: bitrate = SP_BITRATE_320k; break; break;
case pb::spotify::Bitrate160k:
bitrate = SP_BITRATE_160k;
break;
case pb::spotify::Bitrate320k:
bitrate = SP_BITRATE_320k;
break;
} }
qLog(Debug) << "Setting playback settings: bitrate" qLog(Debug) << "Setting playback settings: bitrate" << bitrate
<< bitrate << "normalisation" << req.volume_normalisation(); << "normalisation" << req.volume_normalisation();
sp_session_preferred_bitrate(session_, bitrate); sp_session_preferred_bitrate(session_, bitrate);
sp_session_preferred_offline_bitrate(session_, bitrate, false); sp_session_preferred_offline_bitrate(session_, bitrate, false);
@ -310,7 +320,8 @@ void SpotifyClient::Login(const pb::spotify::LoginRequest& req) {
sp_error error = sp_session_create(&spotify_config_, &session_); sp_error error = sp_session_create(&spotify_config_, &session_);
if (error != SP_ERROR_OK) { if (error != SP_ERROR_OK) {
qLog(Warning) << "Failed to create session" << sp_error_message(error); qLog(Warning) << "Failed to create session" << sp_error_message(error);
SendLoginCompleted(false, sp_error_message(error), pb::spotify::LoginResponse_Error_Other); SendLoginCompleted(false, sp_error_message(error),
pb::spotify::LoginResponse_Error_Other);
return; return;
} }
@ -324,16 +335,15 @@ void SpotifyClient::Login(const pb::spotify::LoginRequest& req) {
pb::spotify::LoginResponse_Error_ReloginFailed); pb::spotify::LoginResponse_Error_ReloginFailed);
} }
} else { } else {
sp_session_login(session_, sp_session_login(session_, req.username().c_str(), req.password().c_str(),
req.username().c_str(), true, // Remember the password.
req.password().c_str(),
true, // Remember the password.
nullptr); nullptr);
} }
} }
void SpotifyClient::SendLoginCompleted(bool success, const QString& error, void SpotifyClient::SendLoginCompleted(
pb::spotify::LoginResponse_Error error_code) { bool success, const QString& error,
pb::spotify::LoginResponse_Error error_code) {
pb::spotify::Message message; pb::spotify::Message message;
pb::spotify::LoginResponse* response = message.mutable_login_response(); pb::spotify::LoginResponse* response = message.mutable_login_response();
@ -347,12 +357,13 @@ void SpotifyClient::SendLoginCompleted(bool success, const QString& error,
SendMessage(message); SendMessage(message);
} }
void SpotifyClient::PlaylistContainerLoadedCallback(sp_playlistcontainer* pc, void* userdata) { void SpotifyClient::PlaylistContainerLoadedCallback(sp_playlistcontainer* pc,
void* userdata) {
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata); SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
// Install callbacks on all the playlists // Install callbacks on all the playlists
const int count = sp_playlistcontainer_num_playlists(pc); const int count = sp_playlistcontainer_num_playlists(pc);
for (int i=0 ; i<count ; ++i) { for (int i = 0; i < count; ++i) {
sp_playlist* playlist = sp_playlistcontainer_playlist(pc, i); sp_playlist* playlist = sp_playlistcontainer_playlist(pc, i);
sp_playlist_add_callbacks(playlist, &me->get_playlists_callbacks_, me); sp_playlist_add_callbacks(playlist, &me->get_playlists_callbacks_, me);
} }
@ -360,7 +371,9 @@ void SpotifyClient::PlaylistContainerLoadedCallback(sp_playlistcontainer* pc, vo
me->SendPlaylistList(); me->SendPlaylistList();
} }
void SpotifyClient::PlaylistAddedCallback(sp_playlistcontainer* pc, sp_playlist* playlist, int position, void* userdata) { void SpotifyClient::PlaylistAddedCallback(sp_playlistcontainer* pc,
sp_playlist* playlist, int position,
void* userdata) {
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata); SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
// Install callbacks on this playlist // Install callbacks on this playlist
@ -369,12 +382,16 @@ void SpotifyClient::PlaylistAddedCallback(sp_playlistcontainer* pc, sp_playlist*
me->SendPlaylistList(); me->SendPlaylistList();
} }
void SpotifyClient::PlaylistMovedCallback(sp_playlistcontainer* pc, sp_playlist* playlist, int position, int new_position, void* userdata) { void SpotifyClient::PlaylistMovedCallback(sp_playlistcontainer* pc,
sp_playlist* playlist, int position,
int new_position, void* userdata) {
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata); SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
me->SendPlaylistList(); me->SendPlaylistList();
} }
void SpotifyClient::PlaylistRemovedCallback(sp_playlistcontainer* pc, sp_playlist* playlist, int position, void* userdata) { void SpotifyClient::PlaylistRemovedCallback(sp_playlistcontainer* pc,
sp_playlist* playlist, int position,
void* userdata) {
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata); SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
// Remove callbacks from this playlist // Remove callbacks from this playlist
@ -395,12 +412,13 @@ void SpotifyClient::SendPlaylistList() {
const int count = sp_playlistcontainer_num_playlists(container); const int count = sp_playlistcontainer_num_playlists(container);
for (int i=0 ; i<count ; ++i) { for (int i = 0; i < count; ++i) {
const int type = sp_playlistcontainer_playlist_type(container, i); const int type = sp_playlistcontainer_playlist_type(container, i);
sp_playlist* playlist = sp_playlistcontainer_playlist(container, i); sp_playlist* playlist = sp_playlistcontainer_playlist(container, i);
const bool is_loaded = sp_playlist_is_loaded(playlist); const bool is_loaded = sp_playlist_is_loaded(playlist);
qLog(Debug) << "Got playlist" << i << is_loaded << type << sp_playlist_name(playlist); qLog(Debug) << "Got playlist" << i << is_loaded << type
<< sp_playlist_name(playlist);
if (!is_loaded) { if (!is_loaded) {
qLog(Info) << "Playlist is not loaded yet, waiting..."; qLog(Info) << "Playlist is not loaded yet, waiting...";
@ -431,7 +449,8 @@ void SpotifyClient::SendPlaylistList() {
SendMessage(message); SendMessage(message);
} }
sp_playlist* SpotifyClient::GetPlaylist(pb::spotify::PlaylistType type, int user_index) { sp_playlist* SpotifyClient::GetPlaylist(pb::spotify::PlaylistType type,
int user_index) {
sp_playlist* playlist = nullptr; sp_playlist* playlist = nullptr;
switch (type) { switch (type) {
case pb::spotify::Inbox: case pb::spotify::Inbox:
@ -446,7 +465,8 @@ sp_playlist* SpotifyClient::GetPlaylist(pb::spotify::PlaylistType type, int user
sp_playlistcontainer* pc = sp_session_playlistcontainer(session_); sp_playlistcontainer* pc = sp_session_playlistcontainer(session_);
if (pc && user_index <= sp_playlistcontainer_num_playlists(pc)) { if (pc && user_index <= sp_playlistcontainer_num_playlists(pc)) {
if (sp_playlistcontainer_playlist_type(pc, user_index) == SP_PLAYLIST_TYPE_PLAYLIST) { if (sp_playlistcontainer_playlist_type(pc, user_index) ==
SP_PLAYLIST_TYPE_PLAYLIST) {
playlist = sp_playlistcontainer_playlist(pc, user_index); playlist = sp_playlistcontainer_playlist(pc, user_index);
sp_playlist_add_ref(playlist); sp_playlist_add_ref(playlist);
} }
@ -469,26 +489,30 @@ void SpotifyClient::LoadPlaylist(const pb::spotify::LoadPlaylistRequest& req) {
qLog(Warning) << "Invalid playlist requested or not logged in"; qLog(Warning) << "Invalid playlist requested or not logged in";
pb::spotify::Message message; pb::spotify::Message message;
pb::spotify::LoadPlaylistResponse* response = message.mutable_load_playlist_response(); pb::spotify::LoadPlaylistResponse* response =
message.mutable_load_playlist_response();
*response->mutable_request() = req; *response->mutable_request() = req;
SendMessage(message); SendMessage(message);
return; return;
} }
sp_playlist_add_callbacks(pending_load.playlist_, &load_playlist_callbacks_, this); sp_playlist_add_callbacks(pending_load.playlist_, &load_playlist_callbacks_,
this);
pending_load_playlists_ << pending_load; pending_load_playlists_ << pending_load;
PlaylistStateChangedForLoadPlaylist(pending_load.playlist_, this); PlaylistStateChangedForLoadPlaylist(pending_load.playlist_, this);
} }
void SpotifyClient::SyncPlaylist(const pb::spotify::SyncPlaylistRequest& req) { void SpotifyClient::SyncPlaylist(const pb::spotify::SyncPlaylistRequest& req) {
sp_playlist* playlist = GetPlaylist(req.request().type(), req.request().user_playlist_index()); sp_playlist* playlist =
GetPlaylist(req.request().type(), req.request().user_playlist_index());
// The playlist should already be loaded. // The playlist should already be loaded.
sp_playlist_set_offline_mode(session_, playlist, req.offline_sync()); sp_playlist_set_offline_mode(session_, playlist, req.offline_sync());
} }
void SpotifyClient::PlaylistStateChangedForLoadPlaylist(sp_playlist* pl, void* userdata) { void SpotifyClient::PlaylistStateChangedForLoadPlaylist(sp_playlist* pl,
void* userdata) {
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata); SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
// If the playlist isn't loaded yet we have to wait // If the playlist isn't loaded yet we have to wait
@ -500,7 +524,7 @@ void SpotifyClient::PlaylistStateChangedForLoadPlaylist(sp_playlist* pl, void* u
// Find this playlist's pending load object // Find this playlist's pending load object
int pending_load_index = -1; int pending_load_index = -1;
PendingLoadPlaylist* pending_load = nullptr; PendingLoadPlaylist* pending_load = nullptr;
for (int i=0 ; i<me->pending_load_playlists_.count() ; ++i) { for (int i = 0; i < me->pending_load_playlists_.count(); ++i) {
if (me->pending_load_playlists_[i].playlist_ == pl) { if (me->pending_load_playlists_[i].playlist_ == pl) {
pending_load_index = i; pending_load_index = i;
pending_load = &me->pending_load_playlists_[i]; pending_load = &me->pending_load_playlists_[i];
@ -516,7 +540,7 @@ void SpotifyClient::PlaylistStateChangedForLoadPlaylist(sp_playlist* pl, void* u
// If the playlist was just loaded then get all its tracks and ref them // If the playlist was just loaded then get all its tracks and ref them
if (pending_load->tracks_.isEmpty()) { if (pending_load->tracks_.isEmpty()) {
const int count = sp_playlist_num_tracks(pl); const int count = sp_playlist_num_tracks(pl);
for (int i=0 ; i<count ; ++i) { for (int i = 0; i < count; ++i) {
sp_track* track = sp_playlist_track(pl, i); sp_track* track = sp_playlist_track(pl, i);
sp_track_add_ref(track); sp_track_add_ref(track);
pending_load->tracks_ << track; pending_load->tracks_ << track;
@ -524,7 +548,7 @@ void SpotifyClient::PlaylistStateChangedForLoadPlaylist(sp_playlist* pl, void* u
} }
// If any of the tracks aren't loaded yet we have to wait // If any of the tracks aren't loaded yet we have to wait
foreach (sp_track* track, pending_load->tracks_) { foreach(sp_track * track, pending_load->tracks_) {
if (!sp_track_is_loaded(track)) { if (!sp_track_is_loaded(track)) {
qLog(Debug) << "One or more tracks aren't loaded yet, waiting"; qLog(Debug) << "One or more tracks aren't loaded yet, waiting";
return; return;
@ -533,17 +557,17 @@ void SpotifyClient::PlaylistStateChangedForLoadPlaylist(sp_playlist* pl, void* u
// Everything is loaded so send the response protobuf and unref everything. // Everything is loaded so send the response protobuf and unref everything.
pb::spotify::Message message; pb::spotify::Message message;
pb::spotify::LoadPlaylistResponse* response = message.mutable_load_playlist_response(); pb::spotify::LoadPlaylistResponse* response =
message.mutable_load_playlist_response();
// For some reason, we receive the starred tracks in reverse order but not // For some reason, we receive the starred tracks in reverse order but not
// other playlists. // other playlists.
if (pending_load->request_.type() == pb::spotify::Starred) { if (pending_load->request_.type() == pb::spotify::Starred) {
std::reverse(pending_load->tracks_.begin(), std::reverse(pending_load->tracks_.begin(), pending_load->tracks_.end());
pending_load->tracks_.end());
} }
*response->mutable_request() = pending_load->request_; *response->mutable_request() = pending_load->request_;
foreach (sp_track* track, pending_load->tracks_) { foreach(sp_track * track, pending_load->tracks_) {
me->ConvertTrack(track, response->add_track()); me->ConvertTrack(track, response->add_track());
sp_track_release(track); sp_track_release(track);
} }
@ -557,7 +581,8 @@ void SpotifyClient::PlaylistStateChangedForLoadPlaylist(sp_playlist* pl, void* u
me->pending_load_playlists_.removeAt(pending_load_index); me->pending_load_playlists_.removeAt(pending_load_index);
} }
void SpotifyClient::PlaylistStateChangedForGetPlaylists(sp_playlist* pl, void* userdata) { void SpotifyClient::PlaylistStateChangedForGetPlaylists(sp_playlist* pl,
void* userdata) {
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata); SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
me->SendPlaylistList(); me->SendPlaylistList();
@ -576,15 +601,14 @@ void SpotifyClient::ConvertTrack(sp_track* track, pb::spotify::Track* pb) {
pb->set_track(sp_track_index(track)); pb->set_track(sp_track_index(track));
// Album art // Album art
const QByteArray art_id( const QByteArray art_id(reinterpret_cast<const char*>(sp_album_cover(
reinterpret_cast<const char*>( sp_track_album(track), SP_IMAGE_SIZE_LARGE)),
sp_album_cover(sp_track_album(track), SP_IMAGE_SIZE_LARGE)), kSpotifyImageIDSize);
kSpotifyImageIDSize);
const QString art_id_b64 = QString::fromAscii(art_id.toBase64()); const QString art_id_b64 = QString::fromAscii(art_id.toBase64());
pb->set_album_art_id(DataCommaSizeFromQString(art_id_b64)); pb->set_album_art_id(DataCommaSizeFromQString(art_id_b64));
// Artists // Artists
for (int i=0 ; i<sp_track_num_artists(track) ; ++i) { for (int i = 0; i < sp_track_num_artists(track); ++i) {
pb->add_artist(sp_artist_name(sp_track_artist(track, i))); pb->add_artist(sp_artist_name(sp_track_artist(track, i)));
} }
@ -613,8 +637,8 @@ void SpotifyClient::ConvertAlbum(sp_album* album, pb::spotify::Track* pb) {
// Album art // Album art
const QByteArray art_id( const QByteArray art_id(
reinterpret_cast<const char*>(sp_album_cover(album, SP_IMAGE_SIZE_LARGE)), reinterpret_cast<const char*>(sp_album_cover(album, SP_IMAGE_SIZE_LARGE)),
kSpotifyImageIDSize); kSpotifyImageIDSize);
const QString art_id_b64 = QString::fromAscii(art_id.toBase64()); const QString art_id_b64 = QString::fromAscii(art_id.toBase64());
pb->set_album_art_id(DataCommaSizeFromQString(art_id_b64)); pb->set_album_art_id(DataCommaSizeFromQString(art_id_b64));
@ -627,25 +651,29 @@ void SpotifyClient::ConvertAlbum(sp_album* album, pb::spotify::Track* pb) {
pb->set_uri(uri); pb->set_uri(uri);
} }
void SpotifyClient::ConvertAlbumBrowse(sp_albumbrowse* browse, pb::spotify::Track* pb) { void SpotifyClient::ConvertAlbumBrowse(sp_albumbrowse* browse,
pb::spotify::Track* pb) {
pb->set_track(sp_albumbrowse_num_tracks(browse)); pb->set_track(sp_albumbrowse_num_tracks(browse));
} }
void SpotifyClient::MetadataUpdatedCallback(sp_session* session) { void SpotifyClient::MetadataUpdatedCallback(sp_session* session) {
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session)); SpotifyClient* me =
reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
foreach (const PendingLoadPlaylist& load, me->pending_load_playlists_) { foreach(const PendingLoadPlaylist & load, me->pending_load_playlists_) {
PlaylistStateChangedForLoadPlaylist(load.playlist_, me); PlaylistStateChangedForLoadPlaylist(load.playlist_, me);
} }
foreach (const PendingPlaybackRequest& playback, me->pending_playback_requests_) { foreach(const PendingPlaybackRequest & playback,
me->pending_playback_requests_) {
me->TryPlaybackAgain(playback); me->TryPlaybackAgain(playback);
} }
} }
int SpotifyClient::MusicDeliveryCallback( int SpotifyClient::MusicDeliveryCallback(sp_session* session,
sp_session* session, const sp_audioformat* format, const sp_audioformat* format,
const void* frames, int num_frames) { const void* frames, int num_frames) {
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session)); SpotifyClient* me =
reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
if (!me->media_pipeline_) { if (!me->media_pipeline_) {
return 0; return 0;
@ -668,21 +696,23 @@ int SpotifyClient::MusicDeliveryCallback(
return 0; return 0;
} }
me->media_pipeline_->WriteData( me->media_pipeline_->WriteData(reinterpret_cast<const char*>(frames),
reinterpret_cast<const char*>(frames), num_frames * format->channels * 2);
num_frames * format->channels * 2);
return num_frames; return num_frames;
} }
void SpotifyClient::EndOfTrackCallback(sp_session* session) { void SpotifyClient::EndOfTrackCallback(sp_session* session) {
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session)); SpotifyClient* me =
reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
me->media_pipeline_.reset(); me->media_pipeline_.reset();
} }
void SpotifyClient::StreamingErrorCallback(sp_session* session, sp_error error) { void SpotifyClient::StreamingErrorCallback(sp_session* session,
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session)); sp_error error) {
SpotifyClient* me =
reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
me->media_pipeline_.reset(); me->media_pipeline_.reset();
@ -690,11 +720,13 @@ void SpotifyClient::StreamingErrorCallback(sp_session* session, sp_error error)
me->SendPlaybackError(QString::fromUtf8(sp_error_message(error))); me->SendPlaybackError(QString::fromUtf8(sp_error_message(error)));
} }
void SpotifyClient::ConnectionErrorCallback(sp_session* session, sp_error error) { void SpotifyClient::ConnectionErrorCallback(sp_session* session,
sp_error error) {
qLog(Debug) << Q_FUNC_INFO << sp_error_message(error); qLog(Debug) << Q_FUNC_INFO << sp_error_message(error);
} }
void SpotifyClient::UserMessageCallback(sp_session* session, const char* message) { void SpotifyClient::UserMessageCallback(sp_session* session,
const char* message) {
qLog(Debug) << Q_FUNC_INFO << message; qLog(Debug) << Q_FUNC_INFO << message;
} }
@ -707,7 +739,8 @@ void SpotifyClient::StopPlaybackCallback(sp_session* session) {
} }
void SpotifyClient::OfflineStatusUpdatedCallback(sp_session* session) { void SpotifyClient::OfflineStatusUpdatedCallback(sp_session* session) {
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session)); SpotifyClient* me =
reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
sp_playlistcontainer* container = sp_session_playlistcontainer(session); sp_playlistcontainer* container = sp_session_playlistcontainer(session);
if (!container) { if (!container) {
qLog(Warning) << "sp_session_playlistcontainer returned nullptr"; qLog(Warning) << "sp_session_playlistcontainer returned nullptr";
@ -716,8 +749,9 @@ void SpotifyClient::OfflineStatusUpdatedCallback(sp_session* session) {
const int count = sp_playlistcontainer_num_playlists(container); const int count = sp_playlistcontainer_num_playlists(container);
for (int i=0 ; i<count ; ++i) { for (int i = 0; i < count; ++i) {
const sp_playlist_type type = sp_playlistcontainer_playlist_type(container, i); const sp_playlist_type type =
sp_playlistcontainer_playlist_type(container, i);
sp_playlist* playlist = sp_playlistcontainer_playlist(container, i); sp_playlist* playlist = sp_playlistcontainer_playlist(container, i);
if (type != SP_PLAYLIST_TYPE_PLAYLIST) { if (type != SP_PLAYLIST_TYPE_PLAYLIST) {
@ -748,10 +782,11 @@ void SpotifyClient::OfflineStatusUpdatedCallback(sp_session* session) {
} }
} }
void SpotifyClient::SendDownloadProgress( void SpotifyClient::SendDownloadProgress(pb::spotify::PlaylistType type,
pb::spotify::PlaylistType type, int index, int download_progress) { int index, int download_progress) {
pb::spotify::Message message; pb::spotify::Message message;
pb::spotify::SyncPlaylistProgress* progress = message.mutable_sync_playlist_progress(); pb::spotify::SyncPlaylistProgress* progress =
message.mutable_sync_playlist_progress();
progress->mutable_request()->set_type(type); progress->mutable_request()->set_type(type);
if (index != -1) { if (index != -1) {
progress->mutable_request()->set_user_playlist_index(index); progress->mutable_request()->set_user_playlist_index(index);
@ -762,7 +797,7 @@ void SpotifyClient::SendDownloadProgress(
int SpotifyClient::GetDownloadProgress(sp_playlist* playlist) { int SpotifyClient::GetDownloadProgress(sp_playlist* playlist) {
sp_playlist_offline_status status = sp_playlist_offline_status status =
sp_playlist_get_offline_status(session_, playlist); sp_playlist_get_offline_status(session_, playlist);
switch (status) { switch (status) {
case SP_PLAYLIST_OFFLINE_STATUS_NO: case SP_PLAYLIST_OFFLINE_STATUS_NO:
return -1; return -1;
@ -864,14 +899,14 @@ void SpotifyClient::LoadImage(const QString& id_b64) {
PendingImageRequest pending_load; PendingImageRequest pending_load;
pending_load.id_ = id; pending_load.id_ = id;
pending_load.id_b64_ = id_b64; pending_load.id_b64_ = id_b64;
pending_load.image_ = sp_image_create(session_, pending_load.image_ =
reinterpret_cast<const byte*>(id.constData())); sp_image_create(session_, reinterpret_cast<const byte*>(id.constData()));
pending_image_requests_ << pending_load; pending_image_requests_ << pending_load;
if (!image_callbacks_registered_[pending_load.image_]) { if (!image_callbacks_registered_[pending_load.image_]) {
sp_image_add_load_callback(pending_load.image_, &ImageLoaded, this); sp_image_add_load_callback(pending_load.image_, &ImageLoaded, this);
} }
image_callbacks_registered_[pending_load.image_] ++; image_callbacks_registered_[pending_load.image_]++;
TryImageAgain(pending_load.image_); TryImageAgain(pending_load.image_);
} }
@ -885,7 +920,7 @@ void SpotifyClient::TryImageAgain(sp_image* image) {
// Find the pending request for this image // Find the pending request for this image
int index = -1; int index = -1;
PendingImageRequest* req = nullptr; PendingImageRequest* req = nullptr;
for (int i=0 ; i<pending_image_requests_.count() ; ++i) { for (int i = 0; i < pending_image_requests_.count(); ++i) {
if (pending_image_requests_[i].image_ == image) { if (pending_image_requests_[i].image_ == image) {
index = i; index = i;
req = &pending_image_requests_[i]; req = &pending_image_requests_[i];
@ -912,7 +947,7 @@ void SpotifyClient::TryImageAgain(sp_image* image) {
SendMessage(message); SendMessage(message);
// Free stuff // Free stuff
image_callbacks_registered_[image] --; image_callbacks_registered_[image]--;
// TODO: memory leak? // TODO: memory leak?
// sp_image_remove_load_callback(image, &ImageLoaded, this); // sp_image_remove_load_callback(image, &ImageLoaded, this);
@ -948,21 +983,22 @@ void SpotifyClient::BrowseAlbum(const QString& uri) {
pending_album_browses_[browse] = uri; pending_album_browses_[browse] = uri;
} }
void SpotifyClient::AlbumBrowseComplete(sp_albumbrowse* result, void* userdata) { void SpotifyClient::AlbumBrowseComplete(sp_albumbrowse* result,
void* userdata) {
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata); SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
if (!me->pending_album_browses_.contains(result)) if (!me->pending_album_browses_.contains(result)) return;
return;
QString uri = me->pending_album_browses_.take(result); QString uri = me->pending_album_browses_.take(result);
pb::spotify::Message message; pb::spotify::Message message;
pb::spotify::BrowseAlbumResponse* msg = message.mutable_browse_album_response(); pb::spotify::BrowseAlbumResponse* msg =
message.mutable_browse_album_response();
msg->set_uri(DataCommaSizeFromQString(uri)); msg->set_uri(DataCommaSizeFromQString(uri));
const int count = sp_albumbrowse_num_tracks(result); const int count = sp_albumbrowse_num_tracks(result);
for (int i=0 ; i<count ; ++i) { for (int i = 0; i < count; ++i) {
me->ConvertTrack(sp_albumbrowse_track(result, i), msg->add_track()); me->ConvertTrack(sp_albumbrowse_track(result, i), msg->add_track());
} }
@ -970,32 +1006,32 @@ void SpotifyClient::AlbumBrowseComplete(sp_albumbrowse* result, void* userdata)
sp_albumbrowse_release(result); sp_albumbrowse_release(result);
} }
void SpotifyClient::BrowseToplist(const pb::spotify::BrowseToplistRequest& req) { void SpotifyClient::BrowseToplist(
const pb::spotify::BrowseToplistRequest& req) {
sp_toplistbrowse* browse = sp_toplistbrowse_create( sp_toplistbrowse* browse = sp_toplistbrowse_create(
session_, session_, SP_TOPLIST_TYPE_TRACKS, // TODO: Support albums and artists.
SP_TOPLIST_TYPE_TRACKS, // TODO: Support albums and artists. SP_TOPLIST_REGION_EVERYWHERE, // TODO: Support other regions.
SP_TOPLIST_REGION_EVERYWHERE, // TODO: Support other regions. nullptr, &ToplistBrowseComplete, this);
nullptr,
&ToplistBrowseComplete,
this);
pending_toplist_browses_[browse] = req; pending_toplist_browses_[browse] = req;
} }
void SpotifyClient::ToplistBrowseComplete(sp_toplistbrowse* result, void* userdata) { void SpotifyClient::ToplistBrowseComplete(sp_toplistbrowse* result,
void* userdata) {
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata); SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
qLog(Debug) << "Toplist browse request took:" qLog(Debug) << "Toplist browse request took:"
<< sp_toplistbrowse_backend_request_duration(result) << sp_toplistbrowse_backend_request_duration(result) << "ms";
<< "ms";
if (!me->pending_toplist_browses_.contains(result)) { if (!me->pending_toplist_browses_.contains(result)) {
return; return;
} }
const pb::spotify::BrowseToplistRequest& request = me->pending_toplist_browses_.take(result); const pb::spotify::BrowseToplistRequest& request =
me->pending_toplist_browses_.take(result);
pb::spotify::Message message; pb::spotify::Message message;
pb::spotify::BrowseToplistResponse* msg = message.mutable_browse_toplist_response(); pb::spotify::BrowseToplistResponse* msg =
message.mutable_browse_toplist_response();
msg->mutable_request()->CopyFrom(request); msg->mutable_request()->CopyFrom(request);
const int count = sp_toplistbrowse_num_tracks(result); const int count = sp_toplistbrowse_num_tracks(result);

View File

@ -18,7 +18,6 @@
// it is used by the Spotify blob which links against libspotify and is not GPL // it is used by the Spotify blob which links against libspotify and is not GPL
// compatible. // compatible.
#ifndef SPOTIFYCLIENT_H #ifndef SPOTIFYCLIENT_H
#define SPOTIFYCLIENT_H #define SPOTIFYCLIENT_H
@ -39,7 +38,7 @@ class ResponseMessage;
class SpotifyClient : public AbstractMessageHandler<pb::spotify::Message> { class SpotifyClient : public AbstractMessageHandler<pb::spotify::Message> {
Q_OBJECT Q_OBJECT
public: public:
SpotifyClient(QObject* parent = 0); SpotifyClient(QObject* parent = 0);
~SpotifyClient(); ~SpotifyClient();
@ -48,14 +47,14 @@ public:
void Init(quint16 port); void Init(quint16 port);
protected: protected:
void MessageArrived(const pb::spotify::Message& message); void MessageArrived(const pb::spotify::Message& message);
void DeviceClosed(); void DeviceClosed();
private slots: private slots:
void ProcessEvents(); void ProcessEvents();
private: private:
void SendLoginCompleted(bool success, const QString& error, void SendLoginCompleted(bool success, const QString& error,
pb::spotify::LoginResponse_Error error_code); pb::spotify::LoginResponse_Error error_code);
void SendPlaybackError(const QString& error); void SendPlaybackError(const QString& error);
@ -64,49 +63,59 @@ private:
// Spotify session callbacks. // Spotify session callbacks.
static void SP_CALLCONV LoggedInCallback(sp_session* session, sp_error error); static void SP_CALLCONV LoggedInCallback(sp_session* session, sp_error error);
static void SP_CALLCONV NotifyMainThreadCallback(sp_session* session); static void SP_CALLCONV NotifyMainThreadCallback(sp_session* session);
static void SP_CALLCONV LogMessageCallback(sp_session* session, const char* data); static void SP_CALLCONV
static void SP_CALLCONV SearchCompleteCallback(sp_search* result, void* userdata); LogMessageCallback(sp_session* session, const char* data);
static void SP_CALLCONV
SearchCompleteCallback(sp_search* result, void* userdata);
static void SP_CALLCONV MetadataUpdatedCallback(sp_session* session); static void SP_CALLCONV MetadataUpdatedCallback(sp_session* session);
static int SP_CALLCONV MusicDeliveryCallback( static int SP_CALLCONV
sp_session* session, const sp_audioformat* format, MusicDeliveryCallback(sp_session* session, const sp_audioformat* format,
const void* frames, int num_frames); const void* frames, int num_frames);
static void SP_CALLCONV EndOfTrackCallback(sp_session* session); static void SP_CALLCONV EndOfTrackCallback(sp_session* session);
static void SP_CALLCONV StreamingErrorCallback(sp_session* session, sp_error error); static void SP_CALLCONV
StreamingErrorCallback(sp_session* session, sp_error error);
static void SP_CALLCONV OfflineStatusUpdatedCallback(sp_session* session); static void SP_CALLCONV OfflineStatusUpdatedCallback(sp_session* session);
static void SP_CALLCONV ConnectionErrorCallback(sp_session* session, sp_error error); static void SP_CALLCONV
static void SP_CALLCONV UserMessageCallback(sp_session* session, const char* message); ConnectionErrorCallback(sp_session* session, sp_error error);
static void SP_CALLCONV
UserMessageCallback(sp_session* session, const char* message);
static void SP_CALLCONV StartPlaybackCallback(sp_session* session); static void SP_CALLCONV StartPlaybackCallback(sp_session* session);
static void SP_CALLCONV StopPlaybackCallback(sp_session* session); static void SP_CALLCONV StopPlaybackCallback(sp_session* session);
// Spotify playlist container callbacks. // Spotify playlist container callbacks.
static void SP_CALLCONV PlaylistAddedCallback( static void SP_CALLCONV PlaylistAddedCallback(sp_playlistcontainer* pc,
sp_playlistcontainer* pc, sp_playlist* playlist, sp_playlist* playlist,
int position, void* userdata); int position, void* userdata);
static void SP_CALLCONV PlaylistRemovedCallback( static void SP_CALLCONV PlaylistRemovedCallback(sp_playlistcontainer* pc,
sp_playlistcontainer* pc, sp_playlist* playlist, sp_playlist* playlist,
int position, void* userdata); int position, void* userdata);
static void SP_CALLCONV PlaylistMovedCallback( static void SP_CALLCONV
sp_playlistcontainer* pc, sp_playlist* playlist, PlaylistMovedCallback(sp_playlistcontainer* pc, sp_playlist* playlist,
int position, int new_position, void* userdata); int position, int new_position, void* userdata);
static void SP_CALLCONV PlaylistContainerLoadedCallback( static void SP_CALLCONV
sp_playlistcontainer* pc, void* userdata); PlaylistContainerLoadedCallback(sp_playlistcontainer* pc, void* userdata);
// Spotify playlist callbacks - when loading the list of playlists // Spotify playlist callbacks - when loading the list of playlists
// initially // initially
static void SP_CALLCONV PlaylistStateChangedForGetPlaylists(sp_playlist* pl, void* userdata); static void SP_CALLCONV
PlaylistStateChangedForGetPlaylists(sp_playlist* pl, void* userdata);
// Spotify playlist callbacks - when loading a playlist // Spotify playlist callbacks - when loading a playlist
static void SP_CALLCONV PlaylistStateChangedForLoadPlaylist(sp_playlist* pl, void* userdata); static void SP_CALLCONV
PlaylistStateChangedForLoadPlaylist(sp_playlist* pl, void* userdata);
// Spotify image callbacks. // Spotify image callbacks.
static void SP_CALLCONV ImageLoaded(sp_image* image, void* userdata); static void SP_CALLCONV ImageLoaded(sp_image* image, void* userdata);
// Spotify album browse callbacks. // Spotify album browse callbacks.
static void SP_CALLCONV SearchAlbumBrowseComplete(sp_albumbrowse* result, void* userdata); static void SP_CALLCONV
static void SP_CALLCONV AlbumBrowseComplete(sp_albumbrowse* result, void* userdata); SearchAlbumBrowseComplete(sp_albumbrowse* result, void* userdata);
static void SP_CALLCONV
AlbumBrowseComplete(sp_albumbrowse* result, void* userdata);
// Spotify toplist browse callbacks. // Spotify toplist browse callbacks.
static void SP_CALLCONV ToplistBrowseComplete(sp_toplistbrowse* result, void* userdata); static void SP_CALLCONV
ToplistBrowseComplete(sp_toplistbrowse* result, void* userdata);
// Request handlers. // Request handlers.
void Login(const pb::spotify::LoginRequest& req); void Login(const pb::spotify::LoginRequest& req);
@ -129,7 +138,7 @@ private:
// Gets the appropriate sp_playlist* but does not load it. // Gets the appropriate sp_playlist* but does not load it.
sp_playlist* GetPlaylist(pb::spotify::PlaylistType type, int user_index); sp_playlist* GetPlaylist(pb::spotify::PlaylistType type, int user_index);
private: private:
struct PendingLoadPlaylist { struct PendingLoadPlaylist {
pb::spotify::LoadPlaylistRequest request_; pb::spotify::LoadPlaylistRequest request_;
sp_playlist* playlist_; sp_playlist* playlist_;
@ -142,7 +151,7 @@ private:
sp_link* link_; sp_link* link_;
sp_track* track_; sp_track* track_;
bool operator ==(const PendingPlaybackRequest& other) const { bool operator==(const PendingPlaybackRequest& other) const {
return request_.track_uri() == other.request_.track_uri() && return request_.track_uri() == other.request_.track_uri() &&
request_.media_port() == other.request_.media_port(); request_.media_port() == other.request_.media_port();
} }
@ -157,7 +166,8 @@ private:
void TryPlaybackAgain(const PendingPlaybackRequest& req); void TryPlaybackAgain(const PendingPlaybackRequest& req);
void TryImageAgain(sp_image* image); void TryImageAgain(sp_image* image);
int GetDownloadProgress(sp_playlist* playlist); int GetDownloadProgress(sp_playlist* playlist);
void SendDownloadProgress(pb::spotify::PlaylistType type, int index, int download_progress); void SendDownloadProgress(pb::spotify::PlaylistType type, int index,
int download_progress);
QByteArray api_key_; QByteArray api_key_;
@ -178,7 +188,8 @@ private:
QMap<sp_image*, int> image_callbacks_registered_; QMap<sp_image*, int> image_callbacks_registered_;
QMap<sp_search*, pb::spotify::SearchRequest> pending_searches_; QMap<sp_search*, pb::spotify::SearchRequest> pending_searches_;
QMap<sp_albumbrowse*, QString> pending_album_browses_; QMap<sp_albumbrowse*, QString> pending_album_browses_;
QMap<sp_toplistbrowse*, pb::spotify::BrowseToplistRequest> pending_toplist_browses_; QMap<sp_toplistbrowse*, pb::spotify::BrowseToplistRequest>
pending_toplist_browses_;
QMap<sp_search*, QList<sp_albumbrowse*> > pending_search_album_browses_; QMap<sp_search*, QList<sp_albumbrowse*> > pending_search_album_browses_;
QMap<sp_albumbrowse*, sp_search*> pending_search_album_browse_responses_; QMap<sp_albumbrowse*, sp_search*> pending_search_album_browse_responses_;
@ -186,4 +197,4 @@ private:
QScopedPointer<MediaPipeline> media_pipeline_; QScopedPointer<MediaPipeline> media_pipeline_;
}; };
#endif // SPOTIFYCLIENT_H #endif // SPOTIFYCLIENT_H

View File

@ -18,16 +18,20 @@
// it is used by the Spotify blob which links against libspotify and is not GPL // it is used by the Spotify blob which links against libspotify and is not GPL
// compatible. // compatible.
// The Spotify terms of service require that application keys are not // The Spotify terms of service require that application keys are not
// accessible to third parties. Therefore this application key is heavily // accessible to third parties. Therefore this application key is heavily
// encrypted here in the source to prevent third parties from viewing it. // encrypted here in the source to prevent third parties from viewing it.
// It is most definitely not base64 encoded. // It is most definitely not base64 encoded.
static const char* kSpotifyApiKey = static const char* kSpotifyApiKey =
"AVlOrvJkKx8T+LEsCk+Kyl24I0MSsjohZAtMFzm2O5Lms1bmAWFWgdZaHkpypzSJPmSd+Wi50wwg" "AVlOrvJkKx8T+LEsCk+Kyl24I0MSsjohZAtMFzm2O5Lms1bmAWFWgdZaHkpypzSJPmSd+"
"JwVCU0sq4Lep1zB4t6Z8h26NK6+z8gmkHVkV9DRPkRgebcUkWTDTflwVPKWF4+gdRjUwprsqBw6O" "Wi50wwg"
"iofRLJzeKaxbmaUGqkSkxVLOiXC9lxylNq6ju7Q7uY8u8XkDUsVM3YIxiWy2+EM7I/lhatzT9xrq" "JwVCU0sq4Lep1zB4t6Z8h26NK6+z8gmkHVkV9DRPkRgebcUkWTDTflwVPKWF4+"
"rxHe2lg7CzOwF5kuFdwgmi8MQ72xTYXIKnNlOry/hJDlN9lKxkbUBLh+pzbYvO92S2fYKK5PAHvX" "gdRjUwprsqBw6O"
"5+SmSBGbh6dlpHeCGqb8MPdaeZ5I1YxMcDkxa2+tbLA/Muat7gKA9u57TFCtYjun/u/i/ONwdBIQ" "iofRLJzeKaxbmaUGqkSkxVLOiXC9lxylNq6ju7Q7uY8u8XkDUsVM3YIxiWy2+EM7I/"
"rePzXZjipO32kYmQAiCkN1p8sgQEcF43QxaVwXGo2X0rRnJf"; "lhatzT9xrq"
"rxHe2lg7CzOwF5kuFdwgmi8MQ72xTYXIKnNlOry/"
"hJDlN9lKxkbUBLh+pzbYvO92S2fYKK5PAHvX"
"5+SmSBGbh6dlpHeCGqb8MPdaeZ5I1YxMcDkxa2+tbLA/Muat7gKA9u57TFCtYjun/u/i/"
"ONwdBIQ"
"rePzXZjipO32kYmQAiCkN1p8sgQEcF43QxaVwXGo2X0rRnJf";

View File

@ -31,15 +31,17 @@ int main(int argc, char** argv) {
QStringList args(a.arguments()); QStringList args(a.arguments());
if (args.count() != 2) { if (args.count() != 2) {
std::cerr << "This program is used internally by Clementine to parse tags in music files\n" std::cerr << "This program is used internally by Clementine to parse tags "
"without exposing the whole application to crashes caused by malformed\n" "in music files\n"
"without exposing the whole application to crashes caused by "
"malformed\n"
"files. It is not meant to be run on its own.\n"; "files. It is not meant to be run on its own.\n";
return 1; return 1;
} }
// Seed random number generator // Seed random number generator
timeval time; timeval time;
gettimeofday(&time,nullptr); gettimeofday(&time, nullptr);
qsrand((time.tv_sec * 1000) + (time.tv_usec / 1000)); qsrand((time.tv_sec * 1000) + (time.tv_usec / 1000));
logging::Init(); logging::Init();

View File

@ -24,11 +24,8 @@
#include <QTextCodec> #include <QTextCodec>
#include <QUrl> #include <QUrl>
TagReaderWorker::TagReaderWorker(QIODevice* socket, QObject* parent) TagReaderWorker::TagReaderWorker(QIODevice* socket, QObject* parent)
: AbstractMessageHandler<pb::tagreader::Message>(socket, parent) : AbstractMessageHandler<pb::tagreader::Message>(socket, parent) {}
{
}
void TagReaderWorker::MessageArrived(const pb::tagreader::Message& message) { void TagReaderWorker::MessageArrived(const pb::tagreader::Message& message) {
pb::tagreader::Message reply; pb::tagreader::Message reply;
@ -42,42 +39,44 @@ void TagReaderWorker::MessageArrived(const pb::tagreader::Message& message) {
#endif #endif
if (message.has_read_file_request()) { if (message.has_read_file_request()) {
tag_reader_.ReadFile(QStringFromStdString(message.read_file_request().filename()), tag_reader_.ReadFile(
reply.mutable_read_file_response()->mutable_metadata()); QStringFromStdString(message.read_file_request().filename()),
reply.mutable_read_file_response()->mutable_metadata());
} else if (message.has_save_file_request()) { } else if (message.has_save_file_request()) {
reply.mutable_save_file_response()->set_success( reply.mutable_save_file_response()->set_success(tag_reader_.SaveFile(
tag_reader_.SaveFile(QStringFromStdString(message.save_file_request().filename()), QStringFromStdString(message.save_file_request().filename()),
message.save_file_request().metadata())); message.save_file_request().metadata()));
} else if (message.has_save_song_statistics_to_file_request()) { } else if (message.has_save_song_statistics_to_file_request()) {
reply.mutable_save_song_statistics_to_file_response()->set_success( reply.mutable_save_song_statistics_to_file_response()->set_success(
tag_reader_.SaveSongStatisticsToFile( tag_reader_.SaveSongStatisticsToFile(
QStringFromStdString(message.save_song_statistics_to_file_request().filename()), QStringFromStdString(
message.save_song_statistics_to_file_request().filename()),
message.save_song_statistics_to_file_request().metadata())); message.save_song_statistics_to_file_request().metadata()));
} else if (message.has_save_song_rating_to_file_request()) { } else if (message.has_save_song_rating_to_file_request()) {
reply.mutable_save_song_rating_to_file_response()->set_success( reply.mutable_save_song_rating_to_file_response()->set_success(
tag_reader_.SaveSongRatingToFile( tag_reader_.SaveSongRatingToFile(
QStringFromStdString(message.save_song_rating_to_file_request().filename()), QStringFromStdString(
message.save_song_rating_to_file_request().filename()),
message.save_song_rating_to_file_request().metadata())); message.save_song_rating_to_file_request().metadata()));
} else if (message.has_is_media_file_request()) { } else if (message.has_is_media_file_request()) {
reply.mutable_is_media_file_response()->set_success( reply.mutable_is_media_file_response()->set_success(tag_reader_.IsMediaFile(
tag_reader_.IsMediaFile(QStringFromStdString(message.is_media_file_request().filename()))); QStringFromStdString(message.is_media_file_request().filename())));
} else if (message.has_load_embedded_art_request()) { } else if (message.has_load_embedded_art_request()) {
QByteArray data = tag_reader_.LoadEmbeddedArt( QByteArray data = tag_reader_.LoadEmbeddedArt(
QStringFromStdString(message.load_embedded_art_request().filename())); QStringFromStdString(message.load_embedded_art_request().filename()));
reply.mutable_load_embedded_art_response()->set_data( reply.mutable_load_embedded_art_response()->set_data(data.constData(),
data.constData(), data.size()); data.size());
} else if (message.has_read_cloud_file_request()) { } else if (message.has_read_cloud_file_request()) {
#ifdef HAVE_GOOGLE_DRIVE #ifdef HAVE_GOOGLE_DRIVE
const pb::tagreader::ReadCloudFileRequest& req = const pb::tagreader::ReadCloudFileRequest& req =
message.read_cloud_file_request(); message.read_cloud_file_request();
if (!tag_reader_.ReadCloudFile( if (!tag_reader_.ReadCloudFile(
QUrl::fromEncoded(QByteArray(req.download_url().data(), QUrl::fromEncoded(QByteArray(req.download_url().data(),
req.download_url().size())), req.download_url().size())),
QStringFromStdString(req.title()), QStringFromStdString(req.title()), req.size(),
req.size(), QStringFromStdString(req.mime_type()),
QStringFromStdString(req.mime_type()), QStringFromStdString(req.authorisation_header()),
QStringFromStdString(req.authorisation_header()), reply.mutable_read_cloud_file_response()->mutable_metadata())) {
reply.mutable_read_cloud_file_response()->mutable_metadata())) {
reply.mutable_read_cloud_file_response()->clear_metadata(); reply.mutable_read_cloud_file_response()->clear_metadata();
} }
#endif #endif
@ -86,10 +85,8 @@ void TagReaderWorker::MessageArrived(const pb::tagreader::Message& message) {
SendReply(message, &reply); SendReply(message, &reply);
} }
void TagReaderWorker::DeviceClosed() { void TagReaderWorker::DeviceClosed() {
AbstractMessageHandler<pb::tagreader::Message>::DeviceClosed(); AbstractMessageHandler<pb::tagreader::Message>::DeviceClosed();
qApp->exit(); qApp->exit();
} }

View File

@ -24,15 +24,15 @@
#include "core/messagehandler.h" #include "core/messagehandler.h"
class TagReaderWorker : public AbstractMessageHandler<pb::tagreader::Message> { class TagReaderWorker : public AbstractMessageHandler<pb::tagreader::Message> {
public: public:
TagReaderWorker(QIODevice* socket, QObject* parent = NULL); TagReaderWorker(QIODevice* socket, QObject* parent = NULL);
protected: protected:
void MessageArrived(const pb::tagreader::Message& message); void MessageArrived(const pb::tagreader::Message& message);
void DeviceClosed(); void DeviceClosed();
private: private:
TagReader tag_reader_; TagReader tag_reader_;
}; };
#endif // TAGREADERWORKER_H #endif // TAGREADERWORKER_H

View File

@ -23,34 +23,22 @@
namespace _detail { namespace _detail {
ClosureBase::ClosureBase(ObjectHelper* helper) ClosureBase::ClosureBase(ObjectHelper* helper) : helper_(helper) {}
: helper_(helper) {
}
ClosureBase::~ClosureBase() { ClosureBase::~ClosureBase() {}
}
CallbackClosure::CallbackClosure( CallbackClosure::CallbackClosure(QObject* sender, const char* signal,
QObject* sender, std::function<void()> callback)
const char* signal, : ClosureBase(new ObjectHelper(sender, signal, this)),
std::function<void()> callback) callback_(callback) {}
: ClosureBase(new ObjectHelper(sender, signal, this)),
callback_(callback) {
}
void CallbackClosure::Invoke() { void CallbackClosure::Invoke() { callback_(); }
callback_();
}
ObjectHelper* ClosureBase::helper() const { ObjectHelper* ClosureBase::helper() const { return helper_; }
return helper_;
}
ObjectHelper::ObjectHelper( ObjectHelper::ObjectHelper(QObject* sender, const char* signal,
QObject* sender, ClosureBase* closure)
const char* signal, : closure_(closure) {
ClosureBase* closure)
: closure_(closure) {
connect(sender, signal, SLOT(Invoked())); connect(sender, signal, SLOT(Invoked()));
connect(sender, SIGNAL(destroyed()), SLOT(deleteLater())); connect(sender, SIGNAL(destroyed()), SLOT(deleteLater()));
} }
@ -64,12 +52,9 @@ void Unpack(QList<QGenericArgument>*) {}
} // namespace _detail } // namespace _detail
_detail::ClosureBase* NewClosure( _detail::ClosureBase* NewClosure(QObject* sender, const char* signal,
QObject* sender, std::function<void()> callback) {
const char* signal, return new _detail::CallbackClosure(sender, signal, callback);
std::function<void()> callback) {
return new _detail::CallbackClosure(
sender, signal, callback);
} }
void DoAfter(QObject* receiver, const char* slot, int msec) { void DoAfter(QObject* receiver, const char* slot, int msec) {

View File

@ -52,10 +52,7 @@ class ClosureBase {
class ObjectHelper : public QObject { class ObjectHelper : public QObject {
Q_OBJECT Q_OBJECT
public: public:
ObjectHelper( ObjectHelper(QObject* parent, const char* signal, ClosureBase* closure);
QObject* parent,
const char* signal,
ClosureBase* closure);
private slots: private slots:
void Invoked(); void Invoked();
@ -76,7 +73,8 @@ void Unpack(QList<QGenericArgument>* list, const Arg& arg) {
} }
template <typename Head, typename... Tail> template <typename Head, typename... Tail>
void Unpack(QList<QGenericArgument>* list, const Head& head, const Tail&... tail) { void Unpack(QList<QGenericArgument>* list, const Head& head,
const Tail&... tail) {
Unpack(list, head); Unpack(list, head);
Unpack(list, tail...); Unpack(list, tail...);
} }
@ -84,45 +82,39 @@ void Unpack(QList<QGenericArgument>* list, const Head& head, const Tail&... tail
template <typename... Args> template <typename... Args>
class Closure : public ClosureBase { class Closure : public ClosureBase {
public: public:
Closure( Closure(QObject* sender, const char* signal, QObject* receiver,
QObject* sender, const char* slot, const Args&... args)
const char* signal, : ClosureBase(new ObjectHelper(sender, signal, this)),
QObject* receiver, // std::bind is the easiest way to store an argument list.
const char* slot, function_(std::bind(&Closure<Args...>::Call, this, args...)),
const Args&... args) receiver_(receiver) {
: ClosureBase(new ObjectHelper(sender, signal, this)),
// std::bind is the easiest way to store an argument list.
function_(std::bind(&Closure<Args...>::Call, this, args...)),
receiver_(receiver) {
const QMetaObject* meta_receiver = receiver->metaObject(); const QMetaObject* meta_receiver = receiver->metaObject();
QByteArray normalised_slot = QMetaObject::normalizedSignature(slot + 1); QByteArray normalised_slot = QMetaObject::normalizedSignature(slot + 1);
const int index = meta_receiver->indexOfSlot(normalised_slot.constData()); const int index = meta_receiver->indexOfSlot(normalised_slot.constData());
Q_ASSERT(index != -1); Q_ASSERT(index != -1);
slot_ = meta_receiver->method(index); slot_ = meta_receiver->method(index);
QObject::connect(receiver_, SIGNAL(destroyed()), helper_, SLOT(deleteLater())); QObject::connect(receiver_, SIGNAL(destroyed()), helper_,
SLOT(deleteLater()));
} }
virtual void Invoke() { virtual void Invoke() { function_(); }
function_();
}
private: private:
void Call(const Args&... args) { void Call(const Args&... args) {
QList<QGenericArgument> arg_list; QList<QGenericArgument> arg_list;
Unpack(&arg_list, args...); Unpack(&arg_list, args...);
slot_.invoke( slot_.invoke(receiver_,
receiver_, arg_list.size() > 0 ? arg_list[0] : QGenericArgument(),
arg_list.size() > 0 ? arg_list[0] : QGenericArgument(), arg_list.size() > 1 ? arg_list[1] : QGenericArgument(),
arg_list.size() > 1 ? arg_list[1] : QGenericArgument(), arg_list.size() > 2 ? arg_list[2] : QGenericArgument(),
arg_list.size() > 2 ? arg_list[2] : QGenericArgument(), arg_list.size() > 3 ? arg_list[3] : QGenericArgument(),
arg_list.size() > 3 ? arg_list[3] : QGenericArgument(), arg_list.size() > 4 ? arg_list[4] : QGenericArgument(),
arg_list.size() > 4 ? arg_list[4] : QGenericArgument(), arg_list.size() > 5 ? arg_list[5] : QGenericArgument(),
arg_list.size() > 5 ? arg_list[5] : QGenericArgument(), arg_list.size() > 6 ? arg_list[6] : QGenericArgument(),
arg_list.size() > 6 ? arg_list[6] : QGenericArgument(), arg_list.size() > 7 ? arg_list[7] : QGenericArgument(),
arg_list.size() > 7 ? arg_list[7] : QGenericArgument(), arg_list.size() > 8 ? arg_list[8] : QGenericArgument(),
arg_list.size() > 8 ? arg_list[8] : QGenericArgument(), arg_list.size() > 9 ? arg_list[9] : QGenericArgument());
arg_list.size() > 9 ? arg_list[9] : QGenericArgument());
} }
std::function<void()> function_; std::function<void()> function_;
@ -133,20 +125,10 @@ class Closure : public ClosureBase {
template <typename T, typename... Args> template <typename T, typename... Args>
class SharedClosure : public Closure<Args...> { class SharedClosure : public Closure<Args...> {
public: public:
SharedClosure( SharedClosure(QSharedPointer<T> sender, const char* signal, QObject* receiver,
QSharedPointer<T> sender, const char* slot, const Args&... args)
const char* signal, : Closure<Args...>(sender.data(), signal, receiver, slot, args...),
QObject* receiver, data_(sender) {}
const char* slot,
const Args&... args)
: Closure<Args...>(
sender.data(),
signal,
receiver,
slot,
args...),
data_(sender) {
}
private: private:
QSharedPointer<T> data_; QSharedPointer<T> data_;
@ -154,10 +136,8 @@ class SharedClosure : public Closure<Args...> {
class CallbackClosure : public ClosureBase { class CallbackClosure : public ClosureBase {
public: public:
CallbackClosure( CallbackClosure(QObject* sender, const char* signal,
QObject* sender, std::function<void()> callback);
const char* signal,
std::function<void()> callback);
virtual void Invoke(); virtual void Invoke();
@ -168,61 +148,45 @@ class CallbackClosure : public ClosureBase {
} // namespace _detail } // namespace _detail
template <typename... Args> template <typename... Args>
_detail::ClosureBase* NewClosure( _detail::ClosureBase* NewClosure(QObject* sender, const char* signal,
QObject* sender, QObject* receiver, const char* slot,
const char* signal, const Args&... args) {
QObject* receiver, return new _detail::Closure<Args...>(sender, signal, receiver, slot, args...);
const char* slot,
const Args&... args) {
return new _detail::Closure<Args...>(
sender, signal, receiver, slot, args...);
} }
// QSharedPointer variant // QSharedPointer variant
template <typename T, typename... Args> template <typename T, typename... Args>
_detail::ClosureBase* NewClosure( _detail::ClosureBase* NewClosure(QSharedPointer<T> sender, const char* signal,
QSharedPointer<T> sender, QObject* receiver, const char* slot,
const char* signal, const Args&... args) {
QObject* receiver, return new _detail::SharedClosure<T, Args...>(sender, signal, receiver, slot,
const char* slot, args...);
const Args&... args) {
return new _detail::SharedClosure<T, Args...>(
sender, signal, receiver, slot, args...);
} }
_detail::ClosureBase* NewClosure( _detail::ClosureBase* NewClosure(QObject* sender, const char* signal,
QObject* sender, std::function<void()> callback);
const char* signal,
std::function<void()> callback);
template <typename... Args> template <typename... Args>
_detail::ClosureBase* NewClosure( _detail::ClosureBase* NewClosure(QObject* sender, const char* signal,
QObject* sender, std::function<void(Args...)> callback,
const char* signal, const Args&... args) {
std::function<void(Args...)> callback,
const Args&... args) {
return NewClosure(sender, signal, std::bind(callback, args...)); return NewClosure(sender, signal, std::bind(callback, args...));
} }
template <typename... Args> template <typename... Args>
_detail::ClosureBase* NewClosure( _detail::ClosureBase* NewClosure(QObject* sender, const char* signal,
QObject* sender, void (*callback)(Args...),
const char* signal, const Args&... args) {
void (*callback)(Args...),
const Args&... args) {
return NewClosure(sender, signal, std::bind(callback, args...)); return NewClosure(sender, signal, std::bind(callback, args...));
} }
template <typename T, typename Unused, typename... Args> template <typename T, typename Unused, typename... Args>
_detail::ClosureBase* NewClosure( _detail::ClosureBase* NewClosure(QObject* sender, const char* signal,
QObject* sender, T* receiver, Unused (T::*callback)(Args...),
const char* signal, const Args&... args) {
T* receiver, Unused (T::*callback)(Args...),
const Args&... args) {
return NewClosure(sender, signal, std::bind(callback, receiver, args...)); return NewClosure(sender, signal, std::bind(callback, receiver, args...));
} }
void DoAfter(QObject* receiver, const char* slot, int msec); void DoAfter(QObject* receiver, const char* slot, int msec);
void DoInAMinuteOrSo(QObject* receiver, const char* slot); void DoInAMinuteOrSo(QObject* receiver, const char* slot);

View File

@ -41,13 +41,13 @@
ThreadFunctor object and start it. ThreadFunctor object and start it.
*/ */
/* /*
Base abstract classes ThreadFunctorBase and ThreadFunctor (for void and Base abstract classes ThreadFunctorBase and ThreadFunctor (for void and
non-void result): non-void result):
*/ */
template<typename ReturnType> template <typename ReturnType>
class ThreadFunctorBase : public QFutureInterface<ReturnType>, public QRunnable { class ThreadFunctorBase : public QFutureInterface<ReturnType>,
public QRunnable {
public: public:
ThreadFunctorBase() {} ThreadFunctorBase() {}
@ -68,10 +68,8 @@ class ThreadFunctorBase : public QFutureInterface<ReturnType>, public QRunnable
template <typename ReturnType, typename... Args> template <typename ReturnType, typename... Args>
class ThreadFunctor : public ThreadFunctorBase<ReturnType> { class ThreadFunctor : public ThreadFunctorBase<ReturnType> {
public: public:
ThreadFunctor(std::function<ReturnType (Args...)> function, ThreadFunctor(std::function<ReturnType(Args...)> function, Args... args)
Args... args) : function_(std::bind(function, args...)) {}
: function_(std::bind(function, args...)) {
}
virtual void run() { virtual void run() {
this->reportResult(function_()); this->reportResult(function_());
@ -84,12 +82,10 @@ class ThreadFunctor : public ThreadFunctorBase<ReturnType> {
// Partial specialisation for void return type. // Partial specialisation for void return type.
template <typename... Args> template <typename... Args>
class ThreadFunctor <void, Args...> : public ThreadFunctorBase<void> { class ThreadFunctor<void, Args...> : public ThreadFunctorBase<void> {
public: public:
ThreadFunctor(std::function<void (Args...)> function, ThreadFunctor(std::function<void(Args...)> function, Args... args)
Args... args) : function_(std::bind(function, args...)) {}
: function_(std::bind(function, args...)) {
}
virtual void run() { virtual void run() {
function_(); function_();
@ -100,39 +96,33 @@ class ThreadFunctor <void, Args...> : public ThreadFunctorBase<void> {
std::function<void()> function_; std::function<void()> function_;
}; };
/* /*
Run functions Run functions
*/ */
namespace ConcurrentRun { namespace ConcurrentRun {
// Empty argument form. // Empty argument form.
template <typename ReturnType> template <typename ReturnType>
QFuture<ReturnType> Run( QFuture<ReturnType> Run(QThreadPool* threadpool,
QThreadPool* threadpool, std::function<ReturnType()> function) {
std::function<ReturnType ()> function) { return (new ThreadFunctor<ReturnType>(function))->Start(threadpool);
return (new ThreadFunctor<ReturnType>(function))->Start(threadpool);
}
// Function object with arguments form.
template <typename ReturnType, typename... Args>
QFuture<ReturnType> Run(
QThreadPool* threadpool,
std::function<ReturnType (Args...)> function,
const Args&... args) {
return (new ThreadFunctor<ReturnType, Args...>(
function, args...))->Start(threadpool);
}
// Support passing C function pointers instead of function objects.
template <typename ReturnType, typename... Args>
QFuture<ReturnType> Run(
QThreadPool* threadpool,
ReturnType (*function) (Args...),
const Args&... args) {
return Run(
threadpool, std::function<ReturnType (Args...)>(function), args...);
}
} }
#endif // CONCURRENTRUN_H // Function object with arguments form.
template <typename ReturnType, typename... Args>
QFuture<ReturnType> Run(QThreadPool* threadpool,
std::function<ReturnType(Args...)> function,
const Args&... args) {
return (new ThreadFunctor<ReturnType, Args...>(function, args...))
->Start(threadpool);
}
// Support passing C function pointers instead of function objects.
template <typename ReturnType, typename... Args>
QFuture<ReturnType> Run(QThreadPool* threadpool,
ReturnType (*function)(Args...), const Args&... args) {
return Run(threadpool, std::function<ReturnType(Args...)>(function), args...);
}
}
#endif // CONCURRENTRUN_H

View File

@ -33,7 +33,6 @@
#include "logging.h" #include "logging.h"
namespace logging { namespace logging {
static Level sDefaultLevel = Level_Debug; static Level sDefaultLevel = Level_Debug;
@ -46,18 +45,25 @@ static const char* kMessageHandlerMagic = "__logging_message__";
static const int kMessageHandlerMagicLength = strlen(kMessageHandlerMagic); static const int kMessageHandlerMagicLength = strlen(kMessageHandlerMagic);
static QtMsgHandler sOriginalMessageHandler = nullptr; static QtMsgHandler sOriginalMessageHandler = nullptr;
void GLog(const char* domain, int level, const char* message, void* user_data) { void GLog(const char* domain, int level, const char* message, void* user_data) {
switch (level) { switch (level) {
case G_LOG_FLAG_RECURSION: case G_LOG_FLAG_RECURSION:
case G_LOG_FLAG_FATAL: case G_LOG_FLAG_FATAL:
case G_LOG_LEVEL_ERROR: case G_LOG_LEVEL_ERROR:
case G_LOG_LEVEL_CRITICAL: qLog(Error) << message; break; case G_LOG_LEVEL_CRITICAL:
case G_LOG_LEVEL_WARNING: qLog(Warning) << message; break; qLog(Error) << message;
break;
case G_LOG_LEVEL_WARNING:
qLog(Warning) << message;
break;
case G_LOG_LEVEL_MESSAGE: case G_LOG_LEVEL_MESSAGE:
case G_LOG_LEVEL_INFO: qLog(Info) << message; break; case G_LOG_LEVEL_INFO:
qLog(Info) << message;
break;
case G_LOG_LEVEL_DEBUG: case G_LOG_LEVEL_DEBUG:
default: qLog(Debug) << message; break; default:
qLog(Debug) << message;
break;
} }
} }
@ -70,13 +76,19 @@ static void MessageHandler(QtMsgType type, const char* message) {
Level level = Level_Debug; Level level = Level_Debug;
switch (type) { switch (type) {
case QtFatalMsg: case QtFatalMsg:
case QtCriticalMsg: level = Level_Error; break; case QtCriticalMsg:
case QtWarningMsg: level = Level_Warning; break; level = Level_Error;
break;
case QtWarningMsg:
level = Level_Warning;
break;
case QtDebugMsg: case QtDebugMsg:
default: level = Level_Debug; break; default:
level = Level_Debug;
break;
} }
foreach (const QString& line, QString::fromLocal8Bit(message).split('\n')) { foreach(const QString & line, QString::fromLocal8Bit(message).split('\n')) {
CreateLogger(level, "unknown", -1) << line.toLocal8Bit().constData(); CreateLogger(level, "unknown", -1) << line.toLocal8Bit().constData();
} }
@ -85,7 +97,6 @@ static void MessageHandler(QtMsgType type, const char* message) {
} }
} }
void Init() { void Init() {
delete sClassLevels; delete sClassLevels;
delete sNullDevice; delete sNullDevice;
@ -100,10 +111,9 @@ void Init() {
} }
void SetLevels(const QString& levels) { void SetLevels(const QString& levels) {
if (!sClassLevels) if (!sClassLevels) return;
return;
foreach (const QString& item, levels.split(',')) { foreach(const QString & item, levels.split(',')) {
const QStringList class_level = item.split(':'); const QStringList class_level = item.split(':');
QString class_name; QString class_name;
@ -122,14 +132,14 @@ void SetLevels(const QString& levels) {
} }
if (class_name.isEmpty() || class_name == "*") { if (class_name.isEmpty() || class_name == "*") {
sDefaultLevel = (Level) level; sDefaultLevel = (Level)level;
} else { } else {
sClassLevels->insert(class_name, (Level) level); sClassLevels->insert(class_name, (Level)level);
} }
} }
} }
QString ParsePrettyFunction(const char * pretty_function) { QString ParsePrettyFunction(const char* pretty_function) {
// Get the class name out of the function name. // Get the class name out of the function name.
QString class_name = pretty_function; QString class_name = pretty_function;
const int paren = class_name.indexOf('('); const int paren = class_name.indexOf('(');
@ -144,7 +154,7 @@ QString ParsePrettyFunction(const char * pretty_function) {
const int space = class_name.lastIndexOf(' '); const int space = class_name.lastIndexOf(' ');
if (space != -1) { if (space != -1) {
class_name = class_name.mid(space+1); class_name = class_name.mid(space + 1);
} }
return class_name; return class_name;
@ -154,11 +164,21 @@ QDebug CreateLogger(Level level, const QString& class_name, int line) {
// Map the level to a string // Map the level to a string
const char* level_name = nullptr; const char* level_name = nullptr;
switch (level) { switch (level) {
case Level_Debug: level_name = " DEBUG "; break; case Level_Debug:
case Level_Info: level_name = " INFO "; break; level_name = " DEBUG ";
case Level_Warning: level_name = " WARN "; break; break;
case Level_Error: level_name = " ERROR "; break; case Level_Info:
case Level_Fatal: level_name = " FATAL "; break; level_name = " INFO ";
break;
case Level_Warning:
level_name = " WARN ";
break;
case Level_Error:
level_name = " ERROR ";
break;
case Level_Fatal:
level_name = " FATAL ";
break;
} }
// Check the settings to see if we're meant to show or hide this message. // Check the settings to see if we're meant to show or hide this message.
@ -182,9 +202,11 @@ QDebug CreateLogger(Level level, const QString& class_name, int line) {
} }
QDebug ret(type); QDebug ret(type);
ret.nospace() << kMessageHandlerMagic ret.nospace() << kMessageHandlerMagic << QDateTime::currentDateTime()
<< QDateTime::currentDateTime().toString("hh:mm:ss.zzz").toAscii().constData() .toString("hh:mm:ss.zzz")
<< level_name << function_line.leftJustified(32).toAscii().constData(); .toAscii()
.constData() << level_name
<< function_line.leftJustified(32).toAscii().constData();
return ret.space(); return ret.space();
} }
@ -192,10 +214,7 @@ QDebug CreateLogger(Level level, const QString& class_name, int line) {
QString CXXDemangle(const QString& mangled_function) { QString CXXDemangle(const QString& mangled_function) {
int status; int status;
char* demangled_function = abi::__cxa_demangle( char* demangled_function = abi::__cxa_demangle(
mangled_function.toAscii().constData(), mangled_function.toAscii().constData(), nullptr, nullptr, &status);
nullptr,
nullptr,
&status);
if (status == 0) { if (status == 0) {
QString ret = QString::fromAscii(demangled_function); QString ret = QString::fromAscii(demangled_function);
free(demangled_function); free(demangled_function);
@ -232,8 +251,10 @@ QString DemangleSymbol(const QString& symbol) {
void DumpStackTrace() { void DumpStackTrace() {
#ifdef Q_OS_UNIX #ifdef Q_OS_UNIX
void* callstack[128]; void* callstack[128];
int callstack_size = backtrace(reinterpret_cast<void**>(&callstack), sizeof(callstack)); int callstack_size =
char** symbols = backtrace_symbols(reinterpret_cast<void**>(&callstack), callstack_size); backtrace(reinterpret_cast<void**>(&callstack), sizeof(callstack));
char** symbols =
backtrace_symbols(reinterpret_cast<void**>(&callstack), callstack_size);
// Start from 1 to skip ourself. // Start from 1 to skip ourself.
for (int i = 1; i < callstack_size; ++i) { for (int i = 1; i < callstack_size; ++i) {
qLog(Debug) << DemangleSymbol(QString::fromAscii(symbols[i])); qLog(Debug) << DemangleSymbol(QString::fromAscii(symbols[i]));
@ -244,4 +265,4 @@ void DumpStackTrace() {
#endif #endif
} }
} // namespace logging } // namespace logging

View File

@ -18,46 +18,47 @@
// it is used by the Spotify blob which links against libspotify and is not GPL // it is used by the Spotify blob which links against libspotify and is not GPL
// compatible. // compatible.
#ifndef LOGGING_H #ifndef LOGGING_H
#define LOGGING_H #define LOGGING_H
#include <QDebug> #include <QDebug>
#ifdef QT_NO_DEBUG_STREAM #ifdef QT_NO_DEBUG_STREAM
# define qLog(level) while (false) QNoDebug() #define qLog(level) \
while (false) QNoDebug()
#else #else
# define qLog(level) \ #define qLog(level) \
logging::CreateLogger(logging::Level_##level, \ logging::CreateLogger(logging::Level_##level, \
logging::ParsePrettyFunction(__PRETTY_FUNCTION__), __LINE__) logging::ParsePrettyFunction(__PRETTY_FUNCTION__), \
__LINE__)
#endif #endif
namespace logging { namespace logging {
class NullDevice : public QIODevice { class NullDevice : public QIODevice {
protected: protected:
qint64 readData(char*, qint64) { return -1; } qint64 readData(char*, qint64) { return -1; }
qint64 writeData(const char*, qint64 len) { return len; } qint64 writeData(const char*, qint64 len) { return len; }
}; };
enum Level { enum Level {
Level_Fatal = -1, Level_Fatal = -1,
Level_Error = 0, Level_Error = 0,
Level_Warning, Level_Warning,
Level_Info, Level_Info,
Level_Debug, Level_Debug,
}; };
void Init(); void Init();
void SetLevels(const QString& levels); void SetLevels(const QString& levels);
void DumpStackTrace(); void DumpStackTrace();
QString ParsePrettyFunction(const char* pretty_function); QString ParsePrettyFunction(const char* pretty_function);
QDebug CreateLogger(Level level, const QString& class_name, int line); QDebug CreateLogger(Level level, const QString& class_name, int line);
void GLog(const char* domain, int level, const char* message, void* user_data); void GLog(const char* domain, int level, const char* message, void* user_data);
extern const char* kDefaultLogLevels; extern const char* kDefaultLogLevels;
} }
#endif // LOGGING_H #endif // LOGGING_H

View File

@ -18,7 +18,6 @@
// it is used by the Spotify blob which links against libspotify and is not GPL // it is used by the Spotify blob which links against libspotify and is not GPL
// compatible. // compatible.
#include "messagehandler.h" #include "messagehandler.h"
#include "core/logging.h" #include "core/logging.h"
@ -26,13 +25,13 @@
#include <QLocalSocket> #include <QLocalSocket>
_MessageHandlerBase::_MessageHandlerBase(QIODevice* device, QObject* parent) _MessageHandlerBase::_MessageHandlerBase(QIODevice* device, QObject* parent)
: QObject(parent), : QObject(parent),
device_(nullptr), device_(nullptr),
flush_abstract_socket_(nullptr), flush_abstract_socket_(nullptr),
flush_local_socket_(nullptr), flush_local_socket_(nullptr),
reading_protobuf_(false), reading_protobuf_(false),
expected_length_(0), expected_length_(0),
is_device_closed_(false) { is_device_closed_(false) {
if (device) { if (device) {
SetDevice(device); SetDevice(device);
} }

View File

@ -18,7 +18,6 @@
// it is used by the Spotify blob which links against libspotify and is not GPL // it is used by the Spotify blob which links against libspotify and is not GPL
// compatible. // compatible.
#ifndef MESSAGEHANDLER_H #ifndef MESSAGEHANDLER_H
#define MESSAGEHANDLER_H #define MESSAGEHANDLER_H
@ -37,11 +36,8 @@ class QAbstractSocket;
class QIODevice; class QIODevice;
class QLocalSocket; class QLocalSocket;
#define QStringFromStdString(x) \ #define QStringFromStdString(x) QString::fromUtf8(x.data(), x.size())
QString::fromUtf8(x.data(), x.size()) #define DataCommaSizeFromQString(x) x.toUtf8().constData(), x.toUtf8().length()
#define DataCommaSizeFromQString(x) \
x.toUtf8().constData(), x.toUtf8().length()
// Reads and writes uint32 length encoded protobufs to a socket. // Reads and writes uint32 length encoded protobufs to a socket.
// This base QObject is separate from AbstractMessageHandler because moc can't // This base QObject is separate from AbstractMessageHandler because moc can't
@ -49,7 +45,7 @@ class QLocalSocket;
class _MessageHandlerBase : public QObject { class _MessageHandlerBase : public QObject {
Q_OBJECT Q_OBJECT
public: public:
// device can be NULL, in which case you must call SetDevice before writing // device can be NULL, in which case you must call SetDevice before writing
// any messages. // any messages.
_MessageHandlerBase(QIODevice* device, QObject* parent); _MessageHandlerBase(QIODevice* device, QObject* parent);
@ -59,16 +55,16 @@ public:
// After this is true, messages cannot be sent to the handler any more. // After this is true, messages cannot be sent to the handler any more.
bool is_device_closed() const { return is_device_closed_; } bool is_device_closed() const { return is_device_closed_; }
protected slots: protected slots:
void WriteMessage(const QByteArray& data); void WriteMessage(const QByteArray& data);
void DeviceReadyRead(); void DeviceReadyRead();
virtual void DeviceClosed(); virtual void DeviceClosed();
protected: protected:
virtual bool RawMessageArrived(const QByteArray& data) = 0; virtual bool RawMessageArrived(const QByteArray& data) = 0;
virtual void AbortAll() = 0; virtual void AbortAll() = 0;
protected: protected:
typedef bool (QAbstractSocket::*FlushAbstractSocket)(); typedef bool (QAbstractSocket::*FlushAbstractSocket)();
typedef bool (QLocalSocket::*FlushLocalSocket)(); typedef bool (QLocalSocket::*FlushLocalSocket)();
@ -83,13 +79,12 @@ protected:
bool is_device_closed_; bool is_device_closed_;
}; };
// Reads and writes uint32 length encoded MessageType messages to a socket. // Reads and writes uint32 length encoded MessageType messages to a socket.
// You should subclass this and implement the MessageArrived(MessageType) // You should subclass this and implement the MessageArrived(MessageType)
// method. // method.
template <typename MT> template <typename MT>
class AbstractMessageHandler : public _MessageHandlerBase { class AbstractMessageHandler : public _MessageHandlerBase {
public: public:
AbstractMessageHandler(QIODevice* device, QObject* parent); AbstractMessageHandler(QIODevice* device, QObject* parent);
~AbstractMessageHandler() { AbortAll(); } ~AbstractMessageHandler() { AbortAll(); }
@ -113,7 +108,7 @@ public:
// reply on the socket. Used on the worker side. // reply on the socket. Used on the worker side.
void SendReply(const MessageType& request, MessageType* reply); void SendReply(const MessageType& request, MessageType* reply);
protected: protected:
// Called when a message is received from the socket. // Called when a message is received from the socket.
virtual void MessageArrived(const MessageType& message) {} virtual void MessageArrived(const MessageType& message) {}
@ -121,19 +116,16 @@ protected:
bool RawMessageArrived(const QByteArray& data); bool RawMessageArrived(const QByteArray& data);
void AbortAll(); void AbortAll();
private: private:
QMap<int, ReplyType*> pending_replies_; QMap<int, ReplyType*> pending_replies_;
}; };
template <typename MT>
AbstractMessageHandler<MT>::AbstractMessageHandler(QIODevice* device,
QObject* parent)
: _MessageHandlerBase(device, parent) {}
template<typename MT> template <typename MT>
AbstractMessageHandler<MT>::AbstractMessageHandler(
QIODevice* device, QObject* parent)
: _MessageHandlerBase(device, parent)
{
}
template<typename MT>
void AbstractMessageHandler<MT>::SendMessage(const MessageType& message) { void AbstractMessageHandler<MT>::SendMessage(const MessageType& message) {
Q_ASSERT(QThread::currentThread() == thread()); Q_ASSERT(QThread::currentThread() == thread());
@ -141,27 +133,28 @@ void AbstractMessageHandler<MT>::SendMessage(const MessageType& message) {
WriteMessage(QByteArray(data.data(), data.size())); WriteMessage(QByteArray(data.data(), data.size()));
} }
template<typename MT> template <typename MT>
void AbstractMessageHandler<MT>::SendMessageAsync(const MessageType& message) { void AbstractMessageHandler<MT>::SendMessageAsync(const MessageType& message) {
std::string data = message.SerializeAsString(); std::string data = message.SerializeAsString();
metaObject()->invokeMethod(this, "WriteMessage", Qt::QueuedConnection, metaObject()->invokeMethod(
Q_ARG(QByteArray, QByteArray(data.data(), data.size()))); this, "WriteMessage", Qt::QueuedConnection,
Q_ARG(QByteArray, QByteArray(data.data(), data.size())));
} }
template<typename MT> template <typename MT>
void AbstractMessageHandler<MT>::SendRequest(ReplyType* reply) { void AbstractMessageHandler<MT>::SendRequest(ReplyType* reply) {
pending_replies_[reply->id()] = reply; pending_replies_[reply->id()] = reply;
SendMessage(reply->request_message()); SendMessage(reply->request_message());
} }
template<typename MT> template <typename MT>
void AbstractMessageHandler<MT>::SendReply(const MessageType& request, void AbstractMessageHandler<MT>::SendReply(const MessageType& request,
MessageType* reply) { MessageType* reply) {
reply->set_id(request.id()); reply->set_id(request.id());
SendMessage(*reply); SendMessage(*reply);
} }
template<typename MT> template <typename MT>
bool AbstractMessageHandler<MT>::RawMessageArrived(const QByteArray& data) { bool AbstractMessageHandler<MT>::RawMessageArrived(const QByteArray& data) {
MessageType message; MessageType message;
if (!message.ParseFromArray(data.constData(), data.size())) { if (!message.ParseFromArray(data.constData(), data.size())) {
@ -180,14 +173,10 @@ bool AbstractMessageHandler<MT>::RawMessageArrived(const QByteArray& data) {
return true; return true;
} }
template<typename MT> template <typename MT>
void AbstractMessageHandler<MT>::AbortAll() { void AbstractMessageHandler<MT>::AbortAll() {
foreach (ReplyType* reply, pending_replies_) { foreach(ReplyType * reply, pending_replies_) { reply->Abort(); }
reply->Abort();
}
pending_replies_.clear(); pending_replies_.clear();
} }
#endif // MESSAGEHANDLER_H
#endif // MESSAGEHANDLER_H

View File

@ -1,16 +1,16 @@
/* This file is part of Clementine. /* This file is part of Clementine.
Copyright 2011, David Sansome <me@davidsansome.com> Copyright 2011, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
Clementine is distributed in the hope that it will be useful, Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>. along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/ */
@ -18,11 +18,7 @@
#include "messagereply.h" #include "messagereply.h"
_MessageReplyBase::_MessageReplyBase(QObject* parent) _MessageReplyBase::_MessageReplyBase(QObject* parent)
: QObject(parent), : QObject(parent), finished_(false), success_(false) {}
finished_(false),
success_(false)
{
}
bool _MessageReplyBase::WaitForFinished() { bool _MessageReplyBase::WaitForFinished() {
qLog(Debug) << "Waiting on ID" << id(); qLog(Debug) << "Waiting on ID" << id();

View File

@ -29,7 +29,7 @@
class _MessageReplyBase : public QObject { class _MessageReplyBase : public QObject {
Q_OBJECT Q_OBJECT
public: public:
_MessageReplyBase(QObject* parent = 0); _MessageReplyBase(QObject* parent = 0);
virtual int id() const = 0; virtual int id() const = 0;
@ -46,19 +46,18 @@ public:
signals: signals:
void Finished(bool success); void Finished(bool success);
protected: protected:
bool finished_; bool finished_;
bool success_; bool success_;
QSemaphore semaphore_; QSemaphore semaphore_;
}; };
// A reply future class that is returned immediately for requests that will // A reply future class that is returned immediately for requests that will
// occur in the background. Similar to QNetworkReply. // occur in the background. Similar to QNetworkReply.
template <typename MessageType> template <typename MessageType>
class MessageReply : public _MessageReplyBase { class MessageReply : public _MessageReplyBase {
public: public:
MessageReply(const MessageType& request_message, QObject* parent = 0); MessageReply(const MessageType& request_message, QObject* parent = 0);
int id() const { return request_message_.id(); } int id() const { return request_message_.id(); }
@ -67,21 +66,19 @@ public:
void SetReply(const MessageType& message); void SetReply(const MessageType& message);
private: private:
MessageType request_message_; MessageType request_message_;
MessageType reply_message_; MessageType reply_message_;
}; };
template <typename MessageType>
template<typename MessageType>
MessageReply<MessageType>::MessageReply(const MessageType& request_message, MessageReply<MessageType>::MessageReply(const MessageType& request_message,
QObject* parent) QObject* parent)
: _MessageReplyBase(parent) : _MessageReplyBase(parent) {
{
request_message_.MergeFrom(request_message); request_message_.MergeFrom(request_message);
} }
template<typename MessageType> template <typename MessageType>
void MessageReply<MessageType>::SetReply(const MessageType& message) { void MessageReply<MessageType>::SetReply(const MessageType& message) {
Q_ASSERT(!finished_); Q_ASSERT(!finished_);
@ -94,4 +91,4 @@ void MessageReply<MessageType>::SetReply(const MessageType& message) {
semaphore_.release(); semaphore_.release();
} }
#endif // MESSAGEREPLY_H #endif // MESSAGEREPLY_H

View File

@ -18,7 +18,6 @@
// it is used by the Spotify blob which links against libspotify and is not GPL // it is used by the Spotify blob which links against libspotify and is not GPL
// compatible. // compatible.
#ifndef OVERRIDE_H #ifndef OVERRIDE_H
#define OVERRIDE_H #define OVERRIDE_H
@ -26,13 +25,13 @@
// it is available. // it is available.
#ifndef __has_extension #ifndef __has_extension
#define __has_extension(x) 0 #define __has_extension(x) 0
#endif #endif
#if __has_extension(cxx_override_control) // Clang feature checking macro. #if __has_extension(cxx_override_control) // Clang feature checking macro.
# define OVERRIDE override #define OVERRIDE override
#else #else
# define OVERRIDE #define OVERRIDE
#endif #endif
#endif // OVERRIDE_H #endif // OVERRIDE_H

View File

@ -22,4 +22,4 @@ class QObject;
void WaitForSignal(QObject* sender, const char* signal); void WaitForSignal(QObject* sender, const char* signal);
#endif // WAITFORSIGNAL_H #endif // WAITFORSIGNAL_H

View File

@ -17,9 +17,4 @@
#include "workerpool.h" #include "workerpool.h"
_WorkerPoolBase::_WorkerPoolBase(QObject* parent) _WorkerPoolBase::_WorkerPoolBase(QObject* parent) : QObject(parent) {}
: QObject(parent)
{
}

View File

@ -32,13 +32,12 @@
#include "core/closure.h" #include "core/closure.h"
#include "core/logging.h" #include "core/logging.h"
// Base class containing signals and slots - required because moc doesn't do // Base class containing signals and slots - required because moc doesn't do
// templated objects. // templated objects.
class _WorkerPoolBase : public QObject { class _WorkerPoolBase : public QObject {
Q_OBJECT Q_OBJECT
public: public:
_WorkerPoolBase(QObject* parent = 0); _WorkerPoolBase(QObject* parent = 0);
signals: signals:
@ -46,14 +45,13 @@ signals:
// worker wasn't found, or couldn't be executed. // worker wasn't found, or couldn't be executed.
void WorkerFailedToStart(); void WorkerFailedToStart();
protected slots: protected slots:
virtual void DoStart() {} virtual void DoStart() {}
virtual void NewConnection() {} virtual void NewConnection() {}
virtual void ProcessError(QProcess::ProcessError) {} virtual void ProcessError(QProcess::ProcessError) {}
virtual void SendQueuedMessages() {} virtual void SendQueuedMessages() {}
}; };
// Manages a pool of one or more external processes. A local socket server is // Manages a pool of one or more external processes. A local socket server is
// started for each process, and the address is passed to the process as // started for each process, and the address is passed to the process as
// argv[1]. The process is expected to connect back to the socket server, and // argv[1]. The process is expected to connect back to the socket server, and
@ -61,7 +59,7 @@ protected slots:
// Instances of HandlerType are created in the WorkerPool's thread. // Instances of HandlerType are created in the WorkerPool's thread.
template <typename HandlerType> template <typename HandlerType>
class WorkerPool : public _WorkerPoolBase { class WorkerPool : public _WorkerPoolBase {
public: public:
WorkerPool(QObject* parent = 0); WorkerPool(QObject* parent = 0);
~WorkerPool(); ~WorkerPool();
@ -90,7 +88,7 @@ public:
// worker. Can be called from any thread. // worker. Can be called from any thread.
ReplyType* SendMessageWithReply(MessageType* message); ReplyType* SendMessageWithReply(MessageType* message);
protected: protected:
// These are all reimplemented slots, they are called on the WorkerPool's // These are all reimplemented slots, they are called on the WorkerPool's
// thread. // thread.
void DoStart(); void DoStart();
@ -98,10 +96,13 @@ protected:
void ProcessError(QProcess::ProcessError error); void ProcessError(QProcess::ProcessError error);
void SendQueuedMessages(); void SendQueuedMessages();
private: private:
struct Worker { struct Worker {
Worker() : local_server_(NULL), local_socket_(NULL), process_(NULL), Worker()
handler_(NULL) {} : local_server_(NULL),
local_socket_(NULL),
process_(NULL),
handler_(NULL) {}
QLocalServer* local_server_; QLocalServer* local_server_;
QLocalSocket* local_socket_; QLocalSocket* local_socket_;
@ -114,8 +115,8 @@ private:
template <typename T> template <typename T>
Worker* FindWorker(T Worker::*member, T value) { Worker* FindWorker(T Worker::*member, T value) {
for (typename QList<Worker>::iterator it = workers_.begin() ; for (typename QList<Worker>::iterator it = workers_.begin();
it != workers_.end() ; ++it) { it != workers_.end(); ++it) {
if ((*it).*member == value) { if ((*it).*member == value) {
return &(*it); return &(*it);
} }
@ -140,7 +141,7 @@ private:
// my thread. // my thread.
HandlerType* NextHandler() const; HandlerType* NextHandler() const;
private: private:
QString local_server_name_; QString local_server_name_;
QString executable_name_; QString executable_name_;
QString executable_path_; QString executable_path_;
@ -155,26 +156,21 @@ private:
QQueue<ReplyType*> message_queue_; QQueue<ReplyType*> message_queue_;
}; };
template <typename HandlerType> template <typename HandlerType>
WorkerPool<HandlerType>::WorkerPool(QObject* parent) WorkerPool<HandlerType>::WorkerPool(QObject* parent)
: _WorkerPoolBase(parent), : _WorkerPoolBase(parent), next_worker_(0), next_id_(0) {
next_worker_(0),
next_id_(0)
{
worker_count_ = qBound(1, QThread::idealThreadCount() / 2, 2); worker_count_ = qBound(1, QThread::idealThreadCount() / 2, 2);
local_server_name_ = qApp->applicationName().toLower(); local_server_name_ = qApp->applicationName().toLower();
if (local_server_name_.isEmpty()) if (local_server_name_.isEmpty()) local_server_name_ = "workerpool";
local_server_name_ = "workerpool";
} }
template <typename HandlerType> template <typename HandlerType>
WorkerPool<HandlerType>::~WorkerPool() { WorkerPool<HandlerType>::~WorkerPool() {
foreach (const Worker& worker, workers_) { foreach(const Worker & worker, workers_) {
if (worker.local_socket_ && worker.process_) { if (worker.local_socket_ && worker.process_) {
disconnect(worker.process_, SIGNAL(error(QProcess::ProcessError)), disconnect(worker.process_, SIGNAL(error(QProcess::ProcessError)), this,
this, SLOT(ProcessError(QProcess::ProcessError))); SLOT(ProcessError(QProcess::ProcessError)));
// The worker is connected. Close his socket and wait for him to exit. // The worker is connected. Close his socket and wait for him to exit.
qLog(Debug) << "Closing worker socket"; qLog(Debug) << "Closing worker socket";
@ -192,9 +188,7 @@ WorkerPool<HandlerType>::~WorkerPool() {
} }
} }
foreach (ReplyType* reply, message_queue_) { foreach(ReplyType * reply, message_queue_) { reply->Abort(); }
reply->Abort();
}
} }
template <typename HandlerType> template <typename HandlerType>
@ -204,13 +198,15 @@ void WorkerPool<HandlerType>::SetWorkerCount(int count) {
} }
template <typename HandlerType> template <typename HandlerType>
void WorkerPool<HandlerType>::SetLocalServerName(const QString& local_server_name) { void WorkerPool<HandlerType>::SetLocalServerName(
const QString& local_server_name) {
Q_ASSERT(workers_.isEmpty()); Q_ASSERT(workers_.isEmpty());
local_server_name_ = local_server_name; local_server_name_ = local_server_name;
} }
template <typename HandlerType> template <typename HandlerType>
void WorkerPool<HandlerType>::SetExecutableName(const QString& executable_name) { void WorkerPool<HandlerType>::SetExecutableName(
const QString& executable_name) {
Q_ASSERT(workers_.isEmpty()); Q_ASSERT(workers_.isEmpty());
executable_name_ = executable_name; executable_name_ = executable_name;
} }
@ -235,7 +231,7 @@ void WorkerPool<HandlerType>::DoStart() {
search_path << qApp->applicationDirPath() + "/../PlugIns"; search_path << qApp->applicationDirPath() + "/../PlugIns";
#endif #endif
foreach (const QString& path_prefix, search_path) { foreach(const QString & path_prefix, search_path) {
const QString executable_path = path_prefix + "/" + executable_name_; const QString executable_path = path_prefix + "/" + executable_name_;
if (QFile::exists(executable_path)) { if (QFile::exists(executable_path)) {
executable_path_ = executable_path; executable_path_ = executable_path;
@ -244,7 +240,7 @@ void WorkerPool<HandlerType>::DoStart() {
} }
// Start all the workers // Start all the workers
for (int i=0 ; i<worker_count_ ; ++i) { for (int i = 0; i < worker_count_; ++i) {
Worker worker; Worker worker;
StartOneWorker(&worker); StartOneWorker(&worker);
@ -264,14 +260,16 @@ void WorkerPool<HandlerType>::StartOneWorker(Worker* worker) {
worker->local_server_ = new QLocalServer(this); worker->local_server_ = new QLocalServer(this);
worker->process_ = new QProcess(this); worker->process_ = new QProcess(this);
connect(worker->local_server_, SIGNAL(newConnection()), SLOT(NewConnection())); connect(worker->local_server_, SIGNAL(newConnection()),
SLOT(NewConnection()));
connect(worker->process_, SIGNAL(error(QProcess::ProcessError)), connect(worker->process_, SIGNAL(error(QProcess::ProcessError)),
SLOT(ProcessError(QProcess::ProcessError))); SLOT(ProcessError(QProcess::ProcessError)));
// Create a server, find an unused name and start listening // Create a server, find an unused name and start listening
forever { forever {
const int unique_number = qrand() ^ ((int)(quint64(this) & 0xFFFFFFFF)); const int unique_number = qrand() ^ ((int)(quint64(this) & 0xFFFFFFFF));
const QString name = QString("%1_%2").arg(local_server_name_).arg(unique_number); const QString name =
QString("%1_%2").arg(local_server_name_).arg(unique_number);
if (worker->local_server_->listen(name)) { if (worker->local_server_->listen(name)) {
break; break;
@ -284,7 +282,8 @@ void WorkerPool<HandlerType>::StartOneWorker(Worker* worker) {
// Start the process // Start the process
worker->process_->setProcessChannelMode(QProcess::ForwardedChannels); worker->process_->setProcessChannelMode(QProcess::ForwardedChannels);
worker->process_->start(executable_path_, worker->process_->start(executable_path_,
QStringList() << worker->local_server_->fullServerName()); QStringList()
<< worker->local_server_->fullServerName());
} }
template <typename HandlerType> template <typename HandlerType>
@ -295,10 +294,10 @@ void WorkerPool<HandlerType>::NewConnection() {
// Find the worker with this server. // Find the worker with this server.
Worker* worker = FindWorker(&Worker::local_server_, server); Worker* worker = FindWorker(&Worker::local_server_, server);
if (!worker) if (!worker) return;
return;
qLog(Debug) << "Worker" << worker << "connected to" << server->fullServerName(); qLog(Debug) << "Worker" << worker << "connected to"
<< server->fullServerName();
// Accept the connection. // Accept the connection.
worker->local_socket_ = server->nextPendingConnection(); worker->local_socket_ = server->nextPendingConnection();
@ -322,29 +321,29 @@ void WorkerPool<HandlerType>::ProcessError(QProcess::ProcessError error) {
// Find the worker with this process. // Find the worker with this process.
Worker* worker = FindWorker(&Worker::process_, process); Worker* worker = FindWorker(&Worker::process_, process);
if (!worker) if (!worker) return;
return;
switch (error) { switch (error) {
case QProcess::FailedToStart: case QProcess::FailedToStart:
// Failed to start errors are bad - it usually means the worker isn't // Failed to start errors are bad - it usually means the worker isn't
// installed. Don't restart the process, but tell our owner, who will // installed. Don't restart the process, but tell our owner, who will
// probably want to do something fatal. // probably want to do something fatal.
qLog(Error) << "Worker failed to start"; qLog(Error) << "Worker failed to start";
emit WorkerFailedToStart(); emit WorkerFailedToStart();
break; break;
default: default:
// On any other error we just restart the process. // On any other error we just restart the process.
qLog(Debug) << "Worker" << worker << "failed with error" << error << "- restarting"; qLog(Debug) << "Worker" << worker << "failed with error" << error
StartOneWorker(worker); << "- restarting";
break; StartOneWorker(worker);
break;
} }
} }
template <typename HandlerType> template <typename HandlerType>
typename WorkerPool<HandlerType>::ReplyType* typename WorkerPool<HandlerType>::ReplyType* WorkerPool<HandlerType>::NewReply(
WorkerPool<HandlerType>::NewReply(MessageType* message) { MessageType* message) {
const int id = next_id_.fetchAndAddOrdered(1); const int id = next_id_.fetchAndAddOrdered(1);
message->set_id(id); message->set_id(id);
@ -390,7 +389,7 @@ void WorkerPool<HandlerType>::SendQueuedMessages() {
template <typename HandlerType> template <typename HandlerType>
HandlerType* WorkerPool<HandlerType>::NextHandler() const { HandlerType* WorkerPool<HandlerType>::NextHandler() const {
for (int i=0 ; i<workers_.count() ; ++i) { for (int i = 0; i < workers_.count(); ++i) {
const int worker_index = (next_worker_ + i) % workers_.count(); const int worker_index = (next_worker_ + i) % workers_.count();
if (workers_[worker_index].handler_ && if (workers_[worker_index].handler_ &&
@ -403,4 +402,4 @@ HandlerType* WorkerPool<HandlerType>::NextHandler() const {
return NULL; return NULL;
} }
#endif // WORKERPOOL_H #endif // WORKERPOOL_H

View File

@ -28,13 +28,13 @@
#include "core/logging.h" #include "core/logging.h"
namespace { namespace {
static const int kTaglibPrefixCacheBytes = 64 * 1024; // Should be enough. static const int kTaglibPrefixCacheBytes = 64 * 1024; // Should be enough.
static const int kTaglibSuffixCacheBytes = 8 * 1024; static const int kTaglibSuffixCacheBytes = 8 * 1024;
} }
CloudStream::CloudStream( CloudStream::CloudStream(const QUrl& url, const QString& filename,
const QUrl& url, const QString& filename, const long length, const long length, const QString& auth,
const QString& auth, QNetworkAccessManager* network) QNetworkAccessManager* network)
: url_(url), : url_(url),
filename_(filename), filename_(filename),
encoded_filename_(filename_.toUtf8()), encoded_filename_(filename_.toUtf8()),
@ -43,12 +43,9 @@ CloudStream::CloudStream(
cursor_(0), cursor_(0),
network_(network), network_(network),
cache_(length), cache_(length),
num_requests_(0) { num_requests_(0) {}
}
TagLib::FileName CloudStream::name() const { TagLib::FileName CloudStream::name() const { return encoded_filename_.data(); }
return encoded_filename_.data();
}
bool CloudStream::CheckCache(int start, int end) { bool CloudStream::CheckCache(int start, int end) {
for (int i = start; i <= end; ++i) { for (int i = start; i <= end; ++i) {
@ -113,8 +110,8 @@ TagLib::ByteVector CloudStream::readBlock(ulong length) {
if (!auth_.isEmpty()) { if (!auth_.isEmpty()) {
request.setRawHeader("Authorization", auth_.toUtf8()); request.setRawHeader("Authorization", auth_.toUtf8());
} }
request.setRawHeader( request.setRawHeader("Range",
"Range", QString("bytes=%1-%2").arg(start).arg(end).toUtf8()); QString("bytes=%1-%2").arg(start).arg(end).toUtf8());
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
QNetworkRequest::AlwaysNetwork); QNetworkRequest::AlwaysNetwork);
// The Ubuntu One server applies the byte range to the gzipped data, rather // The Ubuntu One server applies the byte range to the gzipped data, rather
@ -124,7 +121,8 @@ TagLib::ByteVector CloudStream::readBlock(ulong length) {
} }
QNetworkReply* reply = network_->get(request); QNetworkReply* reply = network_->get(request);
connect(reply, SIGNAL(sslErrors(QList<QSslError>)), SLOT(SSLErrors(QList<QSslError>))); connect(reply, SIGNAL(sslErrors(QList<QSslError>)),
SLOT(SSLErrors(QList<QSslError>)));
++num_requests_; ++num_requests_;
QEventLoop loop; QEventLoop loop;
@ -163,9 +161,7 @@ bool CloudStream::readOnly() const {
return true; return true;
} }
bool CloudStream::isOpen() const { bool CloudStream::isOpen() const { return true; }
return true;
}
void CloudStream::seek(long offset, TagLib::IOStream::Position p) { void CloudStream::seek(long offset, TagLib::IOStream::Position p) {
switch (p) { switch (p) {
@ -184,24 +180,18 @@ void CloudStream::seek(long offset, TagLib::IOStream::Position p) {
} }
} }
void CloudStream::clear() { void CloudStream::clear() { cursor_ = 0; }
cursor_ = 0;
}
long CloudStream::tell() const { long CloudStream::tell() const { return cursor_; }
return cursor_;
}
long CloudStream::length() { long CloudStream::length() { return length_; }
return length_;
}
void CloudStream::truncate(long) { void CloudStream::truncate(long) {
qLog(Debug) << Q_FUNC_INFO << "not implemented"; qLog(Debug) << Q_FUNC_INFO << "not implemented";
} }
void CloudStream::SSLErrors(const QList<QSslError>& errors) { void CloudStream::SSLErrors(const QList<QSslError>& errors) {
foreach (const QSslError& error, errors) { foreach(const QSslError & error, errors) {
qLog(Debug) << error.error() << error.errorString(); qLog(Debug) << error.error() << error.errorString();
qLog(Debug) << error.certificate(); qLog(Debug) << error.certificate();
} }

View File

@ -31,11 +31,8 @@ class QNetworkAccessManager;
class CloudStream : public QObject, public TagLib::IOStream { class CloudStream : public QObject, public TagLib::IOStream {
Q_OBJECT Q_OBJECT
public: public:
CloudStream(const QUrl& url, CloudStream(const QUrl& url, const QString& filename, const long length,
const QString& filename, const QString& auth, QNetworkAccessManager* network);
const long length,
const QString& auth,
QNetworkAccessManager* network);
// Taglib::IOStream // Taglib::IOStream
virtual TagLib::FileName name() const; virtual TagLib::FileName name() const;
@ -55,9 +52,7 @@ class CloudStream : public QObject, public TagLib::IOStream {
return cache_.num_nonempty(); return cache_.num_nonempty();
} }
int num_requests() const { int num_requests() const { return num_requests_; }
return num_requests_;
}
// Use educated guess to request the bytes that TagLib will probably want. // Use educated guess to request the bytes that TagLib will probably want.
void Precache(); void Precache();
@ -84,4 +79,4 @@ class CloudStream : public QObject, public TagLib::IOStream {
int num_requests_; int num_requests_;
}; };
#endif // GOOGLEDRIVESTREAM_H #endif // GOOGLEDRIVESTREAM_H

View File

@ -25,19 +25,18 @@
using std::placeholders::_1; using std::placeholders::_1;
using std::placeholders::_2; using std::placeholders::_2;
FMPSParser::FMPSParser() : FMPSParser::FMPSParser()
// The float regex ends with (?:$|(?=::|;;)) to ensure it matches all the way : // The float regex ends with (?:$|(?=::|;;)) to ensure it matches all the
// up to the end of the value. Without it, it would match a string that // way
// starts with a number, like "123abc". // up to the end of the value. Without it, it would match a string that
float_re_("\\s*([+-]?\\d+(?:\\.\\d+)?)\\s*(?:$|(?=::|;;))"), // starts with a number, like "123abc".
float_re_("\\s*([+-]?\\d+(?:\\.\\d+)?)\\s*(?:$|(?=::|;;))"),
// Matches any character except unescaped slashes, colons and semicolons. // Matches any character except unescaped slashes, colons and semicolons.
string_re_("((?:[^\\\\;:]|(?:\\\\[\\\\:;]))+)(?:$|(?=::|;;))"), string_re_("((?:[^\\\\;:]|(?:\\\\[\\\\:;]))+)(?:$|(?=::|;;))"),
// Used for replacing escaped characters. // Used for replacing escaped characters.
escape_re_("\\\\([\\\\:;])") escape_re_("\\\\([\\\\:;])") {}
{
}
// Parses a list of things (of type T) that are separated by two consecutive // Parses a list of things (of type T) that are separated by two consecutive
// Separator characters. Each individual thing is parsed by the F function. // Separator characters. Each individual thing is parsed by the F function.
@ -59,16 +58,16 @@ static int ParseContainer(const QStringRef& data, F f, QList<T>* ret) {
int pos = 0; int pos = 0;
while (pos < data.length()) { while (pos < data.length()) {
const int len = data.length() - pos; const int len = data.length() - pos;
int matched_len = f(QStringRef(data.string(), data.position() + pos, len), &value); int matched_len =
if (matched_len == -1 || matched_len > len) f(QStringRef(data.string(), data.position() + pos, len), &value);
break; if (matched_len == -1 || matched_len > len) break;
ret->append(value); ret->append(value);
pos += matched_len; pos += matched_len;
// Expect two separators in a row // Expect two separators in a row
if (pos + 2 <= data.length() && data.at(pos) == Separator if (pos + 2 <= data.length() && data.at(pos) == Separator &&
&& data.at(pos+1) == Separator) { data.at(pos + 1) == Separator) {
pos += 2; pos += 2;
} else { } else {
break; break;

View File

@ -22,7 +22,7 @@
#include <QVariantList> #include <QVariantList>
class FMPSParser { class FMPSParser {
public: public:
FMPSParser(); FMPSParser();
// A FMPS result is a list of lists of values (where a value is a string or // A FMPS result is a list of lists of values (where a value is a string or
@ -48,11 +48,11 @@ public:
int ParseListList(const QString& data, Result* ret) const; int ParseListList(const QString& data, Result* ret) const;
int ParseListListRef(const QStringRef& data, Result* ret) const; int ParseListListRef(const QStringRef& data, Result* ret) const;
private: private:
QRegExp float_re_; QRegExp float_re_;
QRegExp string_re_; QRegExp string_re_;
QRegExp escape_re_; QRegExp escape_re_;
Result result_; Result result_;
}; };
#endif // FMPSPARSER_H #endif // FMPSPARSER_H

View File

@ -59,15 +59,17 @@
#include "core/timeconstants.h" #include "core/timeconstants.h"
// Taglib added support for FLAC pictures in 1.7.0 // Taglib added support for FLAC pictures in 1.7.0
#if (TAGLIB_MAJOR_VERSION > 1) || (TAGLIB_MAJOR_VERSION == 1 && TAGLIB_MINOR_VERSION >= 7) #if (TAGLIB_MAJOR_VERSION > 1) || \
# define TAGLIB_HAS_FLAC_PICTURELIST (TAGLIB_MAJOR_VERSION == 1 && TAGLIB_MINOR_VERSION >= 7)
#define TAGLIB_HAS_FLAC_PICTURELIST
#endif #endif
#ifdef HAVE_GOOGLE_DRIVE #ifdef HAVE_GOOGLE_DRIVE
# include "cloudstream.h" #include "cloudstream.h"
#endif #endif
#define NumberToASFAttribute(x) TagLib::ASF::Attribute(QStringToTaglibString(QString::number(x))) #define NumberToASFAttribute(x) \
TagLib::ASF::Attribute(QStringToTaglibString(QString::number(x)))
class FileRefFactory { class FileRefFactory {
public: public:
@ -78,11 +80,11 @@ class FileRefFactory {
class TagLibFileRefFactory : public FileRefFactory { class TagLibFileRefFactory : public FileRefFactory {
public: public:
virtual TagLib::FileRef* GetFileRef(const QString& filename) { virtual TagLib::FileRef* GetFileRef(const QString& filename) {
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
return new TagLib::FileRef(filename.toStdWString().c_str()); return new TagLib::FileRef(filename.toStdWString().c_str());
#else #else
return new TagLib::FileRef(QFile::encodeName(filename).constData()); return new TagLib::FileRef(QFile::encodeName(filename).constData());
#endif #endif
} }
}; };
@ -95,21 +97,22 @@ TagLib::String StdStringToTaglibString(const std::string& s) {
TagLib::String QStringToTaglibString(const QString& s) { TagLib::String QStringToTaglibString(const QString& s) {
return TagLib::String(s.toUtf8().constData(), TagLib::String::UTF8); return TagLib::String(s.toUtf8().constData(), TagLib::String::UTF8);
} }
} }
const char* TagReader::kMP4_FMPS_Rating_ID = "----:com.apple.iTunes:FMPS_Rating"; const char* TagReader::kMP4_FMPS_Rating_ID =
const char* TagReader::kMP4_FMPS_Playcount_ID = "----:com.apple.iTunes:FMPS_Playcount"; "----:com.apple.iTunes:FMPS_Rating";
const char* TagReader::kMP4_FMPS_Score_ID = "----:com.apple.iTunes:FMPS_Rating_Amarok_Score"; const char* TagReader::kMP4_FMPS_Playcount_ID =
"----:com.apple.iTunes:FMPS_Playcount";
const char* TagReader::kMP4_FMPS_Score_ID =
"----:com.apple.iTunes:FMPS_Rating_Amarok_Score";
TagReader::TagReader() TagReader::TagReader()
: factory_(new TagLibFileRefFactory), : factory_(new TagLibFileRefFactory),
network_(new QNetworkAccessManager), network_(new QNetworkAccessManager),
kEmbeddedCover("(embedded)") kEmbeddedCover("(embedded)") {}
{}
void TagReader::ReadFile(const QString& filename, void TagReader::ReadFile(const QString& filename,
pb::tagreader::SongMetadata* song) const { pb::tagreader::SongMetadata* song) const {
const QByteArray url(QUrl::fromLocalFile(filename).toEncoded()); const QByteArray url(QUrl::fromLocalFile(filename).toEncoded());
const QFileInfo info(filename); const QFileInfo info(filename);
@ -122,7 +125,7 @@ void TagReader::ReadFile(const QString& filename,
song->set_ctime(info.created().toTime_t()); song->set_ctime(info.created().toTime_t());
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename)); std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
if(fileref->isNull()) { if (fileref->isNull()) {
qLog(Info) << "TagLib hasn't been able to read " << filename << " file"; qLog(Info) << "TagLib hasn't been able to read " << filename << " file";
return; return;
} }
@ -130,7 +133,7 @@ void TagReader::ReadFile(const QString& filename,
TagLib::Tag* tag = fileref->tag(); TagLib::Tag* tag = fileref->tag();
if (tag) { if (tag) {
Decode(tag->title(), nullptr, song->mutable_title()); Decode(tag->title(), nullptr, song->mutable_title());
Decode(tag->artist(), nullptr, song->mutable_artist()); // TPE1 Decode(tag->artist(), nullptr, song->mutable_artist()); // TPE1
Decode(tag->album(), nullptr, song->mutable_album()); Decode(tag->album(), nullptr, song->mutable_album());
Decode(tag->genre(), nullptr, song->mutable_genre()); Decode(tag->genre(), nullptr, song->mutable_genre());
song->set_year(tag->year()); song->set_year(tag->year());
@ -141,14 +144,17 @@ void TagReader::ReadFile(const QString& filename,
QString disc; QString disc;
QString compilation; QString compilation;
// Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same way; // Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same
// way;
// apart, so we keep specific behavior for some formats by adding another // apart, so we keep specific behavior for some formats by adding another
// "else if" block below. // "else if" block below.
if (TagLib::Ogg::XiphComment* tag = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) { if (TagLib::Ogg::XiphComment* tag =
dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
ParseOggTag(tag->fieldListMap(), nullptr, &disc, &compilation, song); ParseOggTag(tag->fieldListMap(), nullptr, &disc, &compilation, song);
} }
if (TagLib::MPEG::File* file = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) { if (TagLib::MPEG::File* file =
dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
if (file->ID3v2Tag()) { if (file->ID3v2Tag()) {
const TagLib::ID3v2::FrameListMap& map = file->ID3v2Tag()->frameListMap(); const TagLib::ID3v2::FrameListMap& map = file->ID3v2Tag()->frameListMap();
@ -156,27 +162,32 @@ void TagReader::ReadFile(const QString& filename,
disc = TStringToQString(map["TPOS"].front()->toString()).trimmed(); disc = TStringToQString(map["TPOS"].front()->toString()).trimmed();
if (!map["TBPM"].isEmpty()) if (!map["TBPM"].isEmpty())
song->set_bpm(TStringToQString(map["TBPM"].front()->toString()).trimmed().toFloat()); song->set_bpm(TStringToQString(map["TBPM"].front()->toString())
.trimmed()
.toFloat());
if (!map["TCOM"].isEmpty()) if (!map["TCOM"].isEmpty())
Decode(map["TCOM"].front()->toString(), nullptr, song->mutable_composer()); Decode(map["TCOM"].front()->toString(), nullptr,
song->mutable_composer());
if (!map["TIT1"].isEmpty()) // content group if (!map["TIT1"].isEmpty()) // content group
Decode(map["TIT1"].front()->toString(), nullptr, song->mutable_grouping()); Decode(map["TIT1"].front()->toString(), nullptr,
song->mutable_grouping());
// Skip TPE1 (which is the artist) here because we already fetched it // Skip TPE1 (which is the artist) here because we already fetched it
if (!map["TPE2"].isEmpty()) // non-standard: Apple, Microsoft if (!map["TPE2"].isEmpty()) // non-standard: Apple, Microsoft
Decode(map["TPE2"].front()->toString(), nullptr, song->mutable_albumartist()); Decode(map["TPE2"].front()->toString(), nullptr,
song->mutable_albumartist());
if (!map["TCMP"].isEmpty()) if (!map["TCMP"].isEmpty())
compilation = TStringToQString(map["TCMP"].front()->toString()).trimmed(); compilation =
TStringToQString(map["TCMP"].front()->toString()).trimmed();
if (!map["APIC"].isEmpty()) if (!map["APIC"].isEmpty()) song->set_art_automatic(kEmbeddedCover);
song->set_art_automatic(kEmbeddedCover);
// Find a suitable comment tag. For now we ignore iTunNORM comments. // Find a suitable comment tag. For now we ignore iTunNORM comments.
for (int i=0 ; i<map["COMM"].size() ; ++i) { for (int i = 0; i < map["COMM"].size(); ++i) {
const TagLib::ID3v2::CommentsFrame* frame = const TagLib::ID3v2::CommentsFrame* frame =
dynamic_cast<const TagLib::ID3v2::CommentsFrame*>(map["COMM"][i]); dynamic_cast<const TagLib::ID3v2::CommentsFrame*>(map["COMM"][i]);
@ -187,14 +198,14 @@ void TagReader::ReadFile(const QString& filename,
} }
// Parse FMPS frames // Parse FMPS frames
for (int i=0 ; i<map["TXXX"].size() ; ++i) { for (int i = 0; i < map["TXXX"].size(); ++i) {
const TagLib::ID3v2::UserTextIdentificationFrame* frame = const TagLib::ID3v2::UserTextIdentificationFrame* frame =
dynamic_cast<const TagLib::ID3v2::UserTextIdentificationFrame*>(map["TXXX"][i]); dynamic_cast<const TagLib::ID3v2::UserTextIdentificationFrame*>(
map["TXXX"][i]);
if (frame && frame->description().startsWith("FMPS_")) { if (frame && frame->description().startsWith("FMPS_")) {
ParseFMPSFrame(TStringToQString(frame->description()), ParseFMPSFrame(TStringToQString(frame->description()),
TStringToQString(frame->fieldList()[1]), TStringToQString(frame->fieldList()[1]), song);
song);
} }
} }
@ -203,7 +214,8 @@ void TagReader::ReadFile(const QString& filename,
// will consider POPM tags iff song has no rating/playcount already set. // will consider POPM tags iff song has no rating/playcount already set.
if (!map["POPM"].isEmpty()) { if (!map["POPM"].isEmpty()) {
const TagLib::ID3v2::PopularimeterFrame* frame = const TagLib::ID3v2::PopularimeterFrame* frame =
dynamic_cast<const TagLib::ID3v2::PopularimeterFrame*>(map["POPM"].front()); dynamic_cast<const TagLib::ID3v2::PopularimeterFrame*>(
map["POPM"].front());
if (frame) { if (frame) {
// Take a user rating only if there's no rating already set // Take a user rating only if there's no rating already set
if (song->rating() <= 0 && frame->rating() > 0) { if (song->rating() <= 0 && frame->rating() > 0) {
@ -214,11 +226,12 @@ void TagReader::ReadFile(const QString& filename,
} }
} }
} }
} }
} else if (TagLib::FLAC::File* file = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) { } else if (TagLib::FLAC::File* file =
if ( file->xiphComment() ) { dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
ParseOggTag(file->xiphComment()->fieldListMap(), nullptr, &disc, &compilation, song); if (file->xiphComment()) {
ParseOggTag(file->xiphComment()->fieldListMap(), nullptr, &disc,
&compilation, song);
#ifdef TAGLIB_HAS_FLAC_PICTURELIST #ifdef TAGLIB_HAS_FLAC_PICTURELIST
if (!file->pictureList().isEmpty()) { if (!file->pictureList().isEmpty()) {
song->set_art_automatic(kEmbeddedCover); song->set_art_automatic(kEmbeddedCover);
@ -226,7 +239,8 @@ void TagReader::ReadFile(const QString& filename,
#endif #endif
} }
Decode(tag->comment(), nullptr, song->mutable_comment()); Decode(tag->comment(), nullptr, song->mutable_comment());
} else if (TagLib::MP4::File* file = dynamic_cast<TagLib::MP4::File*>(fileref->file())) { } else if (TagLib::MP4::File* file =
dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
if (file->tag()) { if (file->tag()) {
TagLib::MP4::Tag* mp4_tag = file->tag(); TagLib::MP4::Tag* mp4_tag = file->tag();
const TagLib::MP4::ItemListMap& items = mp4_tag->itemListMap(); const TagLib::MP4::ItemListMap& items = mp4_tag->itemListMap();
@ -246,51 +260,67 @@ void TagReader::ReadFile(const QString& filename,
} }
if (items.contains("disk")) { if (items.contains("disk")) {
disc = TStringToQString(TagLib::String::number(items["disk"].toIntPair().first)); disc = TStringToQString(
TagLib::String::number(items["disk"].toIntPair().first));
} }
if (items.contains(kMP4_FMPS_Rating_ID)) { if (items.contains(kMP4_FMPS_Rating_ID)) {
float rating = TStringToQString(items[kMP4_FMPS_Rating_ID].toStringList().toString('\n')).toFloat(); float rating =
TStringToQString(items[kMP4_FMPS_Rating_ID].toStringList().toString(
'\n')).toFloat();
if (song->rating() <= 0 && rating > 0) { if (song->rating() <= 0 && rating > 0) {
song->set_rating(rating); song->set_rating(rating);
} }
} }
if (items.contains(kMP4_FMPS_Playcount_ID)) { if (items.contains(kMP4_FMPS_Playcount_ID)) {
int playcount = TStringToQString(items[kMP4_FMPS_Playcount_ID].toStringList().toString('\n')).toFloat(); int playcount =
TStringToQString(
items[kMP4_FMPS_Playcount_ID].toStringList().toString('\n'))
.toFloat();
if (song->playcount() <= 0 && playcount > 0) { if (song->playcount() <= 0 && playcount > 0) {
song->set_playcount(playcount); song->set_playcount(playcount);
} }
} }
if (items.contains(kMP4_FMPS_Playcount_ID)) { if (items.contains(kMP4_FMPS_Playcount_ID)) {
int score = TStringToQString(items[kMP4_FMPS_Score_ID].toStringList().toString('\n')).toFloat() * 100; int score = TStringToQString(
items[kMP4_FMPS_Score_ID].toStringList().toString('\n'))
.toFloat() *
100;
if (song->score() <= 0 && score > 0) { if (song->score() <= 0 && score > 0) {
song->set_score(score); song->set_score(score);
} }
} }
if(items.contains("\251wrt")) { if (items.contains("\251wrt")) {
Decode(items["\251wrt"].toStringList().toString(", "), nullptr, song->mutable_composer()); Decode(items["\251wrt"].toStringList().toString(", "), nullptr,
song->mutable_composer());
} }
if(items.contains("\251grp")) { if (items.contains("\251grp")) {
Decode(items["\251grp"].toStringList().toString(" "), nullptr, song->mutable_grouping()); Decode(items["\251grp"].toStringList().toString(" "), nullptr,
song->mutable_grouping());
} }
Decode(mp4_tag->comment(), nullptr, song->mutable_comment()); Decode(mp4_tag->comment(), nullptr, song->mutable_comment());
} }
} }
#ifdef TAGLIB_WITH_ASF #ifdef TAGLIB_WITH_ASF
else if (TagLib::ASF::File* file = dynamic_cast<TagLib::ASF::File*>(fileref->file())) { else if (TagLib::ASF::File* file =
const TagLib::ASF::AttributeListMap& attributes_map = file->tag()->attributeListMap(); dynamic_cast<TagLib::ASF::File*>(fileref->file())) {
const TagLib::ASF::AttributeListMap& attributes_map =
file->tag()->attributeListMap();
if (attributes_map.contains("FMPS/Rating")) { if (attributes_map.contains("FMPS/Rating")) {
const TagLib::ASF::AttributeList& attributes = attributes_map["FMPS/Rating"]; const TagLib::ASF::AttributeList& attributes =
attributes_map["FMPS/Rating"];
if (!attributes.isEmpty()) { if (!attributes.isEmpty()) {
float rating = TStringToQString(attributes.front().toString()).toFloat(); float rating =
TStringToQString(attributes.front().toString()).toFloat();
if (song->rating() <= 0 && rating > 0) { if (song->rating() <= 0 && rating > 0) {
song->set_rating(rating); song->set_rating(rating);
} }
} }
} }
if (attributes_map.contains("FMPS/Playcount")) { if (attributes_map.contains("FMPS/Playcount")) {
const TagLib::ASF::AttributeList& attributes = attributes_map["FMPS/Playcount"]; const TagLib::ASF::AttributeList& attributes =
attributes_map["FMPS/Playcount"];
if (!attributes.isEmpty()) { if (!attributes.isEmpty()) {
int playcount = TStringToQString(attributes.front().toString()).toInt(); int playcount = TStringToQString(attributes.front().toString()).toInt();
if (song->playcount() <= 0 && playcount > 0) { if (song->playcount() <= 0 && playcount > 0) {
@ -299,9 +329,11 @@ void TagReader::ReadFile(const QString& filename,
} }
} }
if (attributes_map.contains("FMPS/Rating_Amarok_Score")) { if (attributes_map.contains("FMPS/Rating_Amarok_Score")) {
const TagLib::ASF::AttributeList& attributes = attributes_map["FMPS/Rating_Amarok_Score"]; const TagLib::ASF::AttributeList& attributes =
attributes_map["FMPS/Rating_Amarok_Score"];
if (!attributes.isEmpty()) { if (!attributes.isEmpty()) {
int score = TStringToQString(attributes.front().toString()).toFloat() * 100; int score =
TStringToQString(attributes.front().toString()).toFloat() * 100;
if (song->score() <= 0 && score > 0) { if (song->score() <= 0 && score > 0) {
song->set_score(score); song->set_score(score);
} }
@ -316,7 +348,8 @@ void TagReader::ReadFile(const QString& filename,
if (!disc.isEmpty()) { if (!disc.isEmpty()) {
const int i = disc.indexOf('/'); const int i = disc.indexOf('/');
if (i != -1) { if (i != -1) {
// disc.right( i ).toInt() is total number of discs, we don't use this at the moment // disc.right( i ).toInt() is total number of discs, we don't use this at
// the moment
song->set_disc(disc.left(i).toInt()); song->set_disc(disc.left(i).toInt());
} else { } else {
song->set_disc(disc.toInt()); song->set_disc(disc.toInt());
@ -335,14 +368,18 @@ void TagReader::ReadFile(const QString& filename,
if (fileref->audioProperties()) { if (fileref->audioProperties()) {
song->set_bitrate(fileref->audioProperties()->bitrate()); song->set_bitrate(fileref->audioProperties()->bitrate());
song->set_samplerate(fileref->audioProperties()->sampleRate()); song->set_samplerate(fileref->audioProperties()->sampleRate());
song->set_length_nanosec(fileref->audioProperties()->length() * kNsecPerSec); song->set_length_nanosec(fileref->audioProperties()->length() *
kNsecPerSec);
} }
// Get the filetype if we can // Get the filetype if we can
song->set_type(GuessFileType(fileref.get())); song->set_type(GuessFileType(fileref.get()));
// Set integer fields to -1 if they're not valid // Set integer fields to -1 if they're not valid
#define SetDefault(field) if (song->field() <= 0) { song->set_##field(-1); } #define SetDefault(field) \
if (song->field() <= 0) { \
song->set_##field(-1); \
}
SetDefault(track); SetDefault(track);
SetDefault(disc); SetDefault(disc);
SetDefault(bpm); SetDefault(bpm);
@ -350,16 +387,16 @@ void TagReader::ReadFile(const QString& filename,
SetDefault(bitrate); SetDefault(bitrate);
SetDefault(samplerate); SetDefault(samplerate);
SetDefault(lastplayed); SetDefault(lastplayed);
#undef SetDefault #undef SetDefault
} }
void TagReader::Decode(const TagLib::String& tag, const QTextCodec* codec, void TagReader::Decode(const TagLib::String& tag, const QTextCodec* codec,
std::string* output) { std::string* output) {
QString tmp; QString tmp;
if (codec && tag.isLatin1()) { // Never override UTF-8. if (codec && tag.isLatin1()) { // Never override UTF-8.
const std::string fixed = QString::fromUtf8(tag.toCString(true)).toStdString(); const std::string fixed =
QString::fromUtf8(tag.toCString(true)).toStdString();
tmp = codec->toUnicode(fixed.c_str()).trimmed(); tmp = codec->toUnicode(fixed.c_str()).trimmed();
} else { } else {
tmp = TStringToQString(tag).trimmed(); tmp = TStringToQString(tag).trimmed();
@ -369,7 +406,7 @@ void TagReader::Decode(const TagLib::String& tag, const QTextCodec* codec,
} }
void TagReader::Decode(const QString& tag, const QTextCodec* codec, void TagReader::Decode(const QString& tag, const QTextCodec* codec,
std::string* output) { std::string* output) {
if (!codec) { if (!codec) {
output->assign(DataCommaSizeFromQString(tag)); output->assign(DataCommaSizeFromQString(tag));
} else { } else {
@ -379,11 +416,10 @@ void TagReader::Decode(const QString& tag, const QTextCodec* codec,
} }
void TagReader::ParseFMPSFrame(const QString& name, const QString& value, void TagReader::ParseFMPSFrame(const QString& name, const QString& value,
pb::tagreader::SongMetadata* song) const { pb::tagreader::SongMetadata* song) const {
qLog(Debug) << "Parsing FMPSFrame" << name << ", " << value; qLog(Debug) << "Parsing FMPSFrame" << name << ", " << value;
FMPSParser parser; FMPSParser parser;
if (!parser.Parse(value) || parser.is_empty()) if (!parser.Parse(value) || parser.is_empty()) return;
return;
QVariant var; QVariant var;
if (name == "FMPS_Rating") { if (name == "FMPS_Rating") {
@ -421,9 +457,9 @@ void TagReader::ParseFMPSFrame(const QString& name, const QString& value,
} }
void TagReader::ParseOggTag(const TagLib::Ogg::FieldListMap& map, void TagReader::ParseOggTag(const TagLib::Ogg::FieldListMap& map,
const QTextCodec* codec, const QTextCodec* codec, QString* disc,
QString* disc, QString* compilation, QString* compilation,
pb::tagreader::SongMetadata* song) const { pb::tagreader::SongMetadata* song) const {
if (!map["COMPOSER"].isEmpty()) if (!map["COMPOSER"].isEmpty())
Decode(map["COMPOSER"].front(), codec, song->mutable_composer()); Decode(map["COMPOSER"].front(), codec, song->mutable_composer());
if (!map["PERFORMER"].isEmpty()) if (!map["PERFORMER"].isEmpty())
@ -437,52 +473,76 @@ void TagReader::ParseOggTag(const TagLib::Ogg::FieldListMap& map,
Decode(map["ALBUM ARTIST"].front(), codec, song->mutable_albumartist()); Decode(map["ALBUM ARTIST"].front(), codec, song->mutable_albumartist());
} }
if (!map["BPM"].isEmpty() ) if (!map["BPM"].isEmpty())
song->set_bpm(TStringToQString( map["BPM"].front() ).trimmed().toFloat()); song->set_bpm(TStringToQString(map["BPM"].front()).trimmed().toFloat());
if (!map["DISCNUMBER"].isEmpty() ) if (!map["DISCNUMBER"].isEmpty())
*disc = TStringToQString( map["DISCNUMBER"].front() ).trimmed(); *disc = TStringToQString(map["DISCNUMBER"].front()).trimmed();
if (!map["COMPILATION"].isEmpty() ) if (!map["COMPILATION"].isEmpty())
*compilation = TStringToQString( map["COMPILATION"].front() ).trimmed(); *compilation = TStringToQString(map["COMPILATION"].front()).trimmed();
if (!map["COVERART"].isEmpty()) if (!map["COVERART"].isEmpty()) song->set_art_automatic(kEmbeddedCover);
song->set_art_automatic(kEmbeddedCover);
if (!map["METADATA_BLOCK_PICTURE"].isEmpty()) if (!map["METADATA_BLOCK_PICTURE"].isEmpty())
song->set_art_automatic(kEmbeddedCover); song->set_art_automatic(kEmbeddedCover);
if (!map["FMPS_RATING"].isEmpty() && song->rating() <= 0) if (!map["FMPS_RATING"].isEmpty() && song->rating() <= 0)
song->set_rating(TStringToQString( map["FMPS_RATING"].front() ).trimmed().toFloat()); song->set_rating(
TStringToQString(map["FMPS_RATING"].front()).trimmed().toFloat());
if (!map["FMPS_PLAYCOUNT"].isEmpty() && song->playcount() <= 0) if (!map["FMPS_PLAYCOUNT"].isEmpty() && song->playcount() <= 0)
song->set_playcount(TStringToQString( map["FMPS_PLAYCOUNT"].front() ).trimmed().toFloat()); song->set_playcount(
TStringToQString(map["FMPS_PLAYCOUNT"].front()).trimmed().toFloat());
if (!map["FMPS_RATING_AMAROK_SCORE"].isEmpty() && song->score() <= 0) if (!map["FMPS_RATING_AMAROK_SCORE"].isEmpty() && song->score() <= 0)
song->set_score(TStringToQString( map["FMPS_RATING_AMAROK_SCORE"].front() ).trimmed().toFloat() * 100); song->set_score(TStringToQString(map["FMPS_RATING_AMAROK_SCORE"].front())
.trimmed()
.toFloat() *
100);
} }
void TagReader::SetVorbisComments(TagLib::Ogg::XiphComment* vorbis_comments, void TagReader::SetVorbisComments(TagLib::Ogg::XiphComment* vorbis_comments,
const pb::tagreader::SongMetadata& song) const { const pb::tagreader::SongMetadata& song)
const {
vorbis_comments->addField("COMPOSER", StdStringToTaglibString(song.composer()), true); vorbis_comments->addField("COMPOSER",
vorbis_comments->addField("PERFORMER", StdStringToTaglibString(song.performer()), true); StdStringToTaglibString(song.composer()), true);
vorbis_comments->addField("CONTENT GROUP", StdStringToTaglibString(song.grouping()), true); vorbis_comments->addField("PERFORMER",
vorbis_comments->addField("BPM", QStringToTaglibString(song.bpm() <= 0 -1 ? QString() : QString::number(song.bpm())), true); StdStringToTaglibString(song.performer()), true);
vorbis_comments->addField("DISCNUMBER", QStringToTaglibString(song.disc() <= 0 -1 ? QString() : QString::number(song.disc())), true); vorbis_comments->addField("CONTENT GROUP",
vorbis_comments->addField("COMPILATION", StdStringToTaglibString(song.compilation() ? "1" : "0"), true); StdStringToTaglibString(song.grouping()), true);
vorbis_comments->addField(
"BPM", QStringToTaglibString(
song.bpm() <= 0 - 1 ? QString() : QString::number(song.bpm())),
true);
vorbis_comments->addField(
"DISCNUMBER",
QStringToTaglibString(
song.disc() <= 0 - 1 ? QString() : QString::number(song.disc())),
true);
vorbis_comments->addField(
"COMPILATION", StdStringToTaglibString(song.compilation() ? "1" : "0"),
true);
} }
void TagReader::SetFMPSStatisticsVorbisComments(TagLib::Ogg::XiphComment* vorbis_comments, void TagReader::SetFMPSStatisticsVorbisComments(
const pb::tagreader::SongMetadata& song) const { TagLib::Ogg::XiphComment* vorbis_comments,
vorbis_comments->addField("FMPS_PLAYCOUNT", QStringToTaglibString(QString::number(song.playcount()))); const pb::tagreader::SongMetadata& song) const {
vorbis_comments->addField("FMPS_RATING_AMAROK_SCORE", QStringToTaglibString(QString::number(song.score() / 100.0))); vorbis_comments->addField(
"FMPS_PLAYCOUNT",
QStringToTaglibString(QString::number(song.playcount())));
vorbis_comments->addField(
"FMPS_RATING_AMAROK_SCORE",
QStringToTaglibString(QString::number(song.score() / 100.0)));
} }
void TagReader::SetFMPSRatingVorbisComments(TagLib::Ogg::XiphComment* vorbis_comments, void TagReader::SetFMPSRatingVorbisComments(
const pb::tagreader::SongMetadata& song) const { TagLib::Ogg::XiphComment* vorbis_comments,
const pb::tagreader::SongMetadata& song) const {
vorbis_comments->addField("FMPS_RATING", QStringToTaglibString(QString::number(song.rating()))); vorbis_comments->addField(
"FMPS_RATING", QStringToTaglibString(QString::number(song.rating())));
} }
pb::tagreader::SongMetadata_Type TagReader::GuessFileType( pb::tagreader::SongMetadata_Type TagReader::GuessFileType(
@ -522,135 +582,155 @@ pb::tagreader::SongMetadata_Type TagReader::GuessFileType(
} }
bool TagReader::SaveFile(const QString& filename, bool TagReader::SaveFile(const QString& filename,
const pb::tagreader::SongMetadata& song) const { const pb::tagreader::SongMetadata& song) const {
if (filename.isNull()) if (filename.isNull()) return false;
return false;
qLog(Debug) << "Saving tags to" << filename; qLog(Debug) << "Saving tags to" << filename;
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename)); std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
if (!fileref || fileref->isNull()) // The file probably doesn't exist if (!fileref || fileref->isNull()) // The file probably doesn't exist
return false; return false;
fileref->tag()->setTitle(StdStringToTaglibString(song.title())); fileref->tag()->setTitle(StdStringToTaglibString(song.title()));
fileref->tag()->setArtist(StdStringToTaglibString(song.artist())); // TPE1 fileref->tag()->setArtist(StdStringToTaglibString(song.artist())); // TPE1
fileref->tag()->setAlbum(StdStringToTaglibString(song.album())); fileref->tag()->setAlbum(StdStringToTaglibString(song.album()));
fileref->tag()->setGenre(StdStringToTaglibString(song.genre())); fileref->tag()->setGenre(StdStringToTaglibString(song.genre()));
fileref->tag()->setComment(StdStringToTaglibString(song.comment())); fileref->tag()->setComment(StdStringToTaglibString(song.comment()));
fileref->tag()->setYear(song.year()); fileref->tag()->setYear(song.year());
fileref->tag()->setTrack(song.track()); fileref->tag()->setTrack(song.track());
if (TagLib::MPEG::File* file = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) { if (TagLib::MPEG::File* file =
dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
TagLib::ID3v2::Tag* tag = file->ID3v2Tag(true); TagLib::ID3v2::Tag* tag = file->ID3v2Tag(true);
SetTextFrame("TPOS", song.disc() <= 0 -1 ? QString() : QString::number(song.disc()), tag); SetTextFrame(
SetTextFrame("TBPM", song.bpm() <= 0 -1 ? QString() : QString::number(song.bpm()), tag); "TPOS", song.disc() <= 0 - 1 ? QString() : QString::number(song.disc()),
tag);
SetTextFrame("TBPM",
song.bpm() <= 0 - 1 ? QString() : QString::number(song.bpm()),
tag);
SetTextFrame("TCOM", song.composer(), tag); SetTextFrame("TCOM", song.composer(), tag);
SetTextFrame("TIT1", song.grouping(), tag); SetTextFrame("TIT1", song.grouping(), tag);
// Skip TPE1 (which is the artist) here because we already set it // Skip TPE1 (which is the artist) here because we already set it
SetTextFrame("TPE2", song.albumartist(), tag); SetTextFrame("TPE2", song.albumartist(), tag);
SetTextFrame("TCMP", std::string(song.compilation() ? "1" : "0"), tag); SetTextFrame("TCMP", std::string(song.compilation() ? "1" : "0"), tag);
} else if (TagLib::FLAC::File* file = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) { } else if (TagLib::FLAC::File* file =
dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
TagLib::Ogg::XiphComment* tag = file->xiphComment(); TagLib::Ogg::XiphComment* tag = file->xiphComment();
SetVorbisComments(tag, song); SetVorbisComments(tag, song);
} else if (TagLib::MP4::File* file = dynamic_cast<TagLib::MP4::File*>(fileref->file())) { } else if (TagLib::MP4::File* file =
dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
TagLib::MP4::Tag* tag = file->tag(); TagLib::MP4::Tag* tag = file->tag();
tag->itemListMap()["disk"] = TagLib::MP4::Item(song.disc() <= 0 -1 ? 0 : song.disc(), 0); tag->itemListMap()["disk"] =
tag->itemListMap()["tmpo"] = TagLib::StringList(song.bpm() <= 0 -1 ? "0" : TagLib::String::number(song.bpm())); TagLib::MP4::Item(song.disc() <= 0 - 1 ? 0 : song.disc(), 0);
tag->itemListMap()["tmpo"] = TagLib::StringList(
song.bpm() <= 0 - 1 ? "0" : TagLib::String::number(song.bpm()));
tag->itemListMap()["\251wrt"] = TagLib::StringList(song.composer()); tag->itemListMap()["\251wrt"] = TagLib::StringList(song.composer());
tag->itemListMap()["\251grp"] = TagLib::StringList(song.grouping()); tag->itemListMap()["\251grp"] = TagLib::StringList(song.grouping());
tag->itemListMap()["aART"] = TagLib::StringList(song.albumartist()); tag->itemListMap()["aART"] = TagLib::StringList(song.albumartist());
tag->itemListMap()["cpil"] = TagLib::StringList(song.compilation() ? "1" : "0"); tag->itemListMap()["cpil"] =
TagLib::StringList(song.compilation() ? "1" : "0");
} }
// Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same way; // Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same
// way;
// apart, so we keep specific behavior for some formats by adding another // apart, so we keep specific behavior for some formats by adding another
// "else if" block above. // "else if" block above.
if (TagLib::Ogg::XiphComment* tag = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) { if (TagLib::Ogg::XiphComment* tag =
SetVorbisComments(tag, song); dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
SetVorbisComments(tag, song);
} }
bool ret = fileref->save(); bool ret = fileref->save();
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
if (ret) { if (ret) {
// Linux: inotify doesn't seem to notice the change to the file unless we // Linux: inotify doesn't seem to notice the change to the file unless we
// change the timestamps as well. (this is what touch does) // change the timestamps as well. (this is what touch does)
utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0); utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
} }
#endif // Q_OS_LINUX #endif // Q_OS_LINUX
return ret; return ret;
} }
bool TagReader::SaveSongStatisticsToFile(const QString& filename, bool TagReader::SaveSongStatisticsToFile(
const pb::tagreader::SongMetadata& song) const { const QString& filename, const pb::tagreader::SongMetadata& song) const {
if (filename.isNull()) if (filename.isNull()) return false;
return false;
qLog(Debug) << "Saving song statistics tags to" << filename; qLog(Debug) << "Saving song statistics tags to" << filename;
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename)); std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
if (!fileref || fileref->isNull()) // The file probably doesn't exist if (!fileref || fileref->isNull()) // The file probably doesn't exist
return false; return false;
if (TagLib::MPEG::File* file = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) { if (TagLib::MPEG::File* file =
dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
TagLib::ID3v2::Tag* tag = file->ID3v2Tag(true); TagLib::ID3v2::Tag* tag = file->ID3v2Tag(true);
// Save as FMPS // Save as FMPS
SetUserTextFrame("FMPS_PlayCount", QString::number(song.playcount()), tag); SetUserTextFrame("FMPS_PlayCount", QString::number(song.playcount()), tag);
SetUserTextFrame("FMPS_Rating_Amarok_Score", QString::number(song.score() / 100.0), tag); SetUserTextFrame("FMPS_Rating_Amarok_Score",
QString::number(song.score() / 100.0), tag);
// Also save as POPM // Also save as POPM
TagLib::ID3v2::PopularimeterFrame* frame = GetPOPMFrameFromTag(tag); TagLib::ID3v2::PopularimeterFrame* frame = GetPOPMFrameFromTag(tag);
frame->setCounter(song.playcount()); frame->setCounter(song.playcount());
} else if (TagLib::FLAC::File* file = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) { } else if (TagLib::FLAC::File* file =
dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
TagLib::Ogg::XiphComment* vorbis_comments = file->xiphComment(true); TagLib::Ogg::XiphComment* vorbis_comments = file->xiphComment(true);
SetFMPSStatisticsVorbisComments(vorbis_comments, song); SetFMPSStatisticsVorbisComments(vorbis_comments, song);
} else if (TagLib::Ogg::XiphComment* tag = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) { } else if (TagLib::Ogg::XiphComment* tag =
dynamic_cast<TagLib::Ogg::XiphComment*>(
fileref->file()->tag())) {
SetFMPSStatisticsVorbisComments(tag, song); SetFMPSStatisticsVorbisComments(tag, song);
} }
#ifdef TAGLIB_WITH_ASF #ifdef TAGLIB_WITH_ASF
else if (TagLib::ASF::File* file = dynamic_cast<TagLib::ASF::File*>(fileref->file())) { else if (TagLib::ASF::File* file =
dynamic_cast<TagLib::ASF::File*>(fileref->file())) {
TagLib::ASF::Tag* tag = file->tag(); TagLib::ASF::Tag* tag = file->tag();
tag->addAttribute("FMPS/Playcount", NumberToASFAttribute(song.playcount())); tag->addAttribute("FMPS/Playcount", NumberToASFAttribute(song.playcount()));
tag->addAttribute("FMPS/Rating_Amarok_Score", NumberToASFAttribute(song.score() / 100.0)); tag->addAttribute("FMPS/Rating_Amarok_Score",
NumberToASFAttribute(song.score() / 100.0));
} }
#endif #endif
else if (TagLib::MP4::File* file = dynamic_cast<TagLib::MP4::File*>(fileref->file())) { else if (TagLib::MP4::File* file =
dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
TagLib::MP4::Tag* tag = file->tag(); TagLib::MP4::Tag* tag = file->tag();
tag->itemListMap()[kMP4_FMPS_Score_ID] = TagLib::StringList(QStringToTaglibString(QString::number(song.score() / 100.0))); tag->itemListMap()[kMP4_FMPS_Score_ID] = TagLib::StringList(
tag->itemListMap()[kMP4_FMPS_Playcount_ID] = TagLib::StringList(TagLib::String::number(song.playcount())); QStringToTaglibString(QString::number(song.score() / 100.0)));
tag->itemListMap()[kMP4_FMPS_Playcount_ID] =
TagLib::StringList(TagLib::String::number(song.playcount()));
} else { } else {
// Nothing to save: stop now // Nothing to save: stop now
return true; return true;
} }
bool ret = fileref->save(); bool ret = fileref->save();
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
if (ret) { if (ret) {
// Linux: inotify doesn't seem to notice the change to the file unless we // Linux: inotify doesn't seem to notice the change to the file unless we
// change the timestamps as well. (this is what touch does) // change the timestamps as well. (this is what touch does)
utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0); utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
} }
#endif // Q_OS_LINUX #endif // Q_OS_LINUX
return ret; return ret;
} }
bool TagReader::SaveSongRatingToFile(const QString& filename, bool TagReader::SaveSongRatingToFile(
const pb::tagreader::SongMetadata& song) const { const QString& filename, const pb::tagreader::SongMetadata& song) const {
if (filename.isNull()) if (filename.isNull()) return false;
return false;
qLog(Debug) << "Saving song rating tags to" << filename; qLog(Debug) << "Saving song rating tags to" << filename;
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename)); std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
if (!fileref || fileref->isNull()) // The file probably doesn't exist if (!fileref || fileref->isNull()) // The file probably doesn't exist
return false; return false;
if (TagLib::MPEG::File* file = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) { if (TagLib::MPEG::File* file =
dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
TagLib::ID3v2::Tag* tag = file->ID3v2Tag(true); TagLib::ID3v2::Tag* tag = file->ID3v2Tag(true);
// Save as FMPS // Save as FMPS
@ -660,38 +740,45 @@ bool TagReader::SaveSongRatingToFile(const QString& filename,
TagLib::ID3v2::PopularimeterFrame* frame = GetPOPMFrameFromTag(tag); TagLib::ID3v2::PopularimeterFrame* frame = GetPOPMFrameFromTag(tag);
frame->setRating(ConvertToPOPMRating(song.rating())); frame->setRating(ConvertToPOPMRating(song.rating()));
} else if (TagLib::FLAC::File* file = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) { } else if (TagLib::FLAC::File* file =
dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
TagLib::Ogg::XiphComment* vorbis_comments = file->xiphComment(true); TagLib::Ogg::XiphComment* vorbis_comments = file->xiphComment(true);
SetFMPSRatingVorbisComments(vorbis_comments, song); SetFMPSRatingVorbisComments(vorbis_comments, song);
} else if (TagLib::Ogg::XiphComment* tag = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) { } else if (TagLib::Ogg::XiphComment* tag =
dynamic_cast<TagLib::Ogg::XiphComment*>(
fileref->file()->tag())) {
SetFMPSRatingVorbisComments(tag, song); SetFMPSRatingVorbisComments(tag, song);
} }
#ifdef TAGLIB_WITH_ASF #ifdef TAGLIB_WITH_ASF
else if (TagLib::ASF::File* file = dynamic_cast<TagLib::ASF::File*>(fileref->file())) { else if (TagLib::ASF::File* file =
dynamic_cast<TagLib::ASF::File*>(fileref->file())) {
TagLib::ASF::Tag* tag = file->tag(); TagLib::ASF::Tag* tag = file->tag();
tag->addAttribute("FMPS/Rating", NumberToASFAttribute(song.rating())); tag->addAttribute("FMPS/Rating", NumberToASFAttribute(song.rating()));
} }
#endif #endif
else if (TagLib::MP4::File* file = dynamic_cast<TagLib::MP4::File*>(fileref->file())) { else if (TagLib::MP4::File* file =
dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
TagLib::MP4::Tag* tag = file->tag(); TagLib::MP4::Tag* tag = file->tag();
tag->itemListMap()[kMP4_FMPS_Rating_ID] = TagLib::StringList(QStringToTaglibString(QString::number(song.rating()))); tag->itemListMap()[kMP4_FMPS_Rating_ID] = TagLib::StringList(
QStringToTaglibString(QString::number(song.rating())));
} else { } else {
// Nothing to save: stop now // Nothing to save: stop now
return true; return true;
} }
bool ret = fileref->save(); bool ret = fileref->save();
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
if (ret) { if (ret) {
// Linux: inotify doesn't seem to notice the change to the file unless we // Linux: inotify doesn't seem to notice the change to the file unless we
// change the timestamps as well. (this is what touch does) // change the timestamps as well. (this is what touch does)
utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0); utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
} }
#endif // Q_OS_LINUX #endif // Q_OS_LINUX
return ret; return ret;
} }
void TagReader::SetUserTextFrame(const QString& description, const QString& value, void TagReader::SetUserTextFrame(const QString& description,
const QString& value,
TagLib::ID3v2::Tag* tag) const { TagLib::ID3v2::Tag* tag) const {
const QByteArray descr_utf8(description.toUtf8()); const QByteArray descr_utf8(description.toUtf8());
const QByteArray value_utf8(value.toUtf8()); const QByteArray value_utf8(value.toUtf8());
@ -721,13 +808,13 @@ void TagReader::SetUserTextFrame(const std::string& description,
} }
void TagReader::SetTextFrame(const char* id, const QString& value, void TagReader::SetTextFrame(const char* id, const QString& value,
TagLib::ID3v2::Tag* tag) const { TagLib::ID3v2::Tag* tag) const {
const QByteArray utf8(value.toUtf8()); const QByteArray utf8(value.toUtf8());
SetTextFrame(id, std::string(utf8.constData(), utf8.length()), tag); SetTextFrame(id, std::string(utf8.constData(), utf8.length()), tag);
} }
void TagReader::SetTextFrame(const char* id, const std::string& value, void TagReader::SetTextFrame(const char* id, const std::string& value,
TagLib::ID3v2::Tag* tag) const { TagLib::ID3v2::Tag* tag) const {
TagLib::ByteVector id_vector(id); TagLib::ByteVector id_vector(id);
// Remove the frame if it already exists // Remove the frame if it already exists
@ -752,8 +839,7 @@ bool TagReader::IsMediaFile(const QString& filename) const {
} }
QByteArray TagReader::LoadEmbeddedArt(const QString& filename) const { QByteArray TagReader::LoadEmbeddedArt(const QString& filename) const {
if (filename.isEmpty()) if (filename.isEmpty()) return QByteArray();
return QByteArray();
qLog(Debug) << "Loading art from" << filename; qLog(Debug) << "Loading art from" << filename;
@ -763,20 +849,20 @@ QByteArray TagReader::LoadEmbeddedArt(const QString& filename) const {
TagLib::FileRef ref(QFile::encodeName(filename).constData()); TagLib::FileRef ref(QFile::encodeName(filename).constData());
#endif #endif
if (ref.isNull() || !ref.file()) if (ref.isNull() || !ref.file()) return QByteArray();
return QByteArray();
// MP3 // MP3
TagLib::MPEG::File* file = dynamic_cast<TagLib::MPEG::File*>(ref.file()); TagLib::MPEG::File* file = dynamic_cast<TagLib::MPEG::File*>(ref.file());
if (file && file->ID3v2Tag()) { if (file && file->ID3v2Tag()) {
TagLib::ID3v2::FrameList apic_frames = file->ID3v2Tag()->frameListMap()["APIC"]; TagLib::ID3v2::FrameList apic_frames =
if (apic_frames.isEmpty()) file->ID3v2Tag()->frameListMap()["APIC"];
return QByteArray(); if (apic_frames.isEmpty()) return QByteArray();
TagLib::ID3v2::AttachedPictureFrame* pic = TagLib::ID3v2::AttachedPictureFrame* pic =
static_cast<TagLib::ID3v2::AttachedPictureFrame*>(apic_frames.front()); static_cast<TagLib::ID3v2::AttachedPictureFrame*>(apic_frames.front());
return QByteArray((const char*) pic->picture().data(), pic->picture().size()); return QByteArray((const char*)pic->picture().data(),
pic->picture().size());
} }
// Ogg vorbis/speex // Ogg vorbis/speex
@ -786,12 +872,14 @@ QByteArray TagReader::LoadEmbeddedArt(const QString& filename) const {
if (xiph_comment) { if (xiph_comment) {
TagLib::Ogg::FieldListMap map = xiph_comment->fieldListMap(); TagLib::Ogg::FieldListMap map = xiph_comment->fieldListMap();
// Other than the below mentioned non-standard COVERART, METADATA_BLOCK_PICTURE // Other than the below mentioned non-standard COVERART,
// METADATA_BLOCK_PICTURE
// is the proposed tag for cover pictures. // is the proposed tag for cover pictures.
// (see http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE) // (see http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE)
if (map.contains("METADATA_BLOCK_PICTURE")) { if (map.contains("METADATA_BLOCK_PICTURE")) {
TagLib::StringList pict_list = map["METADATA_BLOCK_PICTURE"]; TagLib::StringList pict_list = map["METADATA_BLOCK_PICTURE"];
for(std::list<TagLib::String>::iterator it = pict_list.begin(); it != pict_list.end(); ++it) { for (std::list<TagLib::String>::iterator it = pict_list.begin();
it != pict_list.end(); ++it) {
QByteArray data(QByteArray::fromBase64(it->toCString())); QByteArray data(QByteArray::fromBase64(it->toCString()));
TagLib::ByteVector tdata(data.data(), data.size()); TagLib::ByteVector tdata(data.data(), data.size());
TagLib::FLAC::Picture p(tdata); TagLib::FLAC::Picture p(tdata);
@ -799,7 +887,8 @@ QByteArray TagReader::LoadEmbeddedArt(const QString& filename) const {
return QByteArray(p.data().data(), p.data().size()); return QByteArray(p.data().data(), p.data().size());
} }
// If there was no specific front cover, just take the first picture // If there was no specific front cover, just take the first picture
QByteArray data(QByteArray::fromBase64(map["METADATA_BLOCK_PICTURE"].front().toCString())); QByteArray data(QByteArray::fromBase64(
map["METADATA_BLOCK_PICTURE"].front().toCString()));
TagLib::ByteVector tdata(data.data(), data.size()); TagLib::ByteVector tdata(data.data(), data.size());
TagLib::FLAC::Picture p(tdata); TagLib::FLAC::Picture p(tdata);
return QByteArray(p.data().data(), p.data().size()); return QByteArray(p.data().data(), p.data().size());
@ -807,8 +896,7 @@ QByteArray TagReader::LoadEmbeddedArt(const QString& filename) const {
// Ogg lacks a definitive standard for embedding cover art, but it seems // Ogg lacks a definitive standard for embedding cover art, but it seems
// b64 encoding a field called COVERART is the general convention // b64 encoding a field called COVERART is the general convention
if (!map.contains("COVERART")) if (!map.contains("COVERART")) return QByteArray();
return QByteArray();
return QByteArray::fromBase64(map["COVERART"].toString().toCString()); return QByteArray::fromBase64(map["COVERART"].toString().toCString());
} }
@ -850,62 +938,45 @@ QByteArray TagReader::LoadEmbeddedArt(const QString& filename) const {
return QByteArray(); return QByteArray();
} }
#ifdef HAVE_GOOGLE_DRIVE #ifdef HAVE_GOOGLE_DRIVE
bool TagReader::ReadCloudFile(const QUrl& download_url, bool TagReader::ReadCloudFile(const QUrl& download_url, const QString& title,
const QString& title, int size, const QString& mime_type,
int size, const QString& authorisation_header,
const QString& mime_type, pb::tagreader::SongMetadata* song) const {
const QString& authorisation_header,
pb::tagreader::SongMetadata* song) const {
qLog(Debug) << "Loading tags from" << title; qLog(Debug) << "Loading tags from" << title;
CloudStream* stream = new CloudStream( CloudStream* stream = new CloudStream(download_url, title, size,
download_url, title, size, authorisation_header, network_); authorisation_header, network_);
stream->Precache(); stream->Precache();
std::unique_ptr<TagLib::File> tag; std::unique_ptr<TagLib::File> tag;
if (mime_type == "audio/mpeg" && title.endsWith(".mp3")) { if (mime_type == "audio/mpeg" && title.endsWith(".mp3")) {
tag.reset(new TagLib::MPEG::File( tag.reset(new TagLib::MPEG::File(stream, // Takes ownership.
stream, // Takes ownership. TagLib::ID3v2::FrameFactory::instance(),
TagLib::ID3v2::FrameFactory::instance(), TagLib::AudioProperties::Accurate));
TagLib::AudioProperties::Accurate));
} else if (mime_type == "audio/mp4" || } else if (mime_type == "audio/mp4" ||
(mime_type == "audio/mpeg" && title.endsWith(".m4a"))) { (mime_type == "audio/mpeg" && title.endsWith(".m4a"))) {
tag.reset(new TagLib::MP4::File( tag.reset(
stream, new TagLib::MP4::File(stream, true, TagLib::AudioProperties::Accurate));
true, }
TagLib::AudioProperties::Accurate));
}
#ifdef TAGLIB_HAS_OPUS #ifdef TAGLIB_HAS_OPUS
else if ((mime_type == "application/opus" || else if ((mime_type == "application/opus" || mime_type == "audio/opus" ||
mime_type == "audio/opus" || mime_type == "application/ogg" || mime_type == "audio/ogg") &&
mime_type == "application/ogg" || title.endsWith(".opus")) {
mime_type == "audio/ogg") && title.endsWith(".opus")) { tag.reset(new TagLib::Ogg::Opus::File(stream, true,
tag.reset(new TagLib::Ogg::Opus::File( TagLib::AudioProperties::Accurate));
stream,
true,
TagLib::AudioProperties::Accurate));
} }
#endif #endif
else if (mime_type == "application/ogg" || else if (mime_type == "application/ogg" || mime_type == "audio/ogg") {
mime_type == "audio/ogg") { tag.reset(new TagLib::Ogg::Vorbis::File(stream, true,
tag.reset(new TagLib::Ogg::Vorbis::File( TagLib::AudioProperties::Accurate));
stream, } else if (mime_type == "application/x-flac" || mime_type == "audio/flac" ||
true, mime_type == "audio/x-flac") {
TagLib::AudioProperties::Accurate)); tag.reset(new TagLib::FLAC::File(stream,
} else if (mime_type == "application/x-flac" || TagLib::ID3v2::FrameFactory::instance(),
mime_type == "audio/flac" || true, TagLib::AudioProperties::Accurate));
mime_type == "audio/x-flac") {
tag.reset(new TagLib::FLAC::File(
stream,
TagLib::ID3v2::FrameFactory::instance(),
true,
TagLib::AudioProperties::Accurate));
} else if (mime_type == "audio/x-ms-wma") { } else if (mime_type == "audio/x-ms-wma") {
tag.reset(new TagLib::ASF::File( tag.reset(
stream, new TagLib::ASF::File(stream, true, TagLib::AudioProperties::Accurate));
true,
TagLib::AudioProperties::Accurate));
} else { } else {
qLog(Debug) << "Unknown mime type for tagging:" << mime_type; qLog(Debug) << "Unknown mime type for tagging:" << mime_type;
return false; return false;
@ -914,8 +985,7 @@ bool TagReader::ReadCloudFile(const QUrl& download_url,
if (stream->num_requests() > 2) { if (stream->num_requests() > 2) {
// Warn if pre-caching failed. // Warn if pre-caching failed.
qLog(Warning) << "Total requests for file:" << title qLog(Warning) << "Total requests for file:" << title
<< stream->num_requests() << stream->num_requests() << stream->cached_bytes();
<< stream->cached_bytes();
} }
if (tag->tag() && !tag->tag()->isEmpty()) { if (tag->tag() && !tag->tag()->isEmpty()) {
@ -941,14 +1011,16 @@ bool TagReader::ReadCloudFile(const QUrl& download_url,
return false; return false;
} }
#endif // HAVE_GOOGLE_DRIVE #endif // HAVE_GOOGLE_DRIVE
TagLib::ID3v2::PopularimeterFrame* TagReader::GetPOPMFrameFromTag(TagLib::ID3v2::Tag* tag) { TagLib::ID3v2::PopularimeterFrame* TagReader::GetPOPMFrameFromTag(
TagLib::ID3v2::Tag* tag) {
TagLib::ID3v2::PopularimeterFrame* frame = nullptr; TagLib::ID3v2::PopularimeterFrame* frame = nullptr;
const TagLib::ID3v2::FrameListMap& map = tag->frameListMap(); const TagLib::ID3v2::FrameListMap& map = tag->frameListMap();
if (!map["POPM"].isEmpty()) { if (!map["POPM"].isEmpty()) {
frame = dynamic_cast<TagLib::ID3v2::PopularimeterFrame*>(map["POPM"].front()); frame =
dynamic_cast<TagLib::ID3v2::PopularimeterFrame*>(map["POPM"].front());
} }
if (!frame) { if (!frame) {
@ -962,15 +1034,15 @@ float TagReader::ConvertPOPMRating(const int POPM_rating) {
if (POPM_rating < 0x01) { if (POPM_rating < 0x01) {
return 0.0; return 0.0;
} else if (POPM_rating < 0x40) { } else if (POPM_rating < 0x40) {
return 0.20; // 1 star return 0.20; // 1 star
} else if (POPM_rating < 0x80) { } else if (POPM_rating < 0x80) {
return 0.40; // 2 stars return 0.40; // 2 stars
} else if (POPM_rating < 0xC0) { } else if (POPM_rating < 0xC0) {
return 0.60; // 3 stars return 0.60; // 3 stars
} else if (POPM_rating < 0xFC) { // some players store 5 stars as 0xFC } else if (POPM_rating < 0xFC) { // some players store 5 stars as 0xFC
return 0.80; // 4 stars return 0.80; // 4 stars
} }
return 1.0; // 5 stars return 1.0; // 5 stars
} }
int TagReader::ConvertToPOPMRating(const float rating) { int TagReader::ConvertToPOPMRating(const float rating) {

View File

@ -30,15 +30,14 @@ class QString;
class QTextCodec; class QTextCodec;
class QUrl; class QUrl;
namespace TagLib { namespace TagLib {
class FileRef; class FileRef;
class String; class String;
namespace ID3v2 { namespace ID3v2 {
class Tag; class Tag;
class PopularimeterFrame; class PopularimeterFrame;
} }
} }
class FileRefFactory; class FileRefFactory;
@ -52,25 +51,26 @@ class TagReader {
public: public:
TagReader(); TagReader();
void ReadFile(const QString& filename, pb::tagreader::SongMetadata* song) const; void ReadFile(const QString& filename,
bool SaveFile(const QString& filename, const pb::tagreader::SongMetadata& song) const; pb::tagreader::SongMetadata* song) const;
bool SaveFile(const QString& filename,
const pb::tagreader::SongMetadata& song) const;
// Returns false if something went wrong; returns true otherwise (might // Returns false if something went wrong; returns true otherwise (might
// returns true if the file exists but nothing has been written inside because // returns true if the file exists but nothing has been written inside because
// statistics tag format is not supported for this kind of file) // statistics tag format is not supported for this kind of file)
bool SaveSongStatisticsToFile(const QString& filename, const pb::tagreader::SongMetadata& song) const; bool SaveSongStatisticsToFile(const QString& filename,
bool SaveSongRatingToFile(const QString& filename, const pb::tagreader::SongMetadata& song) const; const pb::tagreader::SongMetadata& song) const;
bool SaveSongRatingToFile(const QString& filename,
const pb::tagreader::SongMetadata& song) const;
bool IsMediaFile(const QString& filename) const; bool IsMediaFile(const QString& filename) const;
QByteArray LoadEmbeddedArt(const QString& filename) const; QByteArray LoadEmbeddedArt(const QString& filename) const;
#ifdef HAVE_GOOGLE_DRIVE #ifdef HAVE_GOOGLE_DRIVE
bool ReadCloudFile(const QUrl& download_url, bool ReadCloudFile(const QUrl& download_url, const QString& title, int size,
const QString& title, const QString& mime_type, const QString& access_token,
int size,
const QString& mime_type,
const QString& access_token,
pb::tagreader::SongMetadata* song) const; pb::tagreader::SongMetadata* song) const;
#endif // HAVE_GOOGLE_DRIVE #endif // HAVE_GOOGLE_DRIVE
static void Decode(const TagLib::String& tag, const QTextCodec* codec, static void Decode(const TagLib::String& tag, const QTextCodec* codec,
std::string* output); std::string* output);
@ -80,21 +80,24 @@ class TagReader {
void ParseFMPSFrame(const QString& name, const QString& value, void ParseFMPSFrame(const QString& name, const QString& value,
pb::tagreader::SongMetadata* song) const; pb::tagreader::SongMetadata* song) const;
void ParseOggTag(const TagLib::Ogg::FieldListMap& map, void ParseOggTag(const TagLib::Ogg::FieldListMap& map,
const QTextCodec* codec, const QTextCodec* codec, QString* disc, QString* compilation,
QString* disc, QString* compilation,
pb::tagreader::SongMetadata* song) const; pb::tagreader::SongMetadata* song) const;
void SetVorbisComments(TagLib::Ogg::XiphComment* vorbis_comments, void SetVorbisComments(TagLib::Ogg::XiphComment* vorbis_comments,
const pb::tagreader::SongMetadata& song) const; const pb::tagreader::SongMetadata& song) const;
void SetFMPSStatisticsVorbisComments(TagLib::Ogg::XiphComment* vorbis_comments, void SetFMPSStatisticsVorbisComments(
const pb::tagreader::SongMetadata& song) const; TagLib::Ogg::XiphComment* vorbis_comments,
const pb::tagreader::SongMetadata& song) const;
void SetFMPSRatingVorbisComments(TagLib::Ogg::XiphComment* vorbis_comments, void SetFMPSRatingVorbisComments(TagLib::Ogg::XiphComment* vorbis_comments,
const pb::tagreader::SongMetadata& song) const; const pb::tagreader::SongMetadata& song)
const;
pb::tagreader::SongMetadata_Type GuessFileType(TagLib::FileRef* fileref) const; pb::tagreader::SongMetadata_Type GuessFileType(TagLib::FileRef* fileref)
const;
void SetUserTextFrame(const QString& description, const QString& value, void SetUserTextFrame(const QString& description, const QString& value,
TagLib::ID3v2::Tag* tag) const; TagLib::ID3v2::Tag* tag) const;
void SetUserTextFrame(const std::string& description, const std::string& value, void SetUserTextFrame(const std::string& description,
const std::string& value,
TagLib::ID3v2::Tag* tag) const; TagLib::ID3v2::Tag* tag) const;
void SetTextFrame(const char* id, const QString& value, void SetTextFrame(const char* id, const QString& value,
@ -102,15 +105,17 @@ class TagReader {
void SetTextFrame(const char* id, const std::string& value, void SetTextFrame(const char* id, const std::string& value,
TagLib::ID3v2::Tag* tag) const; TagLib::ID3v2::Tag* tag) const;
private: private:
static const char* kMP4_FMPS_Rating_ID; static const char* kMP4_FMPS_Rating_ID;
static const char* kMP4_FMPS_Playcount_ID; static const char* kMP4_FMPS_Playcount_ID;
static const char* kMP4_FMPS_Score_ID; static const char* kMP4_FMPS_Score_ID;
// Returns a float in [0.0..1.0] corresponding to the rating range we use in Clementine // Returns a float in [0.0..1.0] corresponding to the rating range we use in
// Clementine
static float ConvertPOPMRating(const int POPM_rating); static float ConvertPOPMRating(const int POPM_rating);
// Reciprocal // Reciprocal
static int ConvertToPOPMRating(const float rating); static int ConvertToPOPMRating(const float rating);
static TagLib::ID3v2::PopularimeterFrame* GetPOPMFrameFromTag(TagLib::ID3v2::Tag* tag); static TagLib::ID3v2::PopularimeterFrame* GetPOPMFrameFromTag(
TagLib::ID3v2::Tag* tag);
FileRefFactory* factory_; FileRefFactory* factory_;
QNetworkAccessManager* network_; QNetworkAccessManager* network_;
@ -118,4 +123,4 @@ private:
const std::string kEmbeddedCover; const std::string kEmbeddedCover;
}; };
#endif // TAGREADER_H #endif // TAGREADER_H

View File

@ -3,15 +3,13 @@
#include "engines/enginebase.h" #include "engines/enginebase.h"
AnalyzerBase::AnalyzerBase(QWidget* parent) AnalyzerBase::AnalyzerBase(QWidget* parent)
: QGLWidget(parent), : QGLWidget(parent), engine_(nullptr) {}
engine_(nullptr) {
}
void AnalyzerBase::set_engine(Engine::Base* engine) { void AnalyzerBase::set_engine(Engine::Base* engine) {
disconnect(engine_); disconnect(engine_);
engine_ = engine; engine_ = engine;
if (engine_) { if (engine_) {
connect(engine_, SIGNAL(SpectrumAvailable(const QVector<float>&)), connect(engine_, SIGNAL(SpectrumAvailable(const QVector<float>&)),
SLOT(SpectrumAvailable(const QVector<float>&))); SLOT(SpectrumAvailable(const QVector<float>&)));
} }
} }

View File

@ -17,9 +17,9 @@
#include "analyzerbase.h" #include "analyzerbase.h"
#include <cmath> //interpolate() #include <cmath> //interpolate()
#include <QEvent> //event() #include <QEvent> //event()
#include <QPainter> #include <QPainter>
#include <QPaintEvent> #include <QPaintEvent>
#include <QtDebug> #include <QtDebug>
@ -27,205 +27,186 @@
#include "engines/enginebase.h" #include "engines/enginebase.h"
// INSTRUCTIONS Base2D // INSTRUCTIONS Base2D
// 1. do anything that depends on height() in init(), Base2D will call it before you are shown // 1. do anything that depends on height() in init(), Base2D will call it before
// you are shown
// 2. otherwise you can use the constructor to initialise things // 2. otherwise you can use the constructor to initialise things
// 3. reimplement analyze(), and paint to canvas(), Base2D will update the widget when you return control to it // 3. reimplement analyze(), and paint to canvas(), Base2D will update the
// widget when you return control to it
// 4. if you want to manipulate the scope, reimplement transform() // 4. if you want to manipulate the scope, reimplement transform()
// 5. for convenience <vector> <qpixmap.h> <qwdiget.h> are pre-included // 5. for convenience <vector> <qpixmap.h> <qwdiget.h> are pre-included
// TODO make an INSTRUCTIONS file // TODO make an INSTRUCTIONS file
//can't mod scope in analyze you have to use transform // can't mod scope in analyze you have to use transform
// TODO for 2D use setErasePixmap Qt function insetead of m_background
//TODO for 2D use setErasePixmap Qt function insetead of m_background
// make the linker happy only for gcc < 4.0 // make the linker happy only for gcc < 4.0
#if !( __GNUC__ > 4 || ( __GNUC__ == 4 && __GNUC_MINOR__ >= 0 ) ) && !defined(Q_OS_WIN32) #if !(__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 0)) && \
!defined(Q_OS_WIN32)
template class Analyzer::Base<QWidget>; template class Analyzer::Base<QWidget>;
#endif #endif
Analyzer::Base::Base(QWidget* parent, uint scopeSize)
: QWidget(parent),
m_timeout(40) // msec
,
m_fht(new FHT(scopeSize)),
m_engine(nullptr),
m_lastScope(512),
new_frame_(false),
is_playing_(false) {}
Analyzer::Base::Base( QWidget *parent, uint scopeSize ) void Analyzer::Base::hideEvent(QHideEvent*) { m_timer.stop(); }
: QWidget( parent )
, m_timeout( 40 ) // msec void Analyzer::Base::showEvent(QShowEvent*) { m_timer.start(timeout(), this); }
, m_fht( new FHT(scopeSize) )
, m_engine(nullptr) void Analyzer::Base::transform(Scope& scope) // virtual
, m_lastScope(512)
, new_frame_(false)
, is_playing_(false)
{ {
// this is a standard transformation that should give
// an FFT scope that has bands for pretty analyzers
// NOTE resizing here is redundant as FHT routines only calculate FHT::size()
// values
// scope.resize( m_fht->size() );
float* front = static_cast<float*>(&scope.front());
float* f = new float[m_fht->size()];
m_fht->copy(&f[0], front);
m_fht->logSpectrum(front, &f[0]);
m_fht->scale(front, 1.0 / 20);
scope.resize(m_fht->size() / 2); // second half of values are rubbish
delete[] f;
} }
void Analyzer::Base::hideEvent(QHideEvent *) { void Analyzer::Base::paintEvent(QPaintEvent* e) {
m_timer.stop(); QPainter p(this);
} p.fillRect(e->rect(), palette().color(QPalette::Window));
void Analyzer::Base::showEvent(QShowEvent *) { switch (m_engine->state()) {
m_timer.start(timeout(), this); case Engine::Playing: {
} const Engine::Scope& thescope = m_engine->scope();
int i = 0;
void Analyzer::Base::transform( Scope &scope ) //virtual // convert to mono here - our built in analyzers need mono, but we the
{ // engines provide interleaved pcm
//this is a standard transformation that should give for (uint x = 0; (int)x < m_fht->size(); ++x) {
//an FFT scope that has bands for pretty analyzers m_lastScope[x] =
double(thescope[i] + thescope[i + 1]) / (2 * (1 << 15));
i += 2;
}
//NOTE resizing here is redundant as FHT routines only calculate FHT::size() values is_playing_ = true;
//scope.resize( m_fht->size() ); transform(m_lastScope);
analyze(p, m_lastScope, new_frame_);
float *front = static_cast<float*>( &scope.front() ); // scope.resize( m_fht->size() );
float* f = new float[ m_fht->size() ]; break;
m_fht->copy( &f[0], front );
m_fht->logSpectrum( front, &f[0] );
m_fht->scale( front, 1.0 / 20 );
scope.resize( m_fht->size() / 2 ); //second half of values are rubbish
delete [] f;
}
void Analyzer::Base::paintEvent(QPaintEvent * e)
{
QPainter p(this);
p.fillRect(e->rect(), palette().color(QPalette::Window));
switch( m_engine->state() )
{
case Engine::Playing:
{
const Engine::Scope &thescope = m_engine->scope();
int i = 0;
// convert to mono here - our built in analyzers need mono, but we the engines provide interleaved pcm
for( uint x = 0; (int)x < m_fht->size(); ++x )
{
m_lastScope[x] = double(thescope[i] + thescope[i+1]) / (2*(1<<15));
i += 2;
}
is_playing_ = true;
transform( m_lastScope );
analyze( p, m_lastScope, new_frame_ );
//scope.resize( m_fht->size() );
break;
} }
case Engine::Paused: case Engine::Paused:
is_playing_ = false; is_playing_ = false;
analyze(p, m_lastScope, new_frame_); analyze(p, m_lastScope, new_frame_);
break; break;
default: default:
is_playing_ = false; is_playing_ = false;
demo(p); demo(p);
} }
new_frame_ = false;
new_frame_ = false;
} }
int Analyzer::Base::resizeExponent( int exp ) int Analyzer::Base::resizeExponent(int exp) {
{ if (exp < 3)
if ( exp < 3 ) exp = 3;
exp = 3; else if (exp > 9)
else if ( exp > 9 ) exp = 9;
exp = 9;
if ( exp != m_fht->sizeExp() ) { if (exp != m_fht->sizeExp()) {
delete m_fht; delete m_fht;
m_fht = new FHT( exp ); m_fht = new FHT(exp);
} }
return exp; return exp;
} }
int Analyzer::Base::resizeForBands( int bands ) int Analyzer::Base::resizeForBands(int bands) {
{ int exp;
int exp; if (bands <= 8)
if ( bands <= 8 ) exp = 4;
exp = 4; else if (bands <= 16)
else if ( bands <= 16 ) exp = 5;
exp = 5; else if (bands <= 32)
else if ( bands <= 32 ) exp = 6;
exp = 6; else if (bands <= 64)
else if ( bands <= 64 ) exp = 7;
exp = 7; else if (bands <= 128)
else if ( bands <= 128 ) exp = 8;
exp = 8; else
else exp = 9;
exp = 9;
resizeExponent( exp ); resizeExponent(exp);
return m_fht->size() / 2; return m_fht->size() / 2;
} }
void Analyzer::Base::demo(QPainter& p) //virtual void Analyzer::Base::demo(QPainter& p) // virtual
{ {
static int t = 201; //FIXME make static to namespace perhaps static int t = 201; // FIXME make static to namespace perhaps
if( t > 999 ) t = 1; //0 = wasted calculations if (t > 999) t = 1; // 0 = wasted calculations
if( t < 201 ) if (t < 201) {
{ Scope s(32);
Scope s( 32 );
const double dt = double(t) / 200; const double dt = double(t) / 200;
for( uint i = 0; i < s.size(); ++i ) for (uint i = 0; i < s.size(); ++i)
s[i] = dt * (sin( M_PI + (i * M_PI) / s.size() ) + 1.0); s[i] = dt * (sin(M_PI + (i * M_PI) / s.size()) + 1.0);
analyze( p, s, new_frame_ ); analyze(p, s, new_frame_);
} } else
else analyze( p, Scope( 32, 0 ), new_frame_ ); analyze(p, Scope(32, 0), new_frame_);
++t; ++t;
} }
void Analyzer::Base::polishEvent() {
void Analyzer::Base::polishEvent() init(); // virtual
{
init(); //virtual
} }
void void Analyzer::interpolate(const Scope& inVec, Scope& outVec) // static
Analyzer::interpolate( const Scope &inVec, Scope &outVec ) //static
{ {
double pos = 0.0; double pos = 0.0;
const double step = (double)inVec.size() / outVec.size(); const double step = (double)inVec.size() / outVec.size();
for ( uint i = 0; i < outVec.size(); ++i, pos += step ) for (uint i = 0; i < outVec.size(); ++i, pos += step) {
{ const double error = pos - std::floor(pos);
const double error = pos - std::floor( pos ); const unsigned long offset = (unsigned long)pos;
const unsigned long offset = (unsigned long)pos;
unsigned long indexLeft = offset + 0; unsigned long indexLeft = offset + 0;
if ( indexLeft >= inVec.size() ) if (indexLeft >= inVec.size()) indexLeft = inVec.size() - 1;
indexLeft = inVec.size() - 1;
unsigned long indexRight = offset + 1; unsigned long indexRight = offset + 1;
if ( indexRight >= inVec.size() ) if (indexRight >= inVec.size()) indexRight = inVec.size() - 1;
indexRight = inVec.size() - 1;
outVec[i] = inVec[indexLeft ] * ( 1.0 - error ) + outVec[i] = inVec[indexLeft] * (1.0 - error) + inVec[indexRight] * error;
inVec[indexRight] * error; }
}
} }
void void Analyzer::initSin(Scope& v, const uint size) // static
Analyzer::initSin( Scope &v, const uint size ) //static
{ {
double step = ( M_PI * 2 ) / size; double step = (M_PI * 2) / size;
double radian = 0; double radian = 0;
for ( uint i = 0; i < size; i++ ) for (uint i = 0; i < size; i++) {
{ v.push_back(sin(radian));
v.push_back( sin( radian ) ); radian += step;
radian += step; }
}
} }
void Analyzer::Base::timerEvent(QTimerEvent* e) { void Analyzer::Base::timerEvent(QTimerEvent* e) {
QWidget::timerEvent(e); QWidget::timerEvent(e);
if (e->timerId() != m_timer.timerId()) if (e->timerId() != m_timer.timerId()) return;
return;
new_frame_ = true; new_frame_ = true;
update(); update();

View File

@ -4,19 +4,18 @@
#ifndef ANALYZERBASE_H #ifndef ANALYZERBASE_H
#define ANALYZERBASE_H #define ANALYZERBASE_H
#ifdef __FreeBSD__ #ifdef __FreeBSD__
#include <sys/types.h> #include <sys/types.h>
#endif #endif
#include "core/fht.h" //stack allocated and convenience #include "core/fht.h" //stack allocated and convenience
#include "engines/engine_fwd.h" #include "engines/engine_fwd.h"
#include <QPixmap> //stack allocated and convenience #include <QPixmap> //stack allocated and convenience
#include <QBasicTimer> //stack allocated #include <QBasicTimer> //stack allocated
#include <QWidget> //baseclass #include <QWidget> //baseclass
#include <vector> //included for convenience #include <vector> //included for convenience
#include <QGLWidget> //baseclass #include <QGLWidget> //baseclass
#ifdef Q_WS_MACX #ifdef Q_WS_MACX
#include <OpenGL/gl.h> //included for convenience #include <OpenGL/gl.h> //included for convenience
#include <OpenGL/glu.h> //included for convenience #include <OpenGL/glu.h> //included for convenience
@ -29,63 +28,60 @@ class QEvent;
class QPaintEvent; class QPaintEvent;
class QResizeEvent; class QResizeEvent;
namespace Analyzer { namespace Analyzer {
typedef std::vector<float> Scope; typedef std::vector<float> Scope;
class Base : public QWidget class Base : public QWidget {
{
Q_OBJECT Q_OBJECT
public: public:
~Base() { delete m_fht; } ~Base() { delete m_fht; }
uint timeout() const { return m_timeout; } uint timeout() const { return m_timeout; }
void set_engine(EngineBase* engine) { m_engine = engine; } void set_engine(EngineBase* engine) { m_engine = engine; }
void changeTimeout( uint newTimeout ) { void changeTimeout(uint newTimeout) {
m_timeout = newTimeout; m_timeout = newTimeout;
if (m_timer.isActive()) { if (m_timer.isActive()) {
m_timer.stop(); m_timer.stop();
m_timer.start(m_timeout, this); m_timer.start(m_timeout, this);
}
} }
}
protected: protected:
Base( QWidget*, uint scopeSize = 7 ); Base(QWidget*, uint scopeSize = 7);
void hideEvent(QHideEvent *); void hideEvent(QHideEvent*);
void showEvent(QShowEvent *); void showEvent(QShowEvent*);
void paintEvent( QPaintEvent* ); void paintEvent(QPaintEvent*);
void timerEvent(QTimerEvent *); void timerEvent(QTimerEvent*);
void polishEvent(); void polishEvent();
int resizeExponent( int ); int resizeExponent(int);
int resizeForBands( int ); int resizeForBands(int);
virtual void init() {} virtual void init() {}
virtual void transform( Scope& ); virtual void transform(Scope&);
virtual void analyze( QPainter& p, const Scope&, bool new_frame) = 0; virtual void analyze(QPainter& p, const Scope&, bool new_frame) = 0;
virtual void demo(QPainter& p); virtual void demo(QPainter& p);
protected: protected:
QBasicTimer m_timer; QBasicTimer m_timer;
uint m_timeout; uint m_timeout;
FHT *m_fht; FHT* m_fht;
EngineBase* m_engine; EngineBase* m_engine;
Scope m_lastScope; Scope m_lastScope;
bool new_frame_; bool new_frame_;
bool is_playing_; bool is_playing_;
}; };
void interpolate(const Scope&, Scope&);
void initSin(Scope&, const uint = 6000);
void interpolate( const Scope&, Scope& ); } // END namespace Analyzer
void initSin( Scope&, const uint = 6000 );
} //END namespace Analyzer
using Analyzer::Scope; using Analyzer::Scope;

View File

@ -39,21 +39,20 @@ const int AnalyzerContainer::kMediumFramerate = 25;
const int AnalyzerContainer::kHighFramerate = 30; const int AnalyzerContainer::kHighFramerate = 30;
const int AnalyzerContainer::kSuperHighFramerate = 60; const int AnalyzerContainer::kSuperHighFramerate = 60;
AnalyzerContainer::AnalyzerContainer(QWidget *parent) AnalyzerContainer::AnalyzerContainer(QWidget* parent)
: QWidget(parent), : QWidget(parent),
current_framerate_(kMediumFramerate), current_framerate_(kMediumFramerate),
context_menu_(new QMenu(this)), context_menu_(new QMenu(this)),
context_menu_framerate_(new QMenu(tr("Framerate"), this)), context_menu_framerate_(new QMenu(tr("Framerate"), this)),
group_(new QActionGroup(this)), group_(new QActionGroup(this)),
group_framerate_(new QActionGroup(this)), group_framerate_(new QActionGroup(this)),
mapper_(new QSignalMapper(this)), mapper_(new QSignalMapper(this)),
mapper_framerate_(new QSignalMapper(this)), mapper_framerate_(new QSignalMapper(this)),
visualisation_action_(nullptr), visualisation_action_(nullptr),
double_click_timer_(new QTimer(this)), double_click_timer_(new QTimer(this)),
ignore_next_click_(false), ignore_next_click_(false),
current_analyzer_(nullptr), current_analyzer_(nullptr),
engine_(nullptr) engine_(nullptr) {
{
QHBoxLayout* layout = new QHBoxLayout(this); QHBoxLayout* layout = new QHBoxLayout(this);
setLayout(layout); setLayout(layout);
layout->setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(0, 0, 0, 0);
@ -62,7 +61,8 @@ AnalyzerContainer::AnalyzerContainer(QWidget *parent)
AddFramerate(tr("Low (%1 fps)").arg(kLowFramerate), kLowFramerate); AddFramerate(tr("Low (%1 fps)").arg(kLowFramerate), kLowFramerate);
AddFramerate(tr("Medium (%1 fps)").arg(kMediumFramerate), kMediumFramerate); AddFramerate(tr("Medium (%1 fps)").arg(kMediumFramerate), kMediumFramerate);
AddFramerate(tr("High (%1 fps)").arg(kHighFramerate), kHighFramerate); AddFramerate(tr("High (%1 fps)").arg(kHighFramerate), kHighFramerate);
AddFramerate(tr("Super high (%1 fps)").arg(kSuperHighFramerate), kSuperHighFramerate); AddFramerate(tr("Super high (%1 fps)").arg(kSuperHighFramerate),
kSuperHighFramerate);
connect(mapper_framerate_, SIGNAL(mapped(int)), SLOT(ChangeFramerate(int))); connect(mapper_framerate_, SIGNAL(mapped(int)), SLOT(ChangeFramerate(int)));
context_menu_->addMenu(context_menu_framerate_); context_menu_->addMenu(context_menu_framerate_);
@ -76,8 +76,8 @@ AnalyzerContainer::AnalyzerContainer(QWidget *parent)
AddAnalyzerType<NyanCatAnalyzer>(); AddAnalyzerType<NyanCatAnalyzer>();
connect(mapper_, SIGNAL(mapped(int)), SLOT(ChangeAnalyzer(int))); connect(mapper_, SIGNAL(mapped(int)), SLOT(ChangeAnalyzer(int)));
disable_action_ = disable_action_ = context_menu_->addAction(tr("No analyzer"), this,
context_menu_->addAction(tr("No analyzer"), this, SLOT(DisableAnalyzer())); SLOT(DisableAnalyzer()));
disable_action_->setCheckable(true); disable_action_->setCheckable(true);
group_->addAction(disable_action_); group_->addAction(disable_action_);
@ -115,12 +115,11 @@ void AnalyzerContainer::ShowPopupMenu() {
context_menu_->popup(last_click_pos_); context_menu_->popup(last_click_pos_);
} }
void AnalyzerContainer::mouseDoubleClickEvent(QMouseEvent *) { void AnalyzerContainer::mouseDoubleClickEvent(QMouseEvent*) {
double_click_timer_->stop(); double_click_timer_->stop();
ignore_next_click_ = true; ignore_next_click_ = true;
if (visualisation_action_) if (visualisation_action_) visualisation_action_->trigger();
visualisation_action_->trigger();
} }
void AnalyzerContainer::wheelEvent(QWheelEvent* e) { void AnalyzerContainer::wheelEvent(QWheelEvent* e) {
@ -128,8 +127,7 @@ void AnalyzerContainer::wheelEvent(QWheelEvent* e) {
} }
void AnalyzerContainer::SetEngine(EngineBase* engine) { void AnalyzerContainer::SetEngine(EngineBase* engine) {
if (current_analyzer_) if (current_analyzer_) current_analyzer_->set_engine(engine);
current_analyzer_->set_engine(engine);
engine_ = engine; engine_ = engine;
} }
@ -144,7 +142,8 @@ void AnalyzerContainer::ChangeAnalyzer(int id) {
QObject* instance = analyzer_types_[id]->newInstance(Q_ARG(QWidget*, this)); QObject* instance = analyzer_types_[id]->newInstance(Q_ARG(QWidget*, this));
if (!instance) { if (!instance) {
qLog(Warning) << "Couldn't intialise a new" << analyzer_types_[id]->className(); qLog(Warning) << "Couldn't intialise a new"
<< analyzer_types_[id]->className();
return; return;
} }
@ -152,7 +151,8 @@ void AnalyzerContainer::ChangeAnalyzer(int id) {
current_analyzer_ = qobject_cast<Analyzer::Base*>(instance); current_analyzer_ = qobject_cast<Analyzer::Base*>(instance);
current_analyzer_->set_engine(engine_); current_analyzer_->set_engine(engine_);
// Even if it is not supposed to happen, I don't want to get a dbz error // Even if it is not supposed to happen, I don't want to get a dbz error
current_framerate_ = current_framerate_ == 0 ? kMediumFramerate : current_framerate_; current_framerate_ =
current_framerate_ == 0 ? kMediumFramerate : current_framerate_;
current_analyzer_->changeTimeout(1000 / current_framerate_); current_analyzer_->changeTimeout(1000 / current_framerate_);
layout()->addWidget(current_analyzer_); layout()->addWidget(current_analyzer_);
@ -161,7 +161,7 @@ void AnalyzerContainer::ChangeAnalyzer(int id) {
} }
void AnalyzerContainer::ChangeFramerate(int new_framerate) { void AnalyzerContainer::ChangeFramerate(int new_framerate) {
if(current_analyzer_) { if (current_analyzer_) {
// Even if it is not supposed to happen, I don't want to get a dbz error // Even if it is not supposed to happen, I don't want to get a dbz error
new_framerate = new_framerate == 0 ? kMediumFramerate : new_framerate; new_framerate = new_framerate == 0 ? kMediumFramerate : new_framerate;
current_analyzer_->changeTimeout(1000 / new_framerate); current_analyzer_->changeTimeout(1000 / new_framerate);
@ -179,7 +179,7 @@ void AnalyzerContainer::Load() {
DisableAnalyzer(); DisableAnalyzer();
disable_action_->setChecked(true); disable_action_->setChecked(true);
} else { } else {
for (int i=0 ; i<analyzer_types_.count() ; ++i) { for (int i = 0; i < analyzer_types_.count(); ++i) {
if (type == analyzer_types_[i]->className()) { if (type == analyzer_types_[i]->className()) {
ChangeAnalyzer(i); ChangeAnalyzer(i);
actions_[i]->setChecked(true); actions_[i]->setChecked(true);
@ -190,8 +190,8 @@ void AnalyzerContainer::Load() {
// Framerate // Framerate
current_framerate_ = s.value(kSettingsFramerate, kMediumFramerate).toInt(); current_framerate_ = s.value(kSettingsFramerate, kMediumFramerate).toInt();
for (int i=0 ; i<framerate_list_.count() ; ++i) { for (int i = 0; i < framerate_list_.count(); ++i) {
if(current_framerate_ == framerate_list_[i]) { if (current_framerate_ == framerate_list_[i]) {
ChangeFramerate(current_framerate_); ChangeFramerate(current_framerate_);
group_framerate_->actions()[i]->setChecked(true); group_framerate_->actions()[i]->setChecked(true);
break; break;
@ -200,8 +200,8 @@ void AnalyzerContainer::Load() {
} }
void AnalyzerContainer::SaveFramerate(int framerate) { void AnalyzerContainer::SaveFramerate(int framerate) {
// For now, framerate is common for all analyzers. Maybe each analyzer should // For now, framerate is common for all analyzers. Maybe each analyzer should
// have its own framerate? // have its own framerate?
current_framerate_ = framerate; current_framerate_ = framerate;
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup);
@ -212,13 +212,14 @@ void AnalyzerContainer::Save() {
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup);
s.setValue("type", current_analyzer_ ? s.setValue("type", current_analyzer_
current_analyzer_->metaObject()->className() : ? current_analyzer_->metaObject()->className()
QVariant()); : QVariant());
} }
void AnalyzerContainer::AddFramerate(const QString& name, int framerate) { void AnalyzerContainer::AddFramerate(const QString& name, int framerate) {
QAction *action = context_menu_framerate_->addAction(name, mapper_framerate_, SLOT(map())); QAction* action =
context_menu_framerate_->addAction(name, mapper_framerate_, SLOT(map()));
mapper_framerate_->setMapping(action, framerate); mapper_framerate_->setMapping(action, framerate);
group_framerate_->addAction(action); group_framerate_->addAction(action);
framerate_list_ << framerate; framerate_list_ << framerate;

View File

@ -28,7 +28,7 @@
class AnalyzerContainer : public QWidget { class AnalyzerContainer : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
AnalyzerContainer(QWidget* parent); AnalyzerContainer(QWidget* parent);
void SetEngine(EngineBase* engine); void SetEngine(EngineBase* engine);
@ -40,18 +40,18 @@ public:
signals: signals:
void WheelEvent(int delta); void WheelEvent(int delta);
protected: protected:
void mouseReleaseEvent(QMouseEvent *); void mouseReleaseEvent(QMouseEvent*);
void mouseDoubleClickEvent(QMouseEvent *); void mouseDoubleClickEvent(QMouseEvent*);
void wheelEvent(QWheelEvent* e); void wheelEvent(QWheelEvent* e);
private slots: private slots:
void ChangeAnalyzer(int id); void ChangeAnalyzer(int id);
void ChangeFramerate(int new_framerate); void ChangeFramerate(int new_framerate);
void DisableAnalyzer(); void DisableAnalyzer();
void ShowPopupMenu(); void ShowPopupMenu();
private: private:
static const int kLowFramerate; static const int kLowFramerate;
static const int kMediumFramerate; static const int kMediumFramerate;
static const int kHighFramerate; static const int kHighFramerate;
@ -61,11 +61,11 @@ private:
void Save(); void Save();
void SaveFramerate(int framerate); void SaveFramerate(int framerate);
template <typename T> template <typename T>
void AddAnalyzerType(); void AddAnalyzerType();
void AddFramerate(const QString& name, int framerate); void AddFramerate(const QString& name, int framerate);
private: private:
int current_framerate_; // fps int current_framerate_; // fps
QMenu* context_menu_; QMenu* context_menu_;
QMenu* context_menu_framerate_; QMenu* context_menu_framerate_;
QActionGroup* group_; QActionGroup* group_;
@ -88,11 +88,12 @@ private:
}; };
template <typename T> template <typename T>
void AnalyzerContainer::AddAnalyzerType() { void AnalyzerContainer::AddAnalyzerType() {
int id = analyzer_types_.count(); int id = analyzer_types_.count();
analyzer_types_ << &T::staticMetaObject; analyzer_types_ << &T::staticMetaObject;
QAction* action = context_menu_->addAction(tr(T::kName), mapper_, SLOT(map())); QAction* action =
context_menu_->addAction(tr(T::kName), mapper_, SLOT(map()));
group_->addAction(action); group_->addAction(action);
mapper_->setMapping(action, id); mapper_->setMapping(action, id);
action->setCheckable(true); action->setCheckable(true);
@ -100,4 +101,3 @@ template <typename T>
} }
#endif #endif

View File

@ -12,155 +12,150 @@
// //
#include "baranalyzer.h" #include "baranalyzer.h"
#include <cmath> //log10(), etc. #include <cmath> //log10(), etc.
#include <QtDebug> #include <QtDebug>
#include <QPainter> #include <QPainter>
const char* BarAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Bar analyzer"); const char* BarAnalyzer::kName =
QT_TRANSLATE_NOOP("AnalyzerContainer", "Bar analyzer");
BarAnalyzer::BarAnalyzer(QWidget* parent)
BarAnalyzer::BarAnalyzer( QWidget *parent ) : Analyzer::Base(parent, 8)
: Analyzer::Base( parent, 8 ) //, m_bands( BAND_COUNT )
//, m_bands( BAND_COUNT ) //, barVector( BAND_COUNT, 0 )
//, barVector( BAND_COUNT, 0 ) //, roofVector( BAND_COUNT, 50 )
//, roofVector( BAND_COUNT, 50 ) //, roofVelocityVector( BAND_COUNT, ROOF_VELOCITY_REDUCTION_FACTOR )
//, roofVelocityVector( BAND_COUNT, ROOF_VELOCITY_REDUCTION_FACTOR )
{ {
//roof pixmaps don't depend on size() so we do in the ctor // roof pixmaps don't depend on size() so we do in the ctor
m_bg = parent->palette().color(QPalette::Background); m_bg = parent->palette().color(QPalette::Background);
QColor fg( 0xff, 0x50, 0x70 ); QColor fg(0xff, 0x50, 0x70);
double dr = double(m_bg.red() - fg.red()) / (NUM_ROOFS-1); //-1 because we start loop below at 0 double dr = double(m_bg.red() - fg.red()) /
double dg = double(m_bg.green() - fg.green()) / (NUM_ROOFS-1); (NUM_ROOFS - 1); //-1 because we start loop below at 0
double db = double(m_bg.blue() - fg.blue()) / (NUM_ROOFS-1); double dg = double(m_bg.green() - fg.green()) / (NUM_ROOFS - 1);
double db = double(m_bg.blue() - fg.blue()) / (NUM_ROOFS - 1);
for ( uint i = 0; i < NUM_ROOFS; ++i )
{
m_pixRoof[i] = QPixmap( COLUMN_WIDTH, 1 );
m_pixRoof[i].fill( QColor( fg.red()+int(dr*i), fg.green()+int(dg*i), fg.blue()+int(db*i) ) );
}
for (uint i = 0; i < NUM_ROOFS; ++i) {
m_pixRoof[i] = QPixmap(COLUMN_WIDTH, 1);
m_pixRoof[i].fill(QColor(fg.red() + int(dr * i), fg.green() + int(dg * i),
fg.blue() + int(db * i)));
}
} }
void BarAnalyzer::resizeEvent( QResizeEvent * e ) void BarAnalyzer::resizeEvent(QResizeEvent* e) { init(); }
{
init();
}
// METHODS ===================================================== // METHODS =====================================================
void BarAnalyzer::init() void BarAnalyzer::init() {
{ const double MAX_AMPLITUDE = 1.0;
const double MAX_AMPLITUDE = 1.0; const double F = double(height() - 2) / (log10(255) * MAX_AMPLITUDE);
const double F = double(height() - 2) / (log10( 255 ) * MAX_AMPLITUDE );
BAND_COUNT = width() / 5;
MAX_DOWN = int(0 - (qMax(1, height() / 50)));
MAX_UP = int(qMax(1, height() / 25));
BAND_COUNT = width() / 5; barVector.resize(BAND_COUNT, 0);
MAX_DOWN = int(0 -(qMax(1, height() / 50))); roofVector.resize(BAND_COUNT, height() - 5);
MAX_UP = int(qMax(1, height() / 25)); roofVelocityVector.resize(BAND_COUNT, ROOF_VELOCITY_REDUCTION_FACTOR);
m_roofMem.resize(BAND_COUNT);
m_scope.resize(BAND_COUNT);
barVector.resize( BAND_COUNT, 0 ); // generate a list of values that express amplitudes in range 0-MAX_AMP as
roofVector.resize( BAND_COUNT, height() -5 ); // ints from 0-height() on log scale
roofVelocityVector.resize( BAND_COUNT, ROOF_VELOCITY_REDUCTION_FACTOR ); for (uint x = 0; x < 256; ++x) {
m_roofMem.resize(BAND_COUNT); m_lvlMapper[x] = uint(F * log10(x + 1));
m_scope.resize(BAND_COUNT); }
//generate a list of values that express amplitudes in range 0-MAX_AMP as ints from 0-height() on log scale m_pixBarGradient = QPixmap(height() * COLUMN_WIDTH, height());
for ( uint x = 0; x < 256; ++x ) m_pixCompose = QPixmap(size());
{
m_lvlMapper[x] = uint( F * log10( x+1 ) ); QPainter p(&m_pixBarGradient);
for (int x = 0, r = 0x40, g = 0x30, b = 0xff, r2 = 255 - r; x < height();
++x) {
for (int y = x; y > 0; --y) {
const double fraction = (double)y / height();
// p.setPen( QColor( r + (int)(r2 * fraction), g, b - (int)(255 *
// fraction) ) );
p.setPen(QColor(r + (int)(r2 * fraction), g, b));
p.drawLine(x * COLUMN_WIDTH, height() - y, (x + 1) * COLUMN_WIDTH,
height() - y);
} }
}
m_pixBarGradient = QPixmap( height()*COLUMN_WIDTH, height() ); setMinimumSize(QSize(BAND_COUNT * COLUMN_WIDTH, 10));
m_pixCompose = QPixmap( size() );
QPainter p( &m_pixBarGradient );
for ( int x=0, r=0x40, g=0x30, b=0xff, r2=255-r;
x < height(); ++x )
{
for ( int y = x; y > 0; --y )
{
const double fraction = (double)y / height();
// p.setPen( QColor( r + (int)(r2 * fraction), g, b - (int)(255 * fraction) ) );
p.setPen( QColor( r + (int)(r2 * fraction), g, b ) );
p.drawLine( x*COLUMN_WIDTH, height() - y, (x+1)*COLUMN_WIDTH, height() - y );
}
}
setMinimumSize( QSize( BAND_COUNT * COLUMN_WIDTH, 10 ) );
} }
void BarAnalyzer::analyze(QPainter& p, const Scope& s, bool new_frame) {
// Analyzer::interpolate( s, m_bands );
void BarAnalyzer::analyze( QPainter& p, const Scope &s, bool new_frame) Scope& v = m_scope;
{ Analyzer::interpolate(s, v);
//Analyzer::interpolate( s, m_bands );
Scope &v = m_scope; for (uint i = 0, x = 0, y2; i < v.size(); ++i, x += COLUMN_WIDTH + 1) {
Analyzer::interpolate( s, v ); // assign pre[log10]'d value
y2 = uint(v[i] *
256); // 256 will be optimised to a bitshift //no, it's a float
y2 = m_lvlMapper[(y2 > 255) ? 255 : y2]; // lvlMapper is array of ints with
// values 0 to height()
for ( uint i = 0, x = 0, y2; i < v.size(); ++i, x+=COLUMN_WIDTH+1 ) int change = y2 - barVector[i];
{
//assign pre[log10]'d value
y2 = uint(v[i] * 256); //256 will be optimised to a bitshift //no, it's a float
y2 = m_lvlMapper[ (y2 > 255) ? 255 : y2 ]; //lvlMapper is array of ints with values 0 to height()
int change = y2 - barVector[i]; // using the best of Markey's, piggz and Max's ideas on the way to shift the
// bars
// we have the following:
// 1. don't adjust shift when doing small up movements
// 2. shift large upwards with a bias towards last value
// 3. fall downwards at a constant pace
//using the best of Markey's, piggz and Max's ideas on the way to shift the bars /*if ( change > MAX_UP ) //anything too much greater than 2 gives "jitter"
//we have the following:
// 1. don't adjust shift when doing small up movements
// 2. shift large upwards with a bias towards last value
// 3. fall downwards at a constant pace
/*if ( change > MAX_UP ) //anything too much greater than 2 gives "jitter"
//add some dynamics - makes the value slightly closer to what it was last time //add some dynamics - makes the value slightly closer to what it was last time
y2 = ( barVector[i] + MAX_UP ); y2 = ( barVector[i] + MAX_UP );
//y2 = ( barVector[i] * 2 + y2 ) / 3; //y2 = ( barVector[i] * 2 + y2 ) / 3;
else*/ if ( change < MAX_DOWN ) else*/ if (change <
y2 = barVector[i] + MAX_DOWN; MAX_DOWN)
y2 = barVector[i] + MAX_DOWN;
if ((int)y2 > roofVector[i]) {
if ( (int)y2 > roofVector[i] ) roofVector[i] = (int)y2;
{ roofVelocityVector[i] = 1;
roofVector[i] = (int)y2;
roofVelocityVector[i] = 1;
}
//remember where we are
barVector[i] = y2;
if ( m_roofMem[i].size() > NUM_ROOFS )
m_roofMem[i].erase( m_roofMem[i].begin() );
//blt last n roofs, a.k.a motion blur
for ( uint c = 0; c < m_roofMem[i].size(); ++c )
//bitBlt( m_pComposePixmap, x, m_roofMem[i]->at( c ), m_roofPixmaps[ c ] );
//bitBlt( canvas(), x, m_roofMem[i][c], &m_pixRoof[ NUM_ROOFS - 1 - c ] );
p.drawPixmap(x, m_roofMem[i][c], m_pixRoof[ NUM_ROOFS - 1 - c ]);
//blt the bar
p.drawPixmap(x, height() - y2,
*gradient(), y2 * COLUMN_WIDTH, height() - y2, COLUMN_WIDTH, y2);
/*bitBlt( canvas(), x, height() - y2,
gradient(), y2 * COLUMN_WIDTH, height() - y2, COLUMN_WIDTH, y2, Qt::CopyROP );*/
m_roofMem[i].push_back( height() - roofVector[i] - 2 );
//set roof parameters for the NEXT draw
if ( roofVelocityVector[i] != 0 )
{
if ( roofVelocityVector[i] > 32 ) //no reason to do == 32
roofVector[i] -= (roofVelocityVector[i] - 32) / 20; //trivial calculation
if ( roofVector[i] < 0 )
{
roofVector[i] = 0; //not strictly necessary
roofVelocityVector[i] = 0;
}
else ++roofVelocityVector[i];
}
} }
// remember where we are
barVector[i] = y2;
if (m_roofMem[i].size() > NUM_ROOFS)
m_roofMem[i].erase(m_roofMem[i].begin());
// blt last n roofs, a.k.a motion blur
for (uint c = 0; c < m_roofMem[i].size(); ++c)
// bitBlt( m_pComposePixmap, x, m_roofMem[i]->at( c ), m_roofPixmaps[ c ]
// );
// bitBlt( canvas(), x, m_roofMem[i][c], &m_pixRoof[ NUM_ROOFS - 1 - c ]
// );
p.drawPixmap(x, m_roofMem[i][c], m_pixRoof[NUM_ROOFS - 1 - c]);
// blt the bar
p.drawPixmap(x, height() - y2, *gradient(), y2 * COLUMN_WIDTH,
height() - y2, COLUMN_WIDTH, y2);
/*bitBlt( canvas(), x, height() - y2,
gradient(), y2 * COLUMN_WIDTH, height() - y2, COLUMN_WIDTH, y2,
Qt::CopyROP );*/
m_roofMem[i].push_back(height() - roofVector[i] - 2);
// set roof parameters for the NEXT draw
if (roofVelocityVector[i] != 0) {
if (roofVelocityVector[i] > 32) // no reason to do == 32
roofVector[i] -=
(roofVelocityVector[i] - 32) / 20; // trivial calculation
if (roofVector[i] < 0) {
roofVector[i] = 0; // not strictly necessary
roofVelocityVector[i] = 0;
} else
++roofVelocityVector[i];
}
}
} }

View File

@ -10,51 +10,50 @@
typedef std::vector<uint> aroofMemVec; typedef std::vector<uint> aroofMemVec;
class BarAnalyzer : public Analyzer::Base {
class BarAnalyzer : public Analyzer::Base
{
Q_OBJECT Q_OBJECT
public: public:
Q_INVOKABLE BarAnalyzer( QWidget* ); Q_INVOKABLE BarAnalyzer(QWidget*);
void init(); void init();
virtual void analyze( QPainter& p, const Scope&, bool new_frame); virtual void analyze(QPainter& p, const Scope&, bool new_frame);
//virtual void transform( Scope& ); // virtual void transform( Scope& );
/** /**
* Resizes the widget to a new geometry according to @p e * Resizes the widget to a new geometry according to @p e
* @param e The resize-event * @param e The resize-event
*/ */
void resizeEvent( QResizeEvent * e); void resizeEvent(QResizeEvent* e);
uint BAND_COUNT; uint BAND_COUNT;
int MAX_DOWN; int MAX_DOWN;
int MAX_UP; int MAX_UP;
static const uint ROOF_HOLD_TIME = 48; static const uint ROOF_HOLD_TIME = 48;
static const int ROOF_VELOCITY_REDUCTION_FACTOR = 32; static const int ROOF_VELOCITY_REDUCTION_FACTOR = 32;
static const uint NUM_ROOFS = 16; static const uint NUM_ROOFS = 16;
static const uint COLUMN_WIDTH = 4; static const uint COLUMN_WIDTH = 4;
static const char* kName; static const char* kName;
protected: protected:
QPixmap m_pixRoof[NUM_ROOFS]; QPixmap m_pixRoof[NUM_ROOFS];
//vector<uint> m_roofMem[BAND_COUNT]; // vector<uint> m_roofMem[BAND_COUNT];
//Scope m_bands; //copy of the Scope to prevent creating/destroying a Scope every iteration // Scope m_bands; //copy of the Scope to prevent creating/destroying a Scope
uint m_lvlMapper[256]; // every iteration
std::vector<aroofMemVec> m_roofMem; uint m_lvlMapper[256];
std::vector<uint> barVector; //positions of bars std::vector<aroofMemVec> m_roofMem;
std::vector<int> roofVector; //positions of roofs std::vector<uint> barVector; // positions of bars
std::vector<uint> roofVelocityVector; //speed that roofs falls std::vector<int> roofVector; // positions of roofs
std::vector<uint> roofVelocityVector; // speed that roofs falls
const QPixmap *gradient() const { return &m_pixBarGradient; } const QPixmap* gradient() const { return &m_pixBarGradient; }
private: private:
QPixmap m_pixBarGradient; QPixmap m_pixBarGradient;
QPixmap m_pixCompose; QPixmap m_pixCompose;
Scope m_scope; //so we don't create a vector every frame Scope m_scope; // so we don't create a vector every frame
QColor m_bg; QColor m_bg;
}; };
#endif #endif

View File

@ -12,389 +12,392 @@
#include <cstdlib> #include <cstdlib>
#include <QPainter> #include <QPainter>
const uint BlockAnalyzer::HEIGHT = 2; const uint BlockAnalyzer::HEIGHT = 2;
const uint BlockAnalyzer::WIDTH = 4; const uint BlockAnalyzer::WIDTH = 4;
const uint BlockAnalyzer::MIN_ROWS = 3; //arbituary const uint BlockAnalyzer::MIN_ROWS = 3; // arbituary
const uint BlockAnalyzer::MIN_COLUMNS = 32; //arbituary const uint BlockAnalyzer::MIN_COLUMNS = 32; // arbituary
const uint BlockAnalyzer::MAX_COLUMNS = 256; //must be 2**n const uint BlockAnalyzer::MAX_COLUMNS = 256; // must be 2**n
const uint BlockAnalyzer::FADE_SIZE = 90; const uint BlockAnalyzer::FADE_SIZE = 90;
const char* BlockAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Block analyzer"); const char* BlockAnalyzer::kName =
QT_TRANSLATE_NOOP("AnalyzerContainer", "Block analyzer");
BlockAnalyzer::BlockAnalyzer( QWidget *parent ) BlockAnalyzer::BlockAnalyzer(QWidget* parent)
: Analyzer::Base( parent, 9 ) : Analyzer::Base(parent, 9),
, m_columns( 0 ) //uint m_columns(0) // uint
, m_rows( 0 ) //uint ,
, m_y( 0 ) //uint m_rows(0) // uint
, m_barPixmap( 1, 1 ) //null qpixmaps cause crashes ,
, m_topBarPixmap( WIDTH, HEIGHT ) m_y(0) // uint
, m_scope( MIN_COLUMNS ) //Scope ,
, m_store( 1 << 8, 0 ) //vector<uint> m_barPixmap(1, 1) // null qpixmaps cause crashes
, m_fade_bars( FADE_SIZE ) //vector<QPixmap> ,
, m_fade_pos( 1 << 8, 50 ) //vector<uint> m_topBarPixmap(WIDTH, HEIGHT),
, m_fade_intensity( 1 << 8, 32 ) //vector<uint> m_scope(MIN_COLUMNS) // Scope
,
m_store(1 << 8, 0) // vector<uint>
,
m_fade_bars(FADE_SIZE) // vector<QPixmap>
,
m_fade_pos(1 << 8, 50) // vector<uint>
,
m_fade_intensity(1 << 8, 32) // vector<uint>
{ {
setMinimumSize( MIN_COLUMNS*(WIDTH+1) -1, MIN_ROWS*(HEIGHT+1) -1 ); //-1 is padding, no drawing takes place there setMinimumSize(MIN_COLUMNS * (WIDTH + 1) - 1,
setMaximumWidth( MAX_COLUMNS*(WIDTH+1) -1 ); MIN_ROWS * (HEIGHT + 1) -
1); //-1 is padding, no drawing takes place there
setMaximumWidth(MAX_COLUMNS * (WIDTH + 1) - 1);
// mxcl says null pixmaps cause crashes, so let's play it safe // mxcl says null pixmaps cause crashes, so let's play it safe
for ( uint i = 0; i < FADE_SIZE; ++i ) for (uint i = 0; i < FADE_SIZE; ++i) m_fade_bars[i] = QPixmap(1, 1);
m_fade_bars[i] = QPixmap( 1, 1 );
} }
BlockAnalyzer::~BlockAnalyzer() BlockAnalyzer::~BlockAnalyzer() {}
{
void BlockAnalyzer::resizeEvent(QResizeEvent* e) {
QWidget::resizeEvent(e);
m_background = QPixmap(size());
const uint oldRows = m_rows;
// all is explained in analyze()..
//+1 to counter -1 in maxSizes, trust me we need this!
m_columns = qMax(uint(double(width() + 1) / (WIDTH + 1)), MAX_COLUMNS);
m_rows = uint(double(height() + 1) / (HEIGHT + 1));
// this is the y-offset for drawing from the top of the widget
m_y = (height() - (m_rows * (HEIGHT + 1)) + 2) / 2;
m_scope.resize(m_columns);
if (m_rows != oldRows) {
m_barPixmap = QPixmap(WIDTH, m_rows * (HEIGHT + 1));
for (uint i = 0; i < FADE_SIZE; ++i)
m_fade_bars[i] = QPixmap(WIDTH, m_rows * (HEIGHT + 1));
m_yscale.resize(m_rows + 1);
const uint PRE = 1,
PRO = 1; // PRE and PRO allow us to restrict the range somewhat
for (uint z = 0; z < m_rows; ++z)
m_yscale[z] = 1 - (log10(PRE + z) / log10(PRE + m_rows + PRO));
m_yscale[m_rows] = 0;
determineStep();
paletteChange(palette());
}
drawBackground();
} }
void void BlockAnalyzer::determineStep() {
BlockAnalyzer::resizeEvent( QResizeEvent *e ) // falltime is dependent on rowcount due to our digital resolution (ie we have
{ // boxes/blocks of pixels)
QWidget::resizeEvent( e ); // I calculated the value 30 based on some trial and error
m_background = QPixmap(size()); const double fallTime = 30 * m_rows;
m_step = double(m_rows * timeout()) / fallTime;
const uint oldRows = m_rows;
//all is explained in analyze()..
//+1 to counter -1 in maxSizes, trust me we need this!
m_columns = qMax( uint(double(width()+1) / (WIDTH+1)), MAX_COLUMNS );
m_rows = uint(double(height()+1) / (HEIGHT+1));
//this is the y-offset for drawing from the top of the widget
m_y = (height() - (m_rows * (HEIGHT+1)) + 2) / 2;
m_scope.resize( m_columns );
if( m_rows != oldRows ) {
m_barPixmap = QPixmap( WIDTH, m_rows*(HEIGHT+1) );
for ( uint i = 0; i < FADE_SIZE; ++i )
m_fade_bars[i] = QPixmap( WIDTH, m_rows*(HEIGHT+1) );
m_yscale.resize( m_rows + 1 );
const uint PRE = 1, PRO = 1; //PRE and PRO allow us to restrict the range somewhat
for( uint z = 0; z < m_rows; ++z )
m_yscale[z] = 1 - (log10( PRE+z ) / log10( PRE+m_rows+PRO ));
m_yscale[m_rows] = 0;
determineStep();
paletteChange( palette() );
}
drawBackground();
} }
void void BlockAnalyzer::transform(Analyzer::Scope& s) // pure virtual
BlockAnalyzer::determineStep()
{ {
// falltime is dependent on rowcount due to our digital resolution (ie we have boxes/blocks of pixels) for (uint x = 0; x < s.size(); ++x) s[x] *= 2;
// I calculated the value 30 based on some trial and error
const double fallTime = 30 * m_rows; float* front = static_cast<float*>(&s.front());
m_step = double(m_rows * timeout()) / fallTime;
m_fht->spectrum(front);
m_fht->scale(front, 1.0 / 20);
// the second half is pretty dull, so only show it if the user has a large
// analyzer
// by setting to m_scope.size() if large we prevent interpolation of large
// analyzers, this is good!
s.resize(m_scope.size() <= MAX_COLUMNS / 2 ? MAX_COLUMNS / 2
: m_scope.size());
} }
void void BlockAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s,
BlockAnalyzer::transform( Analyzer::Scope &s ) //pure virtual bool new_frame) {
{ // y = 2 3 2 1 0 2
for( uint x = 0; x < s.size(); ++x ) // . . . . # .
s[x] *= 2; // . . . # # .
// # . # # # #
// # # # # # #
//
// visual aid for how this analyzer works.
// y represents the number of blanks
// y starts from the top and increases in units of blocks
float *front = static_cast<float*>( &s.front() ); // m_yscale looks similar to: { 0.7, 0.5, 0.25, 0.15, 0.1, 0 }
// if it contains 6 elements there are 5 rows in the analyzer
m_fht->spectrum( front ); Analyzer::interpolate(s, m_scope);
m_fht->scale( front, 1.0 / 20 );
//the second half is pretty dull, so only show it if the user has a large analyzer // Paint the background
//by setting to m_scope.size() if large we prevent interpolation of large analyzers, this is good! p.drawPixmap(0, 0, m_background);
s.resize( m_scope.size() <= MAX_COLUMNS/2 ? MAX_COLUMNS/2 : m_scope.size() );
}
void for (uint y, x = 0; x < m_scope.size(); ++x) {
BlockAnalyzer::analyze( QPainter& p, const Analyzer::Scope &s, bool new_frame) // determine y
{ for (y = 0; m_scope[x] < m_yscale[y]; ++y)
// y = 2 3 2 1 0 2 ;
// . . . . # .
// . . . # # .
// # . # # # #
// # # # # # #
//
// visual aid for how this analyzer works.
// y represents the number of blanks
// y starts from the top and increases in units of blocks
// m_yscale looks similar to: { 0.7, 0.5, 0.25, 0.15, 0.1, 0 } // this is opposite to what you'd think, higher than y
// if it contains 6 elements there are 5 rows in the analyzer // means the bar is lower than y (physically)
if ((float)y > m_store[x])
y = int(m_store[x] += m_step);
else
m_store[x] = y;
Analyzer::interpolate( s, m_scope ); // if y is lower than m_fade_pos, then the bar has exceeded the height of
// the fadeout
// Paint the background // if the fadeout is quite faded now, then display the new one
p.drawPixmap(0, 0, m_background); if (y <= m_fade_pos[x] /*|| m_fade_intensity[x] < FADE_SIZE / 3*/) {
m_fade_pos[x] = y;
for( uint y, x = 0; x < m_scope.size(); ++x ) m_fade_intensity[x] = FADE_SIZE;
{
// determine y
for( y = 0; m_scope[x] < m_yscale[y]; ++y )
;
// this is opposite to what you'd think, higher than y
// means the bar is lower than y (physically)
if( (float)y > m_store[x] )
y = int(m_store[x] += m_step);
else
m_store[x] = y;
// if y is lower than m_fade_pos, then the bar has exceeded the height of the fadeout
// if the fadeout is quite faded now, then display the new one
if( y <= m_fade_pos[x] /*|| m_fade_intensity[x] < FADE_SIZE / 3*/ ) {
m_fade_pos[x] = y;
m_fade_intensity[x] = FADE_SIZE;
}
if( m_fade_intensity[x] > 0 ) {
const uint offset = --m_fade_intensity[x];
const uint y = m_y + (m_fade_pos[x] * (HEIGHT+1));
p.drawPixmap(x*(WIDTH+1), y, m_fade_bars[offset], 0, 0, WIDTH, height() - y);
}
if( m_fade_intensity[x] == 0 )
m_fade_pos[x] = m_rows;
//REMEMBER: y is a number from 0 to m_rows, 0 means all blocks are glowing, m_rows means none are
p.drawPixmap( x*(WIDTH+1), y*(HEIGHT+1) + m_y, *bar(), 0, y*(HEIGHT+1), bar()->width(), bar()->height() );
}
for( uint x = 0; x < m_store.size(); ++x )
p.drawPixmap(x*(WIDTH+1), int(m_store[x])*(HEIGHT+1) + m_y, m_topBarPixmap );
}
static inline void
adjustToLimits( int &b, int &f, uint &amount )
{
// with a range of 0-255 and maximum adjustment of amount,
// maximise the difference between f and b
if( b < f ) {
if( b > 255 - f ) {
amount -= f;
f = 0;
} else {
amount -= (255 - f);
f = 255;
}
} }
else {
if( f > 255 - b ) { if (m_fade_intensity[x] > 0) {
amount -= f; const uint offset = --m_fade_intensity[x];
f = 0; const uint y = m_y + (m_fade_pos[x] * (HEIGHT + 1));
} else { p.drawPixmap(x * (WIDTH + 1), y, m_fade_bars[offset], 0, 0, WIDTH,
amount -= (255 - f); height() - y);
f = 255;
}
} }
if (m_fade_intensity[x] == 0) m_fade_pos[x] = m_rows;
// REMEMBER: y is a number from 0 to m_rows, 0 means all blocks are glowing,
// m_rows means none are
p.drawPixmap(x * (WIDTH + 1), y * (HEIGHT + 1) + m_y, *bar(), 0,
y * (HEIGHT + 1), bar()->width(), bar()->height());
}
for (uint x = 0; x < m_store.size(); ++x)
p.drawPixmap(x * (WIDTH + 1), int(m_store[x]) * (HEIGHT + 1) + m_y,
m_topBarPixmap);
}
static inline void adjustToLimits(int& b, int& f, uint& amount) {
// with a range of 0-255 and maximum adjustment of amount,
// maximise the difference between f and b
if (b < f) {
if (b > 255 - f) {
amount -= f;
f = 0;
} else {
amount -= (255 - f);
f = 255;
}
} else {
if (f > 255 - b) {
amount -= f;
f = 0;
} else {
amount -= (255 - f);
f = 255;
}
}
} }
/** /**
* Clever contrast function * Clever contrast function
* *
* It will try to adjust the foreground color such that it contrasts well with the background * It will try to adjust the foreground color such that it contrasts well with
*the background
* It won't modify the hue of fg unless absolutely necessary * It won't modify the hue of fg unless absolutely necessary
* @return the adjusted form of fg * @return the adjusted form of fg
*/ */
QColor QColor ensureContrast(const QColor& bg, const QColor& fg, uint _amount = 150) {
ensureContrast( const QColor &bg, const QColor &fg, uint _amount = 150 ) class OutputOnExit {
{ public:
class OutputOnExit { OutputOnExit(const QColor& color) : c(color) {}
public: ~OutputOnExit() {
OutputOnExit( const QColor &color ) : c( color ) {} int h, s, v;
~OutputOnExit() { int h,s,v; c.getHsv( &h, &s, &v ); } c.getHsv(&h, &s, &v);
private:
const QColor &c;
};
// hack so I don't have to cast everywhere
#define amount static_cast<int>(_amount)
// #define STAMP debug() << (QValueList<int>() << fh << fs << fv) << endl;
// #define STAMP1( string ) debug() << string << ": " << (QValueList<int>() << fh << fs << fv) << endl;
// #define STAMP2( string, value ) debug() << string << "=" << value << ": " << (QValueList<int>() << fh << fs << fv) << endl;
OutputOnExit allocateOnTheStack( fg );
int bh, bs, bv;
int fh, fs, fv;
bg.getHsv( &bh, &bs, &bv );
fg.getHsv( &fh, &fs, &fv );
int dv = abs( bv - fv );
// STAMP2( "DV", dv );
// value is the best measure of contrast
// if there is enough difference in value already, return fg unchanged
if( dv > amount )
return fg;
int ds = abs( bs - fs );
// STAMP2( "DS", ds );
// saturation is good enough too. But not as good. TODO adapt this a little
if( ds > amount )
return fg;
int dh = abs( bh - fh );
// STAMP2( "DH", dh );
if( dh > 120 ) {
// a third of the colour wheel automatically guarentees contrast
// but only if the values are high enough and saturations significant enough
// to allow the colours to be visible and not be shades of grey or black
// check the saturation for the two colours is sufficient that hue alone can
// provide sufficient contrast
if( ds > amount / 2 && (bs > 125 && fs > 125) )
// STAMP1( "Sufficient saturation difference, and hues are compliemtary" );
return fg;
else if( dv > amount / 2 && (bv > 125 && fv > 125) )
// STAMP1( "Sufficient value difference, and hues are compliemtary" );
return fg;
// STAMP1( "Hues are complimentary but we must modify the value or saturation of the contrasting colour" );
//but either the colours are two desaturated, or too dark
//so we need to adjust the system, although not as much
///_amount /= 2;
} }
if( fs < 50 && ds < 40 ) { private:
// low saturation on a low saturation is sad const QColor& c;
const int tmp = 50 - fs; };
fs = 50;
if( amount > tmp )
_amount -= tmp;
else
_amount = 0;
}
// test that there is available value to honor our contrast requirement // hack so I don't have to cast everywhere
if( 255 - dv < amount ) #define amount static_cast<int>(_amount)
{ // #define STAMP debug() << (QValueList<int>() << fh << fs << fv) << endl;
// we have to modify the value and saturation of fg // #define STAMP1( string ) debug() << string << ": " <<
//adjustToLimits( bv, fv, amount ); // (QValueList<int>() << fh << fs << fv) << endl;
// #define STAMP2( string, value ) debug() << string << "=" << value << ":
// " << (QValueList<int>() << fh << fs << fv) << endl;
// STAMP OutputOnExit allocateOnTheStack(fg);
// see if we need to adjust the saturation int bh, bs, bv;
if( amount > 0 ) int fh, fs, fv;
adjustToLimits( bs, fs, _amount );
// STAMP bg.getHsv(&bh, &bs, &bv);
fg.getHsv(&fh, &fs, &fv);
// see if we need to adjust the hue int dv = abs(bv - fv);
if( amount > 0 )
fh += amount; // cycles around;
// STAMP // STAMP2( "DV", dv );
return QColor::fromHsv(fh, fs, fv); // value is the best measure of contrast
} // if there is enough difference in value already, return fg unchanged
if (dv > amount) return fg;
// STAMP int ds = abs(bs - fs);
if( fv > bv && bv > amount ) // STAMP2( "DS", ds );
return QColor::fromHsv( fh, fs, bv - amount);
// STAMP // saturation is good enough too. But not as good. TODO adapt this a little
if (ds > amount) return fg;
if( fv < bv && fv > amount ) int dh = abs(bh - fh);
return QColor::fromHsv( fh, fs, fv - amount);
// STAMP // STAMP2( "DH", dh );
if( fv > bv && (255 - fv > amount) ) if (dh > 120) {
return QColor::fromHsv( fh, fs, fv + amount); // a third of the colour wheel automatically guarentees contrast
// but only if the values are high enough and saturations significant enough
// to allow the colours to be visible and not be shades of grey or black
// STAMP // check the saturation for the two colours is sufficient that hue alone can
// provide sufficient contrast
if (ds > amount / 2 && (bs > 125 && fs > 125))
// STAMP1( "Sufficient saturation difference, and hues are
// compliemtary" );
return fg;
else if (dv > amount / 2 && (bv > 125 && fv > 125))
// STAMP1( "Sufficient value difference, and hues are
// compliemtary" );
return fg;
if( fv < bv && (255 - bv > amount ) ) // STAMP1( "Hues are complimentary but we must modify the value or
return QColor::fromHsv( fh, fs, bv + amount); // saturation of the contrasting colour" );
// STAMP // but either the colours are two desaturated, or too dark
// debug() << "Something went wrong!\n"; // so we need to adjust the system, although not as much
///_amount /= 2;
}
return Qt::blue; if (fs < 50 && ds < 40) {
// low saturation on a low saturation is sad
const int tmp = 50 - fs;
fs = 50;
if (amount > tmp)
_amount -= tmp;
else
_amount = 0;
}
#undef amount // test that there is available value to honor our contrast requirement
// #undef STAMP if (255 - dv < amount) {
// we have to modify the value and saturation of fg
// adjustToLimits( bv, fv, amount );
// STAMP
// see if we need to adjust the saturation
if (amount > 0) adjustToLimits(bs, fs, _amount);
// STAMP
// see if we need to adjust the hue
if (amount > 0) fh += amount; // cycles around;
// STAMP
return QColor::fromHsv(fh, fs, fv);
}
// STAMP
if (fv > bv && bv > amount) return QColor::fromHsv(fh, fs, bv - amount);
// STAMP
if (fv < bv && fv > amount) return QColor::fromHsv(fh, fs, fv - amount);
// STAMP
if (fv > bv && (255 - fv > amount))
return QColor::fromHsv(fh, fs, fv + amount);
// STAMP
if (fv < bv && (255 - bv > amount))
return QColor::fromHsv(fh, fs, bv + amount);
// STAMP
// debug() << "Something went wrong!\n";
return Qt::blue;
#undef amount
// #undef STAMP
} }
void void BlockAnalyzer::paletteChange(const QPalette&) // virtual
BlockAnalyzer::paletteChange( const QPalette& ) //virtual
{ {
const QColor bg = palette().color(QPalette::Background); const QColor bg = palette().color(QPalette::Background);
const QColor fg = ensureContrast( bg, palette().color(QPalette::Highlight) ); const QColor fg = ensureContrast(bg, palette().color(QPalette::Highlight));
m_topBarPixmap.fill( fg ); m_topBarPixmap.fill(fg);
const double dr = 15*double(bg.red() - fg.red()) / (m_rows*16); const double dr = 15 * double(bg.red() - fg.red()) / (m_rows * 16);
const double dg = 15*double(bg.green() - fg.green()) / (m_rows*16); const double dg = 15 * double(bg.green() - fg.green()) / (m_rows * 16);
const double db = 15*double(bg.blue() - fg.blue()) / (m_rows*16); const double db = 15 * double(bg.blue() - fg.blue()) / (m_rows * 16);
const int r = fg.red(), g = fg.green(), b = fg.blue(); const int r = fg.red(), g = fg.green(), b = fg.blue();
bar()->fill( bg ); bar()->fill(bg);
QPainter p( bar() ); QPainter p(bar());
for( int y = 0; (uint)y < m_rows; ++y ) for (int y = 0; (uint)y < m_rows; ++y)
//graduate the fg color // graduate the fg color
p.fillRect( 0, y*(HEIGHT+1), WIDTH, HEIGHT, QColor( r+int(dr*y), g+int(dg*y), b+int(db*y) ) ); p.fillRect(0, y * (HEIGHT + 1), WIDTH, HEIGHT,
QColor(r + int(dr * y), g + int(dg * y), b + int(db * y)));
{ {
const QColor bg = palette().color(QPalette::Background).dark( 112 ); const QColor bg = palette().color(QPalette::Background).dark(112);
//make a complimentary fadebar colour // make a complimentary fadebar colour
//TODO dark is not always correct, dumbo! // TODO dark is not always correct, dumbo!
int h,s,v; palette().color(QPalette::Background).dark( 150 ).getHsv( &h, &s, &v ); int h, s, v;
const QColor fg( QColor::fromHsv(h + 120, s, v)); palette().color(QPalette::Background).dark(150).getHsv(&h, &s, &v);
const QColor fg(QColor::fromHsv(h + 120, s, v));
const double dr = fg.red() - bg.red(); const double dr = fg.red() - bg.red();
const double dg = fg.green() - bg.green(); const double dg = fg.green() - bg.green();
const double db = fg.blue() - bg.blue(); const double db = fg.blue() - bg.blue();
const int r = bg.red(), g = bg.green(), b = bg.blue(); const int r = bg.red(), g = bg.green(), b = bg.blue();
// Precalculate all fade-bar pixmaps // Precalculate all fade-bar pixmaps
for( uint y = 0; y < FADE_SIZE; ++y ) { for (uint y = 0; y < FADE_SIZE; ++y) {
m_fade_bars[y].fill( palette().color(QPalette::Background) ); m_fade_bars[y].fill(palette().color(QPalette::Background));
QPainter f( &m_fade_bars[y] ); QPainter f(&m_fade_bars[y]);
for( int z = 0; (uint)z < m_rows; ++z ) { for (int z = 0; (uint)z < m_rows; ++z) {
const double Y = 1.0 - (log10( FADE_SIZE - y ) / log10( FADE_SIZE )); const double Y = 1.0 - (log10(FADE_SIZE - y) / log10(FADE_SIZE));
f.fillRect( 0, z*(HEIGHT+1), WIDTH, HEIGHT, QColor( r+int(dr*Y), g+int(dg*Y), b+int(db*Y) ) ); f.fillRect(0, z * (HEIGHT + 1), WIDTH, HEIGHT,
} QColor(r + int(dr * Y), g + int(dg * Y), b + int(db * Y)));
} }
} }
}
drawBackground(); drawBackground();
} }
void void BlockAnalyzer::drawBackground() {
BlockAnalyzer::drawBackground() const QColor bg = palette().color(QPalette::Background);
{ const QColor bgdark = bg.dark(112);
const QColor bg = palette().color(QPalette::Background);
const QColor bgdark = bg.dark( 112 );
m_background.fill( bg ); m_background.fill(bg);
QPainter p( &m_background ); QPainter p(&m_background);
for( int x = 0; (uint)x < m_columns; ++x ) for (int x = 0; (uint)x < m_columns; ++x)
for( int y = 0; (uint)y < m_rows; ++y ) for (int y = 0; (uint)y < m_rows; ++y)
p.fillRect( x*(WIDTH+1), y*(HEIGHT+1) + m_y, WIDTH, HEIGHT, bgdark ); p.fillRect(x * (WIDTH + 1), y * (HEIGHT + 1) + m_y, WIDTH, HEIGHT,
bgdark);
} }

View File

@ -12,54 +12,52 @@ class QResizeEvent;
class QMouseEvent; class QMouseEvent;
class QPalette; class QPalette;
/** /**
* @author Max Howell * @author Max Howell
*/ */
class BlockAnalyzer : public Analyzer::Base class BlockAnalyzer : public Analyzer::Base {
{
Q_OBJECT Q_OBJECT
public: public:
Q_INVOKABLE BlockAnalyzer( QWidget* ); Q_INVOKABLE BlockAnalyzer(QWidget*);
~BlockAnalyzer(); ~BlockAnalyzer();
static const uint HEIGHT; static const uint HEIGHT;
static const uint WIDTH; static const uint WIDTH;
static const uint MIN_ROWS; static const uint MIN_ROWS;
static const uint MIN_COLUMNS; static const uint MIN_COLUMNS;
static const uint MAX_COLUMNS; static const uint MAX_COLUMNS;
static const uint FADE_SIZE; static const uint FADE_SIZE;
static const char* kName; static const char* kName;
protected: protected:
virtual void transform( Scope& ); virtual void transform(Scope&);
virtual void analyze( QPainter& p, const Scope&, bool new_frame); virtual void analyze(QPainter& p, const Scope&, bool new_frame);
virtual void resizeEvent( QResizeEvent* ); virtual void resizeEvent(QResizeEvent*);
virtual void paletteChange( const QPalette& ); virtual void paletteChange(const QPalette&);
void drawBackground(); void drawBackground();
void determineStep(); void determineStep();
private: private:
QPixmap* bar() { return &m_barPixmap; } QPixmap* bar() { return &m_barPixmap; }
uint m_columns, m_rows; //number of rows and columns of blocks uint m_columns, m_rows; // number of rows and columns of blocks
uint m_y; //y-offset from top of widget uint m_y; // y-offset from top of widget
QPixmap m_barPixmap; QPixmap m_barPixmap;
QPixmap m_topBarPixmap; QPixmap m_topBarPixmap;
QPixmap m_background; QPixmap m_background;
Scope m_scope; //so we don't create a vector every frame Scope m_scope; // so we don't create a vector every frame
std::vector<float> m_store; //current bar heights std::vector<float> m_store; // current bar heights
std::vector<float> m_yscale; std::vector<float> m_yscale;
//FIXME why can't I namespace these? c++ issue? // FIXME why can't I namespace these? c++ issue?
std::vector<QPixmap> m_fade_bars; std::vector<QPixmap> m_fade_bars;
std::vector<uint> m_fade_pos; std::vector<uint> m_fade_pos;
std::vector<int> m_fade_intensity; std::vector<int> m_fade_intensity;
float m_step; //rows to fall per frame float m_step; // rows to fall per frame
}; };
#endif #endif

View File

@ -5,131 +5,110 @@
#include <cmath> #include <cmath>
#include <QPainter> #include <QPainter>
const char* BoomAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Boom analyzer"); const char* BoomAnalyzer::kName =
QT_TRANSLATE_NOOP("AnalyzerContainer", "Boom analyzer");
BoomAnalyzer::BoomAnalyzer( QWidget *parent ) BoomAnalyzer::BoomAnalyzer(QWidget* parent)
: Analyzer::Base( parent, 9 ) : Analyzer::Base(parent, 9),
, K_barHeight( 1.271 )//1.471 K_barHeight(1.271) // 1.471
, F_peakSpeed( 1.103 )//1.122 ,
, F( 1.0 ) F_peakSpeed(1.103) // 1.122
, bar_height( BAND_COUNT, 0 ) ,
, peak_height( BAND_COUNT, 0 ) F(1.0),
, peak_speed( BAND_COUNT, 0.01 ) bar_height(BAND_COUNT, 0),
, barPixmap( COLUMN_WIDTH, 50 ) peak_height(BAND_COUNT, 0),
{ peak_speed(BAND_COUNT, 0.01),
barPixmap(COLUMN_WIDTH, 50) {}
void BoomAnalyzer::changeK_barHeight(int newValue) {
K_barHeight = (double)newValue / 1000;
} }
void void BoomAnalyzer::changeF_peakSpeed(int newValue) {
BoomAnalyzer::changeK_barHeight( int newValue ) F_peakSpeed = (double)newValue / 1000;
{
K_barHeight = (double)newValue / 1000;
} }
void void BoomAnalyzer::resizeEvent(QResizeEvent*) { init(); }
BoomAnalyzer::changeF_peakSpeed( int newValue )
{ void BoomAnalyzer::init() {
F_peakSpeed = (double)newValue / 1000; const uint HEIGHT = height() - 2;
const double h = 1.2 / HEIGHT;
F = double(HEIGHT) / (log10(256) * 1.1 /*<- max. amplitude*/);
barPixmap = QPixmap(COLUMN_WIDTH - 2, HEIGHT);
QPainter p(&barPixmap);
for (uint y = 0; y < HEIGHT; ++y) {
const double F = (double)y * h;
p.setPen(QColor(qMax(0, 255 - int(229.0 * F)),
qMax(0, 255 - int(229.0 * F)),
qMax(0, 255 - int(191.0 * F))));
p.drawLine(0, y, COLUMN_WIDTH - 2, y);
}
} }
void BoomAnalyzer::resizeEvent(QResizeEvent *) { void BoomAnalyzer::transform(Scope& s) {
init(); float* front = static_cast<float*>(&s.front());
m_fht->spectrum(front);
m_fht->scale(front, 1.0 / 60);
Scope scope(32, 0);
const uint xscale[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 19, 24, 29, 36,
43, 52, 63, 76, 91, 108, 129, 153, 182, 216, 255};
for (uint j, i = 0; i < 32; i++)
for (j = xscale[i]; j < xscale[i + 1]; j++)
if (s[j] > scope[i]) scope[i] = s[j];
s = scope;
} }
void void BoomAnalyzer::analyze(QPainter& p, const Scope& scope, bool new_frame) {
BoomAnalyzer::init() float h;
{ const uint MAX_HEIGHT = height() - 1;
const uint HEIGHT = height() - 2;
const double h = 1.2 / HEIGHT;
F = double(HEIGHT) / (log10( 256 ) * 1.1 /*<- max. amplitude*/); for (uint i = 0, x = 0, y; i < BAND_COUNT; ++i, x += COLUMN_WIDTH + 1) {
h = log10(scope[i] * 256.0) * F;
barPixmap = QPixmap( COLUMN_WIDTH-2, HEIGHT ); if (h > MAX_HEIGHT) h = MAX_HEIGHT;
QPainter p( &barPixmap ); if (h > bar_height[i]) {
for( uint y = 0; y < HEIGHT; ++y ) bar_height[i] = h;
{
const double F = (double)y * h;
p.setPen(QColor( if (h > peak_height[i]) {
qMax(0, 255 - int(229.0 * F)), peak_height[i] = h;
qMax(0, 255 - int(229.0 * F)), peak_speed[i] = 0.01;
qMax(0, 255 - int(191.0 * F)))); } else
p.drawLine( 0, y, COLUMN_WIDTH-2, y ); goto peak_handling;
} } else {
} if (bar_height[i] > 0.0) {
bar_height[i] -= K_barHeight; // 1.4
void if (bar_height[i] < 0.0) bar_height[i] = 0.0;
BoomAnalyzer::transform( Scope &s ) }
{
float *front = static_cast<float*>( &s.front() ); peak_handling:
m_fht->spectrum( front ); if (peak_height[i] > 0.0) {
m_fht->scale( front, 1.0 / 60 ); peak_height[i] -= peak_speed[i];
peak_speed[i] *= F_peakSpeed; // 1.12
Scope scope( 32, 0 );
if (peak_height[i] < bar_height[i]) peak_height[i] = bar_height[i];
const uint xscale[] = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,19,24,29,36,43,52,63,76,91,108,129,153,182,216,255 }; if (peak_height[i] < 0.0) peak_height[i] = 0.0;
}
for( uint j, i = 0; i < 32; i++ )
for( j = xscale[i]; j < xscale[i + 1]; j++ )
if ( s[j] > scope[i] )
scope[i] = s[j];
s = scope;
}
void
BoomAnalyzer::analyze( QPainter& p, const Scope& scope, bool new_frame)
{
float h;
const uint MAX_HEIGHT = height() - 1;
for( uint i = 0, x = 0, y; i < BAND_COUNT; ++i, x += COLUMN_WIDTH+1 )
{
h = log10( scope[i]*256.0 ) * F;
if( h > MAX_HEIGHT )
h = MAX_HEIGHT;
if( h > bar_height[i] )
{
bar_height[i] = h;
if( h > peak_height[i] )
{
peak_height[i] = h;
peak_speed[i] = 0.01;
}
else goto peak_handling;
}
else
{
if( bar_height[i] > 0.0 )
{
bar_height[i] -= K_barHeight; //1.4
if( bar_height[i] < 0.0 ) bar_height[i] = 0.0;
}
peak_handling:
if( peak_height[i] > 0.0 )
{
peak_height[i] -= peak_speed[i];
peak_speed[i] *= F_peakSpeed; //1.12
if( peak_height[i] < bar_height[i] ) peak_height[i] = bar_height[i];
if( peak_height[i] < 0.0 ) peak_height[i] = 0.0;
}
}
y = height() - uint(bar_height[i]);
p.drawPixmap(x+1, y, barPixmap, 0, y, -1, -1);
p.setPen( palette().color(QPalette::Highlight) );
if (bar_height[i] > 0)
p.drawRect( x, y, COLUMN_WIDTH - 1, height() - y - 1 );
y = height() - uint(peak_height[i]);
p.setPen( palette().color(QPalette::Base) );
p.drawLine( x, y, x+COLUMN_WIDTH-1, y );
} }
y = height() - uint(bar_height[i]);
p.drawPixmap(x + 1, y, barPixmap, 0, y, -1, -1);
p.setPen(palette().color(QPalette::Highlight));
if (bar_height[i] > 0) p.drawRect(x, y, COLUMN_WIDTH - 1, height() - y - 1);
y = height() - uint(peak_height[i]);
p.setPen(palette().color(QPalette::Base));
p.drawLine(x, y, x + COLUMN_WIDTH - 1, y);
}
} }

View File

@ -11,35 +11,34 @@
@author Max Howell @author Max Howell
*/ */
class BoomAnalyzer : public Analyzer::Base class BoomAnalyzer : public Analyzer::Base {
{ Q_OBJECT
Q_OBJECT public:
public: Q_INVOKABLE BoomAnalyzer(QWidget*);
Q_INVOKABLE BoomAnalyzer( QWidget* );
static const char* kName; static const char* kName;
virtual void init(); virtual void init();
virtual void transform( Scope &s ); virtual void transform(Scope& s);
virtual void analyze( QPainter& p, const Scope&, bool new_frame); virtual void analyze(QPainter& p, const Scope&, bool new_frame);
public slots: public slots:
void changeK_barHeight( int ); void changeK_barHeight(int);
void changeF_peakSpeed( int ); void changeF_peakSpeed(int);
protected: protected:
void resizeEvent( QResizeEvent * e); void resizeEvent(QResizeEvent* e);
static const uint COLUMN_WIDTH = 4; static const uint COLUMN_WIDTH = 4;
static const uint BAND_COUNT = 32; static const uint BAND_COUNT = 32;
double K_barHeight, F_peakSpeed, F; double K_barHeight, F_peakSpeed, F;
std::vector<float> bar_height; std::vector<float> bar_height;
std::vector<float> peak_height; std::vector<float> peak_height;
std::vector<float> peak_speed; std::vector<float> peak_speed;
QPixmap barPixmap; QPixmap barPixmap;
}; };
#endif #endif

View File

@ -23,320 +23,295 @@
#include "glanalyzer.h" #include "glanalyzer.h"
#include <kdebug.h> #include <kdebug.h>
GLAnalyzer::GLAnalyzer(QWidget* parent)
: Analyzer::Base3D(parent, 15), m_oldy(32, -10.0f), m_peaks(32) {}
GLAnalyzer::GLAnalyzer( QWidget *parent ) GLAnalyzer::~GLAnalyzer() {}
: Analyzer::Base3D(parent, 15)
, m_oldy(32, -10.0f)
, m_peaks(32)
{}
GLAnalyzer::~GLAnalyzer()
{}
// METHODS ===================================================== // METHODS =====================================================
void GLAnalyzer::analyze( const Scope &s ) void GLAnalyzer::analyze(const Scope& s) {
{ // kdDebug() << "Scope Size: " << s.size() << endl;
//kdDebug() << "Scope Size: " << s.size() << endl; /* Scope t(32);
/* Scope t(32); if (s.size() != 32)
if (s.size() != 32) {
{ Analyzer::interpolate(s, t);
Analyzer::interpolate(s, t); }
} else
else {
{ t = s;
t = s; }*/
}*/ uint offset = 0;
uint offset = 0; static float peak;
static float peak; float mfactor = 0.0;
float mfactor = 0.0; static int drawcount;
static int drawcount;
if (s.size() == 64) if (s.size() == 64) {
{ offset = 8;
offset=8; }
}
glRotatef(0.25f, 0.0f, 1.0f, 0.5f); //Rotate the scene glRotatef(0.25f, 0.0f, 1.0f, 0.5f); // Rotate the scene
drawFloor(); drawFloor();
drawcount++; drawcount++;
if (drawcount > 25) if (drawcount > 25) {
{ drawcount = 0;
drawcount = 0; peak = 0.0;
peak = 0.0; }
}
for ( uint i = 0; i < 32; i++ ) for (uint i = 0; i < 32; i++) {
{ if (s[i] > peak) {
if (s[i] > peak) peak = s[i];
{ }
peak = s[i]; }
}
}
mfactor = 20 / peak; mfactor = 20 / peak;
for ( uint i = 0; i < 32; i++ ) for (uint i = 0; i < 32; i++) {
{
//kdDebug() << "Scope item " << i << " value: " << s[i] << endl; // kdDebug() << "Scope item " << i << " value: " << s[i] << endl;
// Calculate new horizontal position (x) depending on number of samples // Calculate new horizontal position (x) depending on number of samples
x = -16.0f + i; x = -16.0f + i;
// Calculating new vertical position (y) depending on the data passed by amarok // Calculating new vertical position (y) depending on the data passed by
y = float(s[i+offset] * mfactor); //This make it kinda dynamically resize depending on the data // amarok
y = float(s[i + offset] * mfactor); // This make it kinda dynamically
// resize depending on the data
//Some basic bounds checking // Some basic bounds checking
if (y > 30) if (y > 30)
y = 30; y = 30;
else if (y < 0) else if (y < 0)
y = 0; y = 0;
if((y - m_oldy[i]) < -0.6f) // Going Down Too Much if ((y - m_oldy[i]) < -0.6f) // Going Down Too Much
{ {
y = m_oldy[i] - 0.7f; y = m_oldy[i] - 0.7f;
} }
if (y < 0.0f) if (y < 0.0f) {
{ y = 0.0f;
y = 0.0f; }
}
m_oldy[i] = y; //Save value as last value m_oldy[i] = y; // Save value as last value
//Peak Code // Peak Code
if (m_oldy[i] > m_peaks[i].level) if (m_oldy[i] > m_peaks[i].level) {
{ m_peaks[i].level = m_oldy[i];
m_peaks[i].level = m_oldy[i]; m_peaks[i].delay = 30;
m_peaks[i].delay = 30; }
}
if (m_peaks[i].delay > 0) if (m_peaks[i].delay > 0) {
{ m_peaks[i].delay--;
m_peaks[i].delay--; }
}
if (m_peaks[i].level > 1.0f) if (m_peaks[i].level > 1.0f) {
{ if (m_peaks[i].delay <= 0) {
if (m_peaks[i].delay <= 0) m_peaks[i].level -= 0.4f;
{ }
m_peaks[i].level-=0.4f; }
} // Draw the bar
} drawBar(x, y);
// Draw the bar drawPeak(x, m_peaks[i].level);
drawBar(x,y); }
drawPeak(x, m_peaks[i].level);
} updateGL();
updateGL();
} }
void GLAnalyzer::initializeGL() void GLAnalyzer::initializeGL() {
{ // Clear frame (next fading will be preferred to clearing)
// Clear frame (next fading will be preferred to clearing) glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set clear color to black
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);// Set clear color to black glClear(GL_COLOR_BUFFER_BIT);
glClear( GL_COLOR_BUFFER_BIT );
// Set the shading model // Set the shading model
glShadeModel(GL_SMOOTH); glShadeModel(GL_SMOOTH);
// Set the polygon mode to fill // Set the polygon mode to fill
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
// Enable depth testing for hidden line removal // Enable depth testing for hidden line removal
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
// Set blend parameters for 'composting alpha' // Set blend parameters for 'composting alpha'
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
} }
void GLAnalyzer::resizeGL( int w, int h ) void GLAnalyzer::resizeGL(int w, int h) {
{ glViewport(0, 0, (GLint)w, (GLint)h);
glViewport( 0, 0, (GLint)w, (GLint)h ); glMatrixMode(GL_PROJECTION);
glMatrixMode( GL_PROJECTION ); glLoadIdentity();
glLoadIdentity(); glOrtho(-16.0f, 16.0f, -10.0f, 10.0f, -50.0f, 100.0f);
glOrtho(-16.0f, 16.0f, -10.0f, 10.0f, -50.0f, 100.0f); glMatrixMode(GL_MODELVIEW);
glMatrixMode( GL_MODELVIEW ); glLoadIdentity();
glLoadIdentity();
} }
void GLAnalyzer::paintGL() void GLAnalyzer::paintGL() {
{ glMatrixMode(GL_MODELVIEW);
glMatrixMode( GL_MODELVIEW );
#if 0 #if 0
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
#else #else
glEnable( GL_DEPTH_TEST ); glEnable(GL_DEPTH_TEST);
glEnable( GL_BLEND ); glEnable(GL_BLEND);
glPushMatrix(); glPushMatrix();
glLoadIdentity(); glLoadIdentity();
glBegin( GL_TRIANGLE_STRIP ); glBegin(GL_TRIANGLE_STRIP);
glColor4f( 0.0f, 0.0f, 0.1f, 0.08f ); glColor4f(0.0f, 0.0f, 0.1f, 0.08f);
glVertex2f( 20.0f, 10.0f ); glVertex2f(20.0f, 10.0f);
glVertex2f( -20.0f, 10.0f ); glVertex2f(-20.0f, 10.0f);
glVertex2f( 20.0f, -10.0f ); glVertex2f(20.0f, -10.0f);
glVertex2f( -20.0f, -10.0f ); glVertex2f(-20.0f, -10.0f);
glEnd(); glEnd();
glPopMatrix(); glPopMatrix();
glDisable( GL_BLEND ); glDisable(GL_BLEND);
glEnable( GL_DEPTH_TEST ); glEnable(GL_DEPTH_TEST);
glClear( GL_DEPTH_BUFFER_BIT ); glClear(GL_DEPTH_BUFFER_BIT);
#endif #endif
//swapBuffers(); // swapBuffers();
glFlush();
glFlush();
} }
void GLAnalyzer::drawBar(float xPos, float height) void GLAnalyzer::drawBar(float xPos, float height) {
{ glPushMatrix();
glPushMatrix();
//Sets color to blue // Sets color to blue
//Set the colour depending on the height of the bar // Set the colour depending on the height of the bar
glColor3f((height/40) + 0.5f, (height/40) + 0.625f, 1.0f); glColor3f((height / 40) + 0.5f, (height / 40) + 0.625f, 1.0f);
glTranslatef(xPos, -10.0f, 0.0f); glTranslatef(xPos, -10.0f, 0.0f);
glScalef(1.0f, height, 3.0f); glScalef(1.0f, height, 3.0f);
drawCube(); drawCube();
//Set colour to full blue // Set colour to full blue
//glColor3f(0.0f, 0.0f, 1.0f); // glColor3f(0.0f, 0.0f, 1.0f);
//drawFrame(); // drawFrame();
glPopMatrix(); glPopMatrix();
} }
void GLAnalyzer::drawFloor() void GLAnalyzer::drawFloor() {
{ glPushMatrix();
glPushMatrix();
//Sets color to amarok blue // Sets color to amarok blue
glColor3f( 0.5f, 0.625f, 1.0f); glColor3f(0.5f, 0.625f, 1.0f);
glTranslatef(-16.0f,-11.0f, -4.0f); glTranslatef(-16.0f, -11.0f, -4.0f);
glScalef(32.0f, 1.0f, 10.0f); glScalef(32.0f, 1.0f, 10.0f);
drawCube(); drawCube();
//Set colour to full blue // Set colour to full blue
glColor3f(0.0f, 0.0f, 1.0f); glColor3f(0.0f, 0.0f, 1.0f);
drawFrame(); drawFrame();
glPopMatrix(); glPopMatrix();
} }
void GLAnalyzer::drawPeak(float xPos, float ypos) void GLAnalyzer::drawPeak(float xPos, float ypos) {
{ glPushMatrix();
glPushMatrix();
//Set the colour to red // Set the colour to red
glColor3f(1.0f, 0.0f, 0.0f); glColor3f(1.0f, 0.0f, 0.0f);
glTranslatef(xPos, ypos - 10.0f, 0.0f); glTranslatef(xPos, ypos - 10.0f, 0.0f);
glScalef(1.0f, 1.0f, 3.0f); glScalef(1.0f, 1.0f, 3.0f);
drawCube(); drawCube();
glPopMatrix(); glPopMatrix();
} }
void GLAnalyzer::drawCube() void GLAnalyzer::drawCube() {
{ glPushMatrix();
glPushMatrix(); glBegin(GL_POLYGON);
glBegin(GL_POLYGON);
//This is the top face // This is the top face
glVertex3f(0.0f, 1.0f, 0.0f); glVertex3f(0.0f, 1.0f, 0.0f);
glVertex3f(1.0f, 1.0f, 0.0f); glVertex3f(1.0f, 1.0f, 0.0f);
glVertex3f(1.0f, 1.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f);
glVertex3f(0.0f, 1.0f, 1.0f); glVertex3f(0.0f, 1.0f, 1.0f);
glVertex3f(0.0f, 1.0f, 0.0f); glVertex3f(0.0f, 1.0f, 0.0f);
//This is the front face // This is the front face
glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(1.0f, 0.0f, 0.0f); glVertex3f(1.0f, 0.0f, 0.0f);
glVertex3f(1.0f, 1.0f, 0.0f); glVertex3f(1.0f, 1.0f, 0.0f);
glVertex3f(0.0f, 1.0f, 0.0f); glVertex3f(0.0f, 1.0f, 0.0f);
glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(0.0f, 0.0f, 0.0f);
//This is the right face // This is the right face
glVertex3f(1.0f, 0.0f, 0.0f); glVertex3f(1.0f, 0.0f, 0.0f);
glVertex3f(1.0f, 0.0f, 1.0f); glVertex3f(1.0f, 0.0f, 1.0f);
glVertex3f(1.0f, 1.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f);
glVertex3f(1.0f, 1.0f, 0.0f); glVertex3f(1.0f, 1.0f, 0.0f);
glVertex3f(1.0f, 0.0f, 0.0f); glVertex3f(1.0f, 0.0f, 0.0f);
//This is the left face // This is the left face
glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(0.0f, 0.0f, 1.0f); glVertex3f(0.0f, 0.0f, 1.0f);
glVertex3f(0.0f, 1.0f, 1.0f); glVertex3f(0.0f, 1.0f, 1.0f);
glVertex3f(0.0f, 1.0f, 0.0f); glVertex3f(0.0f, 1.0f, 0.0f);
glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(0.0f, 0.0f, 0.0f);
//This is the bottom face // This is the bottom face
glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(1.0f, 0.0f, 0.0f); glVertex3f(1.0f, 0.0f, 0.0f);
glVertex3f(1.0f, 0.0f, 1.0f); glVertex3f(1.0f, 0.0f, 1.0f);
glVertex3f(0.0f, 0.0f, 1.0f); glVertex3f(0.0f, 0.0f, 1.0f);
glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(0.0f, 0.0f, 0.0f);
//This is the back face // This is the back face
glVertex3f(0.0f, 0.0f, 1.0f); glVertex3f(0.0f, 0.0f, 1.0f);
glVertex3f(1.0f, 0.0f, 1.0f); glVertex3f(1.0f, 0.0f, 1.0f);
glVertex3f(1.0f, 1.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f);
glVertex3f(0.0f, 1.0f, 1.0f); glVertex3f(0.0f, 1.0f, 1.0f);
glVertex3f(0.0f, 0.0f, 1.0f); glVertex3f(0.0f, 0.0f, 1.0f);
glEnd(); glEnd();
glPopMatrix(); glPopMatrix();
} }
void GLAnalyzer::drawFrame() void GLAnalyzer::drawFrame() {
{ glPushMatrix();
glPushMatrix(); glBegin(GL_LINES);
glBegin(GL_LINES);
// This is the top face
glVertex3f(0.0f, 1.0f, 0.0f);
glVertex3f(1.0f, 1.0f, 0.0f);
glVertex3f(1.0f, 1.0f, 1.0f);
glVertex3f(0.0f, 1.0f, 1.0f);
glVertex3f(0.0f, 1.0f, 0.0f);
//This is the top face // This is the front face
glVertex3f(0.0f, 1.0f, 0.0f); glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(1.0f, 1.0f, 0.0f); glVertex3f(1.0f, 0.0f, 0.0f);
glVertex3f(1.0f, 1.0f, 1.0f); glVertex3f(1.0f, 1.0f, 0.0f);
glVertex3f(0.0f, 1.0f, 1.0f); glVertex3f(0.0f, 1.0f, 0.0f);
glVertex3f(0.0f, 1.0f, 0.0f); glVertex3f(0.0f, 0.0f, 0.0f);
//This is the front face // This is the right face
glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(1.0f, 0.0f, 0.0f);
glVertex3f(1.0f, 0.0f, 0.0f); glVertex3f(1.0f, 0.0f, 1.0f);
glVertex3f(1.0f, 1.0f, 0.0f); glVertex3f(1.0f, 1.0f, 1.0f);
glVertex3f(0.0f, 1.0f, 0.0f); glVertex3f(1.0f, 1.0f, 0.0f);
glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(1.0f, 0.0f, 0.0f);
//This is the right face // This is the left face
glVertex3f(1.0f, 0.0f, 0.0f); glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(1.0f, 0.0f, 1.0f); glVertex3f(0.0f, 0.0f, 1.0f);
glVertex3f(1.0f, 1.0f, 1.0f); glVertex3f(0.0f, 1.0f, 1.0f);
glVertex3f(1.0f, 1.0f, 0.0f); glVertex3f(0.0f, 1.0f, 0.0f);
glVertex3f(1.0f, 0.0f, 0.0f); glVertex3f(0.0f, 0.0f, 0.0f);
//This is the left face // This is the bottom face
glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(0.0f, 0.0f, 1.0f); glVertex3f(1.0f, 0.0f, 0.0f);
glVertex3f(0.0f, 1.0f, 1.0f); glVertex3f(1.0f, 0.0f, 1.0f);
glVertex3f(0.0f, 1.0f, 0.0f); glVertex3f(0.0f, 0.0f, 1.0f);
glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(0.0f, 0.0f, 0.0f);
//This is the bottom face // This is the back face
glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(0.0f, 0.0f, 1.0f);
glVertex3f(1.0f, 0.0f, 0.0f); glVertex3f(1.0f, 0.0f, 1.0f);
glVertex3f(1.0f, 0.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f);
glVertex3f(0.0f, 0.0f, 1.0f); glVertex3f(0.0f, 1.0f, 1.0f);
glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(0.0f, 0.0f, 1.0f);
//This is the back face glEnd();
glVertex3f(0.0f, 0.0f, 1.0f); glPopMatrix();
glVertex3f(1.0f, 0.0f, 1.0f);
glVertex3f(1.0f, 1.0f, 1.0f);
glVertex3f(0.0f, 1.0f, 1.0f);
glVertex3f(0.0f, 0.0f, 1.0f);
glEnd();
glPopMatrix();
} }
#endif #endif

View File

@ -27,16 +27,13 @@
*@author piggz *@author piggz
*/ */
typedef struct typedef struct {
{
float level; float level;
uint delay; uint delay;
} } peak_tx;
peak_tx;
class GLAnalyzer : public Analyzer::Base3D class GLAnalyzer : public Analyzer::Base3D {
{ private:
private:
std::vector<float> m_oldy; std::vector<float> m_oldy;
std::vector<peak_tx> m_peaks; std::vector<peak_tx> m_peaks;
@ -47,14 +44,15 @@ private:
void drawFloor(); void drawFloor();
GLfloat x, y; GLfloat x, y;
public:
GLAnalyzer(QWidget *); public:
GLAnalyzer(QWidget*);
~GLAnalyzer(); ~GLAnalyzer();
void analyze( const Scope & ); void analyze(const Scope&);
protected: protected:
void initializeGL(); void initializeGL();
void resizeGL( int w, int h ); void resizeGL(int w, int h);
void paintGL(); void paintGL();
}; };

View File

@ -27,307 +27,267 @@
#include <qimage.h> #include <qimage.h>
#include <sys/time.h> #include <sys/time.h>
GLAnalyzer2::GLAnalyzer2(QWidget* parent) : Analyzer::Base3D(parent, 15) {
// initialize openGL context before managing GL calls
makeCurrent();
loadTexture(locate("data", "amarok/data/dot.png"), dotTexture);
loadTexture(locate("data", "amarok/data/wirl1.png"), w1Texture);
loadTexture(locate("data", "amarok/data/wirl2.png"), w2Texture);
GLAnalyzer2::GLAnalyzer2( QWidget *parent ): show.paused = true;
Analyzer::Base3D(parent, 15) show.pauseTimer = 0.0;
{ show.rotDegrees = 0.0;
//initialize openGL context before managing GL calls frame.rotDegrees = 0.0;
makeCurrent();
loadTexture( locate("data","amarok/data/dot.png"), dotTexture );
loadTexture( locate("data","amarok/data/wirl1.png"), w1Texture );
loadTexture( locate("data","amarok/data/wirl2.png"), w2Texture );
show.paused = true;
show.pauseTimer = 0.0;
show.rotDegrees = 0.0;
frame.rotDegrees = 0.0;
} }
GLAnalyzer2::~GLAnalyzer2() GLAnalyzer2::~GLAnalyzer2() {
{ freeTexture(dotTexture);
freeTexture( dotTexture ); freeTexture(w1Texture);
freeTexture( w1Texture ); freeTexture(w2Texture);
freeTexture( w2Texture );
} }
void GLAnalyzer2::initializeGL() void GLAnalyzer2::initializeGL() {
{ // Set a smooth shade model
// Set a smooth shade model glShadeModel(GL_SMOOTH);
glShadeModel(GL_SMOOTH);
// Disable depth test (all is drawn on a 2d plane) // Disable depth test (all is drawn on a 2d plane)
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
// Set blend parameters for 'composting alpha' // Set blend parameters for 'composting alpha'
glBlendFunc( GL_SRC_ALPHA, GL_ONE ); //superpose glBlendFunc(GL_SRC_ALPHA, GL_ONE); // superpose
//glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); //fade // glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); //fade
glEnable( GL_BLEND ); glEnable(GL_BLEND);
// Clear frame with a black background // Clear frame with a black background
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear( GL_COLOR_BUFFER_BIT ); glClear(GL_COLOR_BUFFER_BIT);
} }
void GLAnalyzer2::resizeGL( int w, int h ) void GLAnalyzer2::resizeGL(int w, int h) {
{ // Setup screen. We're going to manually do the perspective projection
// Setup screen. We're going to manually do the perspective projection glViewport(0, 0, (GLint)w, (GLint)h);
glViewport( 0, 0, (GLint)w, (GLint)h ); glMatrixMode(GL_PROJECTION);
glMatrixMode( GL_PROJECTION ); glLoadIdentity();
glLoadIdentity(); glOrtho(-10.0f, 10.0f, -10.0f, 10.0f, -5.0f, 5.0f);
glOrtho( -10.0f, 10.0f, -10.0f, 10.0f, -5.0f, 5.0f );
// Get the aspect ratio of the screen to draw 'cicular' particles // Get the aspect ratio of the screen to draw 'cicular' particles
float ratio = (float)w / (float)h, float ratio = (float)w / (float)h, eqPixH = 60, eqPixW = 80;
eqPixH = 60, if (ratio >= (4.0 / 3.0)) {
eqPixW = 80; unitX = 10.0 / (eqPixH * ratio);
if ( ratio >= (4.0/3.0) ) { unitY = 10.0 / eqPixH;
unitX = 10.0 / (eqPixH * ratio); } else {
unitY = 10.0 / eqPixH; unitX = 10.0 / eqPixW;
} else { unitY = 10.0 / (eqPixW / ratio);
unitX = 10.0 / eqPixW; }
unitY = 10.0 / (eqPixW / ratio);
// Get current timestamp.
timeval tv;
gettimeofday(&tv, nullptr);
show.timeStamp = (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0;
}
void GLAnalyzer2::paused() { analyze(Scope()); }
void GLAnalyzer2::analyze(const Scope& s) {
bool haveNoData = s.empty();
// if we're going into pause mode, clear timers.
if (!show.paused && haveNoData) show.pauseTimer = 0.0;
// if we have got data, interpolate it (asking myself why I'm doing it here..)
if (!(show.paused = haveNoData)) {
int bands = s.size(), lowbands = bands / 4, hibands = bands / 3,
midbands = bands - lowbands - hibands;
Q_UNUSED(midbands);
float currentEnergy = 0, currentMeanBand = 0, maxValue = 0;
for (int i = 0; i < bands; i++) {
float value = s[i];
currentEnergy += value;
currentMeanBand += (float)i * value;
if (value > maxValue) maxValue = value;
} }
frame.silence = currentEnergy < 0.001;
// Get current timestamp. if (!frame.silence) {
timeval tv; frame.meanBand = 100.0 * currentMeanBand / (currentEnergy * bands);
gettimeofday( &tv, nullptr ); currentEnergy = 100.0 * currentEnergy / (float)bands;
show.timeStamp = (double)tv.tv_sec + (double)tv.tv_usec/1000000.0; frame.dEnergy = currentEnergy - frame.energy;
} frame.energy = currentEnergy;
// printf( "%d [%f :: %f ]\t%f \n", bands, frame.energy,
void GLAnalyzer2::paused() // frame.meanBand, maxValue );
{
analyze( Scope() );
}
void GLAnalyzer2::analyze( const Scope &s )
{
bool haveNoData = s.empty();
// if we're going into pause mode, clear timers.
if ( !show.paused && haveNoData )
show.pauseTimer = 0.0;
// if we have got data, interpolate it (asking myself why I'm doing it here..)
if ( !(show.paused = haveNoData) )
{
int bands = s.size(),
lowbands = bands / 4,
hibands = bands / 3,
midbands = bands - lowbands - hibands; Q_UNUSED( midbands );
float currentEnergy = 0,
currentMeanBand = 0,
maxValue = 0;
for ( int i = 0; i < bands; i++ )
{
float value = s[i];
currentEnergy += value;
currentMeanBand += (float)i * value;
if ( value > maxValue )
maxValue = value;
}
frame.silence = currentEnergy < 0.001;
if ( !frame.silence )
{
frame.meanBand = 100.0 * currentMeanBand / (currentEnergy * bands);
currentEnergy = 100.0 * currentEnergy / (float)bands;
frame.dEnergy = currentEnergy - frame.energy;
frame.energy = currentEnergy;
// printf( "%d [%f :: %f ]\t%f \n", bands, frame.energy, frame.meanBand, maxValue );
} else
frame.energy = 0.0;
}
// update the frame
updateGL();
}
void GLAnalyzer2::paintGL()
{
// Compute the dT since the last call to paintGL and update timings
timeval tv;
gettimeofday( &tv, nullptr );
double currentTime = (double)tv.tv_sec + (double)tv.tv_usec/1000000.0;
show.dT = currentTime - show.timeStamp;
show.timeStamp = currentTime;
// Clear frame
glClear( GL_COLOR_BUFFER_BIT );
// Shitch to MODEL matrix and reset it to default
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
// Fade the previous drawings.
/* glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glBegin( GL_TRIANGLE_STRIP );
glColor4f( 0.0f, 0.0f, 0.0f, 0.2f );
glVertex2f( 10.0f, 10.0f );
glVertex2f( -10.0f, 10.0f );
glVertex2f( 10.0f, -10.0f );
glVertex2f( -10.0f, -10.0f );
glEnd();*/
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glEnable( GL_TEXTURE_2D );
float alphaN = show.paused ? 0.2 : (frame.energy / 10.0),
alphaP = show.paused ? 1.0 : (1 - frame.energy / 20.0);
if ( alphaN > 1.0 )
alphaN = 1.0;
if ( alphaP < 0.1 )
alphaP = 0.1;
glBindTexture( GL_TEXTURE_2D, w2Texture );
setTextureMatrix( show.rotDegrees, 0.707*alphaP );
glColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
glBegin( GL_TRIANGLE_STRIP );
glTexCoord2f( 1.0, 1.0 );
glVertex2f( 10.0f, 10.0f );
glTexCoord2f( 0.0, 1.0 );
glVertex2f( -10.0f, 10.0f );
glTexCoord2f( 1.0, 0.0 );
glVertex2f( 10.0f, -10.0f );
glTexCoord2f( 0.0 , 0.0 );
glVertex2f( -10.0f, -10.0f );
glEnd();
glBindTexture( GL_TEXTURE_2D, w1Texture );
setTextureMatrix( -show.rotDegrees * 2, 0.707 );
glColor4f( 1.0f, 1.0f, 1.0f, alphaN );
glBegin( GL_TRIANGLE_STRIP );
glTexCoord2f( 1.0, 1.0 );
glVertex2f( 10.0f, 10.0f );
glTexCoord2f( 0.0, 1.0 );
glVertex2f( -10.0f, 10.0f );
glTexCoord2f( 1.0, 0.0 );
glVertex2f( 10.0f, -10.0f );
glTexCoord2f( 0.0 , 0.0 );
glVertex2f( -10.0f, -10.0f );
glEnd();
setTextureMatrix( 0.0, 0.0 );
glDisable( GL_TEXTURE_2D );
glBlendFunc( GL_SRC_ALPHA, GL_ONE );
// Here begins the real draw loop
// some updates to the show
show.rotDegrees += 40.0 * show.dT;
frame.rotDegrees += 80.0 * show.dT;
// handle the 'pause' status
if ( show.paused )
{
if ( show.pauseTimer > 0.5 )
{
if ( show.pauseTimer > 0.6 )
show.pauseTimer -= 0.6;
drawFullDot( 0.0f, 0.4f, 0.8f, 1.0f );
drawFullDot( 0.0f, 0.4f, 0.8f, 1.0f );
}
show.pauseTimer += show.dT;
return;
}
if ( dotTexture ) {
glEnable( GL_TEXTURE_2D );
glBindTexture( GL_TEXTURE_2D, dotTexture );
} else } else
glDisable( GL_TEXTURE_2D ); frame.energy = 0.0;
}
glLoadIdentity(); // update the frame
// glRotatef( -frame.rotDegrees, 0,0,1 ); updateGL();
glBegin( GL_QUADS ); }
// Particle * particle = particleList.first();
// for (; particle; particle = particleList.next()) void GLAnalyzer2::paintGL() {
{ // Compute the dT since the last call to paintGL and update timings
glColor4f( 0.0f, 1.0f, 0.0f, 1.0f ); timeval tv;
drawDot( 0, 0, kMax(10.0,(10.0 * frame.energy)) ); gettimeofday(&tv, nullptr);
glColor4f( 1.0f, 0.0f, 0.0f, 1.0f ); double currentTime = (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0;
drawDot( 6, 0, kMax(10.0, (5.0 * frame.energy)) ); show.dT = currentTime - show.timeStamp;
glColor4f( 0.0f, 0.4f, 1.0f, 1.0f ); show.timeStamp = currentTime;
drawDot( -6, 0, kMax(10.0, (5.0 * frame.energy)) );
// Clear frame
glClear(GL_COLOR_BUFFER_BIT);
// Shitch to MODEL matrix and reset it to default
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// Fade the previous drawings.
/* glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glBegin( GL_TRIANGLE_STRIP );
glColor4f( 0.0f, 0.0f, 0.0f, 0.2f );
glVertex2f( 10.0f, 10.0f );
glVertex2f( -10.0f, 10.0f );
glVertex2f( 10.0f, -10.0f );
glVertex2f( -10.0f, -10.0f );
glEnd();*/
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_TEXTURE_2D);
float alphaN = show.paused ? 0.2 : (frame.energy / 10.0),
alphaP = show.paused ? 1.0 : (1 - frame.energy / 20.0);
if (alphaN > 1.0) alphaN = 1.0;
if (alphaP < 0.1) alphaP = 0.1;
glBindTexture(GL_TEXTURE_2D, w2Texture);
setTextureMatrix(show.rotDegrees, 0.707 * alphaP);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(1.0, 1.0);
glVertex2f(10.0f, 10.0f);
glTexCoord2f(0.0, 1.0);
glVertex2f(-10.0f, 10.0f);
glTexCoord2f(1.0, 0.0);
glVertex2f(10.0f, -10.0f);
glTexCoord2f(0.0, 0.0);
glVertex2f(-10.0f, -10.0f);
glEnd();
glBindTexture(GL_TEXTURE_2D, w1Texture);
setTextureMatrix(-show.rotDegrees * 2, 0.707);
glColor4f(1.0f, 1.0f, 1.0f, alphaN);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(1.0, 1.0);
glVertex2f(10.0f, 10.0f);
glTexCoord2f(0.0, 1.0);
glVertex2f(-10.0f, 10.0f);
glTexCoord2f(1.0, 0.0);
glVertex2f(10.0f, -10.0f);
glTexCoord2f(0.0, 0.0);
glVertex2f(-10.0f, -10.0f);
glEnd();
setTextureMatrix(0.0, 0.0);
glDisable(GL_TEXTURE_2D);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
// Here begins the real draw loop
// some updates to the show
show.rotDegrees += 40.0 * show.dT;
frame.rotDegrees += 80.0 * show.dT;
// handle the 'pause' status
if (show.paused) {
if (show.pauseTimer > 0.5) {
if (show.pauseTimer > 0.6) show.pauseTimer -= 0.6;
drawFullDot(0.0f, 0.4f, 0.8f, 1.0f);
drawFullDot(0.0f, 0.4f, 0.8f, 1.0f);
} }
glEnd(); show.pauseTimer += show.dT;
return;
}
if (dotTexture) {
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, dotTexture);
} else
glDisable(GL_TEXTURE_2D);
glLoadIdentity();
// glRotatef( -frame.rotDegrees, 0,0,1 );
glBegin(GL_QUADS);
// Particle * particle = particleList.first();
// for (; particle; particle = particleList.next())
{
glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
drawDot(0, 0, kMax(10.0, (10.0 * frame.energy)));
glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
drawDot(6, 0, kMax(10.0, (5.0 * frame.energy)));
glColor4f(0.0f, 0.4f, 1.0f, 1.0f);
drawDot(-6, 0, kMax(10.0, (5.0 * frame.energy)));
}
glEnd();
} }
void GLAnalyzer2::drawDot( float x, float y, float size ) void GLAnalyzer2::drawDot(float x, float y, float size) {
{ float sizeX = size * unitX, sizeY = size * unitY, pLeft = x - sizeX,
float sizeX = size * unitX, pTop = y + sizeY, pRight = x + sizeX, pBottom = y - sizeY;
sizeY = size * unitY, glTexCoord2f(0, 0); // Bottom Left
pLeft = x - sizeX, glVertex2f(pLeft, pBottom);
pTop = y + sizeY, glTexCoord2f(0, 1); // Top Left
pRight = x + sizeX, glVertex2f(pLeft, pTop);
pBottom = y - sizeY; glTexCoord2f(1, 1); // Top Right
glTexCoord2f( 0, 0 ); // Bottom Left glVertex2f(pRight, pTop);
glVertex2f( pLeft, pBottom ); glTexCoord2f(1, 0); // Bottom Right
glTexCoord2f( 0, 1 ); // Top Left glVertex2f(pRight, pBottom);
glVertex2f( pLeft, pTop );
glTexCoord2f( 1, 1 ); // Top Right
glVertex2f( pRight, pTop );
glTexCoord2f( 1, 0 ); // Bottom Right
glVertex2f( pRight, pBottom );
} }
void GLAnalyzer2::drawFullDot( float r, float g, float b, float a ) void GLAnalyzer2::drawFullDot(float r, float g, float b, float a) {
{ glBindTexture(GL_TEXTURE_2D, dotTexture);
glBindTexture( GL_TEXTURE_2D, dotTexture ); glEnable(GL_TEXTURE_2D);
glEnable( GL_TEXTURE_2D ); glColor4f(r, g, b, a);
glColor4f( r, g, b, a ); glBegin(GL_TRIANGLE_STRIP);
glBegin( GL_TRIANGLE_STRIP ); glTexCoord2f(1.0, 1.0);
glTexCoord2f( 1.0, 1.0 ); glVertex2f(10.0f, 10.0f);
glVertex2f( 10.0f, 10.0f ); glTexCoord2f(0.0, 1.0);
glTexCoord2f( 0.0, 1.0 ); glVertex2f(-10.0f, 10.0f);
glVertex2f( -10.0f, 10.0f ); glTexCoord2f(1.0, 0.0);
glTexCoord2f( 1.0, 0.0 ); glVertex2f(10.0f, -10.0f);
glVertex2f( 10.0f, -10.0f ); glTexCoord2f(0.0, 0.0);
glTexCoord2f( 0.0 , 0.0 ); glVertex2f(-10.0f, -10.0f);
glVertex2f( -10.0f, -10.0f ); glEnd();
glEnd(); glDisable(GL_TEXTURE_2D);
glDisable( GL_TEXTURE_2D );
} }
void GLAnalyzer2::setTextureMatrix(float rot, float scale) {
void GLAnalyzer2::setTextureMatrix( float rot, float scale ) glMatrixMode(GL_TEXTURE);
{ glLoadIdentity();
glMatrixMode( GL_TEXTURE); if (rot != 0.0 || scale != 0.0) {
glLoadIdentity(); glTranslatef(0.5f, 0.5f, 0.0f);
if ( rot != 0.0 || scale != 0.0 ) glRotatef(rot, 0.0f, 0.0f, 1.0f);
{ glScalef(scale, scale, 1.0f);
glTranslatef( 0.5f, 0.5f, 0.0f ); glTranslatef(-0.5f, -0.5f, 0.0f);
glRotatef( rot, 0.0f, 0.0f, 1.0f ); }
glScalef( scale, scale, 1.0f ); glMatrixMode(GL_MODELVIEW);
glTranslatef( -0.5f, -0.5f, 0.0f );
}
glMatrixMode( GL_MODELVIEW );
} }
bool GLAnalyzer2::loadTexture( QString fileName, GLuint& textureID ) bool GLAnalyzer2::loadTexture(QString fileName, GLuint& textureID) {
{ // reset texture ID to the default EMPTY value
//reset texture ID to the default EMPTY value textureID = 0;
textureID = 0;
//load image // load image
QImage tmp; QImage tmp;
if ( !tmp.load( fileName ) ) if (!tmp.load(fileName)) return false;
return false;
//convert it to suitable format (flipped RGBA) // convert it to suitable format (flipped RGBA)
QImage texture = QGLWidget::convertToGLFormat( tmp ); QImage texture = QGLWidget::convertToGLFormat(tmp);
if ( texture.isNull() ) if (texture.isNull()) return false;
return false;
//get texture number and bind loaded image to that texture // get texture number and bind loaded image to that texture
glGenTextures( 1, &textureID ); glGenTextures(1, &textureID);
glBindTexture( GL_TEXTURE_2D, textureID ); glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D( GL_TEXTURE_2D, 0, 4, texture.width(), texture.height(), glTexImage2D(GL_TEXTURE_2D, 0, 4, texture.width(), texture.height(), 0,
0, GL_RGBA, GL_UNSIGNED_BYTE, texture.bits() ); GL_RGBA, GL_UNSIGNED_BYTE, texture.bits());
return true; return true;
} }
void GLAnalyzer2::freeTexture(GLuint& textureID) {
void GLAnalyzer2::freeTexture( GLuint & textureID ) if (textureID > 0) glDeleteTextures(1, &textureID);
{ textureID = 0;
if ( textureID > 0 )
glDeleteTextures( 1, &textureID );
textureID = 0;
} }
#endif #endif

View File

@ -25,48 +25,46 @@
#include <qstring.h> #include <qstring.h>
#include <qptrlist.h> #include <qptrlist.h>
class GLAnalyzer2 : public Analyzer::Base3D {
public:
GLAnalyzer2(QWidget*);
~GLAnalyzer2();
void analyze(const Scope&);
void paused();
class GLAnalyzer2 : public Analyzer::Base3D protected:
{ void initializeGL();
public: void resizeGL(int w, int h);
GLAnalyzer2(QWidget *); void paintGL();
~GLAnalyzer2();
void analyze( const Scope & );
void paused();
protected: private:
void initializeGL(); struct ShowProperties {
void resizeGL( int w, int h );
void paintGL();
private:
struct ShowProperties {
bool paused; bool paused;
double timeStamp; double timeStamp;
double dT; double dT;
double pauseTimer; double pauseTimer;
float rotDegrees; float rotDegrees;
} show; } show;
struct FrameProperties { struct FrameProperties {
float energy; float energy;
float dEnergy; float dEnergy;
float meanBand; float meanBand;
float rotDegrees; float rotDegrees;
bool silence; bool silence;
} frame; } frame;
GLuint dotTexture; GLuint dotTexture;
GLuint w1Texture; GLuint w1Texture;
GLuint w2Texture; GLuint w2Texture;
float unitX, unitY; float unitX, unitY;
void drawDot( float x, float y, float size ); void drawDot(float x, float y, float size);
void drawFullDot( float r, float g, float b, float a ); void drawFullDot(float r, float g, float b, float a);
void setTextureMatrix( float rot, float scale ); void setTextureMatrix(float rot, float scale);
bool loadTexture(QString file, GLuint& textureID); bool loadTexture(QString file, GLuint& textureID);
void freeTexture(GLuint& textureID); void freeTexture(GLuint& textureID);
}; };
#endif #endif

View File

@ -28,453 +28,400 @@
#include <sys/time.h> #include <sys/time.h>
#ifndef HAVE_FABSF #ifndef HAVE_FABSF
inline float fabsf(float f) inline float fabsf(float f) { return f < 0.f ? -f : f; }
{
return f < 0.f ? -f : f;
}
#endif #endif
class Ball {
class Ball public:
{ Ball()
public: : x(drand48() - drand48()),
Ball() : x( drand48() - drand48() ), y( 1 - 2.0 * drand48() ), y(1 - 2.0 * drand48()),
z( drand48() ), vx( 0.0 ), vy( 0.0 ), vz( 0.0 ), z(drand48()),
mass( 0.01 + drand48()/10.0 ) vx(0.0),
vy(0.0),
vz(0.0),
mass(0.01 + drand48() / 10.0)
//,color( (float[3]) { 0.0, drand48()*0.5, 0.7 + drand48() * 0.3 } ) //,color( (float[3]) { 0.0, drand48()*0.5, 0.7 + drand48() * 0.3 } )
{ {
//this is because GCC < 3.3 can't compile the above line, we aren't sure why though // this is because GCC < 3.3 can't compile the above line, we aren't sure
color[0] = 0.0; color[1] = drand48()*0.5; color[2] = 0.7 + drand48() * 0.3; // why though
}; color[0] = 0.0;
color[1] = drand48() * 0.5;
color[2] = 0.7 + drand48() * 0.3;
};
float x, y, z, vx, vy, vz, mass; float x, y, z, vx, vy, vz, mass;
float color[3]; float color[3];
void updatePhysics( float dT ) void updatePhysics(float dT) {
{ x += vx * dT; // position
x += vx * dT; // position y += vy * dT; // position
y += vy * dT; // position z += vz * dT; // position
z += vz * dT; // position if (y < -0.8) vy = fabsf(vy);
if ( y < -0.8 ) vy = fabsf( vy ); if (y > 0.8) vy = -fabsf(vy);
if ( y > 0.8 ) vy = -fabsf( vy ); if (z < 0.1) vz = fabsf(vz);
if ( z < 0.1 ) vz = fabsf( vz ); if (z > 0.9) vz = -fabsf(vz);
if ( z > 0.9 ) vz = -fabsf( vz ); vx += ((x > 0) ? 4.94 : -4.94) * dT; // G-force
vx += (( x > 0 ) ? 4.94 : -4.94) * dT; // G-force vx *= (1 - 2.9 * dT); // air friction
vx *= (1 - 2.9 * dT); // air friction vy *= (1 - 2.9 * dT); // air friction
vy *= (1 - 2.9 * dT); // air friction vz *= (1 - 2.9 * dT); // air friction
vz *= (1 - 2.9 * dT); // air friction }
}
}; };
class Paddle class Paddle {
{ public:
public: Paddle(float xPos)
Paddle( float xPos ) : onLeft( xPos < 0 ), mass( 1.0 ), : onLeft(xPos < 0), mass(1.0), X(xPos), x(xPos), vx(0.0) {};
X( xPos ), x( xPos ), vx( 0.0 ) {};
void updatePhysics( float dT ) void updatePhysics(float dT) {
{ x += vx * dT; // posision
x += vx * dT; // posision vx += (1300 * (X - x) / mass) * dT; // elasticity
vx += (1300 * (X - x) / mass) * dT; // elasticity vx *= (1 - 4.0 * dT); // air friction
vx *= (1 - 4.0 * dT); // air friction }
void renderGL() {
glBegin(GL_TRIANGLE_STRIP);
glColor3f(0.0f, 0.1f, 0.3f);
glVertex3f(x, -1.0f, 0.0);
glVertex3f(x, 1.0f, 0.0);
glColor3f(0.1f, 0.2f, 0.6f);
glVertex3f(x, -1.0f, 1.0);
glVertex3f(x, 1.0f, 1.0);
glEnd();
}
void bounce(Ball* ball) {
if (onLeft && ball->x < x) {
ball->vx = vx * mass / (mass + ball->mass) + fabsf(ball->vx);
ball->vy = (drand48() - drand48()) * 1.8;
ball->vz = (drand48() - drand48()) * 0.9;
ball->x = x;
} else if (!onLeft && ball->x > x) {
ball->vx = vx * mass / (mass + ball->mass) - fabsf(ball->vx);
ball->vy = (drand48() - drand48()) * 1.8;
ball->vz = (drand48() - drand48()) * 0.9;
ball->x = x;
} }
}
void renderGL() void impulse(float strength) {
{ if ((onLeft && strength > vx) || (!onLeft && strength < vx)) vx += strength;
glBegin( GL_TRIANGLE_STRIP ); }
glColor3f( 0.0f, 0.1f, 0.3f );
glVertex3f( x, -1.0f, 0.0 );
glVertex3f( x, 1.0f, 0.0 );
glColor3f( 0.1f, 0.2f, 0.6f );
glVertex3f( x, -1.0f, 1.0 );
glVertex3f( x, 1.0f, 1.0 );
glEnd();
}
void bounce( Ball * ball ) private:
{ bool onLeft;
if ( onLeft && ball->x < x ) float mass, X, x, vx;
{
ball->vx = vx * mass / (mass + ball->mass) + fabsf( ball->vx );
ball->vy = (drand48() - drand48()) * 1.8;
ball->vz = (drand48() - drand48()) * 0.9;
ball->x = x;
}
else if ( !onLeft && ball->x > x )
{
ball->vx = vx * mass / (mass + ball->mass) - fabsf( ball->vx );
ball->vy = (drand48() - drand48()) * 1.8;
ball->vz = (drand48() - drand48()) * 0.9;
ball->x = x;
}
}
void impulse( float strength )
{
if ( (onLeft && strength > vx) || (!onLeft && strength < vx) )
vx += strength;
}
private:
bool onLeft;
float mass, X, x, vx;
}; };
GLAnalyzer3::GLAnalyzer3(QWidget* parent) : Analyzer::Base3D(parent, 15) {
// initialize openGL context before managing GL calls
makeCurrent();
loadTexture(locate("data", "amarok/data/ball.png"), ballTexture);
loadTexture(locate("data", "amarok/data/grid.png"), gridTexture);
GLAnalyzer3::GLAnalyzer3( QWidget *parent ): balls.setAutoDelete(true);
Analyzer::Base3D(parent, 15) leftPaddle = new Paddle(-1.0);
{ rightPaddle = new Paddle(1.0);
//initialize openGL context before managing GL calls for (int i = 0; i < NUMBER_OF_BALLS; i++) balls.append(new Ball());
makeCurrent();
loadTexture( locate("data","amarok/data/ball.png"), ballTexture );
loadTexture( locate("data","amarok/data/grid.png"), gridTexture );
balls.setAutoDelete( true ); show.colorK = 0.0;
leftPaddle = new Paddle( -1.0 ); show.gridScrollK = 0.0;
rightPaddle = new Paddle( 1.0 ); show.gridEnergyK = 0.0;
for ( int i = 0; i < NUMBER_OF_BALLS; i++ ) show.camRot = 0.0;
balls.append( new Ball() ); show.camRoll = 0.0;
show.peakEnergy = 1.0;
show.colorK = 0.0; frame.silence = true;
show.gridScrollK = 0.0; frame.energy = 0.0;
show.gridEnergyK = 0.0; frame.dEnergy = 0.0;
show.camRot = 0.0;
show.camRoll = 0.0;
show.peakEnergy = 1.0;
frame.silence = true;
frame.energy = 0.0;
frame.dEnergy = 0.0;
} }
GLAnalyzer3::~GLAnalyzer3() GLAnalyzer3::~GLAnalyzer3() {
{ freeTexture(ballTexture);
freeTexture( ballTexture ); freeTexture(gridTexture);
freeTexture( gridTexture ); delete leftPaddle;
delete leftPaddle; delete rightPaddle;
delete rightPaddle; balls.clear();
balls.clear();
} }
void GLAnalyzer3::initializeGL() void GLAnalyzer3::initializeGL() {
{ // Set a smooth shade model
// Set a smooth shade model glShadeModel(GL_SMOOTH);
glShadeModel(GL_SMOOTH);
// Disable depth test (all is drawn 'z-sorted') // Disable depth test (all is drawn 'z-sorted')
glDisable( GL_DEPTH_TEST ); glDisable(GL_DEPTH_TEST);
// Set blending function (Alpha addition) // Set blending function (Alpha addition)
glBlendFunc( GL_SRC_ALPHA, GL_ONE ); glBlendFunc(GL_SRC_ALPHA, GL_ONE);
// Clear frame with a black background // Clear frame with a black background
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
} }
void GLAnalyzer3::resizeGL( int w, int h ) void GLAnalyzer3::resizeGL(int w, int h) {
{ // Setup screen. We're going to manually do the perspective projection
// Setup screen. We're going to manually do the perspective projection glViewport(0, 0, (GLint)w, (GLint)h);
glViewport( 0, 0, (GLint)w, (GLint)h ); glMatrixMode(GL_PROJECTION);
glMatrixMode( GL_PROJECTION ); glLoadIdentity();
glLoadIdentity(); glFrustum(-0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 4.5f);
glFrustum( -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 4.5f );
// Get the aspect ratio of the screen to draw 'circular' particles // Get the aspect ratio of the screen to draw 'circular' particles
float ratio = (float)w / (float)h; float ratio = (float)w / (float)h;
if ( ratio >= 1.0 ) { if (ratio >= 1.0) {
unitX = 0.34 / ratio; unitX = 0.34 / ratio;
unitY = 0.34; unitY = 0.34;
} else { } else {
unitX = 0.34; unitX = 0.34;
unitY = 0.34 * ratio; unitY = 0.34 * ratio;
} }
// Get current timestamp. // Get current timestamp.
timeval tv; timeval tv;
gettimeofday( &tv, nullptr ); gettimeofday(&tv, nullptr);
show.timeStamp = (double)tv.tv_sec + (double)tv.tv_usec/1000000.0; show.timeStamp = (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0;
} }
void GLAnalyzer3::paused() void GLAnalyzer3::paused() { analyze(Scope()); }
{
analyze( Scope() );
}
void GLAnalyzer3::analyze( const Scope &s ) void GLAnalyzer3::analyze(const Scope& s) {
{ // compute the dTime since the last call
// compute the dTime since the last call timeval tv;
timeval tv; gettimeofday(&tv, nullptr);
gettimeofday( &tv, nullptr ); double currentTime = (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0;
double currentTime = (double)tv.tv_sec + (double)tv.tv_usec/1000000.0; show.dT = currentTime - show.timeStamp;
show.dT = currentTime - show.timeStamp; show.timeStamp = currentTime;
show.timeStamp = currentTime;
// compute energy integrating frame's spectrum // compute energy integrating frame's spectrum
if ( !s.empty() ) if (!s.empty()) {
{
int bands = s.size(); int bands = s.size();
float currentEnergy = 0, float currentEnergy = 0, maxValue = 0;
maxValue = 0;
// integrate spectrum -> energy // integrate spectrum -> energy
for ( int i = 0; i < bands; i++ ) for (int i = 0; i < bands; i++) {
{ float value = s[i];
float value = s[i]; currentEnergy += value;
currentEnergy += value; if (value > maxValue) maxValue = value;
if ( value > maxValue )
maxValue = value;
} }
currentEnergy *= 100.0 / (float)bands; currentEnergy *= 100.0 / (float)bands;
// emulate a peak detector: currentEnergy -> peakEnergy (3tau = 30 seconds) // emulate a peak detector: currentEnergy -> peakEnergy (3tau = 30 seconds)
show.peakEnergy = 1.0 + ( show.peakEnergy - 1.0 ) * exp( - show.dT / 10.0 ); show.peakEnergy = 1.0 + (show.peakEnergy - 1.0) * exp(-show.dT / 10.0);
if ( currentEnergy > show.peakEnergy ) if (currentEnergy > show.peakEnergy) show.peakEnergy = currentEnergy;
show.peakEnergy = currentEnergy;
// check for silence // check for silence
frame.silence = currentEnergy < 0.001; frame.silence = currentEnergy < 0.001;
// normalize frame energy against peak energy and compute frame stats // normalize frame energy against peak energy and compute frame stats
currentEnergy /= show.peakEnergy; currentEnergy /= show.peakEnergy;
frame.dEnergy = currentEnergy - frame.energy; frame.dEnergy = currentEnergy - frame.energy;
frame.energy = currentEnergy; frame.energy = currentEnergy;
} else } else
frame.silence = true; frame.silence = true;
// update the frame // update the frame
updateGL(); updateGL();
} }
void GLAnalyzer3::paintGL() void GLAnalyzer3::paintGL() {
{ // limit max dT to 0.05 and update color and scroll constants
// limit max dT to 0.05 and update color and scroll constants if (show.dT > 0.05) show.dT = 0.05;
if ( show.dT > 0.05 ) show.colorK += show.dT * 0.4;
show.dT = 0.05; if (show.colorK > 3.0) show.colorK -= 3.0;
show.colorK += show.dT * 0.4; show.gridScrollK += 0.2 * show.peakEnergy * show.dT;
if ( show.colorK > 3.0 )
show.colorK -= 3.0;
show.gridScrollK += 0.2 * show.peakEnergy * show.dT;
// Switch to MODEL matrix and clear screen // Switch to MODEL matrix and clear screen
glMatrixMode( GL_MODELVIEW ); glMatrixMode(GL_MODELVIEW);
glLoadIdentity(); glLoadIdentity();
glClear( GL_COLOR_BUFFER_BIT ); glClear(GL_COLOR_BUFFER_BIT);
// Draw scrolling grid // Draw scrolling grid
if ( (show.gridEnergyK > 0.05) || (!frame.silence && frame.dEnergy < -0.3) ) if ((show.gridEnergyK > 0.05) || (!frame.silence && frame.dEnergy < -0.3)) {
{ show.gridEnergyK *= exp(-show.dT / 0.1);
show.gridEnergyK *= exp( -show.dT / 0.1 ); if (-frame.dEnergy > show.gridEnergyK)
if ( -frame.dEnergy > show.gridEnergyK ) show.gridEnergyK = -frame.dEnergy * 2.0;
show.gridEnergyK = -frame.dEnergy*2.0; float gridColor[4] = {0.0, 1.0, 0.6, show.gridEnergyK};
float gridColor[4] = { 0.0, 1.0, 0.6, show.gridEnergyK }; drawScrollGrid(show.gridScrollK, gridColor);
drawScrollGrid( show.gridScrollK, gridColor ); }
}
// Roll camera up/down handling the beat // Roll camera up/down handling the beat
show.camRot += show.camRoll * show.dT; // posision show.camRot += show.camRoll * show.dT; // posision
show.camRoll -= 400 * show.camRot * show.dT; // elasticity show.camRoll -= 400 * show.camRot * show.dT; // elasticity
show.camRoll *= (1 - 2.0 * show.dT); // friction show.camRoll *= (1 - 2.0 * show.dT); // friction
if ( !frame.silence && frame.dEnergy > 0.4 ) if (!frame.silence && frame.dEnergy > 0.4)
show.camRoll += show.peakEnergy*2.0; show.camRoll += show.peakEnergy * 2.0;
glRotatef( show.camRoll / 2.0, 1,0,0 ); glRotatef(show.camRoll / 2.0, 1, 0, 0);
// Translate the drawing plane // Translate the drawing plane
glTranslatef( 0.0f, 0.0f, -1.8f ); glTranslatef(0.0f, 0.0f, -1.8f);
// Draw upper/lower planes and paddles // Draw upper/lower planes and paddles
drawHFace( -1.0 ); drawHFace(-1.0);
drawHFace( 1.0 ); drawHFace(1.0);
leftPaddle->renderGL(); leftPaddle->renderGL();
rightPaddle->renderGL(); rightPaddle->renderGL();
// Draw Balls // Draw Balls
if ( ballTexture ) { if (ballTexture) {
glEnable( GL_TEXTURE_2D ); glEnable(GL_TEXTURE_2D);
glBindTexture( GL_TEXTURE_2D, ballTexture ); glBindTexture(GL_TEXTURE_2D, ballTexture);
} else } else
glDisable( GL_TEXTURE_2D ); glDisable(GL_TEXTURE_2D);
glEnable( GL_BLEND ); glEnable(GL_BLEND);
Ball * ball = balls.first(); Ball* ball = balls.first();
for ( ; ball; ball = balls.next() ) for (; ball; ball = balls.next()) {
{ float color[3], angle = show.colorK;
float color[3],
angle = show.colorK;
// Rotate the color based on 'angle' value [0,3) // Rotate the color based on 'angle' value [0,3)
if ( angle < 1.0 ) if (angle < 1.0) {
{ color[0] = ball->color[0] * (1 - angle) + ball->color[1] * angle;
color[ 0 ] = ball->color[ 0 ] * (1 - angle) + ball->color[ 1 ] * angle; color[1] = ball->color[1] * (1 - angle) + ball->color[2] * angle;
color[ 1 ] = ball->color[ 1 ] * (1 - angle) + ball->color[ 2 ] * angle; color[2] = ball->color[2] * (1 - angle) + ball->color[0] * angle;
color[ 2 ] = ball->color[ 2 ] * (1 - angle) + ball->color[ 0 ] * angle; } else if (angle < 2.0) {
} angle -= 1.0;
else if ( angle < 2.0 ) color[0] = ball->color[1] * (1 - angle) + ball->color[2] * angle;
{ color[1] = ball->color[2] * (1 - angle) + ball->color[0] * angle;
angle -= 1.0; color[2] = ball->color[0] * (1 - angle) + ball->color[1] * angle;
color[ 0 ] = ball->color[ 1 ] * (1 - angle) + ball->color[ 2 ] * angle; } else {
color[ 1 ] = ball->color[ 2 ] * (1 - angle) + ball->color[ 0 ] * angle; angle -= 2.0;
color[ 2 ] = ball->color[ 0 ] * (1 - angle) + ball->color[ 1 ] * angle; color[0] = ball->color[2] * (1 - angle) + ball->color[0] * angle;
} color[1] = ball->color[0] * (1 - angle) + ball->color[1] * angle;
else color[2] = ball->color[1] * (1 - angle) + ball->color[2] * angle;
{
angle -= 2.0;
color[ 0 ] = ball->color[ 2 ] * (1 - angle) + ball->color[ 0 ] * angle;
color[ 1 ] = ball->color[ 0 ] * (1 - angle) + ball->color[ 1 ] * angle;
color[ 2 ] = ball->color[ 1 ] * (1 - angle) + ball->color[ 2 ] * angle;
} }
// Draw the dot and update its physics also checking at bounces // Draw the dot and update its physics also checking at bounces
glColor3fv( color ); glColor3fv(color);
drawDot3s( ball->x, ball->y, ball->z, 1.0 ); drawDot3s(ball->x, ball->y, ball->z, 1.0);
ball->updatePhysics( show.dT ); ball->updatePhysics(show.dT);
if ( ball->x < 0 ) if (ball->x < 0)
leftPaddle->bounce( ball ); leftPaddle->bounce(ball);
else else
rightPaddle->bounce( ball ); rightPaddle->bounce(ball);
} }
glDisable( GL_BLEND ); glDisable(GL_BLEND);
glDisable( GL_TEXTURE_2D ); glDisable(GL_TEXTURE_2D);
// Update physics of paddles // Update physics of paddles
leftPaddle->updatePhysics( show.dT ); leftPaddle->updatePhysics(show.dT);
rightPaddle->updatePhysics( show.dT ); rightPaddle->updatePhysics(show.dT);
if ( !frame.silence ) if (!frame.silence) {
{ leftPaddle->impulse(frame.energy * 3.0 + frame.dEnergy * 6.0);
leftPaddle->impulse( frame.energy*3.0 + frame.dEnergy*6.0 ); rightPaddle->impulse(-frame.energy * 3.0 - frame.dEnergy * 6.0);
rightPaddle->impulse( -frame.energy*3.0 - frame.dEnergy*6.0 ); }
}
} }
void GLAnalyzer3::drawDot3s( float x, float y, float z, float size ) void GLAnalyzer3::drawDot3s(float x, float y, float z, float size) {
{ // Circular XY dot drawing functions
// Circular XY dot drawing functions float sizeX = size * unitX, sizeY = size * unitY, pXm = x - sizeX,
float sizeX = size * unitX, pXM = x + sizeX, pYm = y - sizeY, pYM = y + sizeY;
sizeY = size * unitY, // Draw the Dot
pXm = x - sizeX, glBegin(GL_QUADS);
pXM = x + sizeX, glTexCoord2f(0, 0); // Bottom Left
pYm = y - sizeY, glVertex3f(pXm, pYm, z);
pYM = y + sizeY; glTexCoord2f(0, 1); // Top Left
// Draw the Dot glVertex3f(pXm, pYM, z);
glBegin( GL_QUADS ); glTexCoord2f(1, 1); // Top Right
glTexCoord2f( 0, 0 ); // Bottom Left glVertex3f(pXM, pYM, z);
glVertex3f( pXm, pYm, z ); glTexCoord2f(1, 0); // Bottom Right
glTexCoord2f( 0, 1 ); // Top Left glVertex3f(pXM, pYm, z);
glVertex3f( pXm, pYM, z ); glEnd();
glTexCoord2f( 1, 1 ); // Top Right
glVertex3f( pXM, pYM, z );
glTexCoord2f( 1, 0 ); // Bottom Right
glVertex3f( pXM, pYm, z );
glEnd();
// Shadow XZ drawing functions // Shadow XZ drawing functions
float sizeZ = size / 10.0, float sizeZ = size / 10.0, pZm = z - sizeZ, pZM = z + sizeZ, currentColor[4];
pZm = z - sizeZ, glGetFloatv(GL_CURRENT_COLOR, currentColor);
pZM = z + sizeZ, float alpha = currentColor[3], topSide = (y + 1) / 4,
currentColor[4]; bottomSide = (1 - y) / 4;
glGetFloatv( GL_CURRENT_COLOR, currentColor ); // Draw the top shadow
float alpha = currentColor[3], currentColor[3] = topSide * topSide * alpha;
topSide = (y + 1) / 4, glColor4fv(currentColor);
bottomSide = (1 - y) / 4; glBegin(GL_QUADS);
// Draw the top shadow glTexCoord2f(0, 0); // Bottom Left
currentColor[3] = topSide * topSide * alpha; glVertex3f(pXm, 1, pZm);
glColor4fv( currentColor ); glTexCoord2f(0, 1); // Top Left
glBegin( GL_QUADS ); glVertex3f(pXm, 1, pZM);
glTexCoord2f( 0, 0 ); // Bottom Left glTexCoord2f(1, 1); // Top Right
glVertex3f( pXm, 1, pZm ); glVertex3f(pXM, 1, pZM);
glTexCoord2f( 0, 1 ); // Top Left glTexCoord2f(1, 0); // Bottom Right
glVertex3f( pXm, 1, pZM ); glVertex3f(pXM, 1, pZm);
glTexCoord2f( 1, 1 ); // Top Right glEnd();
glVertex3f( pXM, 1, pZM ); // Draw the bottom shadow
glTexCoord2f( 1, 0 ); // Bottom Right currentColor[3] = bottomSide * bottomSide * alpha;
glVertex3f( pXM, 1, pZm ); glColor4fv(currentColor);
glEnd(); glBegin(GL_QUADS);
// Draw the bottom shadow glTexCoord2f(0, 0); // Bottom Left
currentColor[3] = bottomSide * bottomSide * alpha; glVertex3f(pXm, -1, pZm);
glColor4fv( currentColor ); glTexCoord2f(0, 1); // Top Left
glBegin( GL_QUADS ); glVertex3f(pXm, -1, pZM);
glTexCoord2f( 0, 0 ); // Bottom Left glTexCoord2f(1, 1); // Top Right
glVertex3f( pXm, -1, pZm ); glVertex3f(pXM, -1, pZM);
glTexCoord2f( 0, 1 ); // Top Left glTexCoord2f(1, 0); // Bottom Right
glVertex3f( pXm, -1, pZM ); glVertex3f(pXM, -1, pZm);
glTexCoord2f( 1, 1 ); // Top Right glEnd();
glVertex3f( pXM, -1, pZM );
glTexCoord2f( 1, 0 ); // Bottom Right
glVertex3f( pXM, -1, pZm );
glEnd();
} }
void GLAnalyzer3::drawHFace( float y ) void GLAnalyzer3::drawHFace(float y) {
{ glBegin(GL_TRIANGLE_STRIP);
glBegin( GL_TRIANGLE_STRIP ); glColor3f(0.0f, 0.1f, 0.2f);
glColor3f( 0.0f, 0.1f, 0.2f ); glVertex3f(-1.0f, y, 0.0);
glVertex3f( -1.0f, y, 0.0 ); glVertex3f(1.0f, y, 0.0);
glVertex3f( 1.0f, y, 0.0 ); glColor3f(0.1f, 0.6f, 0.5f);
glColor3f( 0.1f, 0.6f, 0.5f ); glVertex3f(-1.0f, y, 2.0);
glVertex3f( -1.0f, y, 2.0 ); glVertex3f(1.0f, y, 2.0);
glVertex3f( 1.0f, y, 2.0 ); glEnd();
glEnd();
} }
void GLAnalyzer3::drawScrollGrid( float scroll, float color[4] ) void GLAnalyzer3::drawScrollGrid(float scroll, float color[4]) {
{ if (!gridTexture) return;
if ( !gridTexture ) glMatrixMode(GL_TEXTURE);
return; glLoadIdentity();
glMatrixMode( GL_TEXTURE ); glTranslatef(0.0, -scroll, 0.0);
glLoadIdentity(); glMatrixMode(GL_MODELVIEW);
glTranslatef( 0.0, -scroll, 0.0 ); float backColor[4] = {1.0, 1.0, 1.0, 0.0};
glMatrixMode( GL_MODELVIEW ); for (int i = 0; i < 3; i++) backColor[i] = color[i];
float backColor[4] = { 1.0, 1.0, 1.0, 0.0 }; glEnable(GL_TEXTURE_2D);
for ( int i = 0; i < 3; i++ ) glBindTexture(GL_TEXTURE_2D, gridTexture);
backColor[ i ] = color[ i ]; glEnable(GL_BLEND);
glEnable( GL_TEXTURE_2D ); glBegin(GL_TRIANGLE_STRIP);
glBindTexture( GL_TEXTURE_2D, gridTexture ); glColor4fv(color); // top face
glEnable( GL_BLEND ); glTexCoord2f(0.0f, 1.0f);
glBegin( GL_TRIANGLE_STRIP ); glVertex3f(-1.0f, 1.0f, -1.0f);
glColor4fv( color ); // top face glTexCoord2f(1.0f, 1.0f);
glTexCoord2f( 0.0f, 1.0f ); glVertex3f(1.0f, 1.0f, -1.0f);
glVertex3f( -1.0f, 1.0f, -1.0f ); glColor4fv(backColor); // central points
glTexCoord2f( 1.0f, 1.0f ); glTexCoord2f(0.0f, 0.0f);
glVertex3f( 1.0f, 1.0f, -1.0f ); glVertex3f(-1.0f, 0.0f, -3.0f);
glColor4fv( backColor ); // central points glTexCoord2f(1.0f, 0.0f);
glTexCoord2f( 0.0f, 0.0f ); glVertex3f(1.0f, 0.0f, -3.0f);
glVertex3f( -1.0f, 0.0f, -3.0f ); glColor4fv(color); // bottom face
glTexCoord2f( 1.0f, 0.0f ); glTexCoord2f(0.0f, 1.0f);
glVertex3f( 1.0f, 0.0f, -3.0f ); glVertex3f(-1.0f, -1.0f, -1.0f);
glColor4fv( color ); // bottom face glTexCoord2f(1.0f, 1.0f);
glTexCoord2f( 0.0f, 1.0f ); glVertex3f(1.0f, -1.0f, -1.0f);
glVertex3f( -1.0f, -1.0f, -1.0f ); glEnd();
glTexCoord2f( 1.0f, 1.0f ); glDisable(GL_BLEND);
glVertex3f( 1.0f, -1.0f, -1.0f ); glDisable(GL_TEXTURE_2D);
glEnd(); glMatrixMode(GL_TEXTURE);
glDisable( GL_BLEND ); glLoadIdentity();
glDisable( GL_TEXTURE_2D ); glMatrixMode(GL_MODELVIEW);
glMatrixMode( GL_TEXTURE );
glLoadIdentity();
glMatrixMode( GL_MODELVIEW );
} }
bool GLAnalyzer3::loadTexture( QString fileName, GLuint& textureID ) bool GLAnalyzer3::loadTexture(QString fileName, GLuint& textureID) {
{ // reset texture ID to the default EMPTY value
//reset texture ID to the default EMPTY value textureID = 0;
textureID = 0;
//load image // load image
QImage tmp; QImage tmp;
if ( !tmp.load( fileName ) ) if (!tmp.load(fileName)) return false;
return false;
//convert it to suitable format (flipped RGBA) // convert it to suitable format (flipped RGBA)
QImage texture = QGLWidget::convertToGLFormat( tmp ); QImage texture = QGLWidget::convertToGLFormat(tmp);
if ( texture.isNull() ) if (texture.isNull()) return false;
return false;
//get texture number and bind loaded image to that texture // get texture number and bind loaded image to that texture
glGenTextures( 1, &textureID ); glGenTextures(1, &textureID);
glBindTexture( GL_TEXTURE_2D, textureID ); glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D( GL_TEXTURE_2D, 0, 4, texture.width(), texture.height(), glTexImage2D(GL_TEXTURE_2D, 0, 4, texture.width(), texture.height(), 0,
0, GL_RGBA, GL_UNSIGNED_BYTE, texture.bits() ); GL_RGBA, GL_UNSIGNED_BYTE, texture.bits());
return true; return true;
} }
void GLAnalyzer3::freeTexture( GLuint& textureID ) void GLAnalyzer3::freeTexture(GLuint& textureID) {
{ if (textureID > 0) glDeleteTextures(1, &textureID);
if ( textureID > 0 ) textureID = 0;
glDeleteTextures( 1, &textureID );
textureID = 0;
} }
#endif #endif

View File

@ -1,9 +1,9 @@
/*************************************************************************** /***************************************************************************
glanalyzer3.h - description glanalyzer3.h - description
------------------- -------------------
begin : Feb 16 2004 begin : Feb 16 2004
copyright : (C) 2004 by Enrico Ros copyright : (C) 2004 by Enrico Ros
email : eros.kde@email.it email : eros.kde@email.it
***************************************************************************/ ***************************************************************************/
/*************************************************************************** /***************************************************************************
@ -29,51 +29,50 @@ class QWidget;
class Ball; class Ball;
class Paddle; class Paddle;
class GLAnalyzer3 : public Analyzer::Base3D class GLAnalyzer3 : public Analyzer::Base3D {
{ public:
public: GLAnalyzer3(QWidget*);
GLAnalyzer3(QWidget *); ~GLAnalyzer3();
~GLAnalyzer3(); void analyze(const Scope&);
void analyze( const Scope & ); void paused();
void paused();
protected: protected:
void initializeGL(); void initializeGL();
void resizeGL( int w, int h ); void resizeGL(int w, int h);
void paintGL(); void paintGL();
private: private:
struct ShowProperties { struct ShowProperties {
double timeStamp; double timeStamp;
double dT; double dT;
float colorK; float colorK;
float gridScrollK; float gridScrollK;
float gridEnergyK; float gridEnergyK;
float camRot; float camRot;
float camRoll; float camRoll;
float peakEnergy; float peakEnergy;
} show; } show;
struct FrameProperties { struct FrameProperties {
bool silence; bool silence;
float energy; float energy;
float dEnergy; float dEnergy;
} frame; } frame;
static const int NUMBER_OF_BALLS = 16;
QPtrList<Ball> balls;
Paddle * leftPaddle, * rightPaddle;
float unitX, unitY;
GLuint ballTexture;
GLuint gridTexture;
void drawDot3s( float x, float y, float z, float size ); static const int NUMBER_OF_BALLS = 16;
void drawHFace( float y );
void drawScrollGrid( float scroll, float color[4] );
bool loadTexture(QString file, GLuint& textureID); QPtrList<Ball> balls;
void freeTexture(GLuint& textureID); Paddle* leftPaddle, *rightPaddle;
float unitX, unitY;
GLuint ballTexture;
GLuint gridTexture;
void drawDot3s(float x, float y, float z, float size);
void drawHFace(float y);
void drawScrollGrid(float scroll, float color[4]);
bool loadTexture(QString file, GLuint& textureID);
void freeTexture(GLuint& textureID);
}; };
#endif #endif

View File

@ -26,34 +26,31 @@
const char* NyanCatAnalyzer::kName = "Nyanalyzer cat"; const char* NyanCatAnalyzer::kName = "Nyanalyzer cat";
const float NyanCatAnalyzer::kPixelScale = 0.02f; const float NyanCatAnalyzer::kPixelScale = 0.02f;
NyanCatAnalyzer::NyanCatAnalyzer(QWidget* parent) NyanCatAnalyzer::NyanCatAnalyzer(QWidget* parent)
: Analyzer::Base(parent, 9), : Analyzer::Base(parent, 9),
cat_(":/nyancat.png"), cat_(":/nyancat.png"),
timer_id_(startTimer(kFrameIntervalMs)), timer_id_(startTimer(kFrameIntervalMs)),
frame_(0), frame_(0),
current_buffer_(0), current_buffer_(0),
available_rainbow_width_(0), available_rainbow_width_(0),
px_per_frame_(0), px_per_frame_(0),
x_offset_(0), x_offset_(0),
background_brush_(QColor(0x0f, 0x43, 0x73)) background_brush_(QColor(0x0f, 0x43, 0x73)) {
{
memset(history_, 0, sizeof(history_)); memset(history_, 0, sizeof(history_));
for (int i=0 ; i<kRainbowBands ; ++i) { for (int i = 0; i < kRainbowBands; ++i) {
colors_[i] = QPen(QColor::fromHsv(i * 255 / kRainbowBands, 255, 255), colors_[i] = QPen(QColor::fromHsv(i * 255 / kRainbowBands, 255, 255),
kCatHeight/kRainbowBands, kCatHeight / kRainbowBands, Qt::SolidLine, Qt::FlatCap,
Qt::SolidLine, Qt::FlatCap, Qt::RoundJoin); Qt::RoundJoin);
// pow constants computed so that // pow constants computed so that
// | band_scale(0) | ~= .5 and | band_scale(5) | ~= 32 // | band_scale(0) | ~= .5 and | band_scale(5) | ~= 32
band_scale_[i] = -std::cos(M_PI * i / (kRainbowBands-1)) * 0.5 * std::pow(2.3, i); band_scale_[i] =
-std::cos(M_PI * i / (kRainbowBands - 1)) * 0.5 * std::pow(2.3, i);
} }
} }
void NyanCatAnalyzer::transform(Scope& s) { void NyanCatAnalyzer::transform(Scope& s) { m_fht->spectrum(&s.front()); }
m_fht->spectrum(&s.front());
}
void NyanCatAnalyzer::timerEvent(QTimerEvent* e) { void NyanCatAnalyzer::timerEvent(QTimerEvent* e) {
if (e->timerId() == timer_id_) { if (e->timerId() == timer_id_) {
@ -70,18 +67,19 @@ void NyanCatAnalyzer::resizeEvent(QResizeEvent* e) {
buffer_[1] = QPixmap(); buffer_[1] = QPixmap();
available_rainbow_width_ = width() - kCatWidth + kRainbowOverlap; available_rainbow_width_ = width() - kCatWidth + kRainbowOverlap;
px_per_frame_ = float(available_rainbow_width_) / (kHistorySize-1) + 1; px_per_frame_ = float(available_rainbow_width_) / (kHistorySize - 1) + 1;
x_offset_ = px_per_frame_ * (kHistorySize-1) - available_rainbow_width_; x_offset_ = px_per_frame_ * (kHistorySize - 1) - available_rainbow_width_;
} }
void NyanCatAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s, bool new_frame) { void NyanCatAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s,
bool new_frame) {
// Discard the second half of the transform // Discard the second half of the transform
const int scope_size = s.size() / 2; const int scope_size = s.size() / 2;
if ((new_frame && is_playing_) || if ((new_frame && is_playing_) ||
(buffer_[0].isNull() && buffer_[1].isNull())) { (buffer_[0].isNull() && buffer_[1].isNull())) {
// Transform the music into rainbows! // Transform the music into rainbows!
for (int band=0 ; band<kRainbowBands ; ++band) { for (int band = 0; band < kRainbowBands; ++band) {
float* band_start = history_ + band * kHistorySize; float* band_start = history_ + band * kHistorySize;
// Move the history of each band across by 1 frame. // Move the history of each band across by 1 frame.
@ -93,13 +91,13 @@ void NyanCatAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s, bool new_fr
// but for now it's a series of separate square filters. // but for now it's a series of separate square filters.
const int samples_per_band = scope_size / kRainbowBands; const int samples_per_band = scope_size / kRainbowBands;
int sample = 0; int sample = 0;
for (int band=0 ; band<kRainbowBands ; ++band) { for (int band = 0; band < kRainbowBands; ++band) {
float accumulator = 0.0; float accumulator = 0.0;
for (int i=0 ; i<samples_per_band ; ++i) { for (int i = 0; i < samples_per_band; ++i) {
accumulator += s[sample++]; accumulator += s[sample++];
} }
history_[(band+1) * kHistorySize - 1] = accumulator * band_scale_[band]; history_[(band + 1) * kHistorySize - 1] = accumulator * band_scale_[band];
} }
// Create polylines for the rainbows. // Create polylines for the rainbows.
@ -107,22 +105,23 @@ void NyanCatAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s, bool new_fr
QPointF* dest = polyline; QPointF* dest = polyline;
float* source = history_; float* source = history_;
const float top_of_cat = float(height())/2 - float(kCatHeight)/2; const float top_of_cat = float(height()) / 2 - float(kCatHeight) / 2;
for (int band=0 ; band<kRainbowBands ; ++band) { for (int band = 0; band < kRainbowBands; ++band) {
// Calculate the Y position of this band. // Calculate the Y position of this band.
const float y = float(kCatHeight) / (kRainbowBands + 1) * (band + 0.5) + top_of_cat; const float y =
float(kCatHeight) / (kRainbowBands + 1) * (band + 0.5) + top_of_cat;
// Add each point in the line. // Add each point in the line.
for (int x=0 ; x<kHistorySize; ++x) { for (int x = 0; x < kHistorySize; ++x) {
*dest = QPointF(px_per_frame_ * x, y + *source * kPixelScale); *dest = QPointF(px_per_frame_ * x, y + *source * kPixelScale);
++ dest; ++dest;
++ source; ++source;
} }
} }
// Do we have to draw the whole rainbow into the buffer? // Do we have to draw the whole rainbow into the buffer?
if (buffer_[0].isNull()) { if (buffer_[0].isNull()) {
for (int i=0 ; i<2 ; ++i) { for (int i = 0; i < 2; ++i) {
buffer_[i] = QPixmap(QSize(width() + x_offset_, height())); buffer_[i] = QPixmap(QSize(width() + x_offset_, height()));
buffer_[i].fill(background_brush_.color()); buffer_[i].fill(background_brush_.color());
} }
@ -130,29 +129,34 @@ void NyanCatAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s, bool new_fr
QPainter buffer_painter(&buffer_[0]); QPainter buffer_painter(&buffer_[0]);
buffer_painter.setRenderHint(QPainter::Antialiasing); buffer_painter.setRenderHint(QPainter::Antialiasing);
for (int band=kRainbowBands-1 ; band>=0 ; --band) { for (int band = kRainbowBands - 1; band >= 0; --band) {
buffer_painter.setPen(colors_[band]); buffer_painter.setPen(colors_[band]);
buffer_painter.drawPolyline(&polyline[band*kHistorySize], kHistorySize); buffer_painter.drawPolyline(&polyline[band * kHistorySize],
buffer_painter.drawPolyline(&polyline[band*kHistorySize], kHistorySize); kHistorySize);
buffer_painter.drawPolyline(&polyline[band * kHistorySize],
kHistorySize);
} }
} else { } else {
const int last_buffer = current_buffer_; const int last_buffer = current_buffer_;
current_buffer_ = (current_buffer_ + 1) % 2; current_buffer_ = (current_buffer_ + 1) % 2;
// We can just shuffle the buffer along a bit and draw the new frame's data. // We can just shuffle the buffer along a bit and draw the new frame's
// data.
QPainter buffer_painter(&buffer_[current_buffer_]); QPainter buffer_painter(&buffer_[current_buffer_]);
buffer_painter.setRenderHint(QPainter::Antialiasing); buffer_painter.setRenderHint(QPainter::Antialiasing);
buffer_painter.drawPixmap(0, 0, buffer_[last_buffer], buffer_painter.drawPixmap(
px_per_frame_, 0, 0, 0, buffer_[last_buffer], px_per_frame_, 0,
x_offset_ + available_rainbow_width_ - px_per_frame_, 0); x_offset_ + available_rainbow_width_ - px_per_frame_, 0);
buffer_painter.fillRect(x_offset_ + available_rainbow_width_ - px_per_frame_, 0, buffer_painter.fillRect(
kCatWidth - kRainbowOverlap + px_per_frame_, height(), x_offset_ + available_rainbow_width_ - px_per_frame_, 0,
background_brush_); kCatWidth - kRainbowOverlap + px_per_frame_, height(),
background_brush_);
for (int band=kRainbowBands-1 ; band>=0 ; --band) { for (int band = kRainbowBands - 1; band >= 0; --band) {
buffer_painter.setPen(colors_[band]); buffer_painter.setPen(colors_[band]);
buffer_painter.drawPolyline(&polyline[(band+1)*kHistorySize - 3], 3); buffer_painter.drawPolyline(&polyline[(band + 1) * kHistorySize - 3],
3);
} }
} }
} }

View File

@ -25,19 +25,19 @@
class NyanCatAnalyzer : public Analyzer::Base { class NyanCatAnalyzer : public Analyzer::Base {
Q_OBJECT Q_OBJECT
public: public:
Q_INVOKABLE NyanCatAnalyzer(QWidget* parent); Q_INVOKABLE NyanCatAnalyzer(QWidget* parent);
static const char* kName; static const char* kName;
protected: protected:
void transform(Scope&); void transform(Scope&);
void analyze(QPainter& p, const Analyzer::Scope&, bool new_frame); void analyze(QPainter& p, const Analyzer::Scope&, bool new_frame);
void timerEvent(QTimerEvent* e); void timerEvent(QTimerEvent* e);
void resizeEvent(QResizeEvent* e); void resizeEvent(QResizeEvent* e);
private: private:
static const int kCatHeight = 21; static const int kCatHeight = 21;
static const int kCatWidth = 34; static const int kCatWidth = 34;
static const int kCatFrameCount = 6; static const int kCatFrameCount = 6;
@ -50,7 +50,7 @@ private:
static const int kFrameIntervalMs = 150; static const int kFrameIntervalMs = 150;
private: private:
inline QRect CatSourceRect() const { inline QRect CatSourceRect() const {
return QRect(0, kCatHeight * frame_, kCatWidth, kCatHeight); return QRect(0, kCatHeight * frame_, kCatWidth, kCatHeight);
} }
@ -60,8 +60,8 @@ private:
} }
inline QRect CatDestRect() const { inline QRect CatDestRect() const {
return QRect(width() - kCatWidth, (height() - kCatHeight) / 2, return QRect(width() - kCatWidth, (height() - kCatHeight) / 2, kCatWidth,
kCatWidth, kCatHeight); kCatHeight);
} }
inline QRect SleepingCatDestRect() const { inline QRect SleepingCatDestRect() const {
@ -69,7 +69,7 @@ private:
kCatWidth, kSleepingCatHeight); kCatWidth, kSleepingCatHeight);
} }
private: private:
// "constants" that get initialised in the constructor // "constants" that get initialised in the constructor
float band_scale_[kRainbowBands]; float band_scale_[kRainbowBands];
QPen colors_[kRainbowBands]; QPen colors_[kRainbowBands];
@ -102,4 +102,4 @@ private:
QBrush background_brush_; QBrush background_brush_;
}; };
#endif // NYANCATANALYZER_H #endif // NYANCATANALYZER_H

View File

@ -15,76 +15,61 @@
#include <QPainter> #include <QPainter>
const char* Sonogram::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Sonogram"); const char* Sonogram::kName =
QT_TRANSLATE_NOOP("AnalyzerContainer", "Sonogram");
Sonogram::Sonogram(QWidget *parent) : Sonogram::Sonogram(QWidget* parent) : Analyzer::Base(parent, 9) {}
Analyzer::Base(parent, 9)
{
}
Sonogram::~Sonogram() {}
Sonogram::~Sonogram() void Sonogram::resizeEvent(QResizeEvent* e) {
{ QWidget::resizeEvent(e);
}
// only for gcc < 4.0
void Sonogram::resizeEvent(QResizeEvent *e) #if !(__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 0))
{ resizeForBands(height() < 128 ? 128 : height());
QWidget::resizeEvent(e);
//only for gcc < 4.0
#if !( __GNUC__ > 4 || ( __GNUC__ == 4 && __GNUC_MINOR__ >= 0 ) )
resizeForBands(height() < 128 ? 128 : height());
#endif #endif
canvas_ = QPixmap(size()); canvas_ = QPixmap(size());
canvas_.fill(palette().color(QPalette::Background)); canvas_.fill(palette().color(QPalette::Background));
} }
void Sonogram::analyze(QPainter& p, const Scope& s, bool new_frame) {
int x = width() - 1;
QColor c;
void Sonogram::analyze(QPainter& p, const Scope &s, bool new_frame) QPainter canvas_painter(&canvas_);
{ canvas_painter.drawPixmap(0, 0, canvas_, 1, 0, x, -1);
int x = width() - 1;
QColor c;
QPainter canvas_painter(&canvas_); Scope::const_iterator it = s.begin(), end = s.end();
canvas_painter.drawPixmap(0, 0, canvas_, 1, 0, x, -1); for (int y = height() - 1; y;) {
if (it >= end || *it < .005)
c = palette().color(QPalette::Background);
else if (*it < .05)
c.setHsv(95, 255, 255 - int(*it * 4000.0));
else if (*it < 1.0)
c.setHsv(95 - int(*it * 90.0), 255, 255);
else
c = Qt::red;
Scope::const_iterator it = s.begin(), end = s.end(); canvas_painter.setPen(c);
for (int y = height() - 1; y;) { canvas_painter.drawPoint(x, y--);
if (it >= end || *it < .005)
c = palette().color(QPalette::Background);
else if (*it < .05)
c.setHsv(95, 255, 255 - int(*it * 4000.0));
else if (*it < 1.0)
c.setHsv(95 - int(*it * 90.0), 255, 255);
else
c = Qt::red;
canvas_painter.setPen(c); if (it < end) ++it;
canvas_painter.drawPoint(x, y--); }
if (it < end) canvas_painter.end();
++it;
}
canvas_painter.end(); p.drawPixmap(0, 0, canvas_);
p.drawPixmap(0, 0, canvas_);
} }
void Sonogram::transform(Scope& scope) {
void Sonogram::transform(Scope &scope) float* front = static_cast<float*>(&scope.front());
{ m_fht->power2(front);
float *front = static_cast<float*>(&scope.front()); m_fht->scale(front, 1.0 / 256);
m_fht->power2(front); scope.resize(m_fht->size() / 2);
m_fht->scale(front, 1.0 / 256);
scope.resize( m_fht->size() / 2 );
} }
void Sonogram::demo(QPainter& p) {
void Sonogram::demo(QPainter& p) analyze(p, Scope(m_fht->size(), 0), new_frame_);
{
analyze(p, Scope(m_fht->size(), 0), new_frame_);
} }

View File

@ -20,22 +20,21 @@
@author Melchior FRANZ @author Melchior FRANZ
*/ */
class Sonogram : public Analyzer::Base class Sonogram : public Analyzer::Base {
{
Q_OBJECT Q_OBJECT
public: public:
Q_INVOKABLE Sonogram(QWidget*); Q_INVOKABLE Sonogram(QWidget*);
~Sonogram(); ~Sonogram();
static const char* kName; static const char* kName;
protected: protected:
void analyze(QPainter& p, const Scope&, bool new_frame); void analyze(QPainter& p, const Scope&, bool new_frame);
void transform(Scope&); void transform(Scope&);
void demo(QPainter& p); void demo(QPainter& p);
void resizeEvent(QResizeEvent*); void resizeEvent(QResizeEvent*);
QPixmap canvas_; QPixmap canvas_;
}; };
#endif #endif

View File

@ -12,66 +12,57 @@
#include "turbine.h" #include "turbine.h"
const char* TurbineAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Turbine"); const char* TurbineAnalyzer::kName =
QT_TRANSLATE_NOOP("AnalyzerContainer", "Turbine");
void TurbineAnalyzer::analyze( QPainter& p, const Scope &scope, bool new_frame) void TurbineAnalyzer::analyze(QPainter& p, const Scope& scope, bool new_frame) {
{ float h;
float h; const uint hd2 = height() / 2;
const uint hd2 = height() / 2; const uint MAX_HEIGHT = hd2 - 1;
const uint MAX_HEIGHT = hd2 - 1;
for( uint i = 0, x = 0, y; i < BAND_COUNT; ++i, x += COLUMN_WIDTH+1 ) for (uint i = 0, x = 0, y; i < BAND_COUNT; ++i, x += COLUMN_WIDTH + 1) {
{ h = log10(scope[i] * 256.0) * F * 0.5;
h = log10( scope[i]*256.0 ) * F * 0.5;
if( h > MAX_HEIGHT ) if (h > MAX_HEIGHT) h = MAX_HEIGHT;
h = MAX_HEIGHT;
if( h > bar_height[i] ) if (h > bar_height[i]) {
{ bar_height[i] = h;
bar_height[i] = h;
if( h > peak_height[i] ) if (h > peak_height[i]) {
{ peak_height[i] = h;
peak_height[i] = h; peak_speed[i] = 0.01;
peak_speed[i] = 0.01; } else
} goto peak_handling;
else goto peak_handling; } else {
} if (bar_height[i] > 0.0) {
else bar_height[i] -= K_barHeight; // 1.4
{ if (bar_height[i] < 0.0) bar_height[i] = 0.0;
if( bar_height[i] > 0.0 ) }
{
bar_height[i] -= K_barHeight; //1.4
if( bar_height[i] < 0.0 ) bar_height[i] = 0.0;
}
peak_handling: peak_handling:
if( peak_height[i] > 0.0 ) if (peak_height[i] > 0.0) {
{ peak_height[i] -= peak_speed[i];
peak_height[i] -= peak_speed[i]; peak_speed[i] *= F_peakSpeed; // 1.12
peak_speed[i] *= F_peakSpeed; //1.12
if( peak_height[i] < bar_height[i] ) peak_height[i] = bar_height[i]; if (peak_height[i] < bar_height[i]) peak_height[i] = bar_height[i];
if( peak_height[i] < 0.0 ) peak_height[i] = 0.0; if (peak_height[i] < 0.0) peak_height[i] = 0.0;
} }
}
y = hd2 - uint(bar_height[i]);
p.drawPixmap(x+1, y, barPixmap, 0, y, -1, -1);
p.drawPixmap(x+1, hd2, barPixmap, 0, int(bar_height[i]), -1, -1);
p.setPen( palette().color(QPalette::Highlight) );
if (bar_height[i] > 0)
p.drawRect( x, y, COLUMN_WIDTH-1, (int)bar_height[i]*2 -1 );
const uint x2 = x+COLUMN_WIDTH-1;
p.setPen( palette().color(QPalette::Base) );
y = hd2 - uint(peak_height[i]);
p.drawLine( x, y, x2, y );
y = hd2 + uint(peak_height[i]);
p.drawLine( x, y, x2, y );
} }
y = hd2 - uint(bar_height[i]);
p.drawPixmap(x + 1, y, barPixmap, 0, y, -1, -1);
p.drawPixmap(x + 1, hd2, barPixmap, 0, int(bar_height[i]), -1, -1);
p.setPen(palette().color(QPalette::Highlight));
if (bar_height[i] > 0)
p.drawRect(x, y, COLUMN_WIDTH - 1, (int)bar_height[i] * 2 - 1);
const uint x2 = x + COLUMN_WIDTH - 1;
p.setPen(palette().color(QPalette::Base));
y = hd2 - uint(peak_height[i]);
p.drawLine(x, y, x2, y);
y = hd2 + uint(peak_height[i]);
p.drawLine(x, y, x2, y);
}
} }

View File

@ -11,15 +11,14 @@
#include "boomanalyzer.h" #include "boomanalyzer.h"
class TurbineAnalyzer : public BoomAnalyzer class TurbineAnalyzer : public BoomAnalyzer {
{
Q_OBJECT Q_OBJECT
public: public:
Q_INVOKABLE TurbineAnalyzer( QWidget *parent ) : BoomAnalyzer( parent ) {} Q_INVOKABLE TurbineAnalyzer(QWidget* parent) : BoomAnalyzer(parent) {}
void analyze( QPainter& p, const Scope&, bool new_frame); void analyze(QPainter& p, const Scope&, bool new_frame);
static const char* kName; static const char* kName;
}; };
#endif #endif

View File

@ -27,24 +27,21 @@ const char* Appearance::kBackgroundColor = "background-color";
const QPalette Appearance::kDefaultPalette = QPalette(); const QPalette Appearance::kDefaultPalette = QPalette();
Appearance::Appearance(QObject* parent) Appearance::Appearance(QObject* parent) : QObject(parent) {
: QObject(parent)
{
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup);
QPalette p = QApplication::palette(); QPalette p = QApplication::palette();
background_color_ = s.value(kBackgroundColor, background_color_ =
p.color(QPalette::WindowText)).value<QColor>(); s.value(kBackgroundColor, p.color(QPalette::WindowText)).value<QColor>();
foreground_color_ = s.value(kForegroundColor, foreground_color_ =
p.color(QPalette::Window)).value<QColor>(); s.value(kForegroundColor, p.color(QPalette::Window)).value<QColor>();
} }
void Appearance::LoadUserTheme() { void Appearance::LoadUserTheme() {
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup);
bool use_a_custom_color_set = s.value(kUseCustomColorSet).toBool(); bool use_a_custom_color_set = s.value(kUseCustomColorSet).toBool();
if (!use_a_custom_color_set) if (!use_a_custom_color_set) return;
return;
ChangeForegroundColor(foreground_color_); ChangeForegroundColor(foreground_color_);
ChangeBackgroundColor(background_color_); ChangeBackgroundColor(background_color_);

View File

@ -22,24 +22,24 @@
#include <QPalette> #include <QPalette>
class Appearance : public QObject { class Appearance : public QObject {
public: public:
Appearance(QObject* parent = NULL); Appearance(QObject* parent = NULL);
// Load the user preferred theme, which could the default system theme or a // Load the user preferred theme, which could the default system theme or a
// custom set of colors that user has chosen // custom set of colors that user has chosen
void LoadUserTheme(); void LoadUserTheme();
void ResetToSystemDefaultTheme(); void ResetToSystemDefaultTheme();
void ChangeForegroundColor(const QColor& color); void ChangeForegroundColor(const QColor& color);
void ChangeBackgroundColor(const QColor& color); void ChangeBackgroundColor(const QColor& color);
static const char* kSettingsGroup; static const char* kSettingsGroup;
static const char* kUseCustomColorSet; static const char* kUseCustomColorSet;
static const char* kForegroundColor; static const char* kForegroundColor;
static const char* kBackgroundColor; static const char* kBackgroundColor;
static const QPalette kDefaultPalette; static const QPalette kDefaultPalette;
private: private:
QColor foreground_color_; QColor foreground_color_;
QColor background_color_; QColor background_color_;
}; };
#endif // APPEARANCE_H #endif // APPEARANCE_H

View File

@ -40,37 +40,36 @@
#include "podcasts/podcastupdater.h" #include "podcasts/podcastupdater.h"
#ifdef HAVE_MOODBAR #ifdef HAVE_MOODBAR
# include "moodbar/moodbarcontroller.h" #include "moodbar/moodbarcontroller.h"
# include "moodbar/moodbarloader.h" #include "moodbar/moodbarloader.h"
#endif #endif
bool Application::kIsPortable = false; bool Application::kIsPortable = false;
Application::Application(QObject* parent) Application::Application(QObject* parent)
: QObject(parent), : QObject(parent),
tag_reader_client_(nullptr), tag_reader_client_(nullptr),
database_(nullptr), database_(nullptr),
album_cover_loader_(nullptr), album_cover_loader_(nullptr),
playlist_backend_(nullptr), playlist_backend_(nullptr),
podcast_backend_(nullptr), podcast_backend_(nullptr),
appearance_(nullptr), appearance_(nullptr),
cover_providers_(nullptr), cover_providers_(nullptr),
task_manager_(nullptr), task_manager_(nullptr),
player_(nullptr), player_(nullptr),
playlist_manager_(nullptr), playlist_manager_(nullptr),
current_art_loader_(nullptr), current_art_loader_(nullptr),
global_search_(nullptr), global_search_(nullptr),
internet_model_(nullptr), internet_model_(nullptr),
library_(nullptr), library_(nullptr),
device_manager_(nullptr), device_manager_(nullptr),
podcast_updater_(nullptr), podcast_updater_(nullptr),
podcast_downloader_(nullptr), podcast_downloader_(nullptr),
gpodder_sync_(nullptr), gpodder_sync_(nullptr),
moodbar_loader_(nullptr), moodbar_loader_(nullptr),
moodbar_controller_(nullptr), moodbar_controller_(nullptr),
network_remote_(nullptr), network_remote_(nullptr),
network_remote_helper_(nullptr) network_remote_helper_(nullptr) {
{
tag_reader_client_ = new TagReaderClient(this); tag_reader_client_ = new TagReaderClient(this);
MoveToNewThread(tag_reader_client_); MoveToNewThread(tag_reader_client_);
tag_reader_client_->Start(); tag_reader_client_->Start();
@ -111,7 +110,8 @@ Application::Application(QObject* parent)
MoveToNewThread(network_remote_); MoveToNewThread(network_remote_);
// This must be before libraray_->Init(); // This must be before libraray_->Init();
// In the constructor the helper waits for the signal PlaylistManagerInitialized // In the constructor the helper waits for the signal
// PlaylistManagerInitialized
// to start the remote. Without the playlist manager clementine can // to start the remote. Without the playlist manager clementine can
// crash when a client connects before the manager is initialized! // crash when a client connects before the manager is initialized!
network_remote_helper_ = new NetworkRemoteHelper(this); network_remote_helper_ = new NetworkRemoteHelper(this);
@ -125,19 +125,14 @@ Application::~Application() {
// It's important that the device manager is deleted before the database. // It's important that the device manager is deleted before the database.
// Deleting the database deletes all objects that have been created in its // Deleting the database deletes all objects that have been created in its
// thread, including some device library backends. // thread, including some device library backends.
delete device_manager_; device_manager_ = nullptr; delete device_manager_;
device_manager_ = nullptr;
foreach (QObject* object, objects_in_threads_) { foreach(QObject * object, objects_in_threads_) { object->deleteLater(); }
object->deleteLater();
}
foreach (QThread* thread, threads_) { foreach(QThread * thread, threads_) { thread->quit(); }
thread->quit();
}
foreach (QThread* thread, threads_) { foreach(QThread * thread, threads_) { thread->wait(); }
thread->wait();
}
} }
void Application::MoveToNewThread(QObject* object) { void Application::MoveToNewThread(QObject* object) {
@ -155,9 +150,7 @@ void Application::MoveToThread(QObject* object, QThread* thread) {
objects_in_threads_ << object; objects_in_threads_ << object;
} }
void Application::AddError(const QString& message) { void Application::AddError(const QString& message) { emit ErrorAdded(message); }
emit ErrorAdded(message);
}
QString Application::language_without_region() const { QString Application::language_without_region() const {
const int underscore = language_name_.indexOf('_'); const int underscore = language_name_.indexOf('_');
@ -171,13 +164,9 @@ LibraryBackend* Application::library_backend() const {
return library()->backend(); return library()->backend();
} }
LibraryModel* Application::library_model() const { LibraryModel* Application::library_model() const { return library()->model(); }
return library()->model();
}
void Application::ReloadSettings() { void Application::ReloadSettings() { emit SettingsChanged(); }
emit SettingsChanged();
}
void Application::OpenSettingsDialogAtPage(SettingsDialog::Page page) { void Application::OpenSettingsDialogAtPage(SettingsDialog::Page page) {
emit SettingsDialogRequested(page); emit SettingsDialogRequested(page);

View File

@ -47,18 +47,18 @@ class PodcastUpdater;
class TagReaderClient; class TagReaderClient;
class TaskManager; class TaskManager;
class Application : public QObject { class Application : public QObject {
Q_OBJECT Q_OBJECT
public: public:
static bool kIsPortable; static bool kIsPortable;
Application(QObject* parent = NULL); Application(QObject* parent = NULL);
~Application(); ~Application();
const QString& language_name() const { return language_name_; } const QString& language_name() const { return language_name_; }
// Same as language_name, but remove the region code at the end if there is one // Same as language_name, but remove the region code at the end if there is
// one
QString language_without_region() const; QString language_without_region() const;
void set_language_name(const QString& name) { language_name_ = name; } void set_language_name(const QString& name) { language_name_ = name; }
@ -83,7 +83,9 @@ public:
MoodbarLoader* moodbar_loader() const { return moodbar_loader_; } MoodbarLoader* moodbar_loader() const { return moodbar_loader_; }
MoodbarController* moodbar_controller() const { return moodbar_controller_; } MoodbarController* moodbar_controller() const { return moodbar_controller_; }
NetworkRemote* network_remote() const { return network_remote_; } NetworkRemote* network_remote() const { return network_remote_; }
NetworkRemoteHelper* network_remote_helper() const { return network_remote_helper_; } NetworkRemoteHelper* network_remote_helper() const {
return network_remote_helper_;
}
LibraryBackend* library_backend() const; LibraryBackend* library_backend() const;
LibraryModel* library_model() const; LibraryModel* library_model() const;
@ -91,7 +93,7 @@ public:
void MoveToNewThread(QObject* object); void MoveToNewThread(QObject* object);
void MoveToThread(QObject* object, QThread* thread); void MoveToThread(QObject* object, QThread* thread);
public slots: public slots:
void AddError(const QString& message); void AddError(const QString& message);
void ReloadSettings(); void ReloadSettings();
void OpenSettingsDialogAtPage(SettingsDialog::Page page); void OpenSettingsDialogAtPage(SettingsDialog::Page page);
@ -101,7 +103,7 @@ signals:
void SettingsChanged(); void SettingsChanged();
void SettingsDialogRequested(SettingsDialog::Page page); void SettingsDialogRequested(SettingsDialog::Page page);
private: private:
QString language_name_; QString language_name_;
TagReaderClient* tag_reader_client_; TagReaderClient* tag_reader_client_;
@ -131,4 +133,4 @@ private:
QList<QThread*> threads_; QList<QThread*> threads_;
}; };
#endif // APPLICATION_H #endif // APPLICATION_H

View File

@ -9,17 +9,14 @@
const char* BackgroundStreams::kSettingsGroup = "BackgroundStreams"; const char* BackgroundStreams::kSettingsGroup = "BackgroundStreams";
const char* BackgroundStreams::kHypnotoadUrl = "hypnotoad:///"; const char* BackgroundStreams::kHypnotoadUrl = "hypnotoad:///";
const char* BackgroundStreams::kRainUrl = "http://data.clementine-player.org/rainymood"; const char* BackgroundStreams::kRainUrl =
"http://data.clementine-player.org/rainymood";
const char* BackgroundStreams::kEnterpriseUrl = "enterprise:///"; const char* BackgroundStreams::kEnterpriseUrl = "enterprise:///";
BackgroundStreams::BackgroundStreams(EngineBase* engine, QObject* parent) BackgroundStreams::BackgroundStreams(EngineBase* engine, QObject* parent)
: QObject(parent), : QObject(parent), engine_(engine) {}
engine_(engine) {
}
BackgroundStreams::~BackgroundStreams() { BackgroundStreams::~BackgroundStreams() { SaveStreams(); }
SaveStreams();
}
void BackgroundStreams::LoadStreams() { void BackgroundStreams::LoadStreams() {
QSettings s; QSettings s;
@ -39,8 +36,7 @@ void BackgroundStreams::LoadStreams() {
int size = s.beginReadArray("streams"); int size = s.beginReadArray("streams");
for (int i = 0; i < size; ++i) { for (int i = 0; i < size; ++i) {
s.setArrayIndex(i); s.setArrayIndex(i);
AddStream(s.value("name").toString(), AddStream(s.value("name").toString(), s.value("url").toUrl(),
s.value("url").toUrl(),
s.value("volume").toInt()); s.value("volume").toInt());
} }
@ -63,8 +59,7 @@ void BackgroundStreams::SaveStreams() {
s.endArray(); s.endArray();
} }
void BackgroundStreams::AddStream(const QString& name, void BackgroundStreams::AddStream(const QString& name, const QUrl& url,
const QUrl& url,
int volume) { int volume) {
if (streams_.contains(name)) { if (streams_.contains(name)) {
return; return;
@ -134,7 +129,8 @@ bool BackgroundStreams::IsPlaying(const QString& name) const {
void BackgroundStreams::AddAction(const QString& name, QAction* action) { void BackgroundStreams::AddAction(const QString& name, QAction* action) {
if (!streams_.contains(name)) { if (!streams_.contains(name)) {
qLog(Error) << "Tried to add action for stream" << name << "which doesn't exist"; qLog(Error) << "Tried to add action for stream" << name
<< "which doesn't exist";
return; return;
} }
@ -156,7 +152,7 @@ void BackgroundStreams::StreamActionDestroyed() {
return; return;
} }
foreach (Stream* stream, streams_.values()) { foreach(Stream * stream, streams_.values()) {
if (stream->action == action) { if (stream->action == action) {
stream->action = nullptr; stream->action = nullptr;
} }
@ -169,7 +165,7 @@ void BackgroundStreams::StreamActionToggled(bool checked) {
return; return;
} }
foreach (Stream* stream, streams_.values()) { foreach(Stream * stream, streams_.values()) {
if (stream->action == action) { if (stream->action == action) {
EnableStream(stream->name, checked); EnableStream(stream->name, checked);
} }

View File

@ -29,7 +29,7 @@ class BackgroundStreams : public QObject {
void AddAction(const QString& name, QAction* action); void AddAction(const QString& name, QAction* action);
signals: signals:
void StreamStarted(const QString& name); void StreamStarted(const QString& name);
void StreamStopped(const QString& name); void StreamStopped(const QString& name);

View File

@ -9,12 +9,9 @@ template <typename T, typename D>
class BoundFutureWatcher : public QFutureWatcher<T>, boost::noncopyable { class BoundFutureWatcher : public QFutureWatcher<T>, boost::noncopyable {
public: public:
BoundFutureWatcher(const D& data, QObject* parent = 0) BoundFutureWatcher(const D& data, QObject* parent = 0)
: QFutureWatcher<T>(parent), : QFutureWatcher<T>(parent), data_(data) {}
data_(data) {
}
~BoundFutureWatcher() { ~BoundFutureWatcher() {}
}
const D& data() const { return data_; } const D& data() const { return data_; }

View File

@ -23,7 +23,7 @@
template <typename T> template <typename T>
class CachedList { class CachedList {
public: public:
// Use a CachedList when you want to download and save a list of things from a // Use a CachedList when you want to download and save a list of things from a
// remote service, updating it only periodically. // remote service, updating it only periodically.
// T must be a registered metatype and must support being stored in // T must be a registered metatype and must support being stored in
@ -34,10 +34,9 @@ public:
CachedList(const QString& settings_group, const QString& name, CachedList(const QString& settings_group, const QString& name,
int cache_duration_secs) int cache_duration_secs)
: settings_group_(settings_group), : settings_group_(settings_group),
name_(name), name_(name),
cache_duration_secs_(cache_duration_secs) { cache_duration_secs_(cache_duration_secs) {}
}
void Load() { void Load() {
QSettings s; QSettings s;
@ -47,7 +46,7 @@ public:
data_.clear(); data_.clear();
const int count = s.beginReadArray(name_ + "_data"); const int count = s.beginReadArray(name_ + "_data");
for (int i=0 ; i<count ; ++i) { for (int i = 0; i < count; ++i) {
s.setArrayIndex(i); s.setArrayIndex(i);
data_ << s.value("value").value<T>(); data_ << s.value("value").value<T>();
} }
@ -61,7 +60,7 @@ public:
s.setValue("last_refreshed_" + name_, last_updated_); s.setValue("last_refreshed_" + name_, last_updated_);
s.beginWriteArray(name_ + "_data", data_.size()); s.beginWriteArray(name_ + "_data", data_.size());
for (int i=0 ; i<data_.size() ; ++i) { for (int i = 0; i < data_.size(); ++i) {
s.setArrayIndex(i); s.setArrayIndex(i);
s.setValue("value", QVariant::fromValue(data_[i])); s.setValue("value", QVariant::fromValue(data_[i]));
} }
@ -76,12 +75,11 @@ public:
bool IsStale() const { bool IsStale() const {
return last_updated_.isNull() || return last_updated_.isNull() ||
last_updated_.secsTo(QDateTime::currentDateTime()) > cache_duration_secs_; last_updated_.secsTo(QDateTime::currentDateTime()) >
cache_duration_secs_;
} }
void Sort() { void Sort() { qSort(data_); }
qSort(data_);
}
const ListType& Data() const { return data_; } const ListType& Data() const { return data_; }
operator ListType() const { return data_; } operator ListType() const { return data_; }
@ -91,7 +89,7 @@ public:
const_iterator begin() const { return data_.begin(); } const_iterator begin() const { return data_.begin(); }
const_iterator end() const { return data_.end(); } const_iterator end() const { return data_.end(); }
private: private:
const QString settings_group_; const QString settings_group_;
const QString name_; const QString name_;
const int cache_duration_secs_; const int cache_duration_secs_;
@ -100,4 +98,4 @@ private:
ListType data_; ListType data_;
}; };
#endif // CACHEDLIST_H #endif // CACHEDLIST_H

View File

@ -28,7 +28,6 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QFileInfo> #include <QFileInfo>
const char* CommandlineOptions::kHelpText = const char* CommandlineOptions::kHelpText =
"%1: clementine [%2] [%3]\n" "%1: clementine [%2] [%3]\n"
"\n" "\n"
@ -62,23 +61,21 @@ const char* CommandlineOptions::kHelpText =
" --log-levels <levels> %29\n" " --log-levels <levels> %29\n"
" --version %30\n"; " --version %30\n";
const char* CommandlineOptions::kVersionText = const char* CommandlineOptions::kVersionText = "Clementine %1";
"Clementine %1";
CommandlineOptions::CommandlineOptions(int argc, char** argv) CommandlineOptions::CommandlineOptions(int argc, char** argv)
: argc_(argc), : argc_(argc),
argv_(argv), argv_(argv),
url_list_action_(UrlList_Append), url_list_action_(UrlList_Append),
player_action_(Player_None), player_action_(Player_None),
set_volume_(-1), set_volume_(-1),
volume_modifier_(0), volume_modifier_(0),
seek_to_(-1), seek_to_(-1),
seek_by_(0), seek_by_(0),
play_track_at_(-1), play_track_at_(-1),
show_osd_(false), show_osd_(false),
toggle_pretty_osd_(false), toggle_pretty_osd_(false),
log_levels_(logging::kDefaultLogLevels) log_levels_(logging::kDefaultLogLevels) {
{
#ifdef Q_OS_DARWIN #ifdef Q_OS_DARWIN
// Remove -psn_xxx option that Mac passes when opened from Finder. // Remove -psn_xxx option that Mac passes when opened from Finder.
RemoveArg("-psn", 1); RemoveArg("-psn", 1);
@ -93,7 +90,7 @@ void CommandlineOptions::RemoveArg(const QString& starts_with, int count) {
QString opt(argv_[i]); QString opt(argv_[i]);
if (opt.startsWith(starts_with)) { if (opt.startsWith(starts_with)) {
for (int j = i; j < argc_ - count + 1; ++j) { for (int j = i; j < argc_ - count + 1; ++j) {
argv_[j] = argv_[j+count]; argv_[j] = argv_[j + count];
} }
argc_ -= count; argc_ -= count;
break; break;
@ -103,37 +100,32 @@ void CommandlineOptions::RemoveArg(const QString& starts_with, int count) {
bool CommandlineOptions::Parse() { bool CommandlineOptions::Parse() {
static const struct option kOptions[] = { static const struct option kOptions[] = {
{"help", no_argument, 0, 'h'}, {"help", no_argument, 0, 'h'},
{"play", no_argument, 0, 'p'},
{"play", no_argument, 0, 'p'}, {"play-pause", no_argument, 0, 't'},
{"play-pause", no_argument, 0, 't'}, {"pause", no_argument, 0, 'u'},
{"pause", no_argument, 0, 'u'}, {"stop", no_argument, 0, 's'},
{"stop", no_argument, 0, 's'}, {"previous", no_argument, 0, 'r'},
{"previous", no_argument, 0, 'r'}, {"next", no_argument, 0, 'f'},
{"next", no_argument, 0, 'f'}, {"volume", required_argument, 0, 'v'},
{"volume", required_argument, 0, 'v'}, {"volume-up", no_argument, 0, VolumeUp},
{"volume-up", no_argument, 0, VolumeUp}, {"volume-down", no_argument, 0, VolumeDown},
{"volume-increase-by", required_argument, 0, VolumeIncreaseBy},
{"volume-down", no_argument, 0, VolumeDown}, {"volume-decrease-by", required_argument, 0, VolumeDecreaseBy},
{"volume-increase-by", required_argument, 0, VolumeIncreaseBy}, {"seek-to", required_argument, 0, SeekTo},
{"volume-decrease-by", required_argument, 0, VolumeDecreaseBy}, {"seek-by", required_argument, 0, SeekBy},
{"seek-to", required_argument, 0, SeekTo}, {"restart-or-previous", no_argument, 0, RestartOrPrevious},
{"seek-by", required_argument, 0, SeekBy}, {"append", no_argument, 0, 'a'},
{"restart-or-previous", no_argument, 0, RestartOrPrevious}, {"load", no_argument, 0, 'l'},
{"play-track", required_argument, 0, 'k'},
{"append", no_argument, 0, 'a'}, {"show-osd", no_argument, 0, 'o'},
{"load", no_argument, 0, 'l'}, {"toggle-pretty-osd", no_argument, 0, 'y'},
{"play-track", required_argument, 0, 'k'}, {"language", required_argument, 0, 'g'},
{"show-osd", no_argument, 0, 'o'}, {"quiet", no_argument, 0, Quiet},
{"toggle-pretty-osd", no_argument, 0, 'y'}, {"verbose", no_argument, 0, Verbose},
{"language", required_argument, 0, 'g'}, {"log-levels", required_argument, 0, LogLevels},
{"quiet", no_argument, 0, Quiet}, {"version", no_argument, 0, Version},
{"verbose", no_argument, 0, Verbose}, {0, 0, 0, 0}};
{"log-levels", required_argument, 0, LogLevels},
{"version", no_argument, 0, Version},
{0, 0, 0, 0}
};
// Parse the arguments // Parse the arguments
bool ok = false; bool ok = false;
@ -141,62 +133,97 @@ bool CommandlineOptions::Parse() {
int c = getopt_long(argc_, argv_, "hptusrfv:alk:oyg:", kOptions, nullptr); int c = getopt_long(argc_, argv_, "hptusrfv:alk:oyg:", kOptions, nullptr);
// End of the options // End of the options
if (c == -1) if (c == -1) break;
break;
switch (c) { switch (c) {
case 'h': { case 'h': {
QString translated_help_text = QString(kHelpText).arg( QString translated_help_text =
tr("Usage"), tr("options"), tr("URL(s)"), tr("Player options"), QString(kHelpText)
tr("Start the playlist currently playing"), .arg(tr("Usage"), tr("options"), tr("URL(s)"),
tr("Play if stopped, pause if playing"), tr("Player options"),
tr("Pause playback"), tr("Start the playlist currently playing"),
tr("Stop playback"), tr("Play if stopped, pause if playing"),
tr("Skip backwards in playlist")).arg( tr("Pause playback"), tr("Stop playback"),
tr("Skip forwards in playlist"), tr("Skip backwards in playlist"))
tr("Set the volume to <value> percent"), .arg(tr("Skip forwards in playlist"),
tr("Increase the volume by 4%"), tr("Set the volume to <value> percent"),
tr("Decrease the volume by 4%"), tr("Increase the volume by 4%"),
tr("Increase the volume by <value> percent"), tr("Decrease the volume by 4%"),
tr("Decrease the volume by <value> percent")).arg( tr("Increase the volume by <value> percent"),
tr("Seek the currently playing track to an absolute position"), tr("Decrease the volume by <value> percent"))
tr("Seek the currently playing track by a relative amount"), .arg(tr("Seek the currently playing track to an absolute "
tr("Restart the track, or play the previous track if within 8 seconds of start."), "position"),
tr("Playlist options"), tr("Seek the currently playing track by a relative "
tr("Append files/URLs to the playlist"), "amount"),
tr("Loads files/URLs, replacing current playlist"), tr("Restart the track, or play the previous track if "
tr("Play the <n>th track in the playlist")).arg( "within 8 seconds of start."),
tr("Other options"), tr("Playlist options"),
tr("Display the on-screen-display"), tr("Append files/URLs to the playlist"),
tr("Toggle visibility for the pretty on-screen-display"), tr("Loads files/URLs, replacing current playlist"),
tr("Change the language"), tr("Play the <n>th track in the playlist"))
tr("Equivalent to --log-levels *:1"), .arg(tr("Other options"), tr("Display the on-screen-display"),
tr("Equivalent to --log-levels *:3"), tr("Toggle visibility for the pretty on-screen-display"),
tr("Comma separated list of class:level, level is 0-3")).arg( tr("Change the language"),
tr("Print out version information")); tr("Equivalent to --log-levels *:1"),
tr("Equivalent to --log-levels *:3"),
tr("Comma separated list of class:level, level is 0-3"))
.arg(tr("Print out version information"));
std::cout << translated_help_text.toLocal8Bit().constData(); std::cout << translated_help_text.toLocal8Bit().constData();
return false; return false;
} }
case 'p': player_action_ = Player_Play; break; case 'p':
case 't': player_action_ = Player_PlayPause; break; player_action_ = Player_Play;
case 'u': player_action_ = Player_Pause; break; break;
case 's': player_action_ = Player_Stop; break; case 't':
case 'r': player_action_ = Player_Previous; break; player_action_ = Player_PlayPause;
case 'f': player_action_ = Player_Next; break; break;
case 'a': url_list_action_ = UrlList_Append; break; case 'u':
case 'l': url_list_action_ = UrlList_Load; break; player_action_ = Player_Pause;
case 'o': show_osd_ = true; break; break;
case 'y': toggle_pretty_osd_ = true; break; case 's':
case 'g': language_ = QString(optarg); break; player_action_ = Player_Stop;
case VolumeUp: volume_modifier_ = +4; break; break;
case VolumeDown: volume_modifier_ = -4; break; case 'r':
case Quiet: log_levels_ = "1"; break; player_action_ = Player_Previous;
case Verbose: log_levels_ = "3"; break; break;
case LogLevels: log_levels_ = QString(optarg); break; case 'f':
player_action_ = Player_Next;
break;
case 'a':
url_list_action_ = UrlList_Append;
break;
case 'l':
url_list_action_ = UrlList_Load;
break;
case 'o':
show_osd_ = true;
break;
case 'y':
toggle_pretty_osd_ = true;
break;
case 'g':
language_ = QString(optarg);
break;
case VolumeUp:
volume_modifier_ = +4;
break;
case VolumeDown:
volume_modifier_ = -4;
break;
case Quiet:
log_levels_ = "1";
break;
case Verbose:
log_levels_ = "3";
break;
case LogLevels:
log_levels_ = QString(optarg);
break;
case Version: { case Version: {
QString version_text = QString(kVersionText).arg(CLEMENTINE_VERSION_DISPLAY); QString version_text =
QString(kVersionText).arg(CLEMENTINE_VERSION_DISPLAY);
std::cout << version_text.toLocal8Bit().constData() << std::endl; std::cout << version_text.toLocal8Bit().constData() << std::endl;
std::exit(0); std::exit(0);
} }
@ -241,7 +268,7 @@ bool CommandlineOptions::Parse() {
} }
// Get any filenames or URLs following the arguments // Get any filenames or URLs following the arguments
for (int i=optind ; i<argc_ ; ++i) { for (int i = optind; i < argc_; ++i) {
QString value = QFile::decodeName(argv_[i]); QString value = QFile::decodeName(argv_[i]);
QFileInfo file_info(value); QFileInfo file_info(value);
if (file_info.exists()) if (file_info.exists())
@ -254,15 +281,10 @@ bool CommandlineOptions::Parse() {
} }
bool CommandlineOptions::is_empty() const { bool CommandlineOptions::is_empty() const {
return player_action_ == Player_None && return player_action_ == Player_None && set_volume_ == -1 &&
set_volume_ == -1 && volume_modifier_ == 0 && seek_to_ == -1 && seek_by_ == 0 &&
volume_modifier_ == 0 && play_track_at_ == -1 && show_osd_ == false &&
seek_to_ == -1 && toggle_pretty_osd_ == false && urls_.isEmpty();
seek_by_ == 0 &&
play_track_at_ == -1 &&
show_osd_ == false &&
toggle_pretty_osd_ == false &&
urls_.isEmpty();
} }
QByteArray CommandlineOptions::Serialize() const { QByteArray CommandlineOptions::Serialize() const {
@ -276,7 +298,7 @@ QByteArray CommandlineOptions::Serialize() const {
return buf.data(); return buf.data();
} }
void CommandlineOptions::Load(const QByteArray &serialized) { void CommandlineOptions::Load(const QByteArray& serialized) {
QByteArray copy(serialized); QByteArray copy(serialized);
QBuffer buf(&copy); QBuffer buf(&copy);
buf.open(QIODevice::ReadOnly); buf.open(QIODevice::ReadOnly);
@ -285,22 +307,14 @@ void CommandlineOptions::Load(const QByteArray &serialized) {
s >> *this; s >> *this;
} }
QString CommandlineOptions::tr(const char *source_text) { QString CommandlineOptions::tr(const char* source_text) {
return QObject::tr(source_text); return QObject::tr(source_text);
} }
QDataStream& operator<<(QDataStream& s, const CommandlineOptions& a) { QDataStream& operator<<(QDataStream& s, const CommandlineOptions& a) {
s << qint32(a.player_action_) s << qint32(a.player_action_) << qint32(a.url_list_action_) << a.set_volume_
<< qint32(a.url_list_action_) << a.volume_modifier_ << a.seek_to_ << a.seek_by_ << a.play_track_at_
<< a.set_volume_ << a.show_osd_ << a.urls_ << a.log_levels_ << a.toggle_pretty_osd_;
<< a.volume_modifier_
<< a.seek_to_
<< a.seek_by_
<< a.play_track_at_
<< a.show_osd_
<< a.urls_
<< a.log_levels_
<< a.toggle_pretty_osd_;
return s; return s;
} }
@ -308,17 +322,9 @@ QDataStream& operator<<(QDataStream& s, const CommandlineOptions& a) {
QDataStream& operator>>(QDataStream& s, CommandlineOptions& a) { QDataStream& operator>>(QDataStream& s, CommandlineOptions& a) {
quint32 player_action = 0; quint32 player_action = 0;
quint32 url_list_action = 0; quint32 url_list_action = 0;
s >> player_action s >> player_action >> url_list_action >> a.set_volume_ >>
>> url_list_action a.volume_modifier_ >> a.seek_to_ >> a.seek_by_ >> a.play_track_at_ >>
>> a.set_volume_ a.show_osd_ >> a.urls_ >> a.log_levels_ >> a.toggle_pretty_osd_;
>> a.volume_modifier_
>> a.seek_to_
>> a.seek_by_
>> a.play_track_at_
>> a.show_osd_
>> a.urls_
>> a.log_levels_
>> a.toggle_pretty_osd_;
a.player_action_ = CommandlineOptions::PlayerAction(player_action); a.player_action_ = CommandlineOptions::PlayerAction(player_action);
a.url_list_action_ = CommandlineOptions::UrlListAction(url_list_action); a.url_list_action_ = CommandlineOptions::UrlListAction(url_list_action);

View File

@ -27,17 +27,14 @@ class CommandlineOptions {
friend QDataStream& operator>>(QDataStream& s, CommandlineOptions& a); friend QDataStream& operator>>(QDataStream& s, CommandlineOptions& a);
public: public:
CommandlineOptions(int argc = 0, char** argv = NULL); CommandlineOptions(int argc = 0, char* *argv = NULL);
static const char* kHelpText; static const char* kHelpText;
static const char* kVersionText; static const char* kVersionText;
// Don't change the values or order, these get serialised and sent to // Don't change the values or order, these get serialised and sent to
// possibly a different version of Clementine // possibly a different version of Clementine
enum UrlListAction { enum UrlListAction { UrlList_Append = 0, UrlList_Load = 1, };
UrlList_Append = 0,
UrlList_Load = 1,
};
enum PlayerAction { enum PlayerAction {
Player_None = 0, Player_None = 0,
Player_Play = 1, Player_Play = 1,
@ -90,7 +87,6 @@ class CommandlineOptions {
void RemoveArg(const QString& starts_with, int count); void RemoveArg(const QString& starts_with, int count);
private: private:
int argc_; int argc_;
char** argv_; char** argv_;
@ -114,4 +110,4 @@ class CommandlineOptions {
QDataStream& operator<<(QDataStream& s, const CommandlineOptions& a); QDataStream& operator<<(QDataStream& s, const CommandlineOptions& a);
QDataStream& operator>>(QDataStream& s, CommandlineOptions& a); QDataStream& operator>>(QDataStream& s, CommandlineOptions& a);
#endif // COMMANDLINEOPTIONS_H #endif // COMMANDLINEOPTIONS_H

View File

@ -31,25 +31,23 @@
#include <QtDebug> #include <QtDebug>
#if defined(HAVE_BREAKPAD) and defined(Q_OS_LINUX) #if defined(HAVE_BREAKPAD) and defined(Q_OS_LINUX)
# include "client/linux/handler/exception_handler.h" #include "client/linux/handler/exception_handler.h"
# include "third_party/lss/linux_syscall_support.h" #include "third_party/lss/linux_syscall_support.h"
#endif #endif
const char* CrashSender::kUploadURL =
const char* CrashSender::kUploadURL = "http://crashes.clementine-player.org/getuploadurl"; "http://crashes.clementine-player.org/getuploadurl";
const char* CrashReporting::kSendCrashReportOption = "--send-crash-report"; const char* CrashReporting::kSendCrashReportOption = "--send-crash-report";
char* CrashReporting::sPath = nullptr; char* CrashReporting::sPath = nullptr;
#if defined(HAVE_BREAKPAD) and defined(Q_OS_LINUX) #if defined(HAVE_BREAKPAD) and defined(Q_OS_LINUX)
CrashReporting::CrashReporting() CrashReporting::CrashReporting()
: handler_(new google_breakpad::ExceptionHandler( : handler_(new google_breakpad::ExceptionHandler(
QDir::tempPath().toLocal8Bit().constData(), nullptr, QDir::tempPath().toLocal8Bit().constData(), nullptr,
CrashReporting::Handler, this, true)) { CrashReporting::Handler, this, true)) {}
}
CrashReporting::~CrashReporting() { CrashReporting::~CrashReporting() {}
}
bool CrashReporting::SendCrashReport(int argc, char** argv) { bool CrashReporting::SendCrashReport(int argc, char** argv) {
if (argc != 4 || strcmp(argv[1], kSendCrashReportOption) != 0) { if (argc != 4 || strcmp(argv[1], kSendCrashReportOption) != 0) {
@ -76,21 +74,21 @@ void CrashReporting::Print(const char* message) {
} }
} }
bool CrashReporting::Handler(const char* dump_path, bool CrashReporting::Handler(const char* dump_path, const char* minidump_id,
const char* minidump_id, void* context, bool succeeded) {
void* context,
bool succeeded) {
Print("Clementine has crashed! A crash report has been saved to:\n "); Print("Clementine has crashed! A crash report has been saved to:\n ");
Print(dump_path); Print(dump_path);
Print("/"); Print("/");
Print(minidump_id); Print(minidump_id);
Print("\n\nPlease send this to the developers so they can fix the problem:\n" Print(
" http://code.google.com/p/clementine-player/issues/entry\n\n"); "\n\nPlease send this to the developers so they can fix the problem:\n"
" http://code.google.com/p/clementine-player/issues/entry\n\n");
if (sPath) { if (sPath) {
// We know the path to clementine, so exec it again to prompt the user to // We know the path to clementine, so exec it again to prompt the user to
// upload the report. // upload the report.
const char* argv[] = {sPath, kSendCrashReportOption, dump_path, minidump_id, nullptr}; const char* argv[] = {sPath, kSendCrashReportOption, dump_path, minidump_id,
nullptr};
sys_execv(sPath, argv); sys_execv(sPath, argv);
} }
@ -99,11 +97,10 @@ bool CrashReporting::Handler(const char* dump_path,
} }
CrashSender::CrashSender(const QString& path) CrashSender::CrashSender(const QString& path)
: network_(new QNetworkAccessManager(this)), : network_(new QNetworkAccessManager(this)),
path_(path), path_(path),
file_(new QFile(path_, this)), file_(new QFile(path_, this)),
progress_(nullptr) { progress_(nullptr) {}
}
bool CrashSender::Start() { bool CrashSender::Start() {
if (!file_->open(QIODevice::ReadOnly)) { if (!file_->open(QIODevice::ReadOnly)) {
@ -112,10 +109,13 @@ bool CrashSender::Start() {
} }
// No tr() here. // No tr() here.
QMessageBox prompt(QMessageBox::Critical, "Clementine has crashed!", QString( QMessageBox prompt(QMessageBox::Critical, "Clementine has crashed!",
"A crash report has been created and saved to '%1'. With your permission " QString(
"it can be automatically sent to our server so the developers can find " "A crash report has been created and saved to '%1'. "
"out what happened.").arg(path_)); "With your permission "
"it can be automatically sent to our server so the "
"developers can find "
"out what happened.").arg(path_));
prompt.addButton("Don't send", QMessageBox::RejectRole); prompt.addButton("Don't send", QMessageBox::RejectRole);
prompt.addButton("Send crash report", QMessageBox::AcceptRole); prompt.addButton("Send crash report", QMessageBox::AcceptRole);
if (prompt.exec() == QDialog::Rejected) { if (prompt.exec() == QDialog::Rejected) {
@ -141,7 +141,8 @@ void CrashSender::RedirectFinished() {
reply->deleteLater(); reply->deleteLater();
QUrl url = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); QUrl url =
reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
if (!url.isValid()) { if (!url.isValid()) {
progress_->close(); progress_->close();
return; return;
@ -160,14 +161,17 @@ void CrashSender::RedirectFinished() {
} }
QNetworkRequest req(url); QNetworkRequest req(url);
req.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data; boundary=" + boundary); req.setHeader(QNetworkRequest::ContentTypeHeader,
"multipart/form-data; boundary=" + boundary);
// Construct the multipart/form-data // Construct the multipart/form-data
QByteArray form_data; QByteArray form_data;
form_data.reserve(file_data.size() + 1024); form_data.reserve(file_data.size() + 1024);
form_data.append("--"); form_data.append("--");
form_data.append(boundary); form_data.append(boundary);
form_data.append("\nContent-Disposition: form-data; name=\"data\"; filename=\"data.dmp\"\n"); form_data.append(
"\nContent-Disposition: form-data; name=\"data\"; "
"filename=\"data.dmp\"\n");
form_data.append("Content-Type: application/octet-stream\n\n"); form_data.append("Content-Type: application/octet-stream\n\n");
form_data.append(file_data); form_data.append(file_data);
form_data.append("\n--"); form_data.append("\n--");
@ -178,31 +182,25 @@ void CrashSender::RedirectFinished() {
// Upload the data // Upload the data
reply = network_->post(req, form_data); reply = network_->post(req, form_data);
connect(reply, SIGNAL(uploadProgress(qint64,qint64)), SLOT(UploadProgress(qint64))); connect(reply, SIGNAL(uploadProgress(qint64, qint64)),
SLOT(UploadProgress(qint64)));
connect(reply, SIGNAL(finished()), progress_, SLOT(close())); connect(reply, SIGNAL(finished()), progress_, SLOT(close()));
} }
void CrashSender::UploadProgress(qint64 bytes) { void CrashSender::UploadProgress(qint64 bytes) { progress_->setValue(bytes); }
progress_->setValue(bytes);
}
#else // HAVE_BREAKPAD #else // HAVE_BREAKPAD
namespace google_breakpad { namespace google_breakpad {
class ExceptionHandler {}; class ExceptionHandler {};
} }
CrashReporting::CrashReporting() { CrashReporting::CrashReporting() {}
}
CrashReporting::~CrashReporting() { CrashReporting::~CrashReporting() {}
}
bool CrashReporting::SendCrashReport(int, char**) { bool CrashReporting::SendCrashReport(int, char**) { return false; }
return false;
}
void CrashReporting::SetApplicationPath(const QString&) { void CrashReporting::SetApplicationPath(const QString&) {}
}
#endif // HAVE_BREAKPAD #endif // HAVE_BREAKPAD

View File

@ -27,14 +27,13 @@ class QNetworkAccessManager;
class QProgressDialog; class QProgressDialog;
namespace google_breakpad { namespace google_breakpad {
class ExceptionHandler; class ExceptionHandler;
} }
// Wraps google_breakpad::ExceptionHandler - while an instance of this class // Wraps google_breakpad::ExceptionHandler - while an instance of this class
// is alive crashes will be handled. // is alive crashes will be handled.
class CrashReporting { class CrashReporting {
public: public:
CrashReporting(); CrashReporting();
~CrashReporting(); ~CrashReporting();
@ -48,17 +47,15 @@ public:
// --send-crash-report when a crash happens. // --send-crash-report when a crash happens.
static void SetApplicationPath(const QString& path); static void SetApplicationPath(const QString& path);
private: private:
// Prints the message to stdout without using libc. // Prints the message to stdout without using libc.
static void Print(const char* message); static void Print(const char* message);
// Breakpad callback. // Breakpad callback.
static bool Handler(const char* dump_path, static bool Handler(const char* dump_path, const char* minidump_id,
const char* minidump_id, void* context, bool succeeded);
void* context,
bool succeeded);
private: private:
Q_DISABLE_COPY(CrashReporting); Q_DISABLE_COPY(CrashReporting);
static const char* kSendCrashReportOption; static const char* kSendCrashReportOption;
@ -67,24 +64,23 @@ private:
std::unique_ptr<google_breakpad::ExceptionHandler> handler_; std::unique_ptr<google_breakpad::ExceptionHandler> handler_;
}; };
// Asks the user if he wants to send a crash report, and displays a progress // Asks the user if he wants to send a crash report, and displays a progress
// dialog while uploading it if he does. // dialog while uploading it if he does.
class CrashSender : public QObject { class CrashSender : public QObject {
Q_OBJECT Q_OBJECT
public: public:
CrashSender(const QString& path); CrashSender(const QString& path);
// Returns false if the user doesn't want to send the crash report (caller // Returns false if the user doesn't want to send the crash report (caller
// should exit), or true if he does (caller should start the Qt event loop). // should exit), or true if he does (caller should start the Qt event loop).
bool Start(); bool Start();
private slots: private slots:
void RedirectFinished(); void RedirectFinished();
void UploadProgress(qint64 bytes); void UploadProgress(qint64 bytes);
private: private:
static const char* kUploadURL; static const char* kUploadURL;
QNetworkAccessManager* network_; QNetworkAccessManager* network_;
@ -94,4 +90,4 @@ private:
QProgressDialog* progress_; QProgressDialog* progress_;
}; };
#endif // CRASHREPORTING_H #endif // CRASHREPORTING_H

View File

@ -46,65 +46,59 @@ int Database::sNextConnectionId = 1;
QMutex Database::sNextConnectionIdMutex; QMutex Database::sNextConnectionIdMutex;
Database::Token::Token(const QString& token, int start, int end) Database::Token::Token(const QString& token, int start, int end)
: token(token), : token(token), start_offset(start), end_offset(end) {}
start_offset(start),
end_offset(end) {
}
struct sqlite3_tokenizer_module { struct sqlite3_tokenizer_module {
int iVersion; int iVersion;
int (*xCreate)( int (*xCreate)(int argc, /* Size of argv array */
int argc, /* Size of argv array */ const char* const* argv, /* Tokenizer argument strings */
const char *const*argv, /* Tokenizer argument strings */ sqlite3_tokenizer** ppTokenizer); /* OUT: Created tokenizer */
sqlite3_tokenizer **ppTokenizer); /* OUT: Created tokenizer */
int (*xDestroy)(sqlite3_tokenizer *pTokenizer); int (*xDestroy)(sqlite3_tokenizer* pTokenizer);
int (*xOpen)( int (*xOpen)(
sqlite3_tokenizer *pTokenizer, /* Tokenizer object */ sqlite3_tokenizer* pTokenizer, /* Tokenizer object */
const char *pInput, int nBytes, /* Input buffer */ const char* pInput, int nBytes, /* Input buffer */
sqlite3_tokenizer_cursor **ppCursor);/* OUT: Created tokenizer cursor */ sqlite3_tokenizer_cursor** ppCursor); /* OUT: Created tokenizer cursor */
int (*xClose)(sqlite3_tokenizer_cursor *pCursor); int (*xClose)(sqlite3_tokenizer_cursor* pCursor);
int (*xNext)( int (*xNext)(
sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */ sqlite3_tokenizer_cursor* pCursor, /* Tokenizer cursor */
const char **ppToken, int *pnBytes, /* OUT: Normalized text for token */ const char** ppToken, int* pnBytes, /* OUT: Normalized text for token */
int *piStartOffset, /* OUT: Byte offset of token in input buffer */ int* piStartOffset, /* OUT: Byte offset of token in input buffer */
int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */ int* piEndOffset, /* OUT: Byte offset of end of token in input buffer */
int *piPosition); /* OUT: Number of tokens returned before this one */ int* piPosition); /* OUT: Number of tokens returned before this one */
}; };
struct sqlite3_tokenizer { struct sqlite3_tokenizer {
const sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */ const sqlite3_tokenizer_module* pModule; /* The module for this tokenizer */
/* Tokenizer implementations will typically add additional fields */ /* Tokenizer implementations will typically add additional fields */
}; };
struct sqlite3_tokenizer_cursor { struct sqlite3_tokenizer_cursor {
sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */ sqlite3_tokenizer* pTokenizer; /* Tokenizer for this cursor. */
/* Tokenizer implementations will typically add additional fields */ /* Tokenizer implementations will typically add additional fields */
}; };
sqlite3_tokenizer_module* Database::sFTSTokenizer = nullptr; sqlite3_tokenizer_module* Database::sFTSTokenizer = nullptr;
int Database::FTSCreate(int argc, const char* const* argv,
int Database::FTSCreate(int argc, const char* const* argv, sqlite3_tokenizer** tokenizer) { sqlite3_tokenizer** tokenizer) {
*tokenizer = reinterpret_cast<sqlite3_tokenizer*>(new UnicodeTokenizer); *tokenizer = reinterpret_cast<sqlite3_tokenizer*>(new UnicodeTokenizer);
return SQLITE_OK; return SQLITE_OK;
} }
int Database::FTSDestroy(sqlite3_tokenizer* tokenizer) { int Database::FTSDestroy(sqlite3_tokenizer* tokenizer) {
UnicodeTokenizer* real_tokenizer = reinterpret_cast<UnicodeTokenizer*>(tokenizer); UnicodeTokenizer* real_tokenizer =
reinterpret_cast<UnicodeTokenizer*>(tokenizer);
delete real_tokenizer; delete real_tokenizer;
return SQLITE_OK; return SQLITE_OK;
} }
int Database::FTSOpen( int Database::FTSOpen(sqlite3_tokenizer* pTokenizer, const char* input,
sqlite3_tokenizer* pTokenizer, int bytes, sqlite3_tokenizer_cursor** cursor) {
const char* input,
int bytes,
sqlite3_tokenizer_cursor** cursor) {
UnicodeTokenizerCursor* new_cursor = new UnicodeTokenizerCursor; UnicodeTokenizerCursor* new_cursor = new UnicodeTokenizerCursor;
new_cursor->pTokenizer = pTokenizer; new_cursor->pTokenizer = pTokenizer;
new_cursor->position = 0; new_cursor->position = 0;
@ -163,20 +157,18 @@ int Database::FTSOpen(
} }
int Database::FTSClose(sqlite3_tokenizer_cursor* cursor) { int Database::FTSClose(sqlite3_tokenizer_cursor* cursor) {
UnicodeTokenizerCursor* real_cursor = reinterpret_cast<UnicodeTokenizerCursor*>(cursor); UnicodeTokenizerCursor* real_cursor =
reinterpret_cast<UnicodeTokenizerCursor*>(cursor);
delete real_cursor; delete real_cursor;
return SQLITE_OK; return SQLITE_OK;
} }
int Database::FTSNext( int Database::FTSNext(sqlite3_tokenizer_cursor* cursor, const char** token,
sqlite3_tokenizer_cursor* cursor, int* bytes, int* start_offset, int* end_offset,
const char** token, int* position) {
int* bytes, UnicodeTokenizerCursor* real_cursor =
int* start_offset, reinterpret_cast<UnicodeTokenizerCursor*>(cursor);
int* end_offset,
int* position) {
UnicodeTokenizerCursor* real_cursor = reinterpret_cast<UnicodeTokenizerCursor*>(cursor);
QList<Token> tokens = real_cursor->tokens; QList<Token> tokens = real_cursor->tokens;
if (real_cursor->position >= tokens.size()) { if (real_cursor->position >= tokens.size()) {
@ -196,7 +188,6 @@ int Database::FTSNext(
return SQLITE_OK; return SQLITE_OK;
} }
void Database::StaticInit() { void Database::StaticInit() {
sFTSTokenizer = new sqlite3_tokenizer_module; sFTSTokenizer = new sqlite3_tokenizer_module;
sFTSTokenizer->iVersion = 0; sFTSTokenizer->iVersion = 0;
@ -208,24 +199,24 @@ void Database::StaticInit() {
return; return;
} }
Database::Database(Application* app, QObject* parent, const QString& database_name) Database::Database(Application* app, QObject* parent,
: QObject(parent), const QString& database_name)
app_(app), : QObject(parent),
mutex_(QMutex::Recursive), app_(app),
injected_database_name_(database_name), mutex_(QMutex::Recursive),
query_hash_(0), injected_database_name_(database_name),
startup_schema_version_(-1) query_hash_(0),
{ startup_schema_version_(-1) {
{ {
QMutexLocker l(&sNextConnectionIdMutex); QMutexLocker l(&sNextConnectionIdMutex);
connection_id_ = sNextConnectionId++; connection_id_ = sNextConnectionId++;
} }
directory_ = QDir::toNativeSeparators( directory_ =
Utilities::GetConfigPath(Utilities::Path_Root)); QDir::toNativeSeparators(Utilities::GetConfigPath(Utilities::Path_Root));
attached_databases_["jamendo"] = AttachedDatabase( attached_databases_["jamendo"] = AttachedDatabase(
directory_ + "/jamendo.db", ":/schema/jamendo.sql", false); directory_ + "/jamendo.db", ":/schema/jamendo.sql", false);
QMutexLocker l(&mutex_); QMutexLocker l(&mutex_);
Connect(); Connect();
@ -241,9 +232,8 @@ QSqlDatabase Database::Connect() {
} }
} }
const QString connection_id = const QString connection_id = QString("%1_thread_%2").arg(connection_id_).arg(
QString("%1_thread_%2").arg(connection_id_).arg( reinterpret_cast<quint64>(QThread::currentThread()));
reinterpret_cast<quint64>(QThread::currentThread()));
// Try to find an existing connection for this thread // Try to find an existing connection for this thread
QSqlDatabase db = QSqlDatabase::database(connection_id); QSqlDatabase db = QSqlDatabase::database(connection_id);
@ -269,8 +259,9 @@ QSqlDatabase Database::Connect() {
{ {
QSqlQuery set_fts_tokenizer("SELECT fts3_tokenizer(:name, :pointer)", db); QSqlQuery set_fts_tokenizer("SELECT fts3_tokenizer(:name, :pointer)", db);
set_fts_tokenizer.bindValue(":name", "unicode"); set_fts_tokenizer.bindValue(":name", "unicode");
set_fts_tokenizer.bindValue(":pointer", QByteArray( set_fts_tokenizer.bindValue(
reinterpret_cast<const char*>(&sFTSTokenizer), sizeof(&sFTSTokenizer))); ":pointer", QByteArray(reinterpret_cast<const char*>(&sFTSTokenizer),
sizeof(&sFTSTokenizer)));
if (!set_fts_tokenizer.exec()) { if (!set_fts_tokenizer.exec()) {
qLog(Warning) << "Couldn't register FTS3 tokenizer"; qLog(Warning) << "Couldn't register FTS3 tokenizer";
} }
@ -288,15 +279,15 @@ QSqlDatabase Database::Connect() {
for (const QString& key : attached_databases_.keys()) { for (const QString& key : attached_databases_.keys()) {
QString filename = attached_databases_[key].filename_; QString filename = attached_databases_[key].filename_;
if (!injected_database_name_.isNull()) if (!injected_database_name_.isNull()) filename = injected_database_name_;
filename = injected_database_name_;
// Attach the db // Attach the db
QSqlQuery q("ATTACH DATABASE :filename AS :alias", db); QSqlQuery q("ATTACH DATABASE :filename AS :alias", db);
q.bindValue(":filename", filename); q.bindValue(":filename", filename);
q.bindValue(":alias", key); q.bindValue(":alias", key);
if (!q.exec()) { if (!q.exec()) {
qFatal("Couldn't attach external database '%s'", key.toAscii().constData()); qFatal("Couldn't attach external database '%s'",
key.toAscii().constData());
} }
} }
@ -311,8 +302,10 @@ QSqlDatabase Database::Connect() {
attached_databases_[key].schema_.isEmpty()) attached_databases_[key].schema_.isEmpty())
continue; continue;
// Find out if there are any tables in this database // Find out if there are any tables in this database
QSqlQuery q(QString("SELECT ROWID FROM %1.sqlite_master" QSqlQuery q(QString(
" WHERE type='table'").arg(key), db); "SELECT ROWID FROM %1.sqlite_master"
" WHERE type='table'").arg(key),
db);
if (!q.exec() || !q.next()) { if (!q.exec() || !q.next()) {
q.finish(); q.finish();
ExecSchemaCommandsFromFile(db, attached_databases_[key].schema_, 0); ExecSchemaCommandsFromFile(db, attached_databases_[key].schema_, 0);
@ -327,8 +320,7 @@ void Database::UpdateMainSchema(QSqlDatabase* db) {
int schema_version = 0; int schema_version = 0;
{ {
QSqlQuery q("SELECT version FROM schema_version", *db); QSqlQuery q("SELECT version FROM schema_version", *db);
if (q.next()) if (q.next()) schema_version = q.value(0).toInt();
schema_version = q.value(0).toInt();
// Implicit invocation of ~QSqlQuery() when leaving the scope // Implicit invocation of ~QSqlQuery() when leaving the scope
// to release any remaining database locks! // to release any remaining database locks!
} }
@ -336,12 +328,13 @@ void Database::UpdateMainSchema(QSqlDatabase* db) {
startup_schema_version_ = schema_version; startup_schema_version_ = schema_version;
if (schema_version > kSchemaVersion) { if (schema_version > kSchemaVersion) {
qLog(Warning) << "The database schema (version" << schema_version << ") is newer than I was expecting"; qLog(Warning) << "The database schema (version" << schema_version
<< ") is newer than I was expecting";
return; return;
} }
if (schema_version < kSchemaVersion) { if (schema_version < kSchemaVersion) {
// Update the schema // Update the schema
for (int v = schema_version+1; v <= kSchemaVersion; ++v) { for (int v = schema_version + 1; v <= kSchemaVersion; ++v) {
UpdateDatabaseSchema(v, *db); UpdateDatabaseSchema(v, *db);
} }
} }
@ -384,8 +377,8 @@ void Database::AttachDatabase(const QString& database_name,
attached_databases_[database_name] = database; attached_databases_[database_name] = database;
} }
void Database::AttachDatabaseOnDbConnection(const QString &database_name, void Database::AttachDatabaseOnDbConnection(const QString& database_name,
const AttachedDatabase &database, const AttachedDatabase& database,
QSqlDatabase& db) { QSqlDatabase& db) {
AttachDatabase(database_name, database); AttachDatabase(database_name, database);
@ -394,7 +387,8 @@ void Database::AttachDatabaseOnDbConnection(const QString &database_name,
q.bindValue(":filename", database.filename_); q.bindValue(":filename", database.filename_);
q.bindValue(":alias", database_name); q.bindValue(":alias", database_name);
if (!q.exec()) { if (!q.exec()) {
qFatal("Couldn't attach external database '%s'", database_name.toAscii().constData()); qFatal("Couldn't attach external database '%s'",
database_name.toAscii().constData());
} }
} }
@ -414,7 +408,7 @@ void Database::DetachDatabase(const QString& database_name) {
attached_databases_.remove(database_name); attached_databases_.remove(database_name);
} }
void Database::UpdateDatabaseSchema(int version, QSqlDatabase &db) { void Database::UpdateDatabaseSchema(int version, QSqlDatabase& db) {
QString filename; QString filename;
if (version == 0) if (version == 0)
filename = ":/schema/schema.sql"; filename = ":/schema/schema.sql";
@ -434,20 +428,22 @@ void Database::UpdateDatabaseSchema(int version, QSqlDatabase &db) {
UrlEncodeFilenameColumn(table, db); UrlEncodeFilenameColumn(table, db);
} }
} }
qLog(Debug) << "Applying database schema update" << version qLog(Debug) << "Applying database schema update" << version << "from"
<< "from" << filename; << filename;
ExecSchemaCommandsFromFile(db, filename, version - 1, true); ExecSchemaCommandsFromFile(db, filename, version - 1, true);
t.Commit(); t.Commit();
} else { } else {
qLog(Debug) << "Applying database schema update" << version qLog(Debug) << "Applying database schema update" << version << "from"
<< "from" << filename; << filename;
ExecSchemaCommandsFromFile(db, filename, version - 1); ExecSchemaCommandsFromFile(db, filename, version - 1);
} }
} }
void Database::UrlEncodeFilenameColumn(const QString& table, QSqlDatabase& db) { void Database::UrlEncodeFilenameColumn(const QString& table, QSqlDatabase& db) {
QSqlQuery select(QString("SELECT ROWID, filename FROM %1").arg(table), db); QSqlQuery select(QString("SELECT ROWID, filename FROM %1").arg(table), db);
QSqlQuery update(QString("UPDATE %1 SET filename=:filename WHERE ROWID=:id").arg(table), db); QSqlQuery update(
QString("UPDATE %1 SET filename=:filename WHERE ROWID=:id").arg(table),
db);
select.exec(); select.exec();
if (CheckErrors(select)) return; if (CheckErrors(select)) return;
while (select.next()) { while (select.next()) {
@ -475,16 +471,12 @@ void Database::ExecSchemaCommandsFromFile(QSqlDatabase& db,
QFile schema_file(filename); QFile schema_file(filename);
if (!schema_file.open(QIODevice::ReadOnly)) if (!schema_file.open(QIODevice::ReadOnly))
qFatal("Couldn't open schema file %s", filename.toUtf8().constData()); qFatal("Couldn't open schema file %s", filename.toUtf8().constData());
ExecSchemaCommands(db, ExecSchemaCommands(db, QString::fromUtf8(schema_file.readAll()),
QString::fromUtf8(schema_file.readAll()), schema_version, in_transaction);
schema_version,
in_transaction);
} }
void Database::ExecSchemaCommands(QSqlDatabase& db, void Database::ExecSchemaCommands(QSqlDatabase& db, const QString& schema,
const QString& schema, int schema_version, bool in_transaction) {
int schema_version,
bool in_transaction) {
// Run each command // Run each command
const QStringList commands(schema.split(QRegExp("; *\n\n"))); const QStringList commands(schema.split(QRegExp("; *\n\n")));
@ -530,8 +522,7 @@ void Database::ExecSongTablesCommands(QSqlDatabase& db,
} }
} else { } else {
QSqlQuery query(db.exec(command)); QSqlQuery query(db.exec(command));
if (CheckErrors(query)) if (CheckErrors(query)) qFatal("Unable to update music library database");
qFatal("Unable to update music library database");
} }
} }
} }
@ -541,14 +532,17 @@ QStringList Database::SongsTables(QSqlDatabase& db, int schema_version) const {
// look for the tables in the main db // look for the tables in the main db
for (const QString& table : db.tables()) { for (const QString& table : db.tables()) {
if (table == "songs" || table.endsWith("_songs")) if (table == "songs" || table.endsWith("_songs")) ret << table;
ret << table;
} }
// look for the tables in attached dbs // look for the tables in attached dbs
for (const QString& key : attached_databases_.keys()) { for (const QString& key : attached_databases_.keys()) {
QSqlQuery q(QString("SELECT NAME FROM %1.sqlite_master" QSqlQuery q(
" WHERE type='table' AND name='songs' OR name LIKE '%songs'").arg(key), db); QString(
"SELECT NAME FROM %1.sqlite_master"
" WHERE type='table' AND name='songs' OR name LIKE '%songs'")
.arg(key),
db);
if (q.exec()) { if (q.exec()) {
while (q.next()) { while (q.next()) {
QString tab_name = key + "." + q.value(0).toString(); QString tab_name = key + "." + q.value(0).toString();
@ -595,9 +589,11 @@ bool Database::IntegrityCheck(QSqlDatabase db) {
break; break;
} else { } else {
if (!error_reported) { if (!error_reported) {
app_->AddError(tr("Database corruption detected. Please read " app_->AddError(
"https://code.google.com/p/clementine-player/wiki/DatabaseCorruption " tr("Database corruption detected. Please read "
"for instructions on how to recover your database")); "https://code.google.com/p/clementine-player/wiki/"
"DatabaseCorruption "
"for instructions on how to recover your database"));
} }
app_->AddError("Database: " + message); app_->AddError("Database: " + message);
error_reported = true; error_reported = true;
@ -621,17 +617,16 @@ void Database::DoBackup() {
} }
} }
bool Database::OpenDatabase(const QString& filename, sqlite3** connection) const { bool Database::OpenDatabase(const QString& filename,
sqlite3** connection) const {
int ret = sqlite3_open(filename.toUtf8(), connection); int ret = sqlite3_open(filename.toUtf8(), connection);
if (ret != 0) { if (ret != 0) {
if (*connection) { if (*connection) {
const char* error_message = sqlite3_errmsg(*connection); const char* error_message = sqlite3_errmsg(*connection);
qLog(Error) << "Failed to open database for backup:" qLog(Error) << "Failed to open database for backup:" << filename
<< filename
<< error_message; << error_message;
} else { } else {
qLog(Error) << "Failed to open database for backup:" qLog(Error) << "Failed to open database for backup:" << filename;
<< filename;
} }
return false; return false;
} }
@ -641,7 +636,8 @@ bool Database::OpenDatabase(const QString& filename, sqlite3** connection) const
void Database::BackupFile(const QString& filename) { void Database::BackupFile(const QString& filename) {
qLog(Debug) << "Starting database backup"; qLog(Debug) << "Starting database backup";
QString dest_filename = QString("%1.bak").arg(filename); QString dest_filename = QString("%1.bak").arg(filename);
const int task_id = app_->task_manager()->StartTask(tr("Backing up database")); const int task_id =
app_->task_manager()->StartTask(tr("Backing up database"));
sqlite3* source_connection = nullptr; sqlite3* source_connection = nullptr;
sqlite3* dest_connection = nullptr; sqlite3* dest_connection = nullptr;
@ -651,7 +647,8 @@ void Database::BackupFile(const QString& filename) {
sqlite3_close(source_connection); sqlite3_close(source_connection);
sqlite3_close(dest_connection); sqlite3_close(dest_connection);
app_->task_manager()->SetTaskFinished(task_id); app_->task_manager()->SetTaskFinished(task_id);
} BOOST_SCOPE_EXIT_END }
BOOST_SCOPE_EXIT_END
bool success = OpenDatabase(filename, &source_connection); bool success = OpenDatabase(filename, &source_connection);
if (!success) { if (!success) {
@ -663,9 +660,8 @@ void Database::BackupFile(const QString& filename) {
return; return;
} }
sqlite3_backup* backup = sqlite3_backup_init( sqlite3_backup* backup =
dest_connection, "main", sqlite3_backup_init(dest_connection, "main", source_connection, "main");
source_connection, "main");
if (!backup) { if (!backup) {
const char* error_message = sqlite3_errmsg(dest_connection); const char* error_message = sqlite3_errmsg(dest_connection);
qLog(Error) << "Failed to start database backup:" << error_message; qLog(Error) << "Failed to start database backup:" << error_message;

View File

@ -34,7 +34,6 @@ extern "C" {
struct sqlite3_tokenizer; struct sqlite3_tokenizer;
struct sqlite3_tokenizer_cursor; struct sqlite3_tokenizer_cursor;
struct sqlite3_tokenizer_module; struct sqlite3_tokenizer_module;
} }
class Application; class Application;
@ -48,8 +47,9 @@ class Database : public QObject {
struct AttachedDatabase { struct AttachedDatabase {
AttachedDatabase() {} AttachedDatabase() {}
AttachedDatabase(const QString& filename, const QString& schema, bool is_temporary) AttachedDatabase(const QString& filename, const QString& schema,
: filename_(filename), schema_(schema), is_temporary_(is_temporary) {} bool is_temporary)
: filename_(filename), schema_(schema), is_temporary_(is_temporary) {}
QString filename_; QString filename_;
QString schema_; QString schema_;
@ -65,21 +65,20 @@ class Database : public QObject {
QMutex* Mutex() { return &mutex_; } QMutex* Mutex() { return &mutex_; }
void RecreateAttachedDb(const QString& database_name); void RecreateAttachedDb(const QString& database_name);
void ExecSchemaCommands(QSqlDatabase& db, void ExecSchemaCommands(QSqlDatabase& db, const QString& schema,
const QString& schema, int schema_version, bool in_transaction = false);
int schema_version,
bool in_transaction = false);
int startup_schema_version() const { return startup_schema_version_; } int startup_schema_version() const { return startup_schema_version_; }
int current_schema_version() const { return kSchemaVersion; } int current_schema_version() const { return kSchemaVersion; }
void AttachDatabase(const QString& database_name, const AttachedDatabase& database); void AttachDatabase(const QString& database_name,
const AttachedDatabase& database);
void AttachDatabaseOnDbConnection(const QString& database_name, void AttachDatabaseOnDbConnection(const QString& database_name,
const AttachedDatabase& database, const AttachedDatabase& database,
QSqlDatabase& db); QSqlDatabase& db);
void DetachDatabase(const QString& database_name); void DetachDatabase(const QString& database_name);
signals: signals:
void Error(const QString& message); void Error(const QString& message);
public slots: public slots:
@ -88,12 +87,10 @@ class Database : public QObject {
private: private:
void UpdateMainSchema(QSqlDatabase* db); void UpdateMainSchema(QSqlDatabase* db);
void ExecSchemaCommandsFromFile(QSqlDatabase& db, void ExecSchemaCommandsFromFile(QSqlDatabase& db, const QString& filename,
const QString& filename,
int schema_version, int schema_version,
bool in_transaction = false); bool in_transaction = false);
void ExecSongTablesCommands(QSqlDatabase& db, void ExecSongTablesCommands(QSqlDatabase& db, const QStringList& song_tables,
const QStringList& song_tables,
const QStringList& commands); const QStringList& commands);
void UpdateDatabaseSchema(int version, QSqlDatabase& db); void UpdateDatabaseSchema(int version, QSqlDatabase& db);
@ -137,26 +134,23 @@ class Database : public QObject {
// Do static initialisation like loading sqlite functions. // Do static initialisation like loading sqlite functions.
static void StaticInit(); static void StaticInit();
typedef int (*Sqlite3CreateFunc) ( typedef int (*Sqlite3CreateFunc)(sqlite3*, const char*, int, int, void*,
sqlite3*, const char*, int, int, void*, void (*)(sqlite3_context*, int,
void (*) (sqlite3_context*, int, sqlite3_value**), sqlite3_value**),
void (*) (sqlite3_context*, int, sqlite3_value**), void (*)(sqlite3_context*, int,
void (*) (sqlite3_context*)); sqlite3_value**),
void (*)(sqlite3_context*));
static sqlite3_tokenizer_module* sFTSTokenizer; static sqlite3_tokenizer_module* sFTSTokenizer;
static int FTSCreate(int argc, const char* const* argv, sqlite3_tokenizer** tokenizer); static int FTSCreate(int argc, const char* const* argv,
sqlite3_tokenizer** tokenizer);
static int FTSDestroy(sqlite3_tokenizer* tokenizer); static int FTSDestroy(sqlite3_tokenizer* tokenizer);
static int FTSOpen(sqlite3_tokenizer* tokenizer, static int FTSOpen(sqlite3_tokenizer* tokenizer, const char* input, int bytes,
const char* input,
int bytes,
sqlite3_tokenizer_cursor** cursor); sqlite3_tokenizer_cursor** cursor);
static int FTSClose(sqlite3_tokenizer_cursor* cursor); static int FTSClose(sqlite3_tokenizer_cursor* cursor);
static int FTSNext(sqlite3_tokenizer_cursor* cursor, static int FTSNext(sqlite3_tokenizer_cursor* cursor, const char** token,
const char** token, int* bytes, int* start_offset, int* end_offset,
int* bytes,
int* start_offset,
int* end_offset,
int* position); int* position);
struct Token { struct Token {
Token(const QString& token, int start, int end); Token(const QString& token, int start, int end);
@ -182,11 +176,11 @@ class Database : public QObject {
class MemoryDatabase : public Database { class MemoryDatabase : public Database {
public: public:
MemoryDatabase(Application* app, QObject* parent = 0) MemoryDatabase(Application* app, QObject* parent = 0)
: Database(app, parent, ":memory:") {} : Database(app, parent, ":memory:") {}
~MemoryDatabase() { ~MemoryDatabase() {
// Make sure Qt doesn't reuse the same database // Make sure Qt doesn't reuse the same database
QSqlDatabase::removeDatabase(Connect().connectionName()); QSqlDatabase::removeDatabase(Connect().connectionName());
} }
}; };
#endif // DATABASE_H #endif // DATABASE_H

View File

@ -29,22 +29,19 @@ const int DeleteFiles::kBatchSize = 50;
DeleteFiles::DeleteFiles(TaskManager* task_manager, DeleteFiles::DeleteFiles(TaskManager* task_manager,
std::shared_ptr<MusicStorage> storage) std::shared_ptr<MusicStorage> storage)
: thread_(nullptr), : thread_(nullptr),
task_manager_(task_manager), task_manager_(task_manager),
storage_(storage), storage_(storage),
started_(false), started_(false),
task_id_(0), task_id_(0),
progress_(0) progress_(0) {
{
original_thread_ = thread(); original_thread_ = thread();
} }
DeleteFiles::~DeleteFiles() { DeleteFiles::~DeleteFiles() {}
}
void DeleteFiles::Start(const SongList& songs) { void DeleteFiles::Start(const SongList& songs) {
if (thread_) if (thread_) return;
return;
songs_ = songs; songs_ = songs;
@ -60,7 +57,7 @@ void DeleteFiles::Start(const SongList& songs) {
void DeleteFiles::Start(const QStringList& filenames) { void DeleteFiles::Start(const QStringList& filenames) {
SongList songs; SongList songs;
foreach (const QString& filename, filenames) { foreach(const QString & filename, filenames) {
Song song; Song song;
song.set_url(QUrl::fromLocalFile(filename)); song.set_url(QUrl::fromLocalFile(filename));
songs << song; songs << song;
@ -98,7 +95,7 @@ void DeleteFiles::ProcessSomeFiles() {
// We process files in batches so we can be cancelled part-way through. // We process files in batches so we can be cancelled part-way through.
const int n = qMin(songs_.count(), progress_ + kBatchSize); const int n = qMin(songs_.count(), progress_ + kBatchSize);
for ( ; progress_<n ; ++progress_) { for (; progress_ < n; ++progress_) {
task_manager_->SetTaskProgress(task_id_, progress_, songs_.count()); task_manager_->SetTaskProgress(task_id_, progress_, songs_.count());
const Song& song = songs_[progress_]; const Song& song = songs_[progress_];

View File

@ -30,7 +30,7 @@ class TaskManager;
class DeleteFiles : public QObject { class DeleteFiles : public QObject {
Q_OBJECT Q_OBJECT
public: public:
DeleteFiles(TaskManager* task_manager, std::shared_ptr<MusicStorage> storage); DeleteFiles(TaskManager* task_manager, std::shared_ptr<MusicStorage> storage);
~DeleteFiles(); ~DeleteFiles();
@ -42,10 +42,10 @@ public:
signals: signals:
void Finished(const SongList& songs_with_errors); void Finished(const SongList& songs_with_errors);
private slots: private slots:
void ProcessSomeFiles(); void ProcessSomeFiles();
private: private:
QThread* thread_; QThread* thread_;
QThread* original_thread_; QThread* original_thread_;
TaskManager* task_manager_; TaskManager* task_manager_;
@ -61,4 +61,4 @@ private:
SongList songs_with_errors_; SongList songs_with_errors_;
}; };
#endif // DELETEFILES_H #endif // DELETEFILES_H

View File

@ -22,221 +22,182 @@
#include <string.h> #include <string.h>
#include "fht.h" #include "fht.h"
FHT::FHT(int n) : m_buf(0), m_tab(0), m_log(0) {
if (n < 3) {
m_num = 0;
m_exp2 = -1;
return;
}
m_exp2 = n;
m_num = 1 << n;
if (n > 3) {
m_buf = new float[m_num];
m_tab = new float[m_num * 2];
makeCasTable();
}
}
FHT::FHT(int n) : FHT::~FHT() {
m_buf(0), delete[] m_buf;
m_tab(0), delete[] m_tab;
m_log(0) delete[] m_log;
{ }
if (n < 3) {
m_num = 0; void FHT::makeCasTable(void) {
m_exp2 = -1; float d, *costab, *sintab;
return; int ul, ndiv2 = m_num / 2;
for (costab = m_tab, sintab = m_tab + m_num / 2 + 1, ul = 0; ul < m_num;
ul++) {
d = M_PI * ul / ndiv2;
*costab = *sintab = cos(d);
costab += 2, sintab += 2;
if (sintab > m_tab + m_num * 2) sintab = m_tab + 1;
}
}
float* FHT::copy(float* d, float* s) {
return (float*)memcpy(d, s, m_num * sizeof(float));
}
float* FHT::clear(float* d) {
return (float*)memset(d, 0, m_num * sizeof(float));
}
void FHT::scale(float* p, float d) {
for (int i = 0; i < (m_num / 2); i++) *p++ *= d;
}
void FHT::ewma(float* d, float* s, float w) {
for (int i = 0; i < (m_num / 2); i++, d++, s++) *d = *d * w + *s * (1 - w);
}
void FHT::logSpectrum(float* out, float* p) {
int n = m_num / 2, i, j, k, *r;
if (!m_log) {
m_log = new int[n];
float f = n / log10((double)n);
for (i = 0, r = m_log; i < n; i++, r++) {
j = int(rint(log10(i + 1.0) * f));
*r = j >= n ? n - 1 : j;
} }
m_exp2 = n; }
m_num = 1 << n; semiLogSpectrum(p);
if (n > 3) { *out++ = *p = *p / 100;
m_buf = new float[m_num]; for (k = i = 1, r = m_log; i < n; i++) {
m_tab = new float[m_num * 2]; j = *r++;
makeCasTable(); if (i == j)
*out++ = p[i];
else {
float base = p[k - 1];
float step = (p[j] - base) / (j - (k - 1));
for (float corr = 0; k <= j; k++, corr += step) *out++ = base + corr;
} }
}
} }
void FHT::semiLogSpectrum(float* p) {
FHT::~FHT() float e;
{ power2(p);
delete[] m_buf; for (int i = 0; i < (m_num / 2); i++, p++) {
delete[] m_tab; e = 10.0 * log10(sqrt(*p * .5));
delete[] m_log; *p = e < 0 ? 0 : e;
}
} }
void FHT::spectrum(float* p) {
void FHT::makeCasTable(void) power2(p);
{ for (int i = 0; i < (m_num / 2); i++, p++) *p = (float)sqrt(*p * .5);
float d, *costab, *sintab;
int ul, ndiv2 = m_num / 2;
for (costab = m_tab, sintab = m_tab + m_num / 2 + 1, ul = 0; ul < m_num; ul++) {
d = M_PI * ul / ndiv2;
*costab = *sintab = cos(d);
costab += 2, sintab += 2;
if (sintab > m_tab + m_num * 2)
sintab = m_tab + 1;
}
} }
void FHT::power(float* p) {
float* FHT::copy(float *d, float *s) power2(p);
{ for (int i = 0; i < (m_num / 2); i++) *p++ *= .5;
return (float *)memcpy(d, s, m_num * sizeof(float));
} }
void FHT::power2(float* p) {
int i;
float* q;
_transform(p, m_num, 0);
float* FHT::clear(float *d) *p = (*p * *p), *p += *p, p++;
{
return (float *)memset(d, 0, m_num * sizeof(float)); for (i = 1, q = p + m_num - 2; i < (m_num / 2); i++, --q)
*p = (*p * *p) + (*q * *q), p++;
} }
void FHT::transform(float* p) {
void FHT::scale(float *p, float d) if (m_num == 8)
{ transform8(p);
for (int i = 0; i < (m_num / 2); i++) else
*p++ *= d;
}
void FHT::ewma(float *d, float *s, float w)
{
for (int i = 0; i < (m_num / 2); i++, d++, s++)
*d = *d * w + *s * (1 - w);
}
void FHT::logSpectrum(float *out, float *p)
{
int n = m_num / 2, i, j, k, *r;
if (!m_log) {
m_log = new int[n];
float f = n / log10((double)n);
for (i = 0, r = m_log; i < n; i++, r++) {
j = int(rint(log10(i + 1.0) * f));
*r = j >= n ? n - 1 : j;
}
}
semiLogSpectrum(p);
*out++ = *p = *p / 100;
for (k = i = 1, r = m_log; i < n; i++) {
j = *r++;
if (i == j)
*out++ = p[i];
else {
float base = p[k - 1];
float step = (p[j] - base) / (j - (k - 1));
for (float corr = 0; k <= j; k++, corr += step)
*out++ = base + corr;
}
}
}
void FHT::semiLogSpectrum(float *p)
{
float e;
power2(p);
for (int i = 0; i < (m_num / 2); i++, p++) {
e = 10.0 * log10(sqrt(*p * .5));
*p = e < 0 ? 0 : e;
}
}
void FHT::spectrum(float *p)
{
power2(p);
for (int i = 0; i < (m_num / 2); i++, p++)
*p = (float)sqrt(*p * .5);
}
void FHT::power(float *p)
{
power2(p);
for (int i = 0; i < (m_num / 2); i++)
*p++ *= .5;
}
void FHT::power2(float *p)
{
int i;
float *q;
_transform(p, m_num, 0); _transform(p, m_num, 0);
*p = (*p * *p), *p += *p, p++;
for (i = 1, q = p + m_num - 2; i < (m_num / 2); i++, --q)
*p = (*p * *p) + (*q * *q), p++;
} }
void FHT::transform8(float* p) {
float a, b, c, d, e, f, g, h, b_f2, d_h2;
float a_c_eg, a_ce_g, ac_e_g, aceg, b_df_h, bdfh;
void FHT::transform(float *p) a = *p++, b = *p++, c = *p++, d = *p++;
{ e = *p++, f = *p++, g = *p++, h = *p;
if (m_num == 8) b_f2 = (b - f) * M_SQRT2;
transform8(p); d_h2 = (d - h) * M_SQRT2;
else
_transform(p, m_num, 0); a_c_eg = a - c - e + g;
a_ce_g = a - c + e - g;
ac_e_g = a + c - e - g;
aceg = a + c + e + g;
b_df_h = b - d + f - h;
bdfh = b + d + f + h;
*p = a_c_eg - d_h2;
*--p = a_ce_g - b_df_h;
*--p = ac_e_g - b_f2;
*--p = aceg - bdfh;
*--p = a_c_eg + d_h2;
*--p = a_ce_g + b_df_h;
*--p = ac_e_g + b_f2;
*--p = aceg + bdfh;
} }
void FHT::_transform(float* p, int n, int k) {
if (n == 8) {
transform8(p + k);
return;
}
void FHT::transform8(float *p) int i, j, ndiv2 = n / 2;
{ float a, *t1, *t2, *t3, *t4, *ptab, *pp;
float a, b, c, d, e, f, g, h, b_f2, d_h2;
float a_c_eg, a_ce_g, ac_e_g, aceg, b_df_h, bdfh;
a = *p++, b = *p++, c = *p++, d = *p++; for (i = 0, t1 = m_buf, t2 = m_buf + ndiv2, pp = &p[k]; i < ndiv2; i++)
e = *p++, f = *p++, g = *p++, h = *p; *t1++ = *pp++, *t2++ = *pp++;
b_f2 = (b - f) * M_SQRT2;
d_h2 = (d - h) * M_SQRT2;
a_c_eg = a - c - e + g; memcpy(p + k, m_buf, sizeof(float) * n);
a_ce_g = a - c + e - g;
ac_e_g = a + c - e - g;
aceg = a + c + e + g;
b_df_h = b - d + f - h; _transform(p, ndiv2, k);
bdfh = b + d + f + h; _transform(p, ndiv2, k + ndiv2);
*p = a_c_eg - d_h2; j = m_num / ndiv2 - 1;
*--p = a_ce_g - b_df_h; t1 = m_buf;
*--p = ac_e_g - b_f2; t2 = t1 + ndiv2;
*--p = aceg - bdfh; t3 = p + k + ndiv2;
*--p = a_c_eg + d_h2; ptab = m_tab;
*--p = a_ce_g + b_df_h; pp = p + k;
*--p = ac_e_g + b_f2;
*--p = aceg + bdfh;
}
a = *ptab++ * *t3++;
a += *ptab * *pp;
ptab += j;
void FHT::_transform(float *p, int n, int k) *t1++ = *pp + a;
{ *t2++ = *pp++ - a;
if (n == 8) {
transform8(p + k);
return;
}
int i, j, ndiv2 = n / 2;
float a, *t1, *t2, *t3, *t4, *ptab, *pp;
for (i = 0, t1 = m_buf, t2 = m_buf + ndiv2, pp = &p[k]; i < ndiv2; i++)
*t1++ = *pp++, *t2++ = *pp++;
memcpy(p + k, m_buf, sizeof(float) * n);
_transform(p, ndiv2, k);
_transform(p, ndiv2, k + ndiv2);
j = m_num / ndiv2 - 1;
t1 = m_buf;
t2 = t1 + ndiv2;
t3 = p + k + ndiv2;
ptab = m_tab;
pp = p + k;
for (i = 1, t4 = p + k + n; i < ndiv2; i++, ptab += j) {
a = *ptab++ * *t3++; a = *ptab++ * *t3++;
a += *ptab * *pp; a += *ptab * *--t4;
ptab += j;
*t1++ = *pp + a; *t1++ = *pp + a;
*t2++ = *pp++ - a; *t2++ = *pp++ - a;
}
for (i = 1, t4 = p + k + n; i < ndiv2; i++, ptab += j) { memcpy(p + k, m_buf, sizeof(float) * n);
a = *ptab++ * *t3++;
a += *ptab * *--t4;
*t1++ = *pp + a;
*t2++ = *pp++ - a;
}
memcpy(p + k, m_buf, sizeof(float) * n);
} }

View File

@ -29,91 +29,90 @@
* *
* [1] Computer in Physics, Vol. 9, No. 4, Jul/Aug 1995 pp 373-379 * [1] Computer in Physics, Vol. 9, No. 4, Jul/Aug 1995 pp 373-379
*/ */
class FHT class FHT {
{ int m_exp2;
int m_exp2; int m_num;
int m_num; float* m_buf;
float *m_buf; float* m_tab;
float *m_tab; int* m_log;
int *m_log;
/** /**
* Create a table of "cas" (cosine and sine) values. * Create a table of "cas" (cosine and sine) values.
* Has only to be done in the constructor and saves from * Has only to be done in the constructor and saves from
* calculating the same values over and over while transforming. * calculating the same values over and over while transforming.
*/ */
void makeCasTable(); void makeCasTable();
/** /**
* Recursive in-place Hartley transform. For internal use only! * Recursive in-place Hartley transform. For internal use only!
*/ */
void _transform(float *, int, int); void _transform(float*, int, int);
public: public:
/** /**
* Prepare transform for data sets with @f$2^n@f$ numbers, whereby @f$n@f$ * Prepare transform for data sets with @f$2^n@f$ numbers, whereby @f$n@f$
* should be at least 3. Values of more than 3 need a trigonometry table. * should be at least 3. Values of more than 3 need a trigonometry table.
* @see makeCasTable() * @see makeCasTable()
*/ */
FHT(int); FHT(int);
~FHT(); ~FHT();
inline int sizeExp() const { return m_exp2; } inline int sizeExp() const { return m_exp2; }
inline int size() const { return m_num; } inline int size() const { return m_num; }
float *copy(float *, float *); float* copy(float*, float*);
float *clear(float *); float* clear(float*);
void scale(float *, float); void scale(float*, float);
/** /**
* Exponentially Weighted Moving Average (EWMA) filter. * Exponentially Weighted Moving Average (EWMA) filter.
* @param d is the filtered data. * @param d is the filtered data.
* @param s is fresh input. * @param s is fresh input.
* @param w is the weighting factor. * @param w is the weighting factor.
*/ */
void ewma(float *d, float *s, float w); void ewma(float* d, float* s, float w);
/** /**
* Logarithmic audio spectrum. Maps semi-logarithmic spectrum * Logarithmic audio spectrum. Maps semi-logarithmic spectrum
* to logarithmic frequency scale, interpolates missing values. * to logarithmic frequency scale, interpolates missing values.
* A logarithmic index map is calculated at the first run only. * A logarithmic index map is calculated at the first run only.
* @param p is the input array. * @param p is the input array.
* @param out is the spectrum. * @param out is the spectrum.
*/ */
void logSpectrum(float *out, float *p); void logSpectrum(float* out, float* p);
/** /**
* Semi-logarithmic audio spectrum. * Semi-logarithmic audio spectrum.
*/ */
void semiLogSpectrum(float *); void semiLogSpectrum(float*);
/** /**
* Fourier spectrum. * Fourier spectrum.
*/ */
void spectrum(float *); void spectrum(float*);
/** /**
* Calculates a mathematically correct FFT power spectrum. * Calculates a mathematically correct FFT power spectrum.
* If further scaling is applied later, use power2 instead * If further scaling is applied later, use power2 instead
* and factor the 0.5 in the final scaling factor. * and factor the 0.5 in the final scaling factor.
* @see FHT::power2() * @see FHT::power2()
*/ */
void power(float *); void power(float*);
/** /**
* Calculates an FFT power spectrum with doubled values as a * Calculates an FFT power spectrum with doubled values as a
* result. The values need to be multiplied by 0.5 to be exact. * result. The values need to be multiplied by 0.5 to be exact.
* Note that you only get @f$2^{n-1}@f$ power values for a data set * Note that you only get @f$2^{n-1}@f$ power values for a data set
* of @f$2^n@f$ input values. This is the fastest transform. * of @f$2^n@f$ input values. This is the fastest transform.
* @see FHT::power() * @see FHT::power()
*/ */
void power2(float *); void power2(float*);
/** /**
* Discrete Hartley transform of data sets with 8 values. * Discrete Hartley transform of data sets with 8 values.
*/ */
void transform8(float *); void transform8(float*);
void transform(float *); void transform(float*);
}; };
#endif #endif

View File

@ -23,17 +23,14 @@
#include <QUrl> #include <QUrl>
FilesystemMusicStorage::FilesystemMusicStorage(const QString& root) FilesystemMusicStorage::FilesystemMusicStorage(const QString& root)
: root_(root) : root_(root) {}
{
}
bool FilesystemMusicStorage::CopyToStorage(const CopyJob& job) { bool FilesystemMusicStorage::CopyToStorage(const CopyJob& job) {
const QFileInfo src = QFileInfo(job.source_); const QFileInfo src = QFileInfo(job.source_);
const QFileInfo dest = QFileInfo(root_ + "/" + job.destination_ ); const QFileInfo dest = QFileInfo(root_ + "/" + job.destination_);
// Don't do anything if the destination is the same as the source // Don't do anything if the destination is the same as the source
if (src == dest) if (src == dest) return true;
return true;
// Create directories as required // Create directories as required
QDir dir; QDir dir;
@ -43,8 +40,7 @@ bool FilesystemMusicStorage::CopyToStorage(const CopyJob& job) {
} }
// Remove the destination file if it exists and we want to overwrite // Remove the destination file if it exists and we want to overwrite
if (job.overwrite_ && dest.exists()) if (job.overwrite_ && dest.exists()) QFile::remove(dest.absoluteFilePath());
QFile::remove(dest.absoluteFilePath());
// Copy or move // Copy or move
if (job.remove_original_) if (job.remove_original_)

View File

@ -21,7 +21,7 @@
#include "musicstorage.h" #include "musicstorage.h"
class FilesystemMusicStorage : public virtual MusicStorage { class FilesystemMusicStorage : public virtual MusicStorage {
public: public:
FilesystemMusicStorage(const QString& root); FilesystemMusicStorage(const QString& root);
~FilesystemMusicStorage() {} ~FilesystemMusicStorage() {}
@ -30,8 +30,8 @@ public:
bool CopyToStorage(const CopyJob& job); bool CopyToStorage(const CopyJob& job);
bool DeleteFromStorage(const DeleteJob& job); bool DeleteFromStorage(const DeleteJob& job);
private: private:
QString root_; QString root_;
}; };
#endif // FILESYSTEMMUSICSTORAGE_H #endif // FILESYSTEMMUSICSTORAGE_H

View File

@ -24,10 +24,10 @@
#endif #endif
FileSystemWatcherInterface::FileSystemWatcherInterface(QObject* parent) FileSystemWatcherInterface::FileSystemWatcherInterface(QObject* parent)
: QObject(parent) { : QObject(parent) {}
}
FileSystemWatcherInterface* FileSystemWatcherInterface::Create(QObject* parent) { FileSystemWatcherInterface* FileSystemWatcherInterface::Create(
QObject* parent) {
FileSystemWatcherInterface* ret; FileSystemWatcherInterface* ret;
#ifdef Q_OS_DARWIN #ifdef Q_OS_DARWIN
ret = new MacFSListener(parent); ret = new MacFSListener(parent);

View File

@ -31,7 +31,7 @@ class FileSystemWatcherInterface : public QObject {
static FileSystemWatcherInterface* Create(QObject* parent = 0); static FileSystemWatcherInterface* Create(QObject* parent = 0);
signals: signals:
void PathChanged(const QString& path); void PathChanged(const QString& path);
}; };

View File

@ -20,16 +20,11 @@
#include "globalshortcuts.h" #include "globalshortcuts.h"
GlobalShortcutBackend::GlobalShortcutBackend(GlobalShortcuts* parent) GlobalShortcutBackend::GlobalShortcutBackend(GlobalShortcuts* parent)
: QObject(parent), : QObject(parent), manager_(parent), active_(false) {}
manager_(parent),
active_(false)
{
}
bool GlobalShortcutBackend::Register() { bool GlobalShortcutBackend::Register() {
bool ret = DoRegister(); bool ret = DoRegister();
if (ret) if (ret) active_ = true;
active_ = true;
return ret; return ret;
} }

View File

@ -25,7 +25,7 @@ class GlobalShortcuts;
class GlobalShortcutBackend : public QObject { class GlobalShortcutBackend : public QObject {
Q_OBJECT Q_OBJECT
public: public:
GlobalShortcutBackend(GlobalShortcuts* parent = 0); GlobalShortcutBackend(GlobalShortcuts* parent = 0);
virtual ~GlobalShortcutBackend() {} virtual ~GlobalShortcutBackend() {}
@ -37,7 +37,7 @@ public:
signals: signals:
void RegisterFinished(bool success); void RegisterFinished(bool success);
protected: protected:
virtual bool DoRegister() = 0; virtual bool DoRegister() = 0;
virtual void DoUnregister() = 0; virtual void DoUnregister() = 0;
@ -45,4 +45,4 @@ protected:
bool active_; bool active_;
}; };
#endif // GLOBALSHORTCUTBACKEND_H #endif // GLOBALSHORTCUTBACKEND_H

View File

@ -28,28 +28,32 @@
#include <QtDebug> #include <QtDebug>
#ifdef QT_DBUS_LIB #ifdef QT_DBUS_LIB
# include <QtDBus> #include <QtDBus>
#endif #endif
const char* GlobalShortcuts::kSettingsGroup = "Shortcuts"; const char* GlobalShortcuts::kSettingsGroup = "Shortcuts";
GlobalShortcuts::GlobalShortcuts(QWidget *parent) GlobalShortcuts::GlobalShortcuts(QWidget* parent)
: QWidget(parent), : QWidget(parent),
gnome_backend_(nullptr), gnome_backend_(nullptr),
system_backend_(nullptr), system_backend_(nullptr),
use_gnome_(false), use_gnome_(false),
rating_signals_mapper_(new QSignalMapper(this)) rating_signals_mapper_(new QSignalMapper(this)) {
{
settings_.beginGroup(kSettingsGroup); settings_.beginGroup(kSettingsGroup);
// Create actions // Create actions
AddShortcut("play", tr("Play"), SIGNAL(Play())); AddShortcut("play", tr("Play"), SIGNAL(Play()));
AddShortcut("pause", tr("Pause"), SIGNAL(Pause())); AddShortcut("pause", tr("Pause"), SIGNAL(Pause()));
AddShortcut("play_pause", tr("Play/Pause"), SIGNAL(PlayPause()), QKeySequence(Qt::Key_MediaPlay)); AddShortcut("play_pause", tr("Play/Pause"), SIGNAL(PlayPause()),
AddShortcut("stop", tr("Stop"), SIGNAL(Stop()), QKeySequence(Qt::Key_MediaStop)); QKeySequence(Qt::Key_MediaPlay));
AddShortcut("stop_after", tr("Stop playing after current track"), SIGNAL(StopAfter())); AddShortcut("stop", tr("Stop"), SIGNAL(Stop()),
AddShortcut("next_track", tr("Next track"), SIGNAL(Next()), QKeySequence(Qt::Key_MediaNext)); QKeySequence(Qt::Key_MediaStop));
AddShortcut("prev_track", tr("Previous track"), SIGNAL(Previous()), QKeySequence(Qt::Key_MediaPrevious)); AddShortcut("stop_after", tr("Stop playing after current track"),
SIGNAL(StopAfter()));
AddShortcut("next_track", tr("Next track"), SIGNAL(Next()),
QKeySequence(Qt::Key_MediaNext));
AddShortcut("prev_track", tr("Previous track"), SIGNAL(Previous()),
QKeySequence(Qt::Key_MediaPrevious));
AddShortcut("inc_volume", tr("Increase volume"), SIGNAL(IncVolume())); AddShortcut("inc_volume", tr("Increase volume"), SIGNAL(IncVolume()));
AddShortcut("dec_volume", tr("Decrease volume"), SIGNAL(DecVolume())); AddShortcut("dec_volume", tr("Decrease volume"), SIGNAL(DecVolume()));
AddShortcut("mute", tr("Mute"), SIGNAL(Mute())); AddShortcut("mute", tr("Mute"), SIGNAL(Mute()));
@ -57,19 +61,32 @@ GlobalShortcuts::GlobalShortcuts(QWidget *parent)
AddShortcut("seek_backward", tr("Seek backward"), SIGNAL(SeekBackward())); AddShortcut("seek_backward", tr("Seek backward"), SIGNAL(SeekBackward()));
AddShortcut("show_hide", tr("Show/Hide"), SIGNAL(ShowHide())); AddShortcut("show_hide", tr("Show/Hide"), SIGNAL(ShowHide()));
AddShortcut("show_osd", tr("Show OSD"), SIGNAL(ShowOSD())); AddShortcut("show_osd", tr("Show OSD"), SIGNAL(ShowOSD()));
AddShortcut("toggle_pretty_osd", tr("Toggle Pretty OSD"), SIGNAL(TogglePrettyOSD())); // Toggling possible only for pretty OSD AddShortcut(
AddShortcut("shuffle_mode", tr("Change shuffle mode"), SIGNAL(CycleShuffleMode())); "toggle_pretty_osd", tr("Toggle Pretty OSD"),
AddShortcut("repeat_mode", tr("Change repeat mode"), SIGNAL(CycleRepeatMode())); SIGNAL(TogglePrettyOSD())); // Toggling possible only for pretty OSD
AddShortcut("toggle_last_fm_scrobbling", tr("Enable/disable Last.fm scrobbling"), SIGNAL(ToggleScrobbling())); AddShortcut("shuffle_mode", tr("Change shuffle mode"),
SIGNAL(CycleShuffleMode()));
AddShortcut("repeat_mode", tr("Change repeat mode"),
SIGNAL(CycleRepeatMode()));
AddShortcut("toggle_last_fm_scrobbling",
tr("Enable/disable Last.fm scrobbling"),
SIGNAL(ToggleScrobbling()));
AddRatingShortcut("rate_zero_star", tr("Rate the current song 0 stars"), rating_signals_mapper_, 0); AddRatingShortcut("rate_zero_star", tr("Rate the current song 0 stars"),
AddRatingShortcut("rate_one_star", tr("Rate the current song 1 star"), rating_signals_mapper_, 1); rating_signals_mapper_, 0);
AddRatingShortcut("rate_two_star", tr("Rate the current song 2 stars"), rating_signals_mapper_, 2); AddRatingShortcut("rate_one_star", tr("Rate the current song 1 star"),
AddRatingShortcut("rate_three_star", tr("Rate the current song 3 stars"), rating_signals_mapper_, 3); rating_signals_mapper_, 1);
AddRatingShortcut("rate_four_star", tr("Rate the current song 4 stars"), rating_signals_mapper_, 4); AddRatingShortcut("rate_two_star", tr("Rate the current song 2 stars"),
AddRatingShortcut("rate_five_star", tr("Rate the current song 5 stars"), rating_signals_mapper_, 5); rating_signals_mapper_, 2);
AddRatingShortcut("rate_three_star", tr("Rate the current song 3 stars"),
rating_signals_mapper_, 3);
AddRatingShortcut("rate_four_star", tr("Rate the current song 4 stars"),
rating_signals_mapper_, 4);
AddRatingShortcut("rate_five_star", tr("Rate the current song 5 stars"),
rating_signals_mapper_, 5);
connect(rating_signals_mapper_, SIGNAL(mapped(int)), SIGNAL(RateCurrentSong(int))); connect(rating_signals_mapper_, SIGNAL(mapped(int)),
SIGNAL(RateCurrentSong(int)));
// Create backends - these do the actual shortcut registration // Create backends - these do the actual shortcut registration
gnome_backend_ = new GnomeGlobalShortcutBackend(this); gnome_backend_ = new GnomeGlobalShortcutBackend(this);
@ -98,8 +115,8 @@ void GlobalShortcuts::AddRatingShortcut(const QString& id, const QString& name,
mapper->setMapping(shortcut.action, rating); mapper->setMapping(shortcut.action, rating);
} }
GlobalShortcuts::Shortcut GlobalShortcuts::AddShortcut(const QString& id, const QString& name, GlobalShortcuts::Shortcut GlobalShortcuts::AddShortcut(
const QKeySequence& default_key) { const QString& id, const QString& name, const QKeySequence& default_key) {
Shortcut shortcut; Shortcut shortcut;
shortcut.action = new QAction(name, this); shortcut.action = new QAction(name, this);
QKeySequence key_sequence = QKeySequence::fromString( QKeySequence key_sequence = QKeySequence::fromString(
@ -122,7 +139,7 @@ bool GlobalShortcuts::IsGsdAvailable() const {
#ifdef QT_DBUS_LIB #ifdef QT_DBUS_LIB
return QDBusConnection::sessionBus().interface()->isServiceRegistered( return QDBusConnection::sessionBus().interface()->isServiceRegistered(
GnomeGlobalShortcutBackend::kGsdService); GnomeGlobalShortcutBackend::kGsdService);
#else // QT_DBUS_LIB #else // QT_DBUS_LIB
return false; return false;
#endif #endif
} }
@ -137,21 +154,19 @@ void GlobalShortcuts::ReloadSettings() {
} }
void GlobalShortcuts::Unregister() { void GlobalShortcuts::Unregister() {
if (gnome_backend_->is_active()) if (gnome_backend_->is_active()) gnome_backend_->Unregister();
gnome_backend_->Unregister(); if (system_backend_->is_active()) system_backend_->Unregister();
if (system_backend_->is_active())
system_backend_->Unregister();
} }
void GlobalShortcuts::Register() { void GlobalShortcuts::Register() {
if (use_gnome_ && gnome_backend_->Register()) if (use_gnome_ && gnome_backend_->Register()) return;
return;
system_backend_->Register(); system_backend_->Register();
} }
bool GlobalShortcuts::IsMacAccessibilityEnabled() const { bool GlobalShortcuts::IsMacAccessibilityEnabled() const {
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
return static_cast<MacGlobalShortcutBackend*>(system_backend_)->IsAccessibilityEnabled(); return static_cast<MacGlobalShortcutBackend*>(system_backend_)
->IsAccessibilityEnabled();
#else #else
return true; return true;
#endif #endif
@ -159,6 +174,7 @@ bool GlobalShortcuts::IsMacAccessibilityEnabled() const {
void GlobalShortcuts::ShowMacAccessibilityDialog() { void GlobalShortcuts::ShowMacAccessibilityDialog() {
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
static_cast<MacGlobalShortcutBackend*>(system_backend_)->ShowAccessibilityDialog(); static_cast<MacGlobalShortcutBackend*>(system_backend_)
->ShowAccessibilityDialog();
#endif #endif
} }

View File

@ -32,7 +32,7 @@ class QSignalMapper;
class GlobalShortcuts : public QWidget { class GlobalShortcuts : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
GlobalShortcuts(QWidget* parent = 0); GlobalShortcuts(QWidget* parent = 0);
static const char* kSettingsGroup; static const char* kSettingsGroup;
@ -48,7 +48,7 @@ public:
bool IsGsdAvailable() const; bool IsGsdAvailable() const;
bool IsMacAccessibilityEnabled() const; bool IsMacAccessibilityEnabled() const;
public slots: public slots:
void ReloadSettings(); void ReloadSettings();
void ShowMacAccessibilityDialog(); void ShowMacAccessibilityDialog();
@ -76,14 +76,16 @@ signals:
void CycleRepeatMode(); void CycleRepeatMode();
void ToggleScrobbling(); void ToggleScrobbling();
private: private:
void AddShortcut(const QString& id, const QString& name, const char* signal, void AddShortcut(const QString& id, const QString& name, const char* signal,
const QKeySequence& default_key = QKeySequence(0)); const QKeySequence& default_key = QKeySequence(0));
void AddRatingShortcut(const QString& id, const QString& name, QSignalMapper* mapper, void AddRatingShortcut(const QString& id, const QString& name,
int rating, const QKeySequence& default_key = QKeySequence(0)); QSignalMapper* mapper, int rating,
Shortcut AddShortcut(const QString& id, const QString& name, const QKeySequence& default_key); const QKeySequence& default_key = QKeySequence(0));
Shortcut AddShortcut(const QString& id, const QString& name,
const QKeySequence& default_key);
private: private:
GlobalShortcutBackend* gnome_backend_; GlobalShortcutBackend* gnome_backend_;
GlobalShortcutBackend* system_backend_; GlobalShortcutBackend* system_backend_;

View File

@ -21,7 +21,7 @@
#include "core/logging.h" #include "core/logging.h"
#ifdef QT_DBUS_LIB #ifdef QT_DBUS_LIB
# include "dbus/gnomesettingsdaemon.h" #include "dbus/gnomesettingsdaemon.h"
#endif #endif
#include <QAction> #include <QAction>
@ -30,26 +30,27 @@
#include <QtDebug> #include <QtDebug>
#ifdef QT_DBUS_LIB #ifdef QT_DBUS_LIB
# include <QtDBus> #include <QtDBus>
#endif #endif
const char* GnomeGlobalShortcutBackend::kGsdService = "org.gnome.SettingsDaemon"; const char* GnomeGlobalShortcutBackend::kGsdService =
const char* GnomeGlobalShortcutBackend::kGsdPath = "/org/gnome/SettingsDaemon/MediaKeys"; "org.gnome.SettingsDaemon";
const char* GnomeGlobalShortcutBackend::kGsdInterface = "org.gnome.SettingsDaemon.MediaKeys"; const char* GnomeGlobalShortcutBackend::kGsdPath =
"/org/gnome/SettingsDaemon/MediaKeys";
const char* GnomeGlobalShortcutBackend::kGsdInterface =
"org.gnome.SettingsDaemon.MediaKeys";
GnomeGlobalShortcutBackend::GnomeGlobalShortcutBackend(GlobalShortcuts* parent) GnomeGlobalShortcutBackend::GnomeGlobalShortcutBackend(GlobalShortcuts* parent)
: GlobalShortcutBackend(parent), : GlobalShortcutBackend(parent),
interface_(nullptr), interface_(nullptr),
is_connected_(false) is_connected_(false) {}
{
}
bool GnomeGlobalShortcutBackend::DoRegister() { bool GnomeGlobalShortcutBackend::DoRegister() {
#ifdef QT_DBUS_LIB #ifdef QT_DBUS_LIB
qLog(Debug) << "registering"; qLog(Debug) << "registering";
// Check if the GSD service is available // Check if the GSD service is available
if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(kGsdService)) { if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(
kGsdService)) {
qLog(Warning) << "gnome settings daemon not registered"; qLog(Warning) << "gnome settings daemon not registered";
return false; return false;
} }
@ -64,56 +65,57 @@ bool GnomeGlobalShortcutBackend::DoRegister() {
QDateTime::currentDateTime().toTime_t()); QDateTime::currentDateTime().toTime_t());
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
NewClosure(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), NewClosure(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
this, SLOT(RegisterFinished(QDBusPendingCallWatcher*)), SLOT(RegisterFinished(QDBusPendingCallWatcher*)), watcher);
watcher);
return true; return true;
#else // QT_DBUS_LIB #else // QT_DBUS_LIB
qLog(Warning) << "dbus not available"; qLog(Warning) << "dbus not available";
return false; return false;
#endif #endif
} }
void GnomeGlobalShortcutBackend::RegisterFinished(QDBusPendingCallWatcher* watcher) { void GnomeGlobalShortcutBackend::RegisterFinished(
QDBusPendingCallWatcher* watcher) {
#ifdef QT_DBUS_LIB #ifdef QT_DBUS_LIB
QDBusMessage reply = watcher->reply(); QDBusMessage reply = watcher->reply();
watcher->deleteLater(); watcher->deleteLater();
if (reply.type() == QDBusMessage::ErrorMessage) { if (reply.type() == QDBusMessage::ErrorMessage) {
qLog(Warning) << "Failed to grab media keys" qLog(Warning) << "Failed to grab media keys" << reply.errorName()
<< reply.errorName() <<reply.errorMessage(); << reply.errorMessage();
return; return;
} }
connect(interface_, SIGNAL(MediaPlayerKeyPressed(QString,QString)), connect(interface_, SIGNAL(MediaPlayerKeyPressed(QString, QString)), this,
this, SLOT(GnomeMediaKeyPressed(QString,QString))); SLOT(GnomeMediaKeyPressed(QString, QString)));
is_connected_ = true; is_connected_ = true;
qLog(Debug) << "registered"; qLog(Debug) << "registered";
#endif // QT_DBUS_LIB #endif // QT_DBUS_LIB
} }
void GnomeGlobalShortcutBackend::DoUnregister() { void GnomeGlobalShortcutBackend::DoUnregister() {
qLog(Debug) << "unregister"; qLog(Debug) << "unregister";
#ifdef QT_DBUS_LIB #ifdef QT_DBUS_LIB
// Check if the GSD service is available // Check if the GSD service is available
if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(kGsdService)) if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(
return; kGsdService))
if (!interface_ || !is_connected_)
return; return;
if (!interface_ || !is_connected_) return;
is_connected_ = false; is_connected_ = false;
interface_->ReleaseMediaPlayerKeys(QCoreApplication::applicationName()); interface_->ReleaseMediaPlayerKeys(QCoreApplication::applicationName());
disconnect(interface_, SIGNAL(MediaPlayerKeyPressed(QString,QString)), disconnect(interface_, SIGNAL(MediaPlayerKeyPressed(QString, QString)), this,
this, SLOT(GnomeMediaKeyPressed(QString,QString))); SLOT(GnomeMediaKeyPressed(QString, QString)));
#endif #endif
} }
void GnomeGlobalShortcutBackend::GnomeMediaKeyPressed(const QString&, const QString& key) { void GnomeGlobalShortcutBackend::GnomeMediaKeyPressed(const QString&,
if (key == "Play") manager_->shortcuts()["play_pause"].action->trigger(); const QString& key) {
if (key == "Stop") manager_->shortcuts()["stop"].action->trigger(); if (key == "Play") manager_->shortcuts()["play_pause"].action->trigger();
if (key == "Next") manager_->shortcuts()["next_track"].action->trigger(); if (key == "Stop") manager_->shortcuts()["stop"].action->trigger();
if (key == "Next") manager_->shortcuts()["next_track"].action->trigger();
if (key == "Previous") manager_->shortcuts()["prev_track"].action->trigger(); if (key == "Previous") manager_->shortcuts()["prev_track"].action->trigger();
} }

View File

@ -27,26 +27,26 @@ class QDBusPendingCallWatcher;
class GnomeGlobalShortcutBackend : public GlobalShortcutBackend { class GnomeGlobalShortcutBackend : public GlobalShortcutBackend {
Q_OBJECT Q_OBJECT
public: public:
GnomeGlobalShortcutBackend(GlobalShortcuts* parent); GnomeGlobalShortcutBackend(GlobalShortcuts* parent);
static const char* kGsdService; static const char* kGsdService;
static const char* kGsdPath; static const char* kGsdPath;
static const char* kGsdInterface; static const char* kGsdInterface;
protected: protected:
bool RegisterInNewThread() const { return true; } bool RegisterInNewThread() const { return true; }
bool DoRegister(); bool DoRegister();
void DoUnregister(); void DoUnregister();
private slots: private slots:
void RegisterFinished(QDBusPendingCallWatcher* watcher); void RegisterFinished(QDBusPendingCallWatcher* watcher);
void GnomeMediaKeyPressed(const QString& application, const QString& key); void GnomeMediaKeyPressed(const QString& application, const QString& key);
private: private:
OrgGnomeSettingsDaemonMediaKeysInterface* interface_; OrgGnomeSettingsDaemonMediaKeysInterface* interface_;
bool is_connected_; bool is_connected_;
}; };
#endif // GNOMEGLOBALSHORTCUTBACKEND_H #endif // GNOMEGLOBALSHORTCUTBACKEND_H

View File

@ -7,11 +7,10 @@
#import <Breakpad/Breakpad.h> #import <Breakpad/Breakpad.h>
#endif #endif
class PlatformInterface; class PlatformInterface;
@class SPMediaKeyTap; @class SPMediaKeyTap;
@interface AppDelegate :NSObject <NSApplicationDelegate> { @interface AppDelegate : NSObject<NSApplicationDelegate> {
PlatformInterface* application_handler_; PlatformInterface* application_handler_;
NSMenu* dock_menu_; NSMenu* dock_menu_;
MacGlobalShortcutBackend* shortcut_handler_; MacGlobalShortcutBackend* shortcut_handler_;
@ -22,21 +21,23 @@ class PlatformInterface;
#endif #endif
} }
- (id) initWithHandler: (PlatformInterface*)handler; - (id)initWithHandler:(PlatformInterface*)handler;
// NSApplicationDelegate // NSApplicationDelegate
- (BOOL) applicationShouldHandleReopen: (NSApplication*)app hasVisibleWindows:(BOOL)flag; - (BOOL)applicationShouldHandleReopen:(NSApplication*)app
- (NSMenu*) applicationDockMenu: (NSApplication*)sender; hasVisibleWindows:(BOOL)flag;
- (NSMenu*)applicationDockMenu:(NSApplication*)sender;
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification; - (void)applicationDidFinishLaunching:(NSNotification*)aNotification;
- (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication*)sender; - (NSApplicationTerminateReply)applicationShouldTerminate:
(NSApplication*)sender;
// NSUserNotificationCenterDelegate // NSUserNotificationCenterDelegate
- (BOOL) userNotificationCenter: (id)center - (BOOL)userNotificationCenter:(id)center
shouldPresentNotification: (id)notification; shouldPresentNotification:(id)notification;
- (void) setDockMenu: (NSMenu*)menu; - (void)setDockMenu:(NSMenu*)menu;
- (MacGlobalShortcutBackend*) shortcut_handler; - (MacGlobalShortcutBackend*)shortcut_handler;
- (void) setShortcutHandler: (MacGlobalShortcutBackend*)backend; - (void)setShortcutHandler:(MacGlobalShortcutBackend*)backend;
- (void) mediaKeyTap: (SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event; - (void)mediaKeyTap:(SPMediaKeyTap*)keyTap
receivedMediaKeyEvent:(NSEvent*)event;
@end @end

View File

@ -30,5 +30,4 @@ namespace mac {
QKeySequence KeySequenceFromNSEvent(NSEvent* event); QKeySequence KeySequenceFromNSEvent(NSEvent* event);
void DumpDictionary(CFDictionaryRef dict); void DumpDictionary(CFDictionaryRef dict);
float GetDevicePixelRatio(QWidget* widget); float GetDevicePixelRatio(QWidget* widget);
} }

View File

@ -36,7 +36,7 @@ class MacFSListener : public FileSystemWatcherInterface {
void RemovePath(const QString& path); void RemovePath(const QString& path);
void Clear(); void Clear();
signals: signals:
void PathChanged(const QString& path); void PathChanged(const QString& path);
private slots: private slots:
@ -45,13 +45,10 @@ class MacFSListener : public FileSystemWatcherInterface {
private: private:
void UpdateStreamAsync(); void UpdateStreamAsync();
static void EventStreamCallback( static void EventStreamCallback(ConstFSEventStreamRef stream, void* user_data,
ConstFSEventStreamRef stream, size_t num_events, void* event_paths,
void* user_data, const FSEventStreamEventFlags event_flags[],
size_t num_events, const FSEventStreamEventId event_ids[]);
void* event_paths,
const FSEventStreamEventFlags event_flags[],
const FSEventStreamEventId event_ids[]);
CFRunLoopRef run_loop_; CFRunLoopRef run_loop_;
FSEventStreamRef stream_; FSEventStreamRef stream_;

View File

@ -31,7 +31,7 @@ class QAction;
class MacGlobalShortcutBackend : public GlobalShortcutBackend { class MacGlobalShortcutBackend : public GlobalShortcutBackend {
Q_OBJECT Q_OBJECT
public: public:
MacGlobalShortcutBackend(GlobalShortcuts* parent); MacGlobalShortcutBackend(GlobalShortcuts* parent);
virtual ~MacGlobalShortcutBackend(); virtual ~MacGlobalShortcutBackend();
@ -40,11 +40,11 @@ public:
void MacMediaKeyPressed(int key); void MacMediaKeyPressed(int key);
protected: protected:
bool DoRegister(); bool DoRegister();
void DoUnregister(); void DoUnregister();
private: private:
bool KeyPressed(const QKeySequence& sequence); bool KeyPressed(const QKeySequence& sequence);
QMap<QKeySequence, QAction*> shortcuts_; QMap<QKeySequence, QAction*> shortcuts_;
@ -53,4 +53,4 @@ private:
std::unique_ptr<MacGlobalShortcutBackendPrivate> p_; std::unique_ptr<MacGlobalShortcutBackendPrivate> p_;
}; };
#endif // MACGLOBALSHORTCUTBACKEND_H #endif // MACGLOBALSHORTCUTBACKEND_H

View File

@ -39,15 +39,12 @@ using boost::multi_index::multi_index_container;
using boost::multi_index::ordered_unique; using boost::multi_index::ordered_unique;
using boost::multi_index::tag; using boost::multi_index::tag;
std::size_t hash_value(const QModelIndex& index) { std::size_t hash_value(const QModelIndex& index) { return qHash(index); }
return qHash(index);
}
namespace { namespace {
struct Mapping { struct Mapping {
Mapping(const QModelIndex& _source_index) Mapping(const QModelIndex& _source_index) : source_index(_source_index) {}
: source_index(_source_index) {}
QModelIndex source_index; QModelIndex source_index;
}; };
@ -60,28 +57,23 @@ struct tag_by_pointer {};
class MergedProxyModelPrivate { class MergedProxyModelPrivate {
private: private:
typedef multi_index_container< typedef multi_index_container<
Mapping*, Mapping*,
indexed_by< indexed_by<
hashed_unique<tag<tag_by_source>, hashed_unique<tag<tag_by_source>,
member<Mapping, QModelIndex, &Mapping::source_index> >, member<Mapping, QModelIndex, &Mapping::source_index> >,
ordered_unique<tag<tag_by_pointer>, ordered_unique<tag<tag_by_pointer>, identity<Mapping*> > > >
identity<Mapping*> > MappingContainer;
>
> MappingContainer;
public: public:
MappingContainer mappings_; MappingContainer mappings_;
}; };
MergedProxyModel::MergedProxyModel(QObject* parent) MergedProxyModel::MergedProxyModel(QObject* parent)
: QAbstractProxyModel(parent), : QAbstractProxyModel(parent),
resetting_model_(nullptr), resetting_model_(nullptr),
p_(new MergedProxyModelPrivate) { p_(new MergedProxyModelPrivate) {}
}
MergedProxyModel::~MergedProxyModel() { MergedProxyModel::~MergedProxyModel() { DeleteAllMappings(); }
DeleteAllMappings();
}
void MergedProxyModel::DeleteAllMappings() { void MergedProxyModel::DeleteAllMappings() {
const auto& begin = p_->mappings_.get<tag_by_pointer>().begin(); const auto& begin = p_->mappings_.get<tag_by_pointer>().begin();
@ -92,30 +84,28 @@ void MergedProxyModel::DeleteAllMappings() {
void MergedProxyModel::AddSubModel(const QModelIndex& source_parent, void MergedProxyModel::AddSubModel(const QModelIndex& source_parent,
QAbstractItemModel* submodel) { QAbstractItemModel* submodel) {
connect(submodel, SIGNAL(modelReset()), this, SLOT(SubModelReset())); connect(submodel, SIGNAL(modelReset()), this, SLOT(SubModelReset()));
connect(submodel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), connect(submodel, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this,
this, SLOT(RowsAboutToBeInserted(QModelIndex,int,int))); SLOT(RowsAboutToBeInserted(QModelIndex, int, int)));
connect(submodel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), connect(submodel, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this,
this, SLOT(RowsAboutToBeRemoved(QModelIndex,int,int))); SLOT(RowsAboutToBeRemoved(QModelIndex, int, int)));
connect(submodel, SIGNAL(rowsInserted(QModelIndex,int,int)), connect(submodel, SIGNAL(rowsInserted(QModelIndex, int, int)), this,
this, SLOT(RowsInserted(QModelIndex,int,int))); SLOT(RowsInserted(QModelIndex, int, int)));
connect(submodel, SIGNAL(rowsRemoved(QModelIndex,int,int)), connect(submodel, SIGNAL(rowsRemoved(QModelIndex, int, int)), this,
this, SLOT(RowsRemoved(QModelIndex,int,int))); SLOT(RowsRemoved(QModelIndex, int, int)));
connect(submodel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), connect(submodel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this,
this, SLOT(DataChanged(QModelIndex,QModelIndex))); SLOT(DataChanged(QModelIndex, QModelIndex)));
QModelIndex proxy_parent = mapFromSource(source_parent); QModelIndex proxy_parent = mapFromSource(source_parent);
const int rows = submodel->rowCount(); const int rows = submodel->rowCount();
if (rows) if (rows) beginInsertRows(proxy_parent, 0, rows - 1);
beginInsertRows(proxy_parent, 0, rows-1);
merge_points_.insert(submodel, source_parent); merge_points_.insert(submodel, source_parent);
if (rows) if (rows) endInsertRows();
endInsertRows();
} }
void MergedProxyModel::RemoveSubModel(const QModelIndex &source_parent) { void MergedProxyModel::RemoveSubModel(const QModelIndex& source_parent) {
// Find the submodel that the parent corresponded to // Find the submodel that the parent corresponded to
QAbstractItemModel* submodel = merge_points_.key(source_parent); QAbstractItemModel* submodel = merge_points_.key(source_parent);
merge_points_.remove(submodel); merge_points_.remove(submodel);
@ -147,40 +137,42 @@ void MergedProxyModel::RemoveSubModel(const QModelIndex &source_parent) {
void MergedProxyModel::setSourceModel(QAbstractItemModel* source_model) { void MergedProxyModel::setSourceModel(QAbstractItemModel* source_model) {
if (sourceModel()) { if (sourceModel()) {
disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(SourceModelReset())); disconnect(sourceModel(), SIGNAL(modelReset()), this,
disconnect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), SLOT(SourceModelReset()));
this, SLOT(RowsAboutToBeInserted(QModelIndex,int,int))); disconnect(sourceModel(),
disconnect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this,
this, SLOT(RowsAboutToBeRemoved(QModelIndex,int,int))); SLOT(RowsAboutToBeInserted(QModelIndex, int, int)));
disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), disconnect(sourceModel(),
this, SLOT(RowsInserted(QModelIndex,int,int))); SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this,
disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(RowsAboutToBeRemoved(QModelIndex, int, int)));
this, SLOT(RowsRemoved(QModelIndex,int,int))); disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex, int, int)), this,
disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(RowsInserted(QModelIndex, int, int)));
this, SLOT(DataChanged(QModelIndex,QModelIndex))); disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex, int, int)), this,
disconnect(sourceModel(), SIGNAL(layoutAboutToBeChanged()), SLOT(RowsRemoved(QModelIndex, int, int)));
this, SLOT(LayoutAboutToBeChanged())); disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex, QModelIndex)),
disconnect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(DataChanged(QModelIndex, QModelIndex)));
this, SLOT(LayoutChanged())); disconnect(sourceModel(), SIGNAL(layoutAboutToBeChanged()), this,
SLOT(LayoutAboutToBeChanged()));
disconnect(sourceModel(), SIGNAL(layoutChanged()), this,
SLOT(LayoutChanged()));
} }
QAbstractProxyModel::setSourceModel(source_model); QAbstractProxyModel::setSourceModel(source_model);
connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(SourceModelReset())); connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(SourceModelReset()));
connect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), connect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)),
this, SLOT(RowsAboutToBeInserted(QModelIndex,int,int))); this, SLOT(RowsAboutToBeInserted(QModelIndex, int, int)));
connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)),
this, SLOT(RowsAboutToBeRemoved(QModelIndex,int,int))); this, SLOT(RowsAboutToBeRemoved(QModelIndex, int, int)));
connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex, int, int)), this,
this, SLOT(RowsInserted(QModelIndex,int,int))); SLOT(RowsInserted(QModelIndex, int, int)));
connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex, int, int)), this,
this, SLOT(RowsRemoved(QModelIndex,int,int))); SLOT(RowsRemoved(QModelIndex, int, int)));
connect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), connect(sourceModel(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), this,
this, SLOT(DataChanged(QModelIndex,QModelIndex))); SLOT(DataChanged(QModelIndex, QModelIndex)));
connect(sourceModel(), SIGNAL(layoutAboutToBeChanged()), connect(sourceModel(), SIGNAL(layoutAboutToBeChanged()), this,
this, SLOT(LayoutAboutToBeChanged())); SLOT(LayoutAboutToBeChanged()));
connect(sourceModel(), SIGNAL(layoutChanged()), connect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(LayoutChanged()));
this, SLOT(LayoutChanged()));
} }
void MergedProxyModel::SourceModelReset() { void MergedProxyModel::SourceModelReset() {
@ -227,15 +219,15 @@ void MergedProxyModel::SubModelReset() {
// "Insert" items from the newly reset submodel // "Insert" items from the newly reset submodel
int count = submodel->rowCount(); int count = submodel->rowCount();
if (count) { if (count) {
beginInsertRows(proxy_parent, 0, count-1); beginInsertRows(proxy_parent, 0, count - 1);
endInsertRows(); endInsertRows();
} }
emit SubModelReset(proxy_parent, submodel); emit SubModelReset(proxy_parent, submodel);
} }
QModelIndex MergedProxyModel::GetActualSourceParent(const QModelIndex& source_parent, QModelIndex MergedProxyModel::GetActualSourceParent(
QAbstractItemModel* model) const { const QModelIndex& source_parent, QAbstractItemModel* model) const {
if (!source_parent.isValid() && model != sourceModel()) if (!source_parent.isValid() && model != sourceModel())
return merge_points_.value(model); return merge_points_.value(model);
return source_parent; return source_parent;
@ -243,8 +235,9 @@ QModelIndex MergedProxyModel::GetActualSourceParent(const QModelIndex& source_pa
void MergedProxyModel::RowsAboutToBeInserted(const QModelIndex& source_parent, void MergedProxyModel::RowsAboutToBeInserted(const QModelIndex& source_parent,
int start, int end) { int start, int end) {
beginInsertRows(mapFromSource(GetActualSourceParent( beginInsertRows(
source_parent, static_cast<QAbstractItemModel*>(sender()))), mapFromSource(GetActualSourceParent(
source_parent, static_cast<QAbstractItemModel*>(sender()))),
start, end); start, end);
} }
@ -254,8 +247,9 @@ void MergedProxyModel::RowsInserted(const QModelIndex&, int, int) {
void MergedProxyModel::RowsAboutToBeRemoved(const QModelIndex& source_parent, void MergedProxyModel::RowsAboutToBeRemoved(const QModelIndex& source_parent,
int start, int end) { int start, int end) {
beginRemoveRows(mapFromSource(GetActualSourceParent( beginRemoveRows(
source_parent, static_cast<QAbstractItemModel*>(sender()))), mapFromSource(GetActualSourceParent(
source_parent, static_cast<QAbstractItemModel*>(sender()))),
start, end); start, end);
} }
@ -263,29 +257,26 @@ void MergedProxyModel::RowsRemoved(const QModelIndex&, int, int) {
endRemoveRows(); endRemoveRows();
} }
QModelIndex MergedProxyModel::mapToSource(const QModelIndex& proxy_index) const { QModelIndex MergedProxyModel::mapToSource(const QModelIndex& proxy_index)
if (!proxy_index.isValid()) const {
return QModelIndex(); if (!proxy_index.isValid()) return QModelIndex();
Mapping* mapping = static_cast<Mapping*>(proxy_index.internalPointer()); Mapping* mapping = static_cast<Mapping*>(proxy_index.internalPointer());
if (p_->mappings_.get<tag_by_pointer>().find(mapping) == if (p_->mappings_.get<tag_by_pointer>().find(mapping) ==
p_->mappings_.get<tag_by_pointer>().end()) p_->mappings_.get<tag_by_pointer>().end())
return QModelIndex(); return QModelIndex();
if (mapping->source_index.model() == resetting_model_) if (mapping->source_index.model() == resetting_model_) return QModelIndex();
return QModelIndex();
return mapping->source_index; return mapping->source_index;
} }
QModelIndex MergedProxyModel::mapFromSource(const QModelIndex& source_index) const { QModelIndex MergedProxyModel::mapFromSource(const QModelIndex& source_index)
if (!source_index.isValid()) const {
return QModelIndex(); if (!source_index.isValid()) return QModelIndex();
if (source_index.model() == resetting_model_) if (source_index.model() == resetting_model_) return QModelIndex();
return QModelIndex();
// Add a mapping if we don't have one already // Add a mapping if we don't have one already
const auto& it = const auto& it = p_->mappings_.get<tag_by_source>().find(source_index);
p_->mappings_.get<tag_by_source>().find(source_index);
Mapping* mapping; Mapping* mapping;
if (it != p_->mappings_.get<tag_by_source>().end()) { if (it != p_->mappings_.get<tag_by_source>().end()) {
mapping = *it; mapping = *it;
@ -297,7 +288,8 @@ QModelIndex MergedProxyModel::mapFromSource(const QModelIndex& source_index) con
return createIndex(source_index.row(), source_index.column(), mapping); return createIndex(source_index.row(), source_index.column(), mapping);
} }
QModelIndex MergedProxyModel::index(int row, int column, const QModelIndex &parent) const { QModelIndex MergedProxyModel::index(int row, int column,
const QModelIndex& parent) const {
QModelIndex source_index; QModelIndex source_index;
if (!parent.isValid()) { if (!parent.isValid()) {
@ -315,26 +307,23 @@ QModelIndex MergedProxyModel::index(int row, int column, const QModelIndex &pare
return mapFromSource(source_index); return mapFromSource(source_index);
} }
QModelIndex MergedProxyModel::parent(const QModelIndex &child) const { QModelIndex MergedProxyModel::parent(const QModelIndex& child) const {
QModelIndex source_child = mapToSource(child); QModelIndex source_child = mapToSource(child);
if (source_child.model() == sourceModel()) if (source_child.model() == sourceModel())
return mapFromSource(source_child.parent()); return mapFromSource(source_child.parent());
if (!IsKnownModel(source_child.model())) if (!IsKnownModel(source_child.model())) return QModelIndex();
return QModelIndex();
if (!source_child.parent().isValid()) if (!source_child.parent().isValid())
return mapFromSource(merge_points_.value(GetModel(source_child))); return mapFromSource(merge_points_.value(GetModel(source_child)));
return mapFromSource(source_child.parent()); return mapFromSource(source_child.parent());
} }
int MergedProxyModel::rowCount(const QModelIndex &parent) const { int MergedProxyModel::rowCount(const QModelIndex& parent) const {
if (!parent.isValid()) if (!parent.isValid()) return sourceModel()->rowCount(QModelIndex());
return sourceModel()->rowCount(QModelIndex());
QModelIndex source_parent = mapToSource(parent); QModelIndex source_parent = mapToSource(parent);
if (!IsKnownModel(source_parent.model())) if (!IsKnownModel(source_parent.model())) return 0;
return 0;
const QAbstractItemModel* child_model = merge_points_.key(source_parent); const QAbstractItemModel* child_model = merge_points_.key(source_parent);
if (child_model) { if (child_model) {
@ -348,27 +337,22 @@ int MergedProxyModel::rowCount(const QModelIndex &parent) const {
return source_parent.model()->rowCount(source_parent); return source_parent.model()->rowCount(source_parent);
} }
int MergedProxyModel::columnCount(const QModelIndex &parent) const { int MergedProxyModel::columnCount(const QModelIndex& parent) const {
if (!parent.isValid()) if (!parent.isValid()) return sourceModel()->columnCount(QModelIndex());
return sourceModel()->columnCount(QModelIndex());
QModelIndex source_parent = mapToSource(parent); QModelIndex source_parent = mapToSource(parent);
if (!IsKnownModel(source_parent.model())) if (!IsKnownModel(source_parent.model())) return 0;
return 0;
const QAbstractItemModel* child_model = merge_points_.key(source_parent); const QAbstractItemModel* child_model = merge_points_.key(source_parent);
if (child_model) if (child_model) return child_model->columnCount(QModelIndex());
return child_model->columnCount(QModelIndex());
return source_parent.model()->columnCount(source_parent); return source_parent.model()->columnCount(source_parent);
} }
bool MergedProxyModel::hasChildren(const QModelIndex &parent) const { bool MergedProxyModel::hasChildren(const QModelIndex& parent) const {
if (!parent.isValid()) if (!parent.isValid()) return sourceModel()->hasChildren(QModelIndex());
return sourceModel()->hasChildren(QModelIndex());
QModelIndex source_parent = mapToSource(parent); QModelIndex source_parent = mapToSource(parent);
if (!IsKnownModel(source_parent.model())) if (!IsKnownModel(source_parent.model())) return false;
return false;
const QAbstractItemModel* child_model = merge_points_.key(source_parent); const QAbstractItemModel* child_model = merge_points_.key(source_parent);
@ -380,29 +364,27 @@ bool MergedProxyModel::hasChildren(const QModelIndex &parent) const {
QVariant MergedProxyModel::data(const QModelIndex& proxyIndex, int role) const { QVariant MergedProxyModel::data(const QModelIndex& proxyIndex, int role) const {
QModelIndex source_index = mapToSource(proxyIndex); QModelIndex source_index = mapToSource(proxyIndex);
if (!IsKnownModel(source_index.model())) if (!IsKnownModel(source_index.model())) return QVariant();
return QVariant();
return source_index.model()->data(source_index, role); return source_index.model()->data(source_index, role);
} }
QMap<int, QVariant> MergedProxyModel::itemData(const QModelIndex& proxy_index) const { QMap<int, QVariant> MergedProxyModel::itemData(const QModelIndex& proxy_index)
const {
QModelIndex source_index = mapToSource(proxy_index); QModelIndex source_index = mapToSource(proxy_index);
if (!source_index.isValid()) if (!source_index.isValid()) return sourceModel()->itemData(QModelIndex());
return sourceModel()->itemData(QModelIndex());
return source_index.model()->itemData(source_index); return source_index.model()->itemData(source_index);
} }
Qt::ItemFlags MergedProxyModel::flags(const QModelIndex &index) const { Qt::ItemFlags MergedProxyModel::flags(const QModelIndex& index) const {
QModelIndex source_index = mapToSource(index); QModelIndex source_index = mapToSource(index);
if (!source_index.isValid()) if (!source_index.isValid()) return sourceModel()->flags(QModelIndex());
return sourceModel()->flags(QModelIndex());
return source_index.model()->flags(source_index); return source_index.model()->flags(source_index);
} }
bool MergedProxyModel::setData(const QModelIndex &index, const QVariant &value, bool MergedProxyModel::setData(const QModelIndex& index, const QVariant& value,
int role) { int role) {
QModelIndex source_index = mapToSource(index); QModelIndex source_index = mapToSource(index);
@ -415,16 +397,15 @@ QStringList MergedProxyModel::mimeTypes() const {
QStringList ret; QStringList ret;
ret << sourceModel()->mimeTypes(); ret << sourceModel()->mimeTypes();
foreach (const QAbstractItemModel* model, merge_points_.keys()) { foreach(const QAbstractItemModel * model, merge_points_.keys()) {
ret << model->mimeTypes(); ret << model->mimeTypes();
} }
return ret; return ret;
} }
QMimeData* MergedProxyModel::mimeData(const QModelIndexList &indexes) const { QMimeData* MergedProxyModel::mimeData(const QModelIndexList& indexes) const {
if (indexes.isEmpty()) if (indexes.isEmpty()) return 0;
return 0;
// Only ask the first index's model // Only ask the first index's model
const QAbstractItemModel* model = mapToSource(indexes[0]).model(); const QAbstractItemModel* model = mapToSource(indexes[0]).model();
@ -435,17 +416,18 @@ QMimeData* MergedProxyModel::mimeData(const QModelIndexList &indexes) const {
// Only ask about the indexes that are actually in that model // Only ask about the indexes that are actually in that model
QModelIndexList indexes_in_model; QModelIndexList indexes_in_model;
foreach (const QModelIndex& proxy_index, indexes) { foreach(const QModelIndex & proxy_index, indexes) {
QModelIndex source_index = mapToSource(proxy_index); QModelIndex source_index = mapToSource(proxy_index);
if (source_index.model() != model) if (source_index.model() != model) continue;
continue;
indexes_in_model << source_index; indexes_in_model << source_index;
} }
return model->mimeData(indexes_in_model); return model->mimeData(indexes_in_model);
} }
bool MergedProxyModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) { bool MergedProxyModel::dropMimeData(const QMimeData* data,
Qt::DropAction action, int row, int column,
const QModelIndex& parent) {
if (!parent.isValid()) { if (!parent.isValid()) {
return false; return false;
} }
@ -453,17 +435,16 @@ bool MergedProxyModel::dropMimeData(const QMimeData* data, Qt::DropAction action
return sourceModel()->dropMimeData(data, action, row, column, parent); return sourceModel()->dropMimeData(data, action, row, column, parent);
} }
QModelIndex MergedProxyModel::FindSourceParent(const QModelIndex& proxy_index) const { QModelIndex MergedProxyModel::FindSourceParent(const QModelIndex& proxy_index)
if (!proxy_index.isValid()) const {
return QModelIndex(); if (!proxy_index.isValid()) return QModelIndex();
QModelIndex source_index = mapToSource(proxy_index); QModelIndex source_index = mapToSource(proxy_index);
if (source_index.model() == sourceModel()) if (source_index.model() == sourceModel()) return source_index;
return source_index;
return merge_points_.value(GetModel(source_index)); return merge_points_.value(GetModel(source_index));
} }
bool MergedProxyModel::canFetchMore(const QModelIndex &parent) const { bool MergedProxyModel::canFetchMore(const QModelIndex& parent) const {
QModelIndex source_index = mapToSource(parent); QModelIndex source_index = mapToSource(parent);
if (!source_index.isValid()) if (!source_index.isValid())
@ -480,34 +461,33 @@ void MergedProxyModel::fetchMore(const QModelIndex& parent) {
GetModel(source_index)->fetchMore(source_index); GetModel(source_index)->fetchMore(source_index);
} }
QAbstractItemModel* MergedProxyModel::GetModel(const QModelIndex& source_index) const { QAbstractItemModel* MergedProxyModel::GetModel(const QModelIndex& source_index)
const {
// This is essentially const_cast<QAbstractItemModel*>(source_index.model()), // This is essentially const_cast<QAbstractItemModel*>(source_index.model()),
// but without the const_cast // but without the const_cast
const QAbstractItemModel* const_model = source_index.model(); const QAbstractItemModel* const_model = source_index.model();
if (const_model == sourceModel()) if (const_model == sourceModel()) return sourceModel();
return sourceModel(); foreach(QAbstractItemModel * submodel, merge_points_.keys()) {
foreach (QAbstractItemModel* submodel, merge_points_.keys()) { if (submodel == const_model) return submodel;
if (submodel == const_model)
return submodel;
} }
return nullptr; return nullptr;
} }
void MergedProxyModel::DataChanged(const QModelIndex& top_left, const QModelIndex& bottom_right) { void MergedProxyModel::DataChanged(const QModelIndex& top_left,
const QModelIndex& bottom_right) {
emit dataChanged(mapFromSource(top_left), mapFromSource(bottom_right)); emit dataChanged(mapFromSource(top_left), mapFromSource(bottom_right));
} }
void MergedProxyModel::LayoutAboutToBeChanged() { void MergedProxyModel::LayoutAboutToBeChanged() {
old_merge_points_.clear(); old_merge_points_.clear();
foreach (QAbstractItemModel* key, merge_points_.keys()) { foreach(QAbstractItemModel * key, merge_points_.keys()) {
old_merge_points_[key] = merge_points_.value(key); old_merge_points_[key] = merge_points_.value(key);
} }
} }
void MergedProxyModel::LayoutChanged() { void MergedProxyModel::LayoutChanged() {
foreach (QAbstractItemModel* key, merge_points_.keys()) { foreach(QAbstractItemModel * key, merge_points_.keys()) {
if (!old_merge_points_.contains(key)) if (!old_merge_points_.contains(key)) continue;
continue;
const int old_row = old_merge_points_[key].row(); const int old_row = old_merge_points_[key].row();
const int new_row = merge_points_[key].row(); const int new_row = merge_points_[key].row();
@ -526,17 +506,19 @@ bool MergedProxyModel::IsKnownModel(const QAbstractItemModel* model) const {
return false; return false;
} }
QModelIndexList MergedProxyModel::mapFromSource(const QModelIndexList& source_indexes) const { QModelIndexList MergedProxyModel::mapFromSource(
const QModelIndexList& source_indexes) const {
QModelIndexList ret; QModelIndexList ret;
foreach (const QModelIndex& index, source_indexes) { foreach(const QModelIndex & index, source_indexes) {
ret << mapFromSource(index); ret << mapFromSource(index);
} }
return ret; return ret;
} }
QModelIndexList MergedProxyModel::mapToSource(const QModelIndexList& proxy_indexes) const { QModelIndexList MergedProxyModel::mapToSource(
const QModelIndexList& proxy_indexes) const {
QModelIndexList ret; QModelIndexList ret;
foreach (const QModelIndex& index, proxy_indexes) { foreach(const QModelIndex & index, proxy_indexes) {
ret << mapToSource(index); ret << mapToSource(index);
} }
return ret; return ret;

View File

@ -34,7 +34,8 @@ class MergedProxyModel : public QAbstractProxyModel {
~MergedProxyModel(); ~MergedProxyModel();
// Make another model appear as a child of the given item in the source model. // Make another model appear as a child of the given item in the source model.
void AddSubModel(const QModelIndex& source_parent, QAbstractItemModel* submodel); void AddSubModel(const QModelIndex& source_parent,
QAbstractItemModel* submodel);
void RemoveSubModel(const QModelIndex& source_parent); void RemoveSubModel(const QModelIndex& source_parent);
// Find the item in the source model that is the parent of the model // Find the item in the source model that is the parent of the model
@ -43,45 +44,50 @@ class MergedProxyModel : public QAbstractProxyModel {
QModelIndex FindSourceParent(const QModelIndex& proxy_index) const; QModelIndex FindSourceParent(const QModelIndex& proxy_index) const;
// QAbstractItemModel // QAbstractItemModel
QModelIndex index(int row, int column, const QModelIndex &parent) const; QModelIndex index(int row, int column, const QModelIndex& parent) const;
QModelIndex parent(const QModelIndex &child) const; QModelIndex parent(const QModelIndex& child) const;
int rowCount(const QModelIndex &parent) const; int rowCount(const QModelIndex& parent) const;
int columnCount(const QModelIndex &parent) const; int columnCount(const QModelIndex& parent) const;
QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const; QVariant data(const QModelIndex& proxyIndex,
bool hasChildren(const QModelIndex &parent) const; int role = Qt::DisplayRole) const;
QMap<int, QVariant> itemData(const QModelIndex &proxyIndex) const; bool hasChildren(const QModelIndex& parent) const;
Qt::ItemFlags flags(const QModelIndex &index) const; QMap<int, QVariant> itemData(const QModelIndex& proxyIndex) const;
bool setData(const QModelIndex &index, const QVariant &value, int role); Qt::ItemFlags flags(const QModelIndex& index) const;
bool setData(const QModelIndex& index, const QVariant& value, int role);
QStringList mimeTypes() const; QStringList mimeTypes() const;
QMimeData* mimeData(const QModelIndexList &indexes) const; QMimeData* mimeData(const QModelIndexList& indexes) const;
bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row,
bool canFetchMore(const QModelIndex &parent) const; int column, const QModelIndex& parent);
bool canFetchMore(const QModelIndex& parent) const;
void fetchMore(const QModelIndex& parent); void fetchMore(const QModelIndex& parent);
// QAbstractProxyModel // QAbstractProxyModel
// Note that these implementations of map{To,From}Source will not always // Note that these implementations of map{To,From}Source will not always
// give you an index in sourceModel(), you might get an index in one of the // give you an index in sourceModel(), you might get an index in one of the
// child models instead. // child models instead.
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const; QModelIndex mapFromSource(const QModelIndex& sourceIndex) const;
QModelIndex mapToSource(const QModelIndex &proxyIndex) const; QModelIndex mapToSource(const QModelIndex& proxyIndex) const;
void setSourceModel(QAbstractItemModel *sourceModel); void setSourceModel(QAbstractItemModel* sourceModel);
// Convenience functions that call map{To,From}Source multiple times. // Convenience functions that call map{To,From}Source multiple times.
QModelIndexList mapFromSource(const QModelIndexList& source_indexes) const; QModelIndexList mapFromSource(const QModelIndexList& source_indexes) const;
QModelIndexList mapToSource(const QModelIndexList& proxy_indexes) const; QModelIndexList mapToSource(const QModelIndexList& proxy_indexes) const;
signals: signals:
void SubModelReset(const QModelIndex& root, QAbstractItemModel* model); void SubModelReset(const QModelIndex& root, QAbstractItemModel* model);
private slots: private slots:
void SourceModelReset(); void SourceModelReset();
void SubModelReset(); void SubModelReset();
void RowsAboutToBeInserted(const QModelIndex& source_parent, int start, int end); void RowsAboutToBeInserted(const QModelIndex& source_parent, int start,
int end);
void RowsInserted(const QModelIndex& source_parent, int start, int end); void RowsInserted(const QModelIndex& source_parent, int start, int end);
void RowsAboutToBeRemoved(const QModelIndex& source_parent, int start, int end); void RowsAboutToBeRemoved(const QModelIndex& source_parent, int start,
int end);
void RowsRemoved(const QModelIndex& source_parent, int start, int end); void RowsRemoved(const QModelIndex& source_parent, int start, int end);
void DataChanged(const QModelIndex& top_left, const QModelIndex& bottom_right); void DataChanged(const QModelIndex& top_left,
const QModelIndex& bottom_right);
void LayoutAboutToBeChanged(); void LayoutAboutToBeChanged();
void LayoutChanged(); void LayoutChanged();
@ -93,7 +99,6 @@ class MergedProxyModel : public QAbstractProxyModel {
void DeleteAllMappings(); void DeleteAllMappings();
bool IsKnownModel(const QAbstractItemModel* model) const; bool IsKnownModel(const QAbstractItemModel* model) const;
QMap<QAbstractItemModel*, QPersistentModelIndex> merge_points_; QMap<QAbstractItemModel*, QPersistentModelIndex> merge_points_;
QAbstractItemModel* resetting_model_; QAbstractItemModel* resetting_model_;
@ -102,4 +107,4 @@ class MergedProxyModel : public QAbstractProxyModel {
std::unique_ptr<MergedProxyModelPrivate> p_; std::unique_ptr<MergedProxyModelPrivate> p_;
}; };
#endif // MERGEDPROXYMODEL_H #endif // MERGEDPROXYMODEL_H

View File

@ -32,7 +32,8 @@ void RegisterMetaTypes() {
qRegisterMetaType<const char*>("const char*"); qRegisterMetaType<const char*>("const char*");
qRegisterMetaType<CoverSearchResult>("CoverSearchResult"); qRegisterMetaType<CoverSearchResult>("CoverSearchResult");
qRegisterMetaType<CoverSearchResults>("CoverSearchResults"); qRegisterMetaType<CoverSearchResults>("CoverSearchResults");
qRegisterMetaType<DigitallyImportedClient::Channel>("DigitallyImportedClient::Channel"); qRegisterMetaType<DigitallyImportedClient::Channel>(
"DigitallyImportedClient::Channel");
qRegisterMetaType<Directory>("Directory"); qRegisterMetaType<Directory>("Directory");
qRegisterMetaType<DirectoryList>("DirectoryList"); qRegisterMetaType<DirectoryList>("DirectoryList");
qRegisterMetaType<Engine::SimpleMetaBundle>("Engine::SimpleMetaBundle"); qRegisterMetaType<Engine::SimpleMetaBundle>("Engine::SimpleMetaBundle");
@ -49,8 +50,10 @@ void RegisterMetaTypes() {
qRegisterMetaType<PodcastList>("PodcastList"); qRegisterMetaType<PodcastList>("PodcastList");
qRegisterMetaType<QList<CoverSearchResult> >("QList<CoverSearchResult>"); qRegisterMetaType<QList<CoverSearchResult> >("QList<CoverSearchResult>");
qRegisterMetaType<QList<PlaylistItemPtr> >("QList<PlaylistItemPtr>"); qRegisterMetaType<QList<PlaylistItemPtr> >("QList<PlaylistItemPtr>");
qRegisterMetaType<PlaylistSequence::RepeatMode>("PlaylistSequence::RepeatMode"); qRegisterMetaType<PlaylistSequence::RepeatMode>(
qRegisterMetaType<PlaylistSequence::ShuffleMode>("PlaylistSequence::ShuffleMode"); "PlaylistSequence::RepeatMode");
qRegisterMetaType<PlaylistSequence::ShuffleMode>(
"PlaylistSequence::ShuffleMode");
qRegisterMetaType<QList<PodcastEpisode> >("QList<PodcastEpisode>"); qRegisterMetaType<QList<PodcastEpisode> >("QList<PodcastEpisode>");
qRegisterMetaType<QList<Podcast> >("QList<Podcast>"); qRegisterMetaType<QList<Podcast> >("QList<Podcast>");
qRegisterMetaType<QList<QNetworkCookie> >("QList<QNetworkCookie>"); qRegisterMetaType<QList<QNetworkCookie> >("QList<QNetworkCookie>");
@ -60,14 +63,17 @@ void RegisterMetaTypes() {
qRegisterMetaType<QNetworkReply**>("QNetworkReply**"); qRegisterMetaType<QNetworkReply**>("QNetworkReply**");
qRegisterMetaType<SearchProvider::ResultList>("SearchProvider::ResultList"); qRegisterMetaType<SearchProvider::ResultList>("SearchProvider::ResultList");
qRegisterMetaType<SearchProvider::Result>("SearchProvider::Result"); qRegisterMetaType<SearchProvider::Result>("SearchProvider::Result");
qRegisterMetaType<smart_playlists::GeneratorPtr>("smart_playlists::GeneratorPtr"); qRegisterMetaType<smart_playlists::GeneratorPtr>(
"smart_playlists::GeneratorPtr");
qRegisterMetaType<SomaFMService::Stream>("SomaFMService::Stream"); qRegisterMetaType<SomaFMService::Stream>("SomaFMService::Stream");
qRegisterMetaType<SongList>("SongList"); qRegisterMetaType<SongList>("SongList");
qRegisterMetaType<Song>("Song"); qRegisterMetaType<Song>("Song");
qRegisterMetaTypeStreamOperators<DigitallyImportedClient::Channel>("DigitallyImportedClient::Channel"); qRegisterMetaTypeStreamOperators<DigitallyImportedClient::Channel>(
"DigitallyImportedClient::Channel");
qRegisterMetaTypeStreamOperators<Equalizer::Params>("Equalizer::Params"); qRegisterMetaTypeStreamOperators<Equalizer::Params>("Equalizer::Params");
qRegisterMetaTypeStreamOperators<QMap<int, int> >("ColumnAlignmentMap"); qRegisterMetaTypeStreamOperators<QMap<int, int> >("ColumnAlignmentMap");
qRegisterMetaTypeStreamOperators<SomaFMService::Stream>("SomaFMService::Stream"); qRegisterMetaTypeStreamOperators<SomaFMService::Stream>(
"SomaFMService::Stream");
qRegisterMetaType<SubdirectoryList>("SubdirectoryList"); qRegisterMetaType<SubdirectoryList>("SubdirectoryList");
qRegisterMetaType<Subdirectory>("Subdirectory"); qRegisterMetaType<Subdirectory>("Subdirectory");
qRegisterMetaType<QList<QUrl> >("QList<QUrl>"); qRegisterMetaType<QList<QUrl> >("QList<QUrl>");

View File

@ -23,16 +23,16 @@
class MimeData : public QMimeData { class MimeData : public QMimeData {
Q_OBJECT Q_OBJECT
public: public:
MimeData(bool clear = false, bool play_now = false, MimeData(bool clear = false, bool play_now = false, bool enqueue = false,
bool enqueue = false, bool open_in_new_playlist = false) bool open_in_new_playlist = false)
: override_user_settings_(false), : override_user_settings_(false),
clear_first_(clear), clear_first_(clear),
play_now_(play_now), play_now_(play_now),
enqueue_now_(enqueue), enqueue_now_(enqueue),
open_in_new_playlist_(open_in_new_playlist), open_in_new_playlist_(open_in_new_playlist),
name_for_new_playlist_(QString()), name_for_new_playlist_(QString()),
from_doubleclick_(false) {} from_doubleclick_(false) {}
// If this is set then MainWindow will not touch any of the other flags. // If this is set then MainWindow will not touch any of the other flags.
bool override_user_settings_; bool override_user_settings_;
@ -43,7 +43,8 @@ public:
// If this is set then the first item that is inserted will start playing // If this is set then the first item that is inserted will start playing
// immediately. Note: this is always overridden with the user's preference // immediately. Note: this is always overridden with the user's preference
// if the MimeData goes via MainWindow, unless you set override_user_settings_. // if the MimeData goes via MainWindow, unless you set
// override_user_settings_.
bool play_now_; bool play_now_;
// If this is set then the items are added to the queue after being inserted. // If this is set then the items are added to the queue after being inserted.
@ -60,12 +61,15 @@ public:
// the defaults set by the user. // the defaults set by the user.
bool from_doubleclick_; bool from_doubleclick_;
// Returns a pretty name for a playlist containing songs described by this MimeData // Returns a pretty name for a playlist containing songs described by this
// object. By pretty name we mean the value of 'name_for_new_playlist_' or generic // MimeData
// object. By pretty name we mean the value of 'name_for_new_playlist_' or
// generic
// "Playlist" string if the 'name_for_new_playlist_' attribute is empty. // "Playlist" string if the 'name_for_new_playlist_' attribute is empty.
QString get_name_for_new_playlist() { QString get_name_for_new_playlist() {
return name_for_new_playlist_.isEmpty() ? tr("Playlist") : name_for_new_playlist_; return name_for_new_playlist_.isEmpty() ? tr("Playlist")
: name_for_new_playlist_;
} }
}; };
#endif // MIMEDATA_H #endif // MIMEDATA_H

View File

@ -8,18 +8,14 @@ template <typename T>
class ModelFutureWatcher : public QFutureWatcher<T> { class ModelFutureWatcher : public QFutureWatcher<T> {
public: public:
ModelFutureWatcher(const QModelIndex& index, QObject* parent = 0) ModelFutureWatcher(const QModelIndex& index, QObject* parent = 0)
: QFutureWatcher<T>(parent), : QFutureWatcher<T>(parent), index_(index) {}
index_(index) {
}
~ModelFutureWatcher() { ~ModelFutureWatcher() {}
}
const QPersistentModelIndex& index() const { return index_; } const QPersistentModelIndex& index() const { return index_; }
private: private:
QPersistentModelIndex index_; QPersistentModelIndex index_;
}; };
#endif #endif

View File

@ -22,11 +22,10 @@
namespace mpris { namespace mpris {
Mpris::Mpris(Application* app, QObject* parent) Mpris::Mpris(Application* app, QObject* parent)
: QObject(parent), : QObject(parent),
mpris1_(new mpris::Mpris1(app, this)), mpris1_(new mpris::Mpris1(app, this)),
mpris2_(new mpris::Mpris2(app, mpris1_, this)) mpris2_(new mpris::Mpris2(app, mpris1_, this)) {
{
connect(mpris2_, SIGNAL(RaiseMainWindow()), SIGNAL(RaiseMainWindow())); connect(mpris2_, SIGNAL(RaiseMainWindow()), SIGNAL(RaiseMainWindow()));
} }
} // namespace mpris } // namespace mpris

View File

@ -30,17 +30,17 @@ class Mpris2;
class Mpris : public QObject { class Mpris : public QObject {
Q_OBJECT Q_OBJECT
public: public:
Mpris(Application* app, QObject* parent = 0); Mpris(Application* app, QObject* parent = 0);
signals: signals:
void RaiseMainWindow(); void RaiseMainWindow();
private: private:
Mpris1* mpris1_; Mpris1* mpris1_;
Mpris2* mpris2_; Mpris2* mpris2_;
}; };
} // namespace mpris } // namespace mpris
#endif // MPRIS_H #endif // MPRIS_H

View File

@ -39,12 +39,11 @@ const char* Mpris1::kDefaultDbusServiceName = "org.mpris.clementine";
Mpris1::Mpris1(Application* app, QObject* parent, Mpris1::Mpris1(Application* app, QObject* parent,
const QString& dbus_service_name) const QString& dbus_service_name)
: QObject(parent), : QObject(parent),
dbus_service_name_(dbus_service_name), dbus_service_name_(dbus_service_name),
root_(nullptr), root_(nullptr),
player_(nullptr), player_(nullptr),
tracklist_(nullptr) tracklist_(nullptr) {
{
qDBusRegisterMetaType<DBusStatus>(); qDBusRegisterMetaType<DBusStatus>();
qDBusRegisterMetaType<Version>(); qDBusRegisterMetaType<Version>();
@ -53,7 +52,8 @@ Mpris1::Mpris1(Application* app, QObject* parent,
} }
if (!QDBusConnection::sessionBus().registerService(dbus_service_name_)) { if (!QDBusConnection::sessionBus().registerService(dbus_service_name_)) {
qLog(Warning) << "Failed to register" << dbus_service_name_ << "on the session bus"; qLog(Warning) << "Failed to register" << dbus_service_name_
<< "on the session bus";
return; return;
} }
@ -61,8 +61,10 @@ Mpris1::Mpris1(Application* app, QObject* parent,
player_ = new Mpris1Player(app, this); player_ = new Mpris1Player(app, this);
tracklist_ = new Mpris1TrackList(app, this); tracklist_ = new Mpris1TrackList(app, this);
connect(app->current_art_loader(), SIGNAL(ArtLoaded(const Song&, const QString&, const QImage&)), connect(app->current_art_loader(),
player_, SLOT(CurrentSongChanged(const Song&, const QString&, const QImage&))); SIGNAL(ArtLoaded(const Song&, const QString&, const QImage&)),
player_,
SLOT(CurrentSongChanged(const Song&, const QString&, const QImage&)));
} }
Mpris1::~Mpris1() { Mpris1::~Mpris1() {
@ -70,27 +72,29 @@ Mpris1::~Mpris1() {
} }
Mpris1Root::Mpris1Root(Application* app, QObject* parent) Mpris1Root::Mpris1Root(Application* app, QObject* parent)
: QObject(parent), : QObject(parent), app_(app) {
app_(app) {
new MprisRoot(this); new MprisRoot(this);
QDBusConnection::sessionBus().registerObject("/", this); QDBusConnection::sessionBus().registerObject("/", this);
} }
Mpris1Player::Mpris1Player(Application* app, QObject* parent) Mpris1Player::Mpris1Player(Application* app, QObject* parent)
: QObject(parent), : QObject(parent), app_(app) {
app_(app) {
new MprisPlayer(this); new MprisPlayer(this);
QDBusConnection::sessionBus().registerObject("/Player", this); QDBusConnection::sessionBus().registerObject("/Player", this);
connect(app_->player()->engine(), SIGNAL(StateChanged(Engine::State)), SLOT(EngineStateChanged(Engine::State))); connect(app_->player()->engine(), SIGNAL(StateChanged(Engine::State)),
connect(app_->playlist_manager(), SIGNAL(PlaylistManagerInitialized()), SLOT(PlaylistManagerInitialized())); SLOT(EngineStateChanged(Engine::State)));
connect(app_->playlist_manager(), SIGNAL(PlaylistManagerInitialized()),
SLOT(PlaylistManagerInitialized()));
} }
// when PlaylistManager gets it ready, we connect PlaylistSequence with this // when PlaylistManager gets it ready, we connect PlaylistSequence with this
void Mpris1Player::PlaylistManagerInitialized() { void Mpris1Player::PlaylistManagerInitialized() {
connect(app_->playlist_manager()->sequence(), SIGNAL(ShuffleModeChanged(PlaylistSequence::ShuffleMode)), connect(app_->playlist_manager()->sequence(),
SIGNAL(ShuffleModeChanged(PlaylistSequence::ShuffleMode)),
SLOT(ShuffleModeChanged())); SLOT(ShuffleModeChanged()));
connect(app_->playlist_manager()->sequence(), SIGNAL(RepeatModeChanged(PlaylistSequence::RepeatMode)), connect(app_->playlist_manager()->sequence(),
SIGNAL(RepeatModeChanged(PlaylistSequence::RepeatMode)),
SLOT(RepeatModeChanged())); SLOT(RepeatModeChanged()));
} }
@ -99,22 +103,23 @@ Mpris1TrackList::Mpris1TrackList(Application* app, QObject* parent)
new MprisTrackList(this); new MprisTrackList(this);
QDBusConnection::sessionBus().registerObject("/TrackList", this); QDBusConnection::sessionBus().registerObject("/TrackList", this);
connect(app_->playlist_manager(), SIGNAL(PlaylistChanged(Playlist*)), SLOT(PlaylistChanged(Playlist*))); connect(app_->playlist_manager(), SIGNAL(PlaylistChanged(Playlist*)),
SLOT(PlaylistChanged(Playlist*)));
} }
void Mpris1TrackList::PlaylistChanged(Playlist* playlist) { void Mpris1TrackList::PlaylistChanged(Playlist* playlist) {
emit TrackListChange(playlist->rowCount()); emit TrackListChange(playlist->rowCount());
} }
// we use the state from event and don't try to obtain it from Player // we use the state from event and don't try to obtain it from Player
// later because only the event's version is really the current one // later because only the event's version is really the current one
void Mpris1Player::EngineStateChanged(Engine::State state) { void Mpris1Player::EngineStateChanged(Engine::State state) {
emit StatusChange(GetStatus(state)); emit StatusChange(GetStatus(state));
emit CapsChange(GetCaps(state)); emit CapsChange(GetCaps(state));
} }
void Mpris1Player::CurrentSongChanged( void Mpris1Player::CurrentSongChanged(const Song& song, const QString& art_uri,
const Song& song, const QString& art_uri, const QImage&) { const QImage&) {
last_metadata_ = Mpris1::GetMetadata(song); last_metadata_ = Mpris1::GetMetadata(song);
if (!art_uri.isEmpty()) { if (!art_uri.isEmpty()) {
@ -126,11 +131,9 @@ void Mpris1Player::CurrentSongChanged(
emit CapsChange(GetCaps()); emit CapsChange(GetCaps());
} }
QString Mpris1Root::Identity() { QString Mpris1Root::Identity() {
return QString("%1 %2").arg( return QString("%1 %2").arg(QCoreApplication::applicationName(),
QCoreApplication::applicationName(), QCoreApplication::applicationVersion());
QCoreApplication::applicationVersion());
} }
Version Mpris1Root::MprisVersion() { Version Mpris1Root::MprisVersion() {
@ -140,42 +143,26 @@ Version Mpris1Root::MprisVersion() {
return version; return version;
} }
void Mpris1Root::Quit() { void Mpris1Root::Quit() { qApp->quit(); }
qApp->quit();
}
void Mpris1Player::Pause() { void Mpris1Player::Pause() { app_->player()->PlayPause(); }
app_->player()->PlayPause();
}
void Mpris1Player::Stop() { void Mpris1Player::Stop() { app_->player()->Stop(); }
app_->player()->Stop();
}
void Mpris1Player::Prev() { void Mpris1Player::Prev() { app_->player()->Previous(); }
app_->player()->Previous();
}
void Mpris1Player::Play() { void Mpris1Player::Play() { app_->player()->Play(); }
app_->player()->Play();
}
void Mpris1Player::Next() { void Mpris1Player::Next() { app_->player()->Next(); }
app_->player()->Next();
}
void Mpris1Player::Repeat(bool repeat) { void Mpris1Player::Repeat(bool repeat) {
app_->playlist_manager()->sequence()->SetRepeatMode( app_->playlist_manager()->sequence()->SetRepeatMode(
repeat ? PlaylistSequence::Repeat_Track : PlaylistSequence::Repeat_Off); repeat ? PlaylistSequence::Repeat_Track : PlaylistSequence::Repeat_Off);
} }
void Mpris1Player::ShuffleModeChanged() { void Mpris1Player::ShuffleModeChanged() { emit StatusChange(GetStatus()); }
emit StatusChange(GetStatus());
}
void Mpris1Player::RepeatModeChanged() { void Mpris1Player::RepeatModeChanged() { emit StatusChange(GetStatus()); }
emit StatusChange(GetStatus());
}
DBusStatus Mpris1Player::GetStatus() const { DBusStatus Mpris1Player::GetStatus() const {
return GetStatus(app_->player()->GetState()); return GetStatus(app_->player()->GetState());
@ -199,25 +186,27 @@ DBusStatus Mpris1Player::GetStatus(Engine::State state) const {
if (app_->playlist_manager()->sequence()) { if (app_->playlist_manager()->sequence()) {
PlaylistManagerInterface* playlists_ = app_->playlist_manager(); PlaylistManagerInterface* playlists_ = app_->playlist_manager();
PlaylistSequence::RepeatMode repeat_mode = playlists_->sequence()->repeat_mode(); PlaylistSequence::RepeatMode repeat_mode =
playlists_->sequence()->repeat_mode();
status.random = playlists_->sequence()->shuffle_mode() == PlaylistSequence::Shuffle_Off ? 0 : 1;
status.random =
playlists_->sequence()->shuffle_mode() == PlaylistSequence::Shuffle_Off
? 0
: 1;
status.repeat = repeat_mode == PlaylistSequence::Repeat_Track ? 1 : 0; status.repeat = repeat_mode == PlaylistSequence::Repeat_Track ? 1 : 0;
status.repeat_playlist = (repeat_mode == PlaylistSequence::Repeat_Album || status.repeat_playlist =
repeat_mode == PlaylistSequence::Repeat_Playlist || (repeat_mode == PlaylistSequence::Repeat_Album ||
repeat_mode == PlaylistSequence::Repeat_Track) ? 1 : 0; repeat_mode == PlaylistSequence::Repeat_Playlist ||
repeat_mode == PlaylistSequence::Repeat_Track)
? 1
: 0;
} }
return status; return status;
} }
void Mpris1Player::VolumeSet(int volume) { void Mpris1Player::VolumeSet(int volume) { app_->player()->SetVolume(volume); }
app_->player()->SetVolume(volume);
}
int Mpris1Player::VolumeGet() const { int Mpris1Player::VolumeGet() const { return app_->player()->GetVolume(); }
return app_->player()->GetVolume();
}
void Mpris1Player::PositionSet(int pos_msec) { void Mpris1Player::PositionSet(int pos_msec) {
app_->player()->SeekTo(pos_msec / kMsecPerSec); app_->player()->SeekTo(pos_msec / kMsecPerSec);
@ -227,9 +216,7 @@ int Mpris1Player::PositionGet() const {
return app_->player()->engine()->position_nanosec() / kNsecPerMsec; return app_->player()->engine()->position_nanosec() / kNsecPerMsec;
} }
QVariantMap Mpris1Player::GetMetadata() const { QVariantMap Mpris1Player::GetMetadata() const { return last_metadata_; }
return last_metadata_;
}
int Mpris1Player::GetCaps() const { int Mpris1Player::GetCaps() const {
return GetCaps(app_->player()->GetState()); return GetCaps(app_->player()->GetState());
@ -241,12 +228,15 @@ int Mpris1Player::GetCaps(Engine::State state) const {
PlaylistManagerInterface* playlists = app_->playlist_manager(); PlaylistManagerInterface* playlists = app_->playlist_manager();
if (playlists->active()) { if (playlists->active()) {
// play is disabled when playlist is empty or when last.fm stream is already playing // play is disabled when playlist is empty or when last.fm stream is already
if (playlists->active() && playlists->active()->rowCount() != 0 // playing
&& !(state == Engine::Playing && (app_->player()->GetCurrentItem()->options() & PlaylistItem::LastFMControls))) { if (playlists->active() && playlists->active()->rowCount() != 0 &&
!(state == Engine::Playing &&
(app_->player()->GetCurrentItem()->options() &
PlaylistItem::LastFMControls))) {
caps |= CAN_PLAY; caps |= CAN_PLAY;
} }
if (playlists->active()->next_row() != -1) { if (playlists->active()->next_row() != -1) {
caps |= CAN_GO_NEXT; caps |= CAN_GO_NEXT;
} }
@ -257,7 +247,8 @@ int Mpris1Player::GetCaps(Engine::State state) const {
if (current_item) { if (current_item) {
caps |= CAN_PROVIDE_METADATA; caps |= CAN_PROVIDE_METADATA;
if (state == Engine::Playing && !(current_item->options() & PlaylistItem::PauseDisabled)) { if (state == Engine::Playing &&
!(current_item->options() & PlaylistItem::PauseDisabled)) {
caps |= CAN_PAUSE; caps |= CAN_PAUSE;
} }
if (state != Engine::Empty && !current_item->Metadata().is_stream()) { if (state != Engine::Empty && !current_item->Metadata().is_stream()) {
@ -268,25 +259,17 @@ int Mpris1Player::GetCaps(Engine::State state) const {
return caps; return caps;
} }
void Mpris1Player::VolumeUp(int change) { void Mpris1Player::VolumeUp(int change) { VolumeSet(VolumeGet() + change); }
VolumeSet(VolumeGet() + change);
}
void Mpris1Player::VolumeDown(int change) { void Mpris1Player::VolumeDown(int change) { VolumeSet(VolumeGet() - change); }
VolumeSet(VolumeGet() - change);
}
void Mpris1Player::Mute() { void Mpris1Player::Mute() { app_->player()->Mute(); }
app_->player()->Mute();
}
void Mpris1Player::ShowOSD() { void Mpris1Player::ShowOSD() { app_->player()->ShowOSD(); }
app_->player()->ShowOSD();
}
int Mpris1TrackList::AddTrack(const QString& track, bool play) { int Mpris1TrackList::AddTrack(const QString& track, bool play) {
app_->playlist_manager()->active()->InsertUrls( app_->playlist_manager()->active()->InsertUrls(QList<QUrl>() << QUrl(track),
QList<QUrl>() << QUrl(track), -1, play); -1, play);
return 0; return 0;
} }
@ -304,15 +287,15 @@ int Mpris1TrackList::GetLength() const {
QVariantMap Mpris1TrackList::GetMetadata(int pos) const { QVariantMap Mpris1TrackList::GetMetadata(int pos) const {
PlaylistItemPtr item = app_->player()->GetItemAt(pos); PlaylistItemPtr item = app_->player()->GetItemAt(pos);
if (!item) if (!item) return QVariantMap();
return QVariantMap();
return Mpris1::GetMetadata(item->Metadata()); return Mpris1::GetMetadata(item->Metadata());
} }
void Mpris1TrackList::SetLoop(bool enable) { void Mpris1TrackList::SetLoop(bool enable) {
app_->playlist_manager()->active()->sequence()->SetRepeatMode( app_->playlist_manager()->active()->sequence()->SetRepeatMode(
enable ? PlaylistSequence::Repeat_Playlist : PlaylistSequence::Repeat_Off); enable ? PlaylistSequence::Repeat_Playlist
: PlaylistSequence::Repeat_Off);
} }
void Mpris1TrackList::SetRandom(bool enable) { void Mpris1TrackList::SetRandom(bool enable) {
@ -351,24 +334,23 @@ QVariantMap Mpris1::GetMetadata(const Song& song) {
return ret; return ret;
} }
} // namespace mpris } // namespace mpris
QDBusArgument& operator<<(QDBusArgument& arg, const Version& version) {
QDBusArgument& operator<< (QDBusArgument& arg, const Version& version) {
arg.beginStructure(); arg.beginStructure();
arg << version.major << version.minor; arg << version.major << version.minor;
arg.endStructure(); arg.endStructure();
return arg; return arg;
} }
const QDBusArgument& operator>> (const QDBusArgument& arg, Version& version) { const QDBusArgument& operator>>(const QDBusArgument& arg, Version& version) {
arg.beginStructure(); arg.beginStructure();
arg >> version.major >> version.minor; arg >> version.major >> version.minor;
arg.endStructure(); arg.endStructure();
return arg; return arg;
} }
QDBusArgument& operator<< (QDBusArgument& arg, const DBusStatus& status) { QDBusArgument& operator<<(QDBusArgument& arg, const DBusStatus& status) {
arg.beginStructure(); arg.beginStructure();
arg << status.play; arg << status.play;
arg << status.random; arg << status.random;
@ -378,7 +360,7 @@ QDBusArgument& operator<< (QDBusArgument& arg, const DBusStatus& status) {
return arg; return arg;
} }
const QDBusArgument& operator>> (const QDBusArgument& arg, DBusStatus& status) { const QDBusArgument& operator>>(const QDBusArgument& arg, DBusStatus& status) {
arg.beginStructure(); arg.beginStructure();
arg >> status.play; arg >> status.play;
arg >> status.random; arg >> status.random;

View File

@ -27,18 +27,15 @@
class Application; class Application;
class Playlist; class Playlist;
struct DBusStatus { // From Amarok. struct DBusStatus { // From Amarok.
DBusStatus() DBusStatus()
: play(Mpris_Stopped), : play(Mpris_Stopped), random(0), repeat(0), repeat_playlist(0) {}
random(0),
repeat(0), int play; // Playing = 0, Paused = 1, Stopped = 2
repeat_playlist(0) int random; // Linearly = 0, Randomly = 1
{} int repeat; // Go_To_Next = 0, Repeat_Current = 1
int repeat_playlist; // Stop_When_Finished = 0, Never_Give_Up_Playing = 1,
int play; // Playing = 0, Paused = 1, Stopped = 2 // Never_Let_You_Down = 42
int random; // Linearly = 0, Randomly = 1
int repeat; // Go_To_Next = 0, Repeat_Current = 1
int repeat_playlist; // Stop_When_Finished = 0, Never_Give_Up_Playing = 1, Never_Let_You_Down = 42
enum MprisPlayState { enum MprisPlayState {
Mpris_Playing = 0, Mpris_Playing = 0,
@ -48,9 +45,8 @@ struct DBusStatus { // From Amarok.
}; };
Q_DECLARE_METATYPE(DBusStatus); Q_DECLARE_METATYPE(DBusStatus);
QDBusArgument& operator <<(QDBusArgument& arg, const DBusStatus& status); QDBusArgument& operator<<(QDBusArgument& arg, const DBusStatus& status);
const QDBusArgument& operator >>(const QDBusArgument& arg, DBusStatus& status); const QDBusArgument& operator>>(const QDBusArgument& arg, DBusStatus& status);
struct Version { struct Version {
quint16 minor; quint16 minor;
@ -58,31 +54,30 @@ struct Version {
}; };
Q_DECLARE_METATYPE(Version); Q_DECLARE_METATYPE(Version);
QDBusArgument& operator <<(QDBusArgument& arg, const Version& version); QDBusArgument& operator<<(QDBusArgument& arg, const Version& version);
const QDBusArgument& operator >>(const QDBusArgument& arg, Version& version); const QDBusArgument& operator>>(const QDBusArgument& arg, Version& version);
namespace mpris { namespace mpris {
enum DBusCaps { enum DBusCaps {
NONE = 0, NONE = 0,
CAN_GO_NEXT = 1 << 0, CAN_GO_NEXT = 1 << 0,
CAN_GO_PREV = 1 << 1, CAN_GO_PREV = 1 << 1,
CAN_PAUSE = 1 << 2, CAN_PAUSE = 1 << 2,
CAN_PLAY = 1 << 3, CAN_PLAY = 1 << 3,
CAN_SEEK = 1 << 4, CAN_SEEK = 1 << 4,
CAN_PROVIDE_METADATA = 1 << 5, CAN_PROVIDE_METADATA = 1 << 5,
CAN_HAS_TRACKLIST = 1 << 6, CAN_HAS_TRACKLIST = 1 << 6,
}; };
class Mpris1Root; class Mpris1Root;
class Mpris1Player; class Mpris1Player;
class Mpris1TrackList; class Mpris1TrackList;
class Mpris1 : public QObject { class Mpris1 : public QObject {
Q_OBJECT Q_OBJECT
public: public:
Mpris1(Application* app, QObject* parent = 0, Mpris1(Application* app, QObject* parent = 0,
const QString& dbus_service_name = QString()); const QString& dbus_service_name = QString());
~Mpris1(); ~Mpris1();
@ -93,7 +88,7 @@ public:
Mpris1Player* player() const { return player_; } Mpris1Player* player() const { return player_; }
Mpris1TrackList* tracklist() const { return tracklist_; } Mpris1TrackList* tracklist() const { return tracklist_; }
private: private:
static const char* kDefaultDbusServiceName; static const char* kDefaultDbusServiceName;
QString dbus_service_name_; QString dbus_service_name_;
@ -103,26 +98,24 @@ private:
Mpris1TrackList* tracklist_; Mpris1TrackList* tracklist_;
}; };
class Mpris1Root : public QObject { class Mpris1Root : public QObject {
Q_OBJECT Q_OBJECT
public: public:
Mpris1Root(Application* app, QObject* parent = 0); Mpris1Root(Application* app, QObject* parent = 0);
QString Identity(); QString Identity();
void Quit(); void Quit();
Version MprisVersion(); Version MprisVersion();
private: private:
Application* app_; Application* app_;
}; };
class Mpris1Player : public QObject { class Mpris1Player : public QObject {
Q_OBJECT Q_OBJECT
public: public:
Mpris1Player(Application* app, QObject* parent = 0); Mpris1Player(Application* app, QObject* parent = 0);
void Pause(); void Pause();
@ -132,7 +125,8 @@ public:
void Next(); void Next();
void Repeat(bool); void Repeat(bool);
// those methods will use engine's state obtained with player->GetState() method // those methods will use engine's state obtained with player->GetState()
// method
DBusStatus GetStatus() const; DBusStatus GetStatus() const;
int GetCaps() const; int GetCaps() const;
// those methods will use engine's state provided as an argument // those methods will use engine's state provided as an argument
@ -151,33 +145,32 @@ public:
void Mute(); void Mute();
void ShowOSD(); void ShowOSD();
public slots: public slots:
void CurrentSongChanged( void CurrentSongChanged(const Song& song, const QString& art_uri,
const Song& song, const QString& art_uri, const QImage&); const QImage&);
signals: signals:
void CapsChange(int); void CapsChange(int);
void TrackChange(const QVariantMap&); void TrackChange(const QVariantMap&);
void StatusChange(DBusStatus); void StatusChange(DBusStatus);
private slots: private slots:
void PlaylistManagerInitialized(); void PlaylistManagerInitialized();
void EngineStateChanged(Engine::State state); void EngineStateChanged(Engine::State state);
void ShuffleModeChanged(); void ShuffleModeChanged();
void RepeatModeChanged(); void RepeatModeChanged();
private: private:
Application* app_; Application* app_;
QVariantMap last_metadata_; QVariantMap last_metadata_;
}; };
class Mpris1TrackList : public QObject { class Mpris1TrackList : public QObject {
Q_OBJECT Q_OBJECT
public: public:
Mpris1TrackList(Application* app, QObject* parent = 0); Mpris1TrackList(Application* app, QObject* parent = 0);
int AddTrack(const QString&, bool); int AddTrack(const QString&, bool);
@ -194,13 +187,13 @@ public:
signals: signals:
void TrackListChange(int i); void TrackListChange(int i);
private slots: private slots:
void PlaylistChanged(Playlist* playlist); void PlaylistChanged(Playlist* playlist);
private: private:
Application* app_; Application* app_;
}; };
} // namespace mpris } // namespace mpris
#endif // MPRIS1_H #endif // MPRIS1_H

View File

@ -41,22 +41,22 @@
#include <QDBusConnection> #include <QDBusConnection>
#include <QtConcurrentRun> #include <QtConcurrentRun>
QDBusArgument& operator<< (QDBusArgument& arg, const MprisPlaylist& playlist) { QDBusArgument& operator<<(QDBusArgument& arg, const MprisPlaylist& playlist) {
arg.beginStructure(); arg.beginStructure();
arg << playlist.id << playlist.name << playlist.icon; arg << playlist.id << playlist.name << playlist.icon;
arg.endStructure(); arg.endStructure();
return arg; return arg;
} }
const QDBusArgument& operator>> ( const QDBusArgument& operator>>(const QDBusArgument& arg,
const QDBusArgument& arg, MprisPlaylist& playlist) { MprisPlaylist& playlist) {
arg.beginStructure(); arg.beginStructure();
arg >> playlist.id >> playlist.name >> playlist.icon; arg >> playlist.id >> playlist.name >> playlist.icon;
arg.endStructure(); arg.endStructure();
return arg; return arg;
} }
QDBusArgument& operator<< (QDBusArgument& arg, const MaybePlaylist& playlist) { QDBusArgument& operator<<(QDBusArgument& arg, const MaybePlaylist& playlist) {
arg.beginStructure(); arg.beginStructure();
arg << playlist.valid; arg << playlist.valid;
arg << playlist.playlist; arg << playlist.playlist;
@ -64,8 +64,8 @@ QDBusArgument& operator<< (QDBusArgument& arg, const MaybePlaylist& playlist) {
return arg; return arg;
} }
const QDBusArgument& operator>> ( const QDBusArgument& operator>>(const QDBusArgument& arg,
const QDBusArgument& arg, MaybePlaylist& playlist) { MaybePlaylist& playlist) {
arg.beginStructure(); arg.beginStructure();
arg >> playlist.valid >> playlist.playlist; arg >> playlist.valid >> playlist.playlist;
arg.endStructure(); arg.endStructure();
@ -79,121 +79,115 @@ const char* Mpris2::kServiceName = "org.mpris.MediaPlayer2.clementine";
const char* Mpris2::kFreedesktopPath = "org.freedesktop.DBus.Properties"; const char* Mpris2::kFreedesktopPath = "org.freedesktop.DBus.Properties";
Mpris2::Mpris2(Application* app, Mpris1* mpris1, QObject* parent) Mpris2::Mpris2(Application* app, Mpris1* mpris1, QObject* parent)
: QObject(parent), : QObject(parent), app_(app), mpris1_(mpris1) {
app_(app),
mpris1_(mpris1)
{
new Mpris2Root(this); new Mpris2Root(this);
new Mpris2TrackList(this); new Mpris2TrackList(this);
new Mpris2Player(this); new Mpris2Player(this);
new Mpris2Playlists(this); new Mpris2Playlists(this);
if (!QDBusConnection::sessionBus().registerService(kServiceName)) { if (!QDBusConnection::sessionBus().registerService(kServiceName)) {
qLog(Warning) << "Failed to register" << QString(kServiceName) << "on the session bus"; qLog(Warning) << "Failed to register" << QString(kServiceName)
<< "on the session bus";
return; return;
} }
QDBusConnection::sessionBus().registerObject(kMprisObjectPath, this); QDBusConnection::sessionBus().registerObject(kMprisObjectPath, this);
connect(app_->current_art_loader(), SIGNAL(ArtLoaded(Song,QString,QImage)), SLOT(ArtLoaded(Song,QString))); connect(app_->current_art_loader(), SIGNAL(ArtLoaded(Song, QString, QImage)),
SLOT(ArtLoaded(Song, QString)));
connect(app_->player()->engine(), SIGNAL(StateChanged(Engine::State)), SLOT(EngineStateChanged(Engine::State))); connect(app_->player()->engine(), SIGNAL(StateChanged(Engine::State)),
SLOT(EngineStateChanged(Engine::State)));
connect(app_->player(), SIGNAL(VolumeChanged(int)), SLOT(VolumeChanged())); connect(app_->player(), SIGNAL(VolumeChanged(int)), SLOT(VolumeChanged()));
connect(app_->player(), SIGNAL(Seeked(qlonglong)), SIGNAL(Seeked(qlonglong))); connect(app_->player(), SIGNAL(Seeked(qlonglong)), SIGNAL(Seeked(qlonglong)));
connect(app_->playlist_manager(), SIGNAL(PlaylistManagerInitialized()), SLOT(PlaylistManagerInitialized())); connect(app_->playlist_manager(), SIGNAL(PlaylistManagerInitialized()),
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(CurrentSongChanged(Song))); SLOT(PlaylistManagerInitialized()));
connect(app_->playlist_manager(), SIGNAL(PlaylistChanged(Playlist*)), SLOT(PlaylistChanged(Playlist*))); connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)),
connect(app_->playlist_manager(), SIGNAL(CurrentChanged(Playlist*)), SLOT(PlaylistCollectionChanged(Playlist*))); SLOT(CurrentSongChanged(Song)));
connect(app_->playlist_manager(), SIGNAL(PlaylistChanged(Playlist*)),
SLOT(PlaylistChanged(Playlist*)));
connect(app_->playlist_manager(), SIGNAL(CurrentChanged(Playlist*)),
SLOT(PlaylistCollectionChanged(Playlist*)));
} }
// when PlaylistManager gets it ready, we connect PlaylistSequence with this // when PlaylistManager gets it ready, we connect PlaylistSequence with this
void Mpris2::PlaylistManagerInitialized() { void Mpris2::PlaylistManagerInitialized() {
connect(app_->playlist_manager()->sequence(), SIGNAL(ShuffleModeChanged(PlaylistSequence::ShuffleMode)), connect(app_->playlist_manager()->sequence(),
SIGNAL(ShuffleModeChanged(PlaylistSequence::ShuffleMode)),
SLOT(ShuffleModeChanged())); SLOT(ShuffleModeChanged()));
connect(app_->playlist_manager()->sequence(), SIGNAL(RepeatModeChanged(PlaylistSequence::RepeatMode)), connect(app_->playlist_manager()->sequence(),
SIGNAL(RepeatModeChanged(PlaylistSequence::RepeatMode)),
SLOT(RepeatModeChanged())); SLOT(RepeatModeChanged()));
} }
void Mpris2::EngineStateChanged(Engine::State newState) { void Mpris2::EngineStateChanged(Engine::State newState) {
if(newState != Engine::Playing && newState != Engine::Paused) { if (newState != Engine::Playing && newState != Engine::Paused) {
last_metadata_= QVariantMap(); last_metadata_ = QVariantMap();
EmitNotification("Metadata"); EmitNotification("Metadata");
} }
EmitNotification("PlaybackStatus", PlaybackStatus(newState)); EmitNotification("PlaybackStatus", PlaybackStatus(newState));
} }
void Mpris2::VolumeChanged() { void Mpris2::VolumeChanged() { EmitNotification("Volume"); }
EmitNotification("Volume");
}
void Mpris2::ShuffleModeChanged() { void Mpris2::ShuffleModeChanged() { EmitNotification("Shuffle"); }
EmitNotification("Shuffle");
}
void Mpris2::RepeatModeChanged() { void Mpris2::RepeatModeChanged() { EmitNotification("LoopStatus"); }
EmitNotification("LoopStatus");
}
void Mpris2::EmitNotification(const QString& name, const QVariant& val) { void Mpris2::EmitNotification(const QString& name, const QVariant& val) {
EmitNotification(name, val, "org.mpris.MediaPlayer2.Player"); EmitNotification(name, val, "org.mpris.MediaPlayer2.Player");
} }
void Mpris2::EmitNotification(const QString& name, const QVariant& val, const QString& mprisEntity) { void Mpris2::EmitNotification(const QString& name, const QVariant& val,
const QString& mprisEntity) {
QDBusMessage msg = QDBusMessage::createSignal( QDBusMessage msg = QDBusMessage::createSignal(
kMprisObjectPath, kFreedesktopPath, "PropertiesChanged"); kMprisObjectPath, kFreedesktopPath, "PropertiesChanged");
QVariantMap map; QVariantMap map;
map.insert(name, val); map.insert(name, val);
QVariantList args = QVariantList() QVariantList args = QVariantList() << mprisEntity << map << QStringList();
<< mprisEntity
<< map
<< QStringList();
msg.setArguments(args); msg.setArguments(args);
QDBusConnection::sessionBus().send(msg); QDBusConnection::sessionBus().send(msg);
} }
void Mpris2::EmitNotification(const QString& name) { void Mpris2::EmitNotification(const QString& name) {
QVariant value; QVariant value;
if (name == "PlaybackStatus") value = PlaybackStatus(); if (name == "PlaybackStatus")
else if (name == "LoopStatus") value = LoopStatus(); value = PlaybackStatus();
else if (name == "Shuffle") value = Shuffle(); else if (name == "LoopStatus")
else if (name == "Metadata") value = Metadata(); value = LoopStatus();
else if (name == "Volume") value = Volume(); else if (name == "Shuffle")
else if (name == "Position") value = Position(); value = Shuffle();
else if (name == "Metadata")
value = Metadata();
else if (name == "Volume")
value = Volume();
else if (name == "Position")
value = Position();
if (value.isValid()) if (value.isValid()) EmitNotification(name, value);
EmitNotification(name, value);
} }
//------------------Root Interface--------------------------// //------------------Root Interface--------------------------//
bool Mpris2::CanQuit() const { bool Mpris2::CanQuit() const { return true; }
return true;
}
bool Mpris2::CanRaise() const { bool Mpris2::CanRaise() const { return true; }
return true;
}
bool Mpris2::HasTrackList() const { bool Mpris2::HasTrackList() const { return true; }
return true;
}
QString Mpris2::Identity() const { QString Mpris2::Identity() const { return QCoreApplication::applicationName(); }
return QCoreApplication::applicationName();
}
QString Mpris2::DesktopEntryAbsolutePath() const { QString Mpris2::DesktopEntryAbsolutePath() const {
QStringList xdg_data_dirs = QString(getenv("XDG_DATA_DIRS")).split(":"); QStringList xdg_data_dirs = QString(getenv("XDG_DATA_DIRS")).split(":");
xdg_data_dirs.append("/usr/local/share/"); xdg_data_dirs.append("/usr/local/share/");
xdg_data_dirs.append("/usr/share/"); xdg_data_dirs.append("/usr/share/");
foreach (const QString& directory, xdg_data_dirs) { foreach(const QString & directory, xdg_data_dirs) {
QString path = QString("%1/applications/%2.desktop"). QString path = QString("%1/applications/%2.desktop").arg(
arg(directory, QApplication::applicationName().toLower()); directory, QApplication::applicationName().toLower());
if (QFile::exists(path)) if (QFile::exists(path)) return path;
return path;
} }
return QString(); return QString();
} }
@ -203,52 +197,46 @@ QString Mpris2::DesktopEntry() const {
} }
QStringList Mpris2::SupportedUriSchemes() const { QStringList Mpris2::SupportedUriSchemes() const {
static QStringList res = QStringList() static QStringList res = QStringList() << "file"
<< "file" << "http"
<< "http" << "cdda"
<< "cdda" << "smb"
<< "smb" << "sftp";
<< "sftp";
return res; return res;
} }
QStringList Mpris2::SupportedMimeTypes() const { QStringList Mpris2::SupportedMimeTypes() const {
static QStringList res = QStringList() static QStringList res = QStringList() << "application/ogg"
<< "application/ogg" << "application/x-ogg"
<< "application/x-ogg" << "application/x-ogm-audio"
<< "application/x-ogm-audio" << "audio/aac"
<< "audio/aac" << "audio/mp4"
<< "audio/mp4" << "audio/mpeg"
<< "audio/mpeg" << "audio/mpegurl"
<< "audio/mpegurl" << "audio/ogg"
<< "audio/ogg" << "audio/vnd.rn-realaudio"
<< "audio/vnd.rn-realaudio" << "audio/vorbis"
<< "audio/vorbis" << "audio/x-flac"
<< "audio/x-flac" << "audio/x-mp3"
<< "audio/x-mp3" << "audio/x-mpeg"
<< "audio/x-mpeg" << "audio/x-mpegurl"
<< "audio/x-mpegurl" << "audio/x-ms-wma"
<< "audio/x-ms-wma" << "audio/x-musepack"
<< "audio/x-musepack" << "audio/x-oggflac"
<< "audio/x-oggflac" << "audio/x-pn-realaudio"
<< "audio/x-pn-realaudio" << "audio/x-scpls"
<< "audio/x-scpls" << "audio/x-speex"
<< "audio/x-speex" << "audio/x-vorbis"
<< "audio/x-vorbis" << "audio/x-vorbis+ogg"
<< "audio/x-vorbis+ogg" << "audio/x-wav"
<< "audio/x-wav" << "video/x-ms-asf"
<< "video/x-ms-asf" << "x-content/audio-player";
<< "x-content/audio-player";
return res; return res;
} }
void Mpris2::Raise() { void Mpris2::Raise() { emit RaiseMainWindow(); }
emit RaiseMainWindow();
}
void Mpris2::Quit() { void Mpris2::Quit() { qApp->quit(); }
qApp->quit();
}
QString Mpris2::PlaybackStatus() const { QString Mpris2::PlaybackStatus() const {
return PlaybackStatus(app_->player()->GetState()); return PlaybackStatus(app_->player()->GetState());
@ -256,9 +244,12 @@ QString Mpris2::PlaybackStatus() const {
QString Mpris2::PlaybackStatus(Engine::State state) const { QString Mpris2::PlaybackStatus(Engine::State state) const {
switch (state) { switch (state) {
case Engine::Playing: return "Playing"; case Engine::Playing:
case Engine::Paused: return "Paused"; return "Playing";
default: return "Stopped"; case Engine::Paused:
return "Paused";
default:
return "Stopped";
} }
} }
@ -266,12 +257,15 @@ QString Mpris2::LoopStatus() const {
if (!app_->playlist_manager()->sequence()) { if (!app_->playlist_manager()->sequence()) {
return "None"; return "None";
} }
switch (app_->playlist_manager()->sequence()->repeat_mode()) { switch (app_->playlist_manager()->sequence()->repeat_mode()) {
case PlaylistSequence::Repeat_Album: case PlaylistSequence::Repeat_Album:
case PlaylistSequence::Repeat_Playlist: return "Playlist"; case PlaylistSequence::Repeat_Playlist:
case PlaylistSequence::Repeat_Track: return "Track"; return "Playlist";
default: return "None"; case PlaylistSequence::Repeat_Track:
return "Track";
default:
return "None";
} }
} }
@ -289,12 +283,10 @@ void Mpris2::SetLoopStatus(const QString& value) {
app_->playlist_manager()->active()->sequence()->SetRepeatMode(mode); app_->playlist_manager()->active()->sequence()->SetRepeatMode(mode);
} }
double Mpris2::Rate() const { double Mpris2::Rate() const { return 1.0; }
return 1.0;
}
void Mpris2::SetRate(double rate) { void Mpris2::SetRate(double rate) {
if(rate == 0) { if (rate == 0) {
if (mpris1_->player()) { if (mpris1_->player()) {
mpris1_->player()->Pause(); mpris1_->player()->Pause();
} }
@ -315,24 +307,20 @@ void Mpris2::SetShuffle(bool value) {
} }
} }
QVariantMap Mpris2::Metadata() const { QVariantMap Mpris2::Metadata() const { return last_metadata_; }
return last_metadata_;
}
QString Mpris2::current_track_id() const { QString Mpris2::current_track_id() const {
if (!mpris1_->tracklist()) { if (!mpris1_->tracklist()) {
return QString(); return QString();
} }
return QString("/org/mpris/MediaPlayer2/Track/%1").arg( return QString("/org/mpris/MediaPlayer2/Track/%1")
QString::number(mpris1_->tracklist()->GetCurrentTrack())); .arg(QString::number(mpris1_->tracklist()->GetCurrentTrack()));
} }
// We send Metadata change notification as soon as the process of // We send Metadata change notification as soon as the process of
// changing song starts... // changing song starts...
void Mpris2::CurrentSongChanged(const Song& song) { void Mpris2::CurrentSongChanged(const Song& song) { ArtLoaded(song, ""); }
ArtLoaded(song, "");
}
// ... and we add the cover information later, when it's available. // ... and we add the cover information later, when it's available.
void Mpris2::ArtLoaded(const Song& song, const QString& art_uri) { void Mpris2::ArtLoaded(const Song& song, const QString& art_uri) {
@ -363,21 +351,15 @@ double Mpris2::Volume() const {
} }
} }
void Mpris2::SetVolume(double value) { void Mpris2::SetVolume(double value) { app_->player()->SetVolume(value * 100); }
app_->player()->SetVolume(value * 100);
}
qlonglong Mpris2::Position() const { qlonglong Mpris2::Position() const {
return app_->player()->engine()->position_nanosec() / kNsecPerUsec; return app_->player()->engine()->position_nanosec() / kNsecPerUsec;
} }
double Mpris2::MaximumRate() const { double Mpris2::MaximumRate() const { return 1.0; }
return 1.0;
}
double Mpris2::MinimumRate() const { double Mpris2::MinimumRate() const { return 1.0; }
return 1.0;
}
bool Mpris2::CanGoNext() const { bool Mpris2::CanGoNext() const {
if (mpris1_->player()) { if (mpris1_->player()) {
@ -395,17 +377,14 @@ bool Mpris2::CanGoPrevious() const {
} }
} }
bool Mpris2::CanPlay() const { bool Mpris2::CanPlay() const { return mpris1_->player()->GetCaps() & CAN_PLAY; }
return mpris1_->player()->GetCaps() & CAN_PLAY;
}
// This one's a bit different than MPRIS 1 - we want this to be true even when // This one's a bit different than MPRIS 1 - we want this to be true even when
// the song is already paused or stopped. // the song is already paused or stopped.
bool Mpris2::CanPause() const { bool Mpris2::CanPause() const {
if (mpris1_->player()) { if (mpris1_->player()) {
return mpris1_->player()->GetCaps() & CAN_PAUSE return mpris1_->player()->GetCaps() & CAN_PAUSE ||
|| PlaybackStatus() == "Paused" PlaybackStatus() == "Paused" || PlaybackStatus() == "Stopped";
|| PlaybackStatus() == "Stopped";
} else { } else {
return true; return true;
} }
@ -419,24 +398,22 @@ bool Mpris2::CanSeek() const {
} }
} }
bool Mpris2::CanControl() const { bool Mpris2::CanControl() const { return true; }
return true;
}
void Mpris2::Next() { void Mpris2::Next() {
if(CanGoNext()) { if (CanGoNext()) {
app_->player()->Next(); app_->player()->Next();
} }
} }
void Mpris2::Previous() { void Mpris2::Previous() {
if(CanGoPrevious()) { if (CanGoPrevious()) {
app_->player()->Previous(); app_->player()->Previous();
} }
} }
void Mpris2::Pause() { void Mpris2::Pause() {
if(CanPause() && app_->player()->GetState() != Engine::Paused) { if (CanPause() && app_->player()->GetState() != Engine::Paused) {
app_->player()->Pause(); app_->player()->Pause();
} }
} }
@ -447,20 +424,19 @@ void Mpris2::PlayPause() {
} }
} }
void Mpris2::Stop() { void Mpris2::Stop() { app_->player()->Stop(); }
app_->player()->Stop();
}
void Mpris2::Play() { void Mpris2::Play() {
if(CanPlay()) { if (CanPlay()) {
app_->player()->Play(); app_->player()->Play();
} }
} }
void Mpris2::Seek(qlonglong offset) { void Mpris2::Seek(qlonglong offset) {
if(CanSeek()) { if (CanSeek()) {
app_->player()->SeekTo(app_->player()->engine()->position_nanosec() / kNsecPerSec + app_->player()->SeekTo(app_->player()->engine()->position_nanosec() /
offset / kUsecPerSec); kNsecPerSec +
offset / kUsecPerSec);
} }
} }
@ -468,7 +444,8 @@ void Mpris2::SetPosition(const QDBusObjectPath& trackId, qlonglong offset) {
if (CanSeek() && trackId.path() == current_track_id() && offset >= 0) { if (CanSeek() && trackId.path() == current_track_id() && offset >= 0) {
offset *= kNsecPerUsec; offset *= kNsecPerUsec;
if(offset < app_->player()->GetCurrentItem()->Metadata().length_nanosec()) { if (offset <
app_->player()->GetCurrentItem()->Metadata().length_nanosec()) {
app_->player()->SeekTo(offset / kNsecPerSec); app_->player()->SeekTo(offset / kNsecPerSec);
} }
} }
@ -481,46 +458,42 @@ void Mpris2::OpenUri(const QString& uri) {
} }
TrackIds Mpris2::Tracks() const { TrackIds Mpris2::Tracks() const {
//TODO // TODO
return TrackIds(); return TrackIds();
} }
bool Mpris2::CanEditTracks() const { bool Mpris2::CanEditTracks() const { return false; }
return false;
}
TrackMetadata Mpris2::GetTracksMetadata(const TrackIds &tracks) const { TrackMetadata Mpris2::GetTracksMetadata(const TrackIds& tracks) const {
//TODO // TODO
return TrackMetadata(); return TrackMetadata();
} }
void Mpris2::AddTrack(const QString &uri, const QDBusObjectPath &afterTrack, bool setAsCurrent) { void Mpris2::AddTrack(const QString& uri, const QDBusObjectPath& afterTrack,
//TODO bool setAsCurrent) {
// TODO
} }
void Mpris2::RemoveTrack(const QDBusObjectPath &trackId) { void Mpris2::RemoveTrack(const QDBusObjectPath& trackId) {
//TODO // TODO
} }
void Mpris2::GoTo(const QDBusObjectPath &trackId) { void Mpris2::GoTo(const QDBusObjectPath& trackId) {
//TODO // TODO
} }
quint32 Mpris2::PlaylistCount() const { quint32 Mpris2::PlaylistCount() const {
return app_->playlist_manager()->GetAllPlaylists().size(); return app_->playlist_manager()->GetAllPlaylists().size();
} }
QStringList Mpris2::Orderings() const { QStringList Mpris2::Orderings() const { return QStringList() << "User"; }
return QStringList() << "User";
}
namespace { namespace {
QDBusObjectPath MakePlaylistPath(int id) { QDBusObjectPath MakePlaylistPath(int id) {
return QDBusObjectPath(QString( return QDBusObjectPath(
"/org/mpris/MediaPlayer2/Playlists/%1").arg(id)); QString("/org/mpris/MediaPlayer2/Playlists/%1").arg(id));
} }
} }
MaybePlaylist Mpris2::ActivePlaylist() const { MaybePlaylist Mpris2::ActivePlaylist() const {
@ -557,10 +530,11 @@ void Mpris2::ActivatePlaylist(const QDBusObjectPath& playlist_id) {
} }
// TODO: Support sort orders. // TODO: Support sort orders.
MprisPlaylistList Mpris2::GetPlaylists( MprisPlaylistList Mpris2::GetPlaylists(quint32 index, quint32 max_count,
quint32 index, quint32 max_count, const QString& order, bool reverse_order) { const QString& order,
bool reverse_order) {
MprisPlaylistList ret; MprisPlaylistList ret;
foreach (Playlist* p, app_->playlist_manager()->GetAllPlaylists()) { foreach(Playlist * p, app_->playlist_manager()->GetAllPlaylists()) {
MprisPlaylist mpris_playlist; MprisPlaylist mpris_playlist;
mpris_playlist.id = MakePlaylistPath(p->id()); mpris_playlist.id = MakePlaylistPath(p->id());
mpris_playlist.name = app_->playlist_manager()->GetPlaylistName(p->id()); mpris_playlist.name = app_->playlist_manager()->GetPlaylistName(p->id());
@ -577,7 +551,8 @@ MprisPlaylistList Mpris2::GetPlaylists(
void Mpris2::PlaylistChanged(Playlist* playlist) { void Mpris2::PlaylistChanged(Playlist* playlist) {
MprisPlaylist mpris_playlist; MprisPlaylist mpris_playlist;
mpris_playlist.id = MakePlaylistPath(playlist->id()); mpris_playlist.id = MakePlaylistPath(playlist->id());
mpris_playlist.name = app_->playlist_manager()->GetPlaylistName(playlist->id()); mpris_playlist.name =
app_->playlist_manager()->GetPlaylistName(playlist->id());
emit PlaylistChanged(mpris_playlist); emit PlaylistChanged(mpris_playlist);
} }
@ -585,4 +560,4 @@ void Mpris2::PlaylistCollectionChanged(Playlist* playlist) {
EmitNotification("PlaylistCount", "", "org.mpris.MediaPlayer2.Playlists"); EmitNotification("PlaylistCount", "", "org.mpris.MediaPlayer2.Playlists");
} }
} // namespace mpris } // namespace mpris

View File

@ -47,14 +47,13 @@ struct MaybePlaylist {
}; };
Q_DECLARE_METATYPE(MaybePlaylist); Q_DECLARE_METATYPE(MaybePlaylist);
QDBusArgument& operator<< (QDBusArgument& arg, const MprisPlaylist& playlist); QDBusArgument& operator<<(QDBusArgument& arg, const MprisPlaylist& playlist);
const QDBusArgument& operator>> ( const QDBusArgument& operator>>(const QDBusArgument& arg,
const QDBusArgument& arg, MprisPlaylist& playlist); MprisPlaylist& playlist);
QDBusArgument& operator<< (QDBusArgument& arg, const MaybePlaylist& playlist);
const QDBusArgument& operator>> (
const QDBusArgument& arg, MaybePlaylist& playlist);
QDBusArgument& operator<<(QDBusArgument& arg, const MaybePlaylist& playlist);
const QDBusArgument& operator>>(const QDBusArgument& arg,
MaybePlaylist& playlist);
namespace mpris { namespace mpris {
@ -64,44 +63,44 @@ class Mpris2 : public QObject {
Q_OBJECT Q_OBJECT
public: public:
//org.mpris.MediaPlayer2 MPRIS 2.0 Root interface // org.mpris.MediaPlayer2 MPRIS 2.0 Root interface
Q_PROPERTY( bool CanQuit READ CanQuit ) Q_PROPERTY(bool CanQuit READ CanQuit)
Q_PROPERTY( bool CanRaise READ CanRaise ) Q_PROPERTY(bool CanRaise READ CanRaise)
Q_PROPERTY( bool HasTrackList READ HasTrackList ) Q_PROPERTY(bool HasTrackList READ HasTrackList)
Q_PROPERTY( QString Identity READ Identity ) Q_PROPERTY(QString Identity READ Identity)
Q_PROPERTY( QString DesktopEntry READ DesktopEntry ) Q_PROPERTY(QString DesktopEntry READ DesktopEntry)
Q_PROPERTY( QStringList SupportedUriSchemes READ SupportedUriSchemes ) Q_PROPERTY(QStringList SupportedUriSchemes READ SupportedUriSchemes)
Q_PROPERTY( QStringList SupportedMimeTypes READ SupportedMimeTypes ) Q_PROPERTY(QStringList SupportedMimeTypes READ SupportedMimeTypes)
//org.mpris.MediaPlayer2 MPRIS 2.2 Root interface // org.mpris.MediaPlayer2 MPRIS 2.2 Root interface
Q_PROPERTY( bool CanSetFullscreen READ CanSetFullscreen ) Q_PROPERTY(bool CanSetFullscreen READ CanSetFullscreen)
Q_PROPERTY( bool Fullscreen READ Fullscreen WRITE SetFullscreen ) Q_PROPERTY(bool Fullscreen READ Fullscreen WRITE SetFullscreen)
//org.mpris.MediaPlayer2.Player MPRIS 2.0 Player interface // org.mpris.MediaPlayer2.Player MPRIS 2.0 Player interface
Q_PROPERTY( QString PlaybackStatus READ PlaybackStatus ) Q_PROPERTY(QString PlaybackStatus READ PlaybackStatus)
Q_PROPERTY( QString LoopStatus READ LoopStatus WRITE SetLoopStatus ) Q_PROPERTY(QString LoopStatus READ LoopStatus WRITE SetLoopStatus)
Q_PROPERTY( double Rate READ Rate WRITE SetRate ) Q_PROPERTY(double Rate READ Rate WRITE SetRate)
Q_PROPERTY( bool Shuffle READ Shuffle WRITE SetShuffle ) Q_PROPERTY(bool Shuffle READ Shuffle WRITE SetShuffle)
Q_PROPERTY( QVariantMap Metadata READ Metadata ) Q_PROPERTY(QVariantMap Metadata READ Metadata)
Q_PROPERTY( double Volume READ Volume WRITE SetVolume ) Q_PROPERTY(double Volume READ Volume WRITE SetVolume)
Q_PROPERTY( qlonglong Position READ Position ) Q_PROPERTY(qlonglong Position READ Position)
Q_PROPERTY( double MinimumRate READ MinimumRate ) Q_PROPERTY(double MinimumRate READ MinimumRate)
Q_PROPERTY( double MaximumRate READ MaximumRate ) Q_PROPERTY(double MaximumRate READ MaximumRate)
Q_PROPERTY( bool CanGoNext READ CanGoNext ) Q_PROPERTY(bool CanGoNext READ CanGoNext)
Q_PROPERTY( bool CanGoPrevious READ CanGoPrevious ) Q_PROPERTY(bool CanGoPrevious READ CanGoPrevious)
Q_PROPERTY( bool CanPlay READ CanPlay ) Q_PROPERTY(bool CanPlay READ CanPlay)
Q_PROPERTY( bool CanPause READ CanPause ) Q_PROPERTY(bool CanPause READ CanPause)
Q_PROPERTY( bool CanSeek READ CanSeek ) Q_PROPERTY(bool CanSeek READ CanSeek)
Q_PROPERTY( bool CanControl READ CanControl ) Q_PROPERTY(bool CanControl READ CanControl)
//org.mpris.MediaPlayer2.TrackList MPRIS 2.0 Player interface // org.mpris.MediaPlayer2.TrackList MPRIS 2.0 Player interface
Q_PROPERTY( TrackIds Tracks READ Tracks ) Q_PROPERTY(TrackIds Tracks READ Tracks)
Q_PROPERTY( bool CanEditTracks READ CanEditTracks ) Q_PROPERTY(bool CanEditTracks READ CanEditTracks)
//org.mpris.MediaPlayer2.Playlists MPRIS 2.1 Playlists interface // org.mpris.MediaPlayer2.Playlists MPRIS 2.1 Playlists interface
Q_PROPERTY( quint32 PlaylistCount READ PlaylistCount ) Q_PROPERTY(quint32 PlaylistCount READ PlaylistCount)
Q_PROPERTY( QStringList Orderings READ Orderings ) Q_PROPERTY(QStringList Orderings READ Orderings)
Q_PROPERTY( MaybePlaylist ActivePlaylist READ ActivePlaylist ) Q_PROPERTY(MaybePlaylist ActivePlaylist READ ActivePlaylist)
Mpris2(Application* app, Mpris1* mpris1, QObject* parent = 0); Mpris2(Application* app, Mpris1* mpris1, QObject* parent = 0);
@ -161,7 +160,8 @@ class Mpris2 : public QObject {
// Methods // Methods
TrackMetadata GetTracksMetadata(const TrackIds& tracks) const; TrackMetadata GetTracksMetadata(const TrackIds& tracks) const;
void AddTrack(const QString& uri, const QDBusObjectPath& afterTrack, bool setAsCurrent); void AddTrack(const QString& uri, const QDBusObjectPath& afterTrack,
bool setAsCurrent);
void RemoveTrack(const QDBusObjectPath& trackId); void RemoveTrack(const QDBusObjectPath& trackId);
void GoTo(const QDBusObjectPath& trackId); void GoTo(const QDBusObjectPath& trackId);
@ -172,8 +172,8 @@ class Mpris2 : public QObject {
// Methods // Methods
void ActivatePlaylist(const QDBusObjectPath& playlist_id); void ActivatePlaylist(const QDBusObjectPath& playlist_id);
QList<MprisPlaylist> GetPlaylists( QList<MprisPlaylist> GetPlaylists(quint32 index, quint32 max_count,
quint32 index, quint32 max_count, const QString& order, bool reverse_order); const QString& order, bool reverse_order);
signals: signals:
// Player // Player
@ -183,14 +183,15 @@ signals:
void TrackListReplaced(const TrackIds& Tracks, QDBusObjectPath CurrentTrack); void TrackListReplaced(const TrackIds& Tracks, QDBusObjectPath CurrentTrack);
void TrackAdded(const TrackMetadata& Metadata, QDBusObjectPath AfterTrack); void TrackAdded(const TrackMetadata& Metadata, QDBusObjectPath AfterTrack);
void TrackRemoved(const QDBusObjectPath& trackId); void TrackRemoved(const QDBusObjectPath& trackId);
void TrackMetadataChanged(const QDBusObjectPath& trackId, const TrackMetadata& metadata); void TrackMetadataChanged(const QDBusObjectPath& trackId,
const TrackMetadata& metadata);
void RaiseMainWindow(); void RaiseMainWindow();
// Playlist // Playlist
void PlaylistChanged(const MprisPlaylist& playlist); void PlaylistChanged(const MprisPlaylist& playlist);
private slots: private slots:
void ArtLoaded(const Song& song, const QString& art_uri); void ArtLoaded(const Song& song, const QString& art_uri);
void EngineStateChanged(Engine::State newState); void EngineStateChanged(Engine::State newState);
void VolumeChanged(); void VolumeChanged();
@ -202,10 +203,11 @@ private slots:
void PlaylistChanged(Playlist* playlist); void PlaylistChanged(Playlist* playlist);
void PlaylistCollectionChanged(Playlist* playlist); void PlaylistCollectionChanged(Playlist* playlist);
private: private:
void EmitNotification(const QString& name); void EmitNotification(const QString& name);
void EmitNotification(const QString& name, const QVariant& val); void EmitNotification(const QString& name, const QVariant& val);
void EmitNotification(const QString& name, const QVariant& val, const QString& mprisEntity); void EmitNotification(const QString& name, const QVariant& val,
const QString& mprisEntity);
QString PlaybackStatus(Engine::State state) const; QString PlaybackStatus(Engine::State state) const;
@ -213,7 +215,7 @@ private:
QString DesktopEntryAbsolutePath() const; QString DesktopEntryAbsolutePath() const;
private: private:
static const char* kMprisObjectPath; static const char* kMprisObjectPath;
static const char* kServiceName; static const char* kServiceName;
static const char* kFreedesktopPath; static const char* kFreedesktopPath;
@ -224,6 +226,6 @@ private:
Mpris1* mpris1_; Mpris1* mpris1_;
}; };
} // namespace mpris } // namespace mpris
#endif #endif

View File

@ -25,34 +25,37 @@
namespace mpris { namespace mpris {
inline void AddMetadata(const QString& key, const QString& metadata, QVariantMap* map) { inline void AddMetadata(const QString& key, const QString& metadata,
if (!metadata.isEmpty()) (*map)[key] = metadata; QVariantMap* map) {
if (!metadata.isEmpty()) (*map)[key] = metadata;
} }
inline void AddMetadataAsList(const QString& key, const QString& metadata, QVariantMap* map) { inline void AddMetadataAsList(const QString& key, const QString& metadata,
if (!metadata.isEmpty()) (*map)[key] = QStringList() << metadata; QVariantMap* map) {
if (!metadata.isEmpty()) (*map)[key] = QStringList() << metadata;
} }
inline void AddMetadata(const QString& key, int metadata, QVariantMap* map) { inline void AddMetadata(const QString& key, int metadata, QVariantMap* map) {
if (metadata > 0) (*map)[key] = metadata; if (metadata > 0) (*map)[key] = metadata;
} }
inline void AddMetadata(const QString& key, qint64 metadata, QVariantMap* map) { inline void AddMetadata(const QString& key, qint64 metadata, QVariantMap* map) {
if (metadata > 0) (*map)[key] = metadata; if (metadata > 0) (*map)[key] = metadata;
} }
inline void AddMetadata(const QString& key, double metadata, QVariantMap* map) { inline void AddMetadata(const QString& key, double metadata, QVariantMap* map) {
if (metadata != 0.0) (*map)[key] = metadata; if (metadata != 0.0) (*map)[key] = metadata;
} }
inline void AddMetadata(const QString& key, const QDateTime& metadata, QVariantMap* map) { inline void AddMetadata(const QString& key, const QDateTime& metadata,
if (metadata.isValid()) (*map)[key] = metadata; QVariantMap* map) {
if (metadata.isValid()) (*map)[key] = metadata;
} }
inline QString AsMPRISDateTimeType(uint time) { inline QString AsMPRISDateTimeType(uint time) {
return time != -1 ? QDateTime::fromTime_t(time).toString(Qt::ISODate) : ""; return time != -1 ? QDateTime::fromTime_t(time).toString(Qt::ISODate) : "";
} }
} // namespace mpris } // namespace mpris
#endif // MPRIS_COMMON_H #endif // MPRIS_COMMON_H

View File

@ -6,9 +6,7 @@
#include <QTime> #include <QTime>
MultiSortFilterProxy::MultiSortFilterProxy(QObject* parent) MultiSortFilterProxy::MultiSortFilterProxy(QObject* parent)
: QSortFilterProxyModel(parent) : QSortFilterProxyModel(parent) {}
{
}
void MultiSortFilterProxy::AddSortSpec(int role, Qt::SortOrder order) { void MultiSortFilterProxy::AddSortSpec(int role, Qt::SortOrder order) {
sorting_ << SortSpec(role, order); sorting_ << SortSpec(role, order);
@ -16,7 +14,7 @@ void MultiSortFilterProxy::AddSortSpec(int role, Qt::SortOrder order) {
bool MultiSortFilterProxy::lessThan(const QModelIndex& left, bool MultiSortFilterProxy::lessThan(const QModelIndex& left,
const QModelIndex& right) const { const QModelIndex& right) const {
foreach (const SortSpec& spec, sorting_) { foreach(const SortSpec & spec, sorting_) {
const int ret = Compare(left.data(spec.first), right.data(spec.first)); const int ret = Compare(left.data(spec.first), right.data(spec.first));
if (ret < 0) { if (ret < 0) {
@ -31,29 +29,38 @@ bool MultiSortFilterProxy::lessThan(const QModelIndex& left,
template <typename T> template <typename T>
static inline int DoCompare(T left, T right) { static inline int DoCompare(T left, T right) {
if (left < right) if (left < right) return -1;
return -1; if (left > right) return 1;
if (left > right)
return 1;
return 0; return 0;
} }
int MultiSortFilterProxy::Compare(const QVariant& left, const QVariant& right) const { int MultiSortFilterProxy::Compare(const QVariant& left,
const QVariant& right) const {
// Copied from the QSortFilterProxyModel::lessThan implementation, but returns // Copied from the QSortFilterProxyModel::lessThan implementation, but returns
// -1, 0 or 1 instead of true or false. // -1, 0 or 1 instead of true or false.
switch (left.userType()) { switch (left.userType()) {
case QVariant::Invalid: case QVariant::Invalid:
return (right.type() != QVariant::Invalid) ? -1 : 0; return (right.type() != QVariant::Invalid) ? -1 : 0;
case QVariant::Int: return DoCompare(left.toInt(), right.toInt()); case QVariant::Int:
case QVariant::UInt: return DoCompare(left.toUInt(), right.toUInt()); return DoCompare(left.toInt(), right.toInt());
case QVariant::LongLong: return DoCompare(left.toLongLong(), right.toLongLong()); case QVariant::UInt:
case QVariant::ULongLong: return DoCompare(left.toULongLong(), right.toULongLong()); return DoCompare(left.toUInt(), right.toUInt());
case QMetaType::Float: return DoCompare(left.toFloat(), right.toFloat()); case QVariant::LongLong:
case QVariant::Double: return DoCompare(left.toDouble(), right.toDouble()); return DoCompare(left.toLongLong(), right.toLongLong());
case QVariant::Char: return DoCompare(left.toChar(), right.toChar()); case QVariant::ULongLong:
case QVariant::Date: return DoCompare(left.toDate(), right.toDate()); return DoCompare(left.toULongLong(), right.toULongLong());
case QVariant::Time: return DoCompare(left.toTime(), right.toTime()); case QMetaType::Float:
case QVariant::DateTime: return DoCompare(left.toDateTime(), right.toDateTime()); return DoCompare(left.toFloat(), right.toFloat());
case QVariant::Double:
return DoCompare(left.toDouble(), right.toDouble());
case QVariant::Char:
return DoCompare(left.toChar(), right.toChar());
case QVariant::Date:
return DoCompare(left.toDate(), right.toDate());
case QVariant::Time:
return DoCompare(left.toTime(), right.toTime());
case QVariant::DateTime:
return DoCompare(left.toDateTime(), right.toDateTime());
case QVariant::String: case QVariant::String:
default: default:
if (isSortLocaleAware()) if (isSortLocaleAware())

View File

@ -4,19 +4,19 @@
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
class MultiSortFilterProxy : public QSortFilterProxyModel { class MultiSortFilterProxy : public QSortFilterProxyModel {
public: public:
MultiSortFilterProxy(QObject* parent = NULL); MultiSortFilterProxy(QObject* parent = NULL);
void AddSortSpec(int role, Qt::SortOrder order = Qt::AscendingOrder); void AddSortSpec(int role, Qt::SortOrder order = Qt::AscendingOrder);
protected: protected:
bool lessThan(const QModelIndex& left, const QModelIndex& right) const; bool lessThan(const QModelIndex& left, const QModelIndex& right) const;
private: private:
int Compare(const QVariant& left, const QVariant& right) const; int Compare(const QVariant& left, const QVariant& right) const;
typedef QPair<int, Qt::SortOrder> SortSpec; typedef QPair<int, Qt::SortOrder> SortSpec;
QList<SortSpec> sorting_; QList<SortSpec> sorting_;
}; };
#endif // MULTISORTFILTERPROXY_H #endif // MULTISORTFILTERPROXY_H

Some files were not shown because too many files have changed in this diff Show More