Merge branch 'master' into cdrip
This commit is contained in:
commit
34c178af65
|
@ -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})
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
Clementine
|
||||
==========
|
||||
|
||||
Clementine is a modern music player and library organizer for Windows, Linux and Mac OS X.
|
||||
|
||||
- Website: http://www.clementine-player.org/
|
||||
- Github: https://github.com/clementine-player/Clementine
|
||||
- Buildbot: http://buildbot.clementine-player.org/grid
|
||||
- Latest developer builds: http://builds.clementine-player.org/
|
||||
|
||||
Compiling from source
|
||||
---------------------
|
||||
|
||||
Get the code (if you haven't already):
|
||||
|
||||
git clone https://github.com/clementine-player/Clementine.git && cd Clementine
|
||||
|
||||
Compile and install:
|
||||
|
||||
cd bin
|
||||
cmake ..
|
||||
make -j8
|
||||
sudo make install
|
||||
|
||||
See the Wiki for more instructions and a list of dependencies:
|
||||
https://github.com/clementine-player/Clementine/wiki/Compiling-from-Source
|
|
@ -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
|
||||
|
|
|
@ -451,4 +451,12 @@ void EnableFullScreen(const QWidget& main_window) {
|
|||
[window setCollectionBehavior: kFullScreenPrimary];
|
||||
}
|
||||
|
||||
float GetDevicePixelRatio(QWidget* widget) {
|
||||
NSView* view = reinterpret_cast<NSView*>(widget->winId());
|
||||
if ([[view window] respondsToSelector: @selector(backingScaleFactor)]) {
|
||||
return [[view window] backingScaleFactor];
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
} // namespace mac
|
||||
|
|
|
@ -29,5 +29,6 @@ namespace mac {
|
|||
|
||||
QKeySequence KeySequenceFromNSEvent(NSEvent* event);
|
||||
void DumpDictionary(CFDictionaryRef dict);
|
||||
float GetDevicePixelRatio(QWidget* widget);
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
|
|
|
@ -212,6 +212,10 @@ void LibraryFilterWidget::SetQueryMode(QueryOptions::QueryMode query_mode) {
|
|||
model_->SetFilterQueryMode(query_mode);
|
||||
}
|
||||
|
||||
void LibraryFilterWidget::ShowInLibrary(const QString& search) {
|
||||
ui_->filter->setText(search);
|
||||
}
|
||||
|
||||
void LibraryFilterWidget::SetAgeFilterEnabled(bool enabled) {
|
||||
filter_age_menu_->setEnabled(enabled);
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ class LibraryFilterWidget : public QWidget {
|
|||
void SetDelayBehaviour(DelayBehaviour behaviour) { delay_behaviour_ = behaviour; }
|
||||
void SetAgeFilterEnabled(bool enabled);
|
||||
void SetGroupByEnabled(bool enabled);
|
||||
void ShowInLibrary(const QString& search);
|
||||
|
||||
QMenu* menu() const { return library_menu_; }
|
||||
void AddMenuAction(QAction* action);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -16,13 +16,6 @@
|
|||
*/
|
||||
|
||||
#include "playlistdelegates.h"
|
||||
#include "queue.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/player.h"
|
||||
#include "core/utilities.h"
|
||||
#include "library/librarybackend.h"
|
||||
#include "widgets/trackslider.h"
|
||||
#include "ui/iconloader.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
|
@ -39,6 +32,18 @@
|
|||
#include <QWhatsThis>
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
#include "queue.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/player.h"
|
||||
#include "core/utilities.h"
|
||||
#include "library/librarybackend.h"
|
||||
#include "widgets/trackslider.h"
|
||||
#include "ui/iconloader.h"
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
#include "core/mac_utilities.h"
|
||||
#endif // Q_OS_DARWIN
|
||||
|
||||
const int QueuedItemDelegate::kQueueBoxBorder = 1;
|
||||
const int QueuedItemDelegate::kQueueBoxCornerRadius = 3;
|
||||
const int QueuedItemDelegate::kQueueBoxLength = 30;
|
||||
|
@ -492,8 +497,14 @@ void SongSourceDelegate::paint(
|
|||
const QUrl& url = index.data().toUrl();
|
||||
QPixmap pixmap = LookupPixmap(url, option_copy.decorationSize);
|
||||
|
||||
float device_pixel_ratio = 1.0f;
|
||||
#ifdef Q_OS_DARWIN
|
||||
QWidget* parent_widget = reinterpret_cast<QWidget*>(parent());
|
||||
device_pixel_ratio = mac::GetDevicePixelRatio(parent_widget);
|
||||
#endif
|
||||
|
||||
// Draw the pixmap in the middle of the rectangle
|
||||
QRect draw_rect(QPoint(0, 0), option_copy.decorationSize);
|
||||
QRect draw_rect(QPoint(0, 0), option_copy.decorationSize / device_pixel_ratio);
|
||||
draw_rect.moveCenter(option_copy.rect.center());
|
||||
|
||||
painter->drawPixmap(draw_rect, pixmap);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "transcoderoptionsdialog.h"
|
||||
#include "ui_transcodedialog.h"
|
||||
#include "ui_transcodelogdialog.h"
|
||||
#include "ui/iconloader.h"
|
||||
#include "ui/mainwindow.h"
|
||||
#include "widgets/fileview.h"
|
||||
|
||||
|
@ -35,6 +36,7 @@
|
|||
|
||||
const char* TranscodeDialog::kSettingsGroup = "Transcoder";
|
||||
const int TranscodeDialog::kProgressInterval = 500;
|
||||
const int TranscodeDialog::kMaxDestinationItems = 10;
|
||||
|
||||
static bool ComparePresetsByName(const TranscoderPreset& left,
|
||||
const TranscoderPreset& right) {
|
||||
|
@ -103,6 +105,8 @@ TranscodeDialog::TranscodeDialog(QWidget *parent)
|
|||
connect(close_button_, SIGNAL(clicked()), SLOT(hide()));
|
||||
connect(ui_->details, SIGNAL(clicked()), log_dialog_, SLOT(show()));
|
||||
connect(ui_->options, SIGNAL(clicked()), SLOT(Options()));
|
||||
connect(ui_->select, SIGNAL(clicked()), SLOT(AddDestination()));
|
||||
|
||||
|
||||
connect(transcoder_, SIGNAL(JobComplete(QString,bool)), SLOT(JobComplete(QString,bool)));
|
||||
connect(transcoder_, SIGNAL(LogLine(QString)), SLOT(LogLine(QString)));
|
||||
|
@ -138,7 +142,8 @@ void TranscodeDialog::Start() {
|
|||
// Add jobs to the transcoder
|
||||
for (int i=0 ; i<file_model->rowCount() ; ++i) {
|
||||
QString filename = file_model->index(i, 0).data(Qt::UserRole).toString();
|
||||
transcoder_->AddJob(filename, preset);
|
||||
QString outfilename = GetOutputFileName(filename, preset);
|
||||
transcoder_->AddJob(filename, preset, outfilename);
|
||||
}
|
||||
|
||||
// Set up the progressbar
|
||||
|
@ -265,3 +270,50 @@ void TranscodeDialog::Options() {
|
|||
dialog.exec();
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a folder to the destination box.
|
||||
void TranscodeDialog::AddDestination() {
|
||||
int index = ui_->destination->currentIndex();
|
||||
QString initial_dir = (!ui_->destination->itemData(index).isNull() ?
|
||||
ui_->destination->itemData(index).toString() :
|
||||
QDir::homePath());
|
||||
QString dir = QFileDialog::getExistingDirectory(
|
||||
this, tr("Add folder"), initial_dir);
|
||||
|
||||
if (!dir.isEmpty()) {
|
||||
// Keep only a finite number of items in the box.
|
||||
while (ui_->destination->count() >= kMaxDestinationItems) {
|
||||
ui_->destination->removeItem(1); // The oldest folder item.
|
||||
}
|
||||
|
||||
QIcon icon = IconLoader::Load("folder");
|
||||
QVariant data = QVariant::fromValue(dir);
|
||||
// Do not insert duplicates.
|
||||
int duplicate_index = ui_->destination->findData(data);
|
||||
if (duplicate_index == -1) {
|
||||
ui_->destination->addItem(icon, dir, data);
|
||||
ui_->destination->setCurrentIndex(ui_->destination->count() - 1);
|
||||
} else {
|
||||
ui_->destination->setCurrentIndex(duplicate_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the rightmost non-empty part of 'path'.
|
||||
QString TranscodeDialog::TrimPath(const QString& path) const {
|
||||
return path.section('/', -1, -1, QString::SectionSkipEmpty);
|
||||
}
|
||||
|
||||
QString TranscodeDialog::GetOutputFileName(const QString& input,
|
||||
const TranscoderPreset &preset) const {
|
||||
QString path = ui_->destination->itemData(
|
||||
ui_->destination->currentIndex()).toString();
|
||||
if (path.isEmpty()) {
|
||||
// Keep the original path.
|
||||
return input.section('.', 0, -2) + '.' + preset.extension_;
|
||||
} else {
|
||||
QString file_name = TrimPath(input);
|
||||
file_name = file_name.section('.', 0, -2);
|
||||
return path + '/' + file_name + '.' + preset.extension_;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ class Transcoder;
|
|||
class Ui_TranscodeDialog;
|
||||
class Ui_TranscodeLogDialog;
|
||||
|
||||
struct TranscoderPreset;
|
||||
|
||||
class TranscodeDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -34,6 +36,7 @@ class TranscodeDialog : public QDialog {
|
|||
|
||||
static const char* kSettingsGroup;
|
||||
static const int kProgressInterval;
|
||||
static const int kMaxDestinationItems;
|
||||
|
||||
void SetFilenames(const QStringList& filenames);
|
||||
|
||||
|
@ -49,11 +52,15 @@ class TranscodeDialog : public QDialog {
|
|||
void LogLine(const QString& message);
|
||||
void AllJobsComplete();
|
||||
void Options();
|
||||
void AddDestination();
|
||||
|
||||
private:
|
||||
void SetWorking(bool working);
|
||||
void UpdateStatusText();
|
||||
void UpdateProgress();
|
||||
QString TrimPath(const QString& path) const;
|
||||
QString GetOutputFileName(const QString& input,
|
||||
const TranscoderPreset& preset) const;
|
||||
|
||||
private:
|
||||
Ui_TranscodeDialog* ui_;
|
||||
|
|
|
@ -98,7 +98,7 @@
|
|||
<property name="title">
|
||||
<string>Output options</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
|
@ -107,25 +107,21 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QComboBox" name="format">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="options">
|
||||
<property name="text">
|
||||
<string>Options...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<widget class="QComboBox" name="format">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QPushButton" name="options">
|
||||
<property name="text">
|
||||
<string>Options...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
|
@ -136,6 +132,15 @@
|
|||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="destination">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Alongside the originals</string>
|
||||
|
@ -143,6 +148,13 @@
|
|||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="select">
|
||||
<property name="text">
|
||||
<string>Select...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -196,7 +208,6 @@
|
|||
<tabstop>add</tabstop>
|
||||
<tabstop>remove</tabstop>
|
||||
<tabstop>format</tabstop>
|
||||
<tabstop>destination</tabstop>
|
||||
<tabstop>button_box</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -30,6 +30,8 @@ class CoverFromURLDialog;
|
|||
class QFileDialog;
|
||||
class Song;
|
||||
|
||||
struct CoverSearchStatistics;
|
||||
|
||||
// Controller for the common album cover related menu options.
|
||||
class AlbumCoverChoiceController : public QWidget {
|
||||
Q_OBJECT
|
||||
|
@ -52,6 +54,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 +94,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 +109,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 +137,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"));
|
||||
|
@ -509,6 +511,7 @@ MainWindow::MainWindow(Application* app,
|
|||
playlist_copy_to_device_ = playlist_menu_->addAction(IconLoader::Load("multimedia-player-ipod-mini-blue"), tr("Copy to device..."), this, SLOT(PlaylistCopyToDevice()));
|
||||
playlist_delete_ = playlist_menu_->addAction(IconLoader::Load("edit-delete"), tr("Delete from disk..."), this, SLOT(PlaylistDelete()));
|
||||
playlist_open_in_browser_ = playlist_menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, SLOT(PlaylistOpenInBrowser()));
|
||||
playlist_show_in_library_ = playlist_menu_->addAction(IconLoader::Load("edit-find"), tr("Show in library..."), this, SLOT(ShowInLibrary()));
|
||||
playlist_menu_->addSeparator();
|
||||
playlistitem_actions_separator_ = playlist_menu_->addSeparator();
|
||||
playlist_menu_->addAction(ui_->action_clear_playlist);
|
||||
|
@ -690,6 +693,10 @@ MainWindow::MainWindow(Application* app,
|
|||
app_->playlist_manager()->Init(app_->library_backend(), app_->playlist_backend(),
|
||||
ui_->playlist_sequence, ui_->playlist);
|
||||
|
||||
// This connection must be done after the playlists have been initialized.
|
||||
connect(this, SIGNAL(StopAfterToggled(bool)),
|
||||
osd_, SLOT(StopAfterToggle(bool)));
|
||||
|
||||
// We need to connect these global shortcuts here after the playlist have been initialized
|
||||
connect(global_shortcuts_, SIGNAL(CycleShuffleMode()), app_->playlist_manager()->sequence(), SLOT(CycleShuffleMode()));
|
||||
connect(global_shortcuts_, SIGNAL(CycleRepeatMode()), app_->playlist_manager()->sequence(), SLOT(CycleRepeatMode()));
|
||||
|
@ -1058,7 +1065,8 @@ void MainWindow::ToggleShowHide() {
|
|||
}
|
||||
|
||||
void MainWindow::StopAfterCurrent() {
|
||||
app_->playlist_manager()->current()->StopAfter(app_->playlist_manager()->current()->current_row());
|
||||
app_->playlist_manager()->active()->StopAfter(app_->playlist_manager()->active()->current_row());
|
||||
emit StopAfterToggled(app_->playlist_manager()->active()->stop_after_current());
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent* event) {
|
||||
|
@ -1355,6 +1363,7 @@ void MainWindow::PlaylistRightClick(const QPoint& global_pos, const QModelIndex&
|
|||
ui_->action_edit_value->setVisible(editable);
|
||||
ui_->action_remove_from_playlist->setEnabled(!selection.isEmpty());
|
||||
|
||||
playlist_show_in_library_->setVisible(false);
|
||||
playlist_copy_to_library_->setVisible(false);
|
||||
playlist_move_to_library_->setVisible(false);
|
||||
playlist_organise_->setVisible(false);
|
||||
|
@ -1403,6 +1412,7 @@ void MainWindow::PlaylistRightClick(const QPoint& global_pos, const QModelIndex&
|
|||
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
|
||||
if (item->IsLocalLibraryItem() && item->Metadata().id() != -1) {
|
||||
playlist_organise_->setVisible(editable);
|
||||
playlist_show_in_library_->setVisible(editable);
|
||||
} else {
|
||||
playlist_copy_to_library_->setVisible(editable);
|
||||
playlist_move_to_library_->setVisible(editable);
|
||||
|
@ -1678,6 +1688,22 @@ void MainWindow::AddCDTracks() {
|
|||
AddToPlaylist(data);
|
||||
}
|
||||
|
||||
void MainWindow::ShowInLibrary() {
|
||||
// Show the first valid selected track artist/album in LibraryView
|
||||
QModelIndexList proxy_indexes = ui_->playlist->view()->selectionModel()->selectedRows();
|
||||
SongList songs;
|
||||
|
||||
foreach (const QModelIndex& proxy_index, proxy_indexes) {
|
||||
QModelIndex index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
|
||||
if (app_->playlist_manager()->current()->item_at(index.row())->IsLocalLibraryItem()) {
|
||||
songs << app_->playlist_manager()->current()->item_at(index.row())->Metadata();
|
||||
break;
|
||||
}
|
||||
}
|
||||
QString search = "artist:"+songs[0].artist()+" album:"+songs[0].album();
|
||||
library_view_->filter()->ShowInLibrary(search);
|
||||
}
|
||||
|
||||
void MainWindow::PlaylistRemoveCurrent() {
|
||||
ui_->playlist->view()->RemoveSelected();
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ class CoverProviders;
|
|||
class Database;
|
||||
class DeviceManager;
|
||||
class DeviceView;
|
||||
class DeviceViewContainer;
|
||||
class EditTagDialog;
|
||||
class Equalizer;
|
||||
class ErrorDialog;
|
||||
|
@ -135,6 +136,10 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||
void Activate();
|
||||
bool LoadUrl(const QString& url);
|
||||
|
||||
signals:
|
||||
// Signals that stop playing after track was toggled.
|
||||
void StopAfterToggled(bool stop);
|
||||
|
||||
private slots:
|
||||
void FilePathChanged(const QString& path);
|
||||
|
||||
|
@ -168,6 +173,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||
void PlaylistOrganiseSelected(bool copy);
|
||||
void PlaylistDelete();
|
||||
void PlaylistOpenInBrowser();
|
||||
void ShowInLibrary();
|
||||
|
||||
void ChangeLibraryQueryMode(QAction* action);
|
||||
|
||||
|
@ -293,6 +299,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_;
|
||||
|
@ -327,6 +334,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||
QAction* playlist_stop_after_;
|
||||
QAction* playlist_undoredo_;
|
||||
QAction* playlist_organise_;
|
||||
QAction* playlist_show_in_library_;
|
||||
QAction* playlist_copy_to_library_;
|
||||
QAction* playlist_move_to_library_;
|
||||
QAction* playlist_copy_to_device_;
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
const char* FileView::kFileFilter = "*.mp3 *.ogg *.flac *.mpc *.m4a *.aac *.wma "
|
||||
"*.mp4 *.spx *.wav *.m3u *.m3u8 *.pls *.xspf "
|
||||
"*.asx *.asxini *.cue *.ape *.wv *.mka *.opus "
|
||||
"*.oga *.mka";
|
||||
"*.oga *.mka *.mp2";
|
||||
|
||||
FileView::FileView(QWidget* parent)
|
||||
: QWidget(parent),
|
||||
|
|
|
@ -32,7 +32,8 @@ ExtendedEditor::ExtendedEditor(QWidget* widget, int extra_right_padding,
|
|||
reset_button_(new QToolButton(widget)),
|
||||
extra_right_padding_(extra_right_padding),
|
||||
draw_hint_(draw_hint),
|
||||
font_point_size_(widget->font().pointSizeF() - 1)
|
||||
font_point_size_(widget->font().pointSizeF() - 1),
|
||||
is_rtl_(false)
|
||||
{
|
||||
clear_button_->setIcon(IconLoader::Load("edit-clear-locationbar-ltr"));
|
||||
clear_button_->setIconSize(QSize(16, 16));
|
||||
|
@ -118,15 +119,22 @@ void ExtendedEditor::Paint(QPaintDevice* device) {
|
|||
}
|
||||
} else {
|
||||
clear_button_->setVisible(has_clear_button_);
|
||||
Resize();
|
||||
}
|
||||
}
|
||||
|
||||
void ExtendedEditor::Resize() {
|
||||
const QSize sz = clear_button_->sizeHint();
|
||||
const int frame_width = widget_->style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
|
||||
clear_button_->move(frame_width, (widget_->rect().height() - sz.height()) / 2);
|
||||
reset_button_->move(widget_->width() - frame_width - sz.width() - extra_right_padding_,
|
||||
(widget_->rect().height() - sz.height()) / 2);
|
||||
const int y = (widget_->rect().height() - sz.height()) / 2;
|
||||
|
||||
clear_button_->move(frame_width, y);
|
||||
|
||||
if (!is_rtl_) {
|
||||
reset_button_->move(widget_->width() - frame_width - sz.width() - extra_right_padding_, y);
|
||||
} else {
|
||||
reset_button_->move((has_clear_button() ? sz.width() + 4 : 0) + frame_width, y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -137,6 +145,16 @@ LineEdit::LineEdit(QWidget* parent)
|
|||
connect(reset_button_, SIGNAL(clicked()), SIGNAL(Reset()));
|
||||
}
|
||||
|
||||
void LineEdit::set_text(const QString& text) {
|
||||
QLineEdit::setText(text);
|
||||
|
||||
// For some reason Qt will detect any text with LTR at the end as LTR, so instead
|
||||
// compare only the first character
|
||||
if (!text.isEmpty()) {
|
||||
set_rtl(QString(text.at(0)).isRightToLeft());
|
||||
}
|
||||
}
|
||||
|
||||
void LineEdit::paintEvent(QPaintEvent* e) {
|
||||
QLineEdit::paintEvent(e);
|
||||
Paint(this);
|
||||
|
|
|
@ -86,6 +86,7 @@ protected:
|
|||
int extra_right_padding_;
|
||||
bool draw_hint_;
|
||||
qreal font_point_size_;
|
||||
bool is_rtl_;
|
||||
};
|
||||
|
||||
class LineEdit : public QLineEdit,
|
||||
|
@ -102,13 +103,17 @@ public:
|
|||
// ExtendedEditor
|
||||
void set_focus() { QLineEdit::setFocus(); }
|
||||
QString text() const { return QLineEdit::text(); }
|
||||
void set_text(const QString& text) { QLineEdit::setText(text); }
|
||||
void set_text(const QString& text);
|
||||
void set_enabled(bool enabled) { QLineEdit::setEnabled(enabled); }
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent*);
|
||||
void resizeEvent(QResizeEvent*);
|
||||
|
||||
private:
|
||||
bool is_rtl() const { return is_rtl_; }
|
||||
void set_rtl(bool rtl) { is_rtl_ = rtl; }
|
||||
|
||||
signals:
|
||||
void Reset();
|
||||
};
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -166,6 +166,11 @@ void OSD::Stopped() {
|
|||
ShowMessage(QCoreApplication::applicationName(), tr("Stopped"));
|
||||
}
|
||||
|
||||
void OSD::StopAfterToggle(bool stop) {
|
||||
ShowMessage(QCoreApplication::applicationName(),
|
||||
tr("Stop playing after track: %1").arg(stop ? tr("On") : tr("Off")));
|
||||
}
|
||||
|
||||
void OSD::PlaylistFinished() {
|
||||
// We get a PlaylistFinished followed by a Stopped from the player
|
||||
ignore_next_stopped_ = true;
|
||||
|
|
|
@ -71,6 +71,7 @@ class OSD : public QObject {
|
|||
|
||||
void Paused();
|
||||
void Stopped();
|
||||
void StopAfterToggle(bool stop);
|
||||
void PlaylistFinished();
|
||||
void VolumeChanged(int value);
|
||||
void MagnatuneDownloadFinished(const QStringList& albums);
|
||||
|
|
|
@ -38,7 +38,7 @@ TrackSlider::TrackSlider(QWidget* parent)
|
|||
{
|
||||
ui_->setupUi(this);
|
||||
|
||||
QFont font("Courier");
|
||||
QFont font("Comic Sans MS");
|
||||
ui_->elapsed->setFont(font);
|
||||
ui_->remaining->setFont(font);
|
||||
|
||||
|
|
Loading…
Reference in New Issue