mirror of
https://github.com/clementine-player/Clementine
synced 2025-01-31 03:27:40 +01:00
Merge branch 'master' of https://github.com/clementine-player/Clementine
This commit is contained in:
commit
aeb67594ba
@ -158,6 +158,10 @@ include_directories(${Boost_INCLUDE_DIRS})
|
||||
include_directories(${TAGLIB_INCLUDE_DIRS})
|
||||
include_directories(${QJSON_INCLUDE_DIRS})
|
||||
include_directories(${GSTREAMER_INCLUDE_DIRS})
|
||||
include_directories(${GSTREAMER_APP_INCLUDE_DIRS})
|
||||
include_directories(${GSTREAMER_BASE_INCLUDE_DIRS})
|
||||
include_directories(${GSTREAMER_CDDA_INCLUDE_DIRS})
|
||||
include_directories(${GSTREAMER_TAG_INCLUDE_DIRS})
|
||||
include_directories(${GLIB_INCLUDE_DIRS})
|
||||
include_directories(${GLIBCONFIG_INCLUDE_DIRS})
|
||||
include_directories(${LIBXML_INCLUDE_DIRS})
|
||||
|
@ -257,6 +257,7 @@ message ResponseSongFileChunk {
|
||||
optional SongMetadata song_metadata = 6; // only sent with first chunk!
|
||||
optional bytes data = 7;
|
||||
optional int32 size = 8;
|
||||
optional bytes file_hash = 9;
|
||||
}
|
||||
|
||||
message ResponseLibraryChunk {
|
||||
@ -264,6 +265,7 @@ message ResponseLibraryChunk {
|
||||
optional int32 chunk_count = 2;
|
||||
optional bytes data = 3;
|
||||
optional int32 size = 4;
|
||||
optional bytes file_hash = 5;
|
||||
}
|
||||
|
||||
message ResponseSongOffer {
|
||||
@ -276,7 +278,7 @@ message RequestRateSong {
|
||||
|
||||
// The message itself
|
||||
message Message {
|
||||
optional int32 version = 1 [default=13];
|
||||
optional int32 version = 1 [default=14];
|
||||
optional MsgType type = 2 [default=UNKNOWN]; // What data is in the message?
|
||||
|
||||
optional RequestConnect request_connect = 21;
|
||||
|
@ -134,6 +134,7 @@ set(SOURCES
|
||||
devices/deviceproperties.cpp
|
||||
devices/devicestatefiltermodel.cpp
|
||||
devices/deviceview.cpp
|
||||
devices/deviceviewcontainer.cpp
|
||||
devices/filesystemdevice.cpp
|
||||
|
||||
engines/enginebase.cpp
|
||||
@ -447,6 +448,7 @@ set(HEADERS
|
||||
devices/deviceproperties.h
|
||||
devices/devicestatefiltermodel.h
|
||||
devices/deviceview.h
|
||||
devices/deviceviewcontainer.h
|
||||
devices/filesystemdevice.h
|
||||
|
||||
engines/enginebase.h
|
||||
@ -674,6 +676,7 @@ set(UI
|
||||
covers/coversearchstatisticsdialog.ui
|
||||
|
||||
devices/deviceproperties.ui
|
||||
devices/deviceviewcontainer.ui
|
||||
|
||||
globalsearch/globalsearchsettingspage.ui
|
||||
globalsearch/globalsearchview.ui
|
||||
|
@ -442,6 +442,8 @@ void Song::InitFromProtobuf(const pb::tagreader::SongMetadata& pb) {
|
||||
if (pb.has_rating()) {
|
||||
d->rating_ = pb.rating();
|
||||
}
|
||||
|
||||
InitArtManual();
|
||||
}
|
||||
|
||||
void Song::ToProtobuf(pb::tagreader::SongMetadata* pb) const {
|
||||
@ -545,6 +547,8 @@ void Song::InitFromQuery(const SqlRow& q, bool reliable_metadata, int col) {
|
||||
d->performer_ = tostr(col + 38);
|
||||
d->grouping_ = tostr(col + 39);
|
||||
|
||||
InitArtManual();
|
||||
|
||||
#undef tostr
|
||||
#undef toint
|
||||
#undef tolonglong
|
||||
@ -569,6 +573,17 @@ void Song::InitFromFilePartial(const QString& filename) {
|
||||
}
|
||||
}
|
||||
|
||||
void Song::InitArtManual() {
|
||||
// If we don't have an art, check if we have one in the cache
|
||||
if (d->art_manual_.isEmpty() && d->art_automatic_.isEmpty()) {
|
||||
QString filename(Utilities::Sha1CoverHash(d->artist_, d->album_).toHex() + ".jpg");
|
||||
QString path(AlbumCoverLoader::ImageCacheDir() + "/" + filename);
|
||||
if (QFile::exists(path)) {
|
||||
d->art_manual_ = path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBLASTFM
|
||||
void Song::InitFromLastFM(const lastfm::Track& track) {
|
||||
d->valid_ = true;
|
||||
|
@ -106,6 +106,7 @@ class Song {
|
||||
void InitFromProtobuf(const pb::tagreader::SongMetadata& pb);
|
||||
void InitFromQuery(const SqlRow& query, bool reliable_metadata, int col = 0);
|
||||
void InitFromFilePartial(const QString& filename); // Just store the filename: incomplete but fast
|
||||
void InitArtManual(); // Check if there is already a art in the cache and store the filename in art_manual
|
||||
#ifdef HAVE_LIBLASTFM
|
||||
void InitFromLastFM(const lastfm::Track& track);
|
||||
#endif
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <QDateTime>
|
||||
#include <QDesktopServices>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QIODevice>
|
||||
#include <QMetaEnum>
|
||||
#include <QMouseEvent>
|
||||
@ -455,6 +456,31 @@ QByteArray Sha256(const QByteArray& data) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// File must not be open and will be closed afterwards!
|
||||
QByteArray Md5File(QFile &file) {
|
||||
file.open(QIODevice::ReadOnly);
|
||||
QCryptographicHash hash(QCryptographicHash::Md5);
|
||||
QByteArray data;
|
||||
|
||||
while(!file.atEnd()) {
|
||||
data = file.read(1000000); // 1 mib
|
||||
hash.addData(data.data(), data.length());
|
||||
data.clear();
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
return hash.result();
|
||||
}
|
||||
|
||||
QByteArray Sha1CoverHash(const QString& artist, const QString& album) {
|
||||
QCryptographicHash hash(QCryptographicHash::Sha1);
|
||||
hash.addData(artist.toLower().toUtf8().constData());
|
||||
hash.addData(album.toLower().toUtf8().constData());
|
||||
|
||||
return hash.result();
|
||||
}
|
||||
|
||||
QString PrettySize(const QSize& size) {
|
||||
return QString::number(size.width()) + "x" +
|
||||
QString::number(size.height());
|
||||
|
@ -19,6 +19,7 @@
|
||||
#define UTILITIES_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QFile>
|
||||
#include <QLocale>
|
||||
#include <QCryptographicHash>
|
||||
#include <QSize>
|
||||
@ -66,6 +67,8 @@ namespace Utilities {
|
||||
QByteArray HmacSha256(const QByteArray& key, const QByteArray& data);
|
||||
QByteArray HmacSha1(const QByteArray& key, const QByteArray& data);
|
||||
QByteArray Sha256(const QByteArray& data);
|
||||
QByteArray Md5File(QFile& file);
|
||||
QByteArray Sha1CoverHash(const QString& artist, const QString& album);
|
||||
|
||||
|
||||
// Picks an unused ephemeral port number. Doesn't hold the port open so
|
||||
|
56
src/devices/deviceviewcontainer.cpp
Normal file
56
src/devices/deviceviewcontainer.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "deviceviewcontainer.h"
|
||||
#include "ui_deviceviewcontainer.h"
|
||||
#include "ui/iconloader.h"
|
||||
|
||||
DeviceViewContainer::DeviceViewContainer(QWidget *parent)
|
||||
: QWidget(parent),
|
||||
ui_(new Ui::DeviceViewContainer),
|
||||
loaded_icons_(false) {
|
||||
ui_->setupUi(this);
|
||||
|
||||
QPalette palette(ui_->windows_is_broken_frame->palette());
|
||||
palette.setColor(QPalette::Background, QColor(255, 255, 222));
|
||||
ui_->windows_is_broken_frame->setPalette(palette);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
ui_->windows_is_broken_frame->show();
|
||||
#else
|
||||
ui_->windows_is_broken_frame->hide();
|
||||
#endif
|
||||
}
|
||||
|
||||
DeviceViewContainer::~DeviceViewContainer() {
|
||||
delete ui_;
|
||||
}
|
||||
|
||||
void DeviceViewContainer::showEvent(QShowEvent* e) {
|
||||
if (!loaded_icons_) {
|
||||
loaded_icons_ = true;
|
||||
|
||||
ui_->close_frame_button->setIcon(IconLoader::Load("edit-delete"));
|
||||
ui_->warning_icon->setPixmap(IconLoader::Load("dialog-warning").pixmap(22));
|
||||
}
|
||||
|
||||
QWidget::showEvent(e);
|
||||
}
|
||||
|
||||
DeviceView* DeviceViewContainer::view() const {
|
||||
return ui_->view;
|
||||
}
|
46
src/devices/deviceviewcontainer.h
Normal file
46
src/devices/deviceviewcontainer.h
Normal file
@ -0,0 +1,46 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DEVICEVIEWCONTAINER_H
|
||||
#define DEVICEVIEWCONTAINER_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
namespace Ui {
|
||||
class DeviceViewContainer;
|
||||
}
|
||||
|
||||
class DeviceView;
|
||||
|
||||
class DeviceViewContainer : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DeviceViewContainer(QWidget* parent = 0);
|
||||
~DeviceViewContainer();
|
||||
|
||||
DeviceView* view() const;
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent*);
|
||||
|
||||
private:
|
||||
Ui::DeviceViewContainer* ui_;
|
||||
bool loaded_icons_;
|
||||
};
|
||||
|
||||
#endif // DEVICEVIEWCONTAINER_H
|
99
src/devices/deviceviewcontainer.ui
Normal file
99
src/devices/deviceviewcontainer.ui
Normal file
@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DeviceViewContainer</class>
|
||||
<widget class="QWidget" name="DeviceViewContainer">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>391</width>
|
||||
<height>396</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QFrame" name="windows_is_broken_frame">
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="warning_icon"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>iPods and USB devices currently don't work on Windows. Sorry!</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="close_frame_button"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="DeviceView" name="view" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>DeviceView</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>devices/deviceview.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>close_frame_button</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>windows_is_broken_frame</receiver>
|
||||
<slot>hide()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>362</x>
|
||||
<y>31</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>369</x>
|
||||
<y>40</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -1679,7 +1679,12 @@ Song GroovesharkService::ExtractSong(const QVariantMap& result_song) {
|
||||
Song song;
|
||||
if (!result_song.isEmpty()) {
|
||||
int song_id = result_song["SongID"].toInt();
|
||||
QString song_name = result_song["SongName"].toString();
|
||||
QString song_name;
|
||||
if (result_song.contains("SongName")) {
|
||||
song_name = result_song["SongName"].toString();
|
||||
} else {
|
||||
song_name = result_song["Name"].toString();
|
||||
}
|
||||
int artist_id = result_song["ArtistID"].toInt();
|
||||
QString artist_name = result_song["ArtistName"].toString();
|
||||
int album_id = result_song["AlbumID"].toInt();
|
||||
|
@ -641,6 +641,11 @@ void OutgoingDataCreator::SendSingleSong(RemoteClient* client, const Song &song,
|
||||
|
||||
// Open the file
|
||||
QFile file(song.url().toLocalFile());
|
||||
|
||||
// Get md5 for file
|
||||
QByteArray md5 = Utilities::Md5File(file).toHex();
|
||||
qLog(Debug) << "md5 for file" << song.url().toLocalFile() << "=" << md5;
|
||||
|
||||
file.open(QIODevice::ReadOnly);
|
||||
|
||||
QByteArray data;
|
||||
@ -665,6 +670,7 @@ void OutgoingDataCreator::SendSingleSong(RemoteClient* client, const Song &song,
|
||||
chunk->set_file_number(song_no);
|
||||
chunk->set_size(file.size());
|
||||
chunk->set_data(data.data(), data.size());
|
||||
chunk->set_file_hash(md5.data(), md5.size());
|
||||
|
||||
// On the first chunk send the metadata, so the client knows
|
||||
// what file it receives.
|
||||
@ -746,6 +752,11 @@ void OutgoingDataCreator::SendLibrary(RemoteClient *client) {
|
||||
|
||||
// Open the file
|
||||
QFile file(temp_file_name);
|
||||
|
||||
// Get the md5 hash
|
||||
QByteArray md5 = Utilities::Md5File(file).toHex();
|
||||
qLog(Debug) << "Library md5" << md5;
|
||||
|
||||
file.open(QIODevice::ReadOnly);
|
||||
|
||||
QByteArray data;
|
||||
@ -766,6 +777,7 @@ void OutgoingDataCreator::SendLibrary(RemoteClient *client) {
|
||||
chunk->set_chunk_number(chunk_number);
|
||||
chunk->set_size(file.size());
|
||||
chunk->set_data(data.data(), data.size());
|
||||
chunk->set_file_hash(md5.data(), md5.size());
|
||||
|
||||
// Send data directly to the client
|
||||
client->SendData(&msg);
|
||||
|
@ -101,6 +101,7 @@ PlaylistListContainer::~PlaylistListContainer() {
|
||||
void PlaylistListContainer::showEvent(QShowEvent* e) {
|
||||
// Loading icons is expensive so only do it when the view is first opened
|
||||
if (loaded_icons_) {
|
||||
QWidget::showEvent(e);
|
||||
return;
|
||||
}
|
||||
loaded_icons_ = true;
|
||||
|
@ -123,6 +123,7 @@ return_song:
|
||||
void ASXParser::Save(const SongList& songs, QIODevice* device, const QDir&) const {
|
||||
QXmlStreamWriter writer(device);
|
||||
writer.setAutoFormatting(true);
|
||||
writer.setAutoFormattingIndent(2);
|
||||
writer.writeStartDocument();
|
||||
{
|
||||
StreamElement asx("asx", &writer);
|
||||
|
@ -81,6 +81,8 @@ void WplParser::ParseSeq(const QDir& dir, QXmlStreamReader* reader,
|
||||
void WplParser::Save(const SongList& songs, QIODevice* device,
|
||||
const QDir& dir) const {
|
||||
QXmlStreamWriter writer(device);
|
||||
writer.setAutoFormatting(true);
|
||||
writer.setAutoFormattingIndent(2);
|
||||
writer.writeProcessingInstruction("wpl", "version=\"1.0\"");
|
||||
|
||||
StreamElement smil("smil", &writer);
|
||||
|
@ -104,6 +104,8 @@ return_song:
|
||||
|
||||
void XSPFParser::Save(const SongList& songs, QIODevice* device, const QDir&) const {
|
||||
QXmlStreamWriter writer(device);
|
||||
writer.setAutoFormatting(true);
|
||||
writer.setAutoFormattingIndent(2);
|
||||
writer.writeStartDocument();
|
||||
StreamElement playlist("playlist", &writer);
|
||||
writer.writeAttribute("version", "1");
|
||||
|
@ -156,8 +156,8 @@ void PodcastEpisode::BindToQuery(QSqlQuery* query) const {
|
||||
Song PodcastEpisode::ToSong(const Podcast& podcast) const {
|
||||
Song ret;
|
||||
ret.set_valid(true);
|
||||
ret.set_title(title());
|
||||
ret.set_artist(author());
|
||||
ret.set_title(title().simplified());
|
||||
ret.set_artist(author().simplified());
|
||||
ret.set_length_nanosec(kNsecPerSec * duration_secs());
|
||||
ret.set_year(publication_date().date().year());
|
||||
ret.set_comment(description());
|
||||
@ -172,7 +172,7 @@ Song PodcastEpisode::ToSong(const Podcast& podcast) const {
|
||||
|
||||
// Use information from the podcast if it's set
|
||||
if (podcast.is_valid()) {
|
||||
ret.set_album(podcast.title());
|
||||
ret.set_album(podcast.title().simplified());
|
||||
ret.set_art_automatic(podcast.ImageUrlLarge().toString());
|
||||
}
|
||||
|
||||
|
@ -139,7 +139,7 @@ void PodcastService::PopulatePodcastList(QStandardItem* parent) {
|
||||
void PodcastService::UpdatePodcastText(QStandardItem* item, int unlistened_count) const {
|
||||
const Podcast podcast = item->data(Role_Podcast).value<Podcast>();
|
||||
|
||||
QString title = podcast.title();
|
||||
QString title = podcast.title().simplified();
|
||||
QFont font;
|
||||
|
||||
if (unlistened_count > 0) {
|
||||
@ -159,7 +159,7 @@ void PodcastService::UpdateEpisodeText(QStandardItem* item,
|
||||
int percent) {
|
||||
const PodcastEpisode episode = item->data(Role_Episode).value<PodcastEpisode>();
|
||||
|
||||
QString title = episode.title();
|
||||
QString title = episode.title().simplified();
|
||||
QString tooltip;
|
||||
QFont font;
|
||||
QIcon icon;
|
||||
@ -237,7 +237,7 @@ QStandardItem* PodcastService::CreatePodcastItem(const Podcast& podcast) {
|
||||
|
||||
QStandardItem* PodcastService::CreatePodcastEpisodeItem(const PodcastEpisode& episode) {
|
||||
QStandardItem* item = new QStandardItem;
|
||||
item->setText(episode.title());
|
||||
item->setText(episode.title().simplified());
|
||||
item->setData(Type_Episode, InternetModel::Role_Type);
|
||||
item->setData(QVariant::fromValue(episode), Role_Episode);
|
||||
item->setData(InternetModel::PlayBehaviour_UseSongLoader, InternetModel::Role_PlayBehaviour);
|
||||
|
@ -48,13 +48,19 @@ bool ArtistInfoView::NeedsUpdate(const Song& old_metadata, const Song& new_metad
|
||||
|
||||
return old_metadata.artist() != new_metadata.artist();
|
||||
}
|
||||
|
||||
|
||||
void ArtistInfoView::InfoResultReady (int id, const CollapsibleInfoPane::Data& data) {
|
||||
if (id != current_request_id_)
|
||||
return;
|
||||
|
||||
AddSection (new CollapsibleInfoPane(data, this));
|
||||
CollapseSections();
|
||||
}
|
||||
|
||||
void ArtistInfoView::ResultReady(int id, const SongInfoFetcher::Result& result) {
|
||||
if (id != current_request_id_)
|
||||
return;
|
||||
|
||||
Clear();
|
||||
|
||||
if (!result.images_.isEmpty()) {
|
||||
// Image view goes at the top
|
||||
PrettyImageView* image_view = new PrettyImageView(network_, this);
|
||||
@ -64,10 +70,6 @@ void ArtistInfoView::ResultReady(int id, const SongInfoFetcher::Result& result)
|
||||
image_view->AddImage(url);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const CollapsibleInfoPane::Data& data, result.info_) {
|
||||
AddSection(new CollapsibleInfoPane(data, this));
|
||||
}
|
||||
|
||||
CollapseSections();
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,7 @@ public:
|
||||
~ArtistInfoView();
|
||||
|
||||
protected:
|
||||
virtual void InfoResultReady (int id, const CollapsibleInfoPane::Data& data);
|
||||
bool NeedsUpdate(const Song& old_metadata, const Song& new_metadata) const;
|
||||
|
||||
protected slots:
|
||||
@ -43,3 +44,4 @@ protected slots:
|
||||
};
|
||||
|
||||
#endif // ARTISTINFOVIEW_H
|
||||
|
||||
|
@ -89,18 +89,24 @@ void EchoNestBiographies::RequestFinished() {
|
||||
data.icon_ = site_icons_[canonical_site];
|
||||
|
||||
SongInfoTextView* editor = new SongInfoTextView;
|
||||
QString text;
|
||||
// Add a link to the bio webpage at the top if we have one
|
||||
if (!bio.url().isEmpty()) {
|
||||
text += "<p><a href=\"" + bio.url().toEncoded() + "\">" +
|
||||
tr("Open in your browser") +
|
||||
"</a></p>";
|
||||
}
|
||||
|
||||
text += bio.text();
|
||||
if (bio.site() == "last.fm") {
|
||||
// Echonest lost formatting and it seems there is currently no plans on Echonest side for changing this.
|
||||
// But with last.fm, we can guess newlines: " " corresponds to a newline
|
||||
// (this seems to be because on last.fm' website, extra blank is inserted
|
||||
// before <br /> tag, and this blank is kept).
|
||||
// This is tricky, but this make the display nicer for last.fm biographies.
|
||||
QString copy(bio.text());
|
||||
copy.replace(" ","<p>");
|
||||
editor->SetHtml(copy);
|
||||
} else {
|
||||
editor->SetHtml(bio.text());
|
||||
text.replace(" ","<p>");
|
||||
}
|
||||
editor->SetHtml(text);
|
||||
data.contents_ = editor;
|
||||
|
||||
emit InfoReady(request->id_, data);
|
||||
|
@ -64,6 +64,8 @@ SongInfoBase::SongInfoBase(QWidget* parent)
|
||||
|
||||
connect(fetcher_, SIGNAL(ResultReady(int,SongInfoFetcher::Result)),
|
||||
SLOT(ResultReady(int,SongInfoFetcher::Result)));
|
||||
connect(fetcher_, SIGNAL(InfoResultReady(int,CollapsibleInfoPane::Data)),
|
||||
SLOT(InfoResultReady(int,CollapsibleInfoPane::Data)));
|
||||
}
|
||||
|
||||
void SongInfoBase::Clear() {
|
||||
@ -142,9 +144,13 @@ void SongInfoBase::Update(const Song& metadata) {
|
||||
|
||||
// Do this after the new pane has been shown otherwise it'll just grab a
|
||||
// black rectangle.
|
||||
Clear ();
|
||||
QTimer::singleShot(0, fader_, SLOT(StartBlur()));
|
||||
}
|
||||
|
||||
void SongInfoBase::InfoResultReady (int id, const CollapsibleInfoPane::Data& data) {
|
||||
}
|
||||
|
||||
void SongInfoBase::ResultReady(int id, const SongInfoFetcher::Result& result) {
|
||||
foreach (const CollapsibleInfoPane::Data& data, result.info_) {
|
||||
delete data.contents_;
|
||||
@ -208,10 +214,6 @@ void SongInfoBase::ReloadSettings() {
|
||||
|
||||
QMetaObject::invokeMethod(contents, "ReloadSettings");
|
||||
}
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
fetcher_->set_timeout(s.value("timeout", SongInfoFetcher::kDefaultTimeoutDuration).toInt());
|
||||
}
|
||||
|
||||
void SongInfoBase::ConnectWidget(QWidget* widget) {
|
||||
@ -225,3 +227,4 @@ void SongInfoBase::ConnectWidget(QWidget* widget) {
|
||||
connect(widget, SIGNAL(DoGlobalSearch(QString)), SIGNAL(DoGlobalSearch(QString)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,6 +63,7 @@ protected:
|
||||
void CollapseSections();
|
||||
|
||||
protected slots:
|
||||
virtual void InfoResultReady (int id, const CollapsibleInfoPane::Data& data);
|
||||
virtual void ResultReady(int id, const SongInfoFetcher::Result& result);
|
||||
|
||||
protected:
|
||||
@ -94,3 +95,4 @@ private:
|
||||
};
|
||||
|
||||
#endif // SONGINFOBASE_H
|
||||
|
||||
|
@ -68,6 +68,10 @@ void SongInfoFetcher::InfoReady(int id, const CollapsibleInfoPane::Data& data) {
|
||||
if (!results_.contains(id))
|
||||
return;
|
||||
results_[id].info_ << data;
|
||||
|
||||
if (!waiting_for_.contains(id))
|
||||
return;
|
||||
emit InfoResultReady (id, data);
|
||||
}
|
||||
|
||||
void SongInfoFetcher::ProviderFinished(int id) {
|
||||
@ -107,3 +111,4 @@ void SongInfoFetcher::Timeout(int id) {
|
||||
// Remove the timer
|
||||
delete timeout_timers_.take(id);
|
||||
}
|
||||
|
||||
|
@ -40,9 +40,7 @@ public:
|
||||
QList<CollapsibleInfoPane::Data> info_;
|
||||
};
|
||||
|
||||
static const int kDefaultTimeoutDuration = 2500; // msec
|
||||
|
||||
void set_timeout(int msec) { timeout_duration_ = msec; }
|
||||
static const int kDefaultTimeoutDuration = 25000; // msec
|
||||
|
||||
void AddProvider(SongInfoProvider* provider);
|
||||
int FetchInfo(const Song& metadata);
|
||||
@ -50,6 +48,7 @@ public:
|
||||
QList<SongInfoProvider*> providers() const { return providers_; }
|
||||
|
||||
signals:
|
||||
void InfoResultReady (int id, const CollapsibleInfoPane::Data& data);
|
||||
void ResultReady(int id, const SongInfoFetcher::Result& result);
|
||||
|
||||
private slots:
|
||||
@ -72,3 +71,4 @@ private:
|
||||
};
|
||||
|
||||
#endif // SONGINFOFETCHER_H
|
||||
|
||||
|
@ -59,8 +59,6 @@ void SongInfoSettingsPage::Load() {
|
||||
s.beginGroup(SongInfoTextView::kSettingsGroup);
|
||||
ui_->song_info_font_size->setValue(
|
||||
s.value("font_size", SongInfoTextView::kDefaultFontSize).toReal());
|
||||
ui_->song_info_timeout->setValue(
|
||||
s.value("timeout", SongInfoFetcher::kDefaultTimeoutDuration).toInt());
|
||||
s.endGroup();
|
||||
|
||||
QList<const UltimateLyricsProvider*> providers = dialog()->song_info_view()->lyric_providers();
|
||||
@ -80,7 +78,6 @@ void SongInfoSettingsPage::Save() {
|
||||
|
||||
s.beginGroup(SongInfoTextView::kSettingsGroup);
|
||||
s.setValue("font_size", ui_->song_info_font_preview->font().pointSizeF());
|
||||
s.setValue("timeout", ui_->song_info_timeout->value());
|
||||
s.endGroup();
|
||||
|
||||
s.beginGroup(SongInfoView::kSettingsGroup);
|
||||
|
@ -75,38 +75,6 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Network</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_6">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>Timeout</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="song_info_timeout">
|
||||
<property name="suffix">
|
||||
<string> ms</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>60000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>2500</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_6">
|
||||
<property name="sizePolicy">
|
||||
|
@ -76,19 +76,17 @@ bool SongInfoView::NeedsUpdate(const Song& old_metadata, const Song& new_metadat
|
||||
old_metadata.artist() != new_metadata.artist();
|
||||
}
|
||||
|
||||
void SongInfoView::ResultReady(int id, const SongInfoFetcher::Result& result) {
|
||||
void SongInfoView::InfoResultReady (int id, const CollapsibleInfoPane::Data& data) {
|
||||
if (id != current_request_id_)
|
||||
return;
|
||||
|
||||
Clear();
|
||||
|
||||
foreach (const CollapsibleInfoPane::Data& data, result.info_) {
|
||||
AddSection(new CollapsibleInfoPane(data, this));
|
||||
}
|
||||
|
||||
|
||||
AddSection (new CollapsibleInfoPane(data, this));
|
||||
CollapseSections();
|
||||
}
|
||||
|
||||
void SongInfoView::ResultReady(int id, const SongInfoFetcher::Result& result) {
|
||||
}
|
||||
|
||||
void SongInfoView::ReloadSettings() {
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
@ -170,3 +168,4 @@ QList<const UltimateLyricsProvider*> SongInfoView::lyric_providers() const {
|
||||
qSort(ret.begin(), ret.end(), CompareLyricProviders);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,8 @@ protected:
|
||||
bool NeedsUpdate(const Song& old_metadata, const Song& new_metadata) const;
|
||||
|
||||
protected slots:
|
||||
void ResultReady(int id, const SongInfoFetcher::Result& result);
|
||||
virtual void InfoResultReady (int id, const CollapsibleInfoPane::Data& data);
|
||||
virtual void ResultReady(int id, const SongInfoFetcher::Result& result);
|
||||
|
||||
private:
|
||||
SongInfoProvider* ProviderByName(const QString& name) const;
|
||||
@ -56,3 +57,4 @@ private:
|
||||
};
|
||||
|
||||
#endif // SONGINFOVIEW_H
|
||||
|
||||
|
@ -141,7 +141,7 @@ void UltimateLyricsProvider::LyricsFetched() {
|
||||
ApplyExcludeRule(rule, &lyrics);
|
||||
}
|
||||
|
||||
if (!content.isEmpty()) {
|
||||
if (!content.isEmpty() and HTMLHasAlphaNumeric(content)) {
|
||||
lyrics = content;
|
||||
break;
|
||||
}
|
||||
@ -150,7 +150,7 @@ void UltimateLyricsProvider::LyricsFetched() {
|
||||
lyrics = original_content;
|
||||
}
|
||||
|
||||
if (!lyrics.isEmpty()) {
|
||||
if (!lyrics.isEmpty() and HTMLHasAlphaNumeric(lyrics)) {
|
||||
CollapsibleInfoPane::Data data;
|
||||
data.id_ = "ultimatelyrics/" + name_;
|
||||
data.title_ = tr("Lyrics from %1").arg(name_);
|
||||
@ -317,3 +317,19 @@ QString UltimateLyricsProvider::NoSpace(const QString& text) {
|
||||
ret.remove(' ');
|
||||
return ret;
|
||||
}
|
||||
|
||||
// tells whether a html block has alphanumeric characters (skipping tags)
|
||||
// TODO: handle special characters (e.g. ® á)
|
||||
bool UltimateLyricsProvider::HTMLHasAlphaNumeric(const QString& html) {
|
||||
bool in_tag = false;
|
||||
foreach (const QChar& c, html) {
|
||||
if (!in_tag and c.isLetterOrNumber())
|
||||
return true;
|
||||
else if (c == QChar('<'))
|
||||
in_tag = true;
|
||||
else if (c == QChar('>'))
|
||||
in_tag = false;
|
||||
}
|
||||
qLog(Debug) << html;
|
||||
return false;
|
||||
}
|
||||
|
@ -73,6 +73,7 @@ private:
|
||||
static QString FirstChar(const QString& text);
|
||||
static QString TitleCase(const QString& text);
|
||||
static QString NoSpace(const QString& text);
|
||||
static bool HTMLHasAlphaNumeric(const QString& html);
|
||||
|
||||
void ReplaceField(const QString& tag, const QString& value, QString* text) const;
|
||||
void ReplaceFields(const Song& metadata, QString* text) const;
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/utilities.h"
|
||||
#include "covers/albumcoverfetcher.h"
|
||||
#include "covers/albumcoverloader.h"
|
||||
#include "covers/currentartloader.h"
|
||||
@ -29,7 +30,6 @@
|
||||
#include "ui/iconloader.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDialog>
|
||||
#include <QDragEnterEvent>
|
||||
#include <QFileDialog>
|
||||
@ -63,6 +63,10 @@ AlbumCoverChoiceController::AlbumCoverChoiceController(QWidget* parent)
|
||||
unset_cover_ = new QAction(IconLoader::Load("list-remove"), tr("Unset cover"), this);
|
||||
show_cover_ = new QAction(IconLoader::Load("zoom-in"), tr("Show fullsize..."), this);
|
||||
|
||||
search_cover_auto_ = new QAction(IconLoader::Load("find"), tr("Search automatically"), this);
|
||||
search_cover_auto_->setCheckable(true);
|
||||
search_cover_auto_->setChecked(false);
|
||||
|
||||
separator_ = new QAction(this);
|
||||
separator_->setSeparator(true);
|
||||
}
|
||||
@ -77,6 +81,9 @@ void AlbumCoverChoiceController::SetApplication(Application* app) {
|
||||
cover_fetcher_ = new AlbumCoverFetcher(app_->cover_providers(), this);
|
||||
cover_searcher_ = new AlbumCoverSearcher(QIcon(":/nocover.png"), app, this);
|
||||
cover_searcher_->Init(cover_fetcher_);
|
||||
|
||||
connect(cover_fetcher_, SIGNAL(AlbumCoverFetched(quint64,QImage,CoverSearchStatistics)),
|
||||
this, SLOT(AlbumCoverFetched(quint64,QImage,CoverSearchStatistics)));
|
||||
}
|
||||
|
||||
QList<QAction*> AlbumCoverChoiceController::GetAllActions() {
|
||||
@ -204,6 +211,27 @@ void AlbumCoverChoiceController::ShowCover(const Song& song) {
|
||||
dialog->show();
|
||||
}
|
||||
|
||||
void AlbumCoverChoiceController::SearchCoverAutomatically(const Song& song) {
|
||||
qint64 id = cover_fetcher_->FetchAlbumCover(song.artist(), song.album());
|
||||
cover_fetching_tasks_[id] = song;
|
||||
}
|
||||
|
||||
void AlbumCoverChoiceController::AlbumCoverFetched(quint64 id,
|
||||
const QImage& image,
|
||||
const CoverSearchStatistics& statistics) {
|
||||
Song song;
|
||||
if (cover_fetching_tasks_.contains(id)) {
|
||||
song = cover_fetching_tasks_.take(id);
|
||||
}
|
||||
|
||||
if (!image.isNull()) {
|
||||
QString cover = SaveCoverInCache(song.artist(), song.album(), image);
|
||||
SaveCover(&song, cover);
|
||||
}
|
||||
|
||||
emit AutomaticCoverSearchDone();
|
||||
}
|
||||
|
||||
void AlbumCoverChoiceController::SaveCover(Song* song, const QString &cover) {
|
||||
if(song->is_valid() && song->id() != -1) {
|
||||
song->set_art_manual(cover);
|
||||
@ -219,11 +247,7 @@ QString AlbumCoverChoiceController::SaveCoverInCache(
|
||||
const QString& artist, const QString& album, const QImage& image) {
|
||||
|
||||
// Hash the artist and album into a filename for the image
|
||||
QCryptographicHash hash(QCryptographicHash::Sha1);
|
||||
hash.addData(artist.toLower().toUtf8().constData());
|
||||
hash.addData(album.toLower().toUtf8().constData());
|
||||
|
||||
QString filename(hash.result().toHex() + ".jpg");
|
||||
QString filename(Utilities::Sha1CoverHash(artist, album).toHex() + ".jpg");
|
||||
QString path(AlbumCoverLoader::ImageCacheDir() + "/" + filename);
|
||||
|
||||
// Make sure this directory exists first
|
||||
|
@ -27,6 +27,7 @@ class AlbumCoverFetcher;
|
||||
class AlbumCoverSearcher;
|
||||
class Application;
|
||||
class CoverFromURLDialog;
|
||||
class CoverSearchStatistics;
|
||||
class QFileDialog;
|
||||
class Song;
|
||||
|
||||
@ -52,6 +53,7 @@ class AlbumCoverChoiceController : public QWidget {
|
||||
QAction* search_for_cover_action() const { return search_for_cover_; }
|
||||
QAction* unset_cover_action() const { return unset_cover_; }
|
||||
QAction* show_cover_action() const { return show_cover_; }
|
||||
QAction* search_cover_auto_action() const { return search_cover_auto_; }
|
||||
|
||||
// Returns QAction* for every operation implemented by this controller.
|
||||
// The list contains QAction* for:
|
||||
@ -91,6 +93,9 @@ class AlbumCoverChoiceController : public QWidget {
|
||||
// Shows the cover of given song in it's original size.
|
||||
void ShowCover(const Song& song);
|
||||
|
||||
// Search for covers automatically
|
||||
void SearchCoverAutomatically(const Song& song);
|
||||
|
||||
// Saves the chosen cover as manual cover path of this song in library.
|
||||
void SaveCover(Song* song, const QString& cover);
|
||||
|
||||
@ -103,6 +108,13 @@ class AlbumCoverChoiceController : public QWidget {
|
||||
|
||||
static bool CanAcceptDrag(const QDragEnterEvent* e);
|
||||
|
||||
signals:
|
||||
void AutomaticCoverSearchDone();
|
||||
|
||||
private slots:
|
||||
void AlbumCoverFetched(quint64 id, const QImage& image,
|
||||
const CoverSearchStatistics& statistics);
|
||||
|
||||
private:
|
||||
QString GetInitialPathForFileDialog(const Song& song,
|
||||
const QString& filename);
|
||||
@ -124,6 +136,9 @@ private:
|
||||
QAction* search_for_cover_;
|
||||
QAction* unset_cover_;
|
||||
QAction* show_cover_;
|
||||
QAction* search_cover_auto_;
|
||||
|
||||
QMap<quint64, Song> cover_fetching_tasks_;
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVERCHOICECONTROLLER_H
|
||||
|
@ -115,6 +115,8 @@ void Equalizer::LoadDefaultPresets() {
|
||||
AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Classical"), Params(0, 0, 0, 0, 0, 0, -40, -40, -40, -50));
|
||||
AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Club"), Params(0, 0, 20, 30, 30, 30, 20, 0, 0, 0));
|
||||
AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Dance"), Params(50, 35, 10, 0, 0, -30, -40, -40, 0, 0));
|
||||
// Dubstep equalizer created by Devyn Collier Johnson
|
||||
AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Dubstep"), Params(0, 36, 85, 58, 30, 0, 36, 60, 96, 62, 0));
|
||||
AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Full Bass"), Params(70, 70, 70, 40, 20, -45, -50, -55, -55, -55));
|
||||
AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Full Treble"), Params(-50, -50, -50, -25, 15, 55, 80, 80, 80, 85));
|
||||
AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Full Bass + Treble"), Params(35, 30, 0, -40, -25, 10, 45, 55, 60, 60));
|
||||
@ -123,6 +125,8 @@ void Equalizer::LoadDefaultPresets() {
|
||||
AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Live"), Params(-25, 0, 20, 25, 30, 30, 20, 15, 15, 10));
|
||||
AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Party"), Params(35, 35, 0, 0, 0, 0, 0, 0, 35, 35));
|
||||
AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Pop"), Params(-10, 25, 35, 40, 25, -5, -15, -15, -10, -10));
|
||||
// Psychedelic equalizer created by Devyn Collier Johnson
|
||||
AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Psychedelic"), Params(100, 100, 0, 40, 0, 67, 79, 0, 30, -100, 37));
|
||||
AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Reggae"), Params(0, 0, -5, -30, 0, -35, -35, 0, 0, 0));
|
||||
AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Rock"), Params(40, 25, -30, -40, -20, 20, 45, 55, 55, 55));
|
||||
AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Soft"), Params(25, 10, -5, -15, -5, 20, 45, 50, 55, 60));
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include "devices/devicemanager.h"
|
||||
#include "devices/devicestatefiltermodel.h"
|
||||
#include "devices/deviceview.h"
|
||||
#include "devices/deviceviewcontainer.h"
|
||||
#include "engines/enginebase.h"
|
||||
#include "engines/gstengine.h"
|
||||
#include "globalsearch/globalsearch.h"
|
||||
@ -173,7 +174,8 @@ MainWindow::MainWindow(Application* app,
|
||||
file_view_(new FileView(this)),
|
||||
playlist_list_(new PlaylistListContainer(this)),
|
||||
internet_view_(new InternetViewContainer(this)),
|
||||
device_view_(new DeviceView(this)),
|
||||
device_view_container_(new DeviceViewContainer(this)),
|
||||
device_view_(device_view_container_->view()),
|
||||
song_info_view_(new SongInfoView(this)),
|
||||
artist_info_view_(new ArtistInfoView(this)),
|
||||
settings_dialog_(NULL),
|
||||
@ -238,7 +240,7 @@ MainWindow::MainWindow(Application* app,
|
||||
ui_->tabs->AddTab(file_view_, IconLoader::Load("document-open"), tr("Files"));
|
||||
ui_->tabs->AddTab(playlist_list_, IconLoader::Load("view-media-playlist"), tr("Playlists"));
|
||||
ui_->tabs->AddTab(internet_view_, IconLoader::Load("applications-internet"), tr("Internet"));
|
||||
ui_->tabs->AddTab(device_view_, IconLoader::Load("multimedia-player-ipod-mini-blue"), tr("Devices"));
|
||||
ui_->tabs->AddTab(device_view_container_, IconLoader::Load("multimedia-player-ipod-mini-blue"), tr("Devices"));
|
||||
ui_->tabs->AddSpacer();
|
||||
ui_->tabs->AddTab(song_info_view_, IconLoader::Load("view-media-lyrics"), tr("Song info"));
|
||||
ui_->tabs->AddTab(artist_info_view_, IconLoader::Load("x-clementine-artist"), tr("Artist info"));
|
||||
|
@ -44,6 +44,7 @@ class CoverProviders;
|
||||
class Database;
|
||||
class DeviceManager;
|
||||
class DeviceView;
|
||||
class DeviceViewContainer;
|
||||
class EditTagDialog;
|
||||
class Equalizer;
|
||||
class ErrorDialog;
|
||||
@ -293,6 +294,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
||||
boost::scoped_ptr<RipCD> rip_cd_;
|
||||
PlaylistListContainer* playlist_list_;
|
||||
InternetViewContainer* internet_view_;
|
||||
DeviceViewContainer* device_view_container_;
|
||||
DeviceView* device_view_;
|
||||
SongInfoView* song_info_view_;
|
||||
ArtistInfoView* artist_info_view_;
|
||||
|
@ -72,6 +72,7 @@ NowPlayingWidget::NowPlayingWidget(QWidget* parent)
|
||||
details_(new QTextDocument(this)),
|
||||
previous_track_opacity_(0.0),
|
||||
bask_in_his_glory_action_(NULL),
|
||||
downloading_covers_(false),
|
||||
aww_(false),
|
||||
kittens_(NULL),
|
||||
pending_kitten_(0)
|
||||
@ -80,6 +81,7 @@ NowPlayingWidget::NowPlayingWidget(QWidget* parent)
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
mode_ = Mode(s.value("mode", SmallSongDetails).toInt());
|
||||
album_cover_choice_controller_->search_cover_auto_action()->setChecked(s.value("search_for_cover_auto", false).toBool());
|
||||
|
||||
// Accept drops for setting album art
|
||||
setAcceptDrops(true);
|
||||
@ -96,6 +98,9 @@ NowPlayingWidget::NowPlayingWidget(QWidget* parent)
|
||||
|
||||
QList<QAction*> actions = album_cover_choice_controller_->GetAllActions();
|
||||
|
||||
// Here we add the search automatically action, too!
|
||||
actions.append(album_cover_choice_controller_->search_cover_auto_action());
|
||||
|
||||
connect(album_cover_choice_controller_->cover_from_file_action(),
|
||||
SIGNAL(triggered()), this, SLOT(LoadCoverFromFile()));
|
||||
connect(album_cover_choice_controller_->cover_to_file_action(),
|
||||
@ -108,6 +113,8 @@ NowPlayingWidget::NowPlayingWidget(QWidget* parent)
|
||||
SIGNAL(triggered()), this, SLOT(UnsetCover()));
|
||||
connect(album_cover_choice_controller_->show_cover_action(),
|
||||
SIGNAL(triggered()), this, SLOT(ShowCover()));
|
||||
connect(album_cover_choice_controller_->search_cover_auto_action(),
|
||||
SIGNAL(triggered()), this, SLOT(SearchCoverAutomatically()));
|
||||
|
||||
menu_->addActions(actions);
|
||||
menu_->addSeparator();
|
||||
@ -129,6 +136,9 @@ NowPlayingWidget::NowPlayingWidget(QWidget* parent)
|
||||
fade_animation_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0
|
||||
|
||||
UpdateHeight();
|
||||
|
||||
connect(album_cover_choice_controller_, SIGNAL(AutomaticCoverSearchDone()),
|
||||
this, SLOT(AutomaticCoverSearchDone()));
|
||||
}
|
||||
|
||||
NowPlayingWidget::~NowPlayingWidget() {
|
||||
@ -233,9 +243,10 @@ void NowPlayingWidget::KittenLoaded(quint64 id, const QImage& image) {
|
||||
}
|
||||
}
|
||||
|
||||
void NowPlayingWidget::AlbumArtLoaded(const Song& metadata, const QString&,
|
||||
void NowPlayingWidget::AlbumArtLoaded(const Song& metadata, const QString& uri,
|
||||
const QImage& image) {
|
||||
metadata_ = metadata;
|
||||
downloading_covers_ = false;
|
||||
|
||||
if (aww_) {
|
||||
pending_kitten_ = kittens_->LoadKitten(app_->current_art_loader()->options());
|
||||
@ -243,6 +254,9 @@ void NowPlayingWidget::AlbumArtLoaded(const Song& metadata, const QString&,
|
||||
}
|
||||
|
||||
SetImage(image);
|
||||
|
||||
// Search for cover automatically?
|
||||
GetCoverAutomatically();
|
||||
}
|
||||
|
||||
void NowPlayingWidget::SetImage(const QImage& image) {
|
||||
@ -301,6 +315,9 @@ void NowPlayingWidget::DrawContents(QPainter *p) {
|
||||
} else {
|
||||
// Draw the cover
|
||||
p->drawPixmap(0, 0, small_ideal_height_, small_ideal_height_, cover_);
|
||||
if (downloading_covers_) {
|
||||
p->drawPixmap(small_ideal_height_ - 18, 6, 16, 16, spinner_animation_->currentPixmap());
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the details
|
||||
@ -321,6 +338,9 @@ void NowPlayingWidget::DrawContents(QPainter *p) {
|
||||
p->drawPixmap(x_offset, kTopBorder, total_size, total_size, hypnotoad_->currentPixmap());
|
||||
} else {
|
||||
p->drawPixmap(x_offset, kTopBorder, total_size, total_size, cover_);
|
||||
if (downloading_covers_) {
|
||||
p->drawPixmap(total_size - 31, 40, 16, 16, spinner_animation_->currentPixmap());
|
||||
}
|
||||
}
|
||||
|
||||
// Work out how high the text is going to be
|
||||
@ -454,6 +474,15 @@ void NowPlayingWidget::ShowCover() {
|
||||
album_cover_choice_controller_->ShowCover(metadata_);
|
||||
}
|
||||
|
||||
void NowPlayingWidget::SearchCoverAutomatically() {
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
s.setValue("search_for_cover_auto", album_cover_choice_controller_->search_cover_auto_action()->isChecked());
|
||||
|
||||
// Search for cover automatically?
|
||||
GetCoverAutomatically();
|
||||
}
|
||||
|
||||
void NowPlayingWidget::Bask() {
|
||||
big_hypnotoad_.reset(new FullscreenHypnotoad);
|
||||
big_hypnotoad_->showFullScreen();
|
||||
@ -472,3 +501,31 @@ void NowPlayingWidget::dropEvent(QDropEvent* e) {
|
||||
|
||||
QWidget::dropEvent(e);
|
||||
}
|
||||
|
||||
bool NowPlayingWidget::GetCoverAutomatically() {
|
||||
// Search for cover automatically?
|
||||
bool search = album_cover_choice_controller_->search_cover_auto_action()->isChecked() &&
|
||||
!metadata_.has_manually_unset_cover() &&
|
||||
metadata_.art_automatic().isEmpty() &&
|
||||
metadata_.art_manual().isEmpty();
|
||||
|
||||
if (search) {
|
||||
qLog(Debug) << "GetCoverAutomatically";
|
||||
downloading_covers_ = true;
|
||||
album_cover_choice_controller_->SearchCoverAutomatically(metadata_);
|
||||
|
||||
// Show a spinner animation
|
||||
spinner_animation_.reset(new QMovie(":/spinner.gif", QByteArray(), this));
|
||||
connect(spinner_animation_.get(), SIGNAL(updated(const QRect&)), SLOT(update()));
|
||||
spinner_animation_->start();
|
||||
update();
|
||||
}
|
||||
|
||||
return search;
|
||||
}
|
||||
|
||||
void NowPlayingWidget::AutomaticCoverSearchDone() {
|
||||
downloading_covers_ = false;
|
||||
spinner_animation_.reset();
|
||||
update();
|
||||
}
|
||||
|
@ -99,9 +99,12 @@ private slots:
|
||||
void SearchForCover();
|
||||
void UnsetCover();
|
||||
void ShowCover();
|
||||
void SearchCoverAutomatically();
|
||||
|
||||
void Bask();
|
||||
|
||||
void AutomaticCoverSearchDone();
|
||||
|
||||
private:
|
||||
void CreateModeAction(Mode mode, const QString& text, QActionGroup* group,
|
||||
QSignalMapper* mapper);
|
||||
@ -110,6 +113,7 @@ private:
|
||||
void DrawContents(QPainter* p);
|
||||
void SetImage(const QImage& image);
|
||||
void ScaleCover();
|
||||
bool GetCoverAutomatically();
|
||||
|
||||
private:
|
||||
Application* app_;
|
||||
@ -144,6 +148,9 @@ private:
|
||||
boost::scoped_ptr<QMovie> hypnotoad_;
|
||||
boost::scoped_ptr<FullscreenHypnotoad> big_hypnotoad_;
|
||||
|
||||
boost::scoped_ptr<QMovie> spinner_animation_;
|
||||
bool downloading_covers_;
|
||||
|
||||
bool aww_;
|
||||
KittenLoader* kittens_;
|
||||
quint64 pending_kitten_;
|
||||
|
Loading…
x
Reference in New Issue
Block a user