Merge branch 'master' into c++11

This commit is contained in:
John Maguire 2014-01-27 16:30:57 +01:00
commit 767965a2f4
116 changed files with 26791 additions and 20344 deletions

View File

@ -183,31 +183,39 @@ static const unsigned int KeyTbl[] = {
// numeric and function keypad keys // numeric and function keypad keys
XK_KP_Space, Qt::Key_Space,
XK_KP_Tab, Qt::Key_Tab,
XK_KP_Enter, Qt::Key_Enter, XK_KP_Enter, Qt::Key_Enter,
//XK_KP_F1, Qt::Key_F1,
//XK_KP_F2, Qt::Key_F2, // special and additional keys
//XK_KP_F3, Qt::Key_F3,
//XK_KP_F4, Qt::Key_F4, XK_Clear, Qt::Key_Clear,
XK_KP_Home, Qt::Key_Home, XK_Delete, Qt::Key_Delete,
XK_KP_Left, Qt::Key_Left, XK_space, Qt::Key_Space,
XK_KP_Up, Qt::Key_Up, XK_exclam, Qt::Key_Exclam,
XK_KP_Right, Qt::Key_Right, XK_quotedbl, Qt::Key_QuoteDbl,
XK_KP_Down, Qt::Key_Down, XK_numbersign, Qt::Key_NumberSign,
XK_KP_Prior, Qt::Key_PageUp, XK_dollar, Qt::Key_Dollar,
XK_KP_Next, Qt::Key_PageDown, XK_percent, Qt::Key_Percent,
XK_KP_End, Qt::Key_End, XK_ampersand, Qt::Key_Ampersand,
XK_KP_Begin, Qt::Key_Clear, XK_apostrophe, Qt::Key_Apostrophe,
XK_KP_Insert, Qt::Key_Insert, XK_parenleft, Qt::Key_ParenLeft,
XK_KP_Delete, Qt::Key_Delete, XK_parenright, Qt::Key_ParenRight,
XK_KP_Equal, Qt::Key_Equal, XK_asterisk, Qt::Key_Asterisk,
XK_KP_Multiply, Qt::Key_Asterisk, XK_plus, Qt::Key_Plus,
XK_KP_Add, Qt::Key_Plus, XK_comma, Qt::Key_Comma,
XK_KP_Separator, Qt::Key_Comma, XK_minus, Qt::Key_Minus,
XK_KP_Subtract, Qt::Key_Minus, XK_period, Qt::Key_Period,
XK_KP_Decimal, Qt::Key_Period, XK_slash, Qt::Key_Slash,
XK_KP_Divide, Qt::Key_Slash, XK_colon, Qt::Key_Colon,
XK_semicolon, Qt::Key_Semicolon,
XK_less, Qt::Key_Less,
XK_equal, Qt::Key_Equal,
XK_greater, Qt::Key_Greater,
XK_question, Qt::Key_Question,
XK_bracketleft, Qt::Key_BracketLeft,
XK_backslash, Qt::Key_Backslash,
XK_bracketright, Qt::Key_BracketRight,
XK_asciicircum, Qt::Key_AsciiCircum,
XK_underscore, Qt::Key_Underscore,
// International input method support keys // International input method support keys

View File

@ -96,11 +96,9 @@ find_path(SPARSEHASH_INCLUDE_DIRS google/sparsetable)
# distros. If the user seems to want Drive support (ie. they have sparsehash # distros. If the user seems to want Drive support (ie. they have sparsehash
# installed and haven't disabled drive), and has an old taglib, compile our # installed and haven't disabled drive), and has an old taglib, compile our
# internal one and use that instead. # internal one and use that instead.
option(USE_BUILTIN_TAGLIB "If the system's version of Taglib is too old for Google Drive support, compile our builtin version instead" ON) option(USE_BUILTIN_TAGLIB "If the system's version of Taglib is too old, compile our builtin version instead" ON)
if (USE_BUILTIN_TAGLIB AND if (USE_BUILTIN_TAGLIB AND TAGLIB_VERSION VERSION_LESS 1.8)
(NOT "${ENABLE_GOOGLE_DRIVE}" STREQUAL "OFF") AND message(STATUS "Using builtin taglib because your system's version is too old")
SPARSEHASH_INCLUDE_DIRS AND
TAGLIB_VERSION VERSION_LESS 1.8)
set(TAGLIB_VERSION 1.9.1) set(TAGLIB_VERSION 1.9.1)
set(TAGLIB_INCLUDE_DIRS "${CMAKE_BINARY_DIR}/3rdparty/taglib/headers/taglib/;${CMAKE_BINARY_DIR}/3rdparty/taglib/headers/") set(TAGLIB_INCLUDE_DIRS "${CMAKE_BINARY_DIR}/3rdparty/taglib/headers/taglib/;${CMAKE_BINARY_DIR}/3rdparty/taglib/headers/")
set(TAGLIB_LIBRARY_DIRS "") set(TAGLIB_LIBRARY_DIRS "")

26
README.md Normal file
View File

@ -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

View File

@ -1027,9 +1027,13 @@ optional_source(HAVE_AUDIOCD
SOURCES SOURCES
devices/cddadevice.cpp devices/cddadevice.cpp
devices/cddalister.cpp devices/cddalister.cpp
ui/ripcd.cpp
HEADERS HEADERS
devices/cddadevice.h devices/cddadevice.h
devices/cddalister.h devices/cddalister.h
ui/ripcd.h
UI
ui/ripcd.ui
) )
# mtp device # mtp device

View File

@ -451,4 +451,12 @@ void EnableFullScreen(const QWidget& main_window) {
[window setCollectionBehavior: kFullScreenPrimary]; [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 } // namespace mac

View File

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

View File

@ -69,7 +69,7 @@ void Player::Init() {
connect(engine_.get(), SIGNAL(TrackAboutToEnd()), SLOT(TrackAboutToEnd())); connect(engine_.get(), SIGNAL(TrackAboutToEnd()), SLOT(TrackAboutToEnd()));
connect(engine_.get(), SIGNAL(TrackEnded()), SLOT(TrackEnded())); connect(engine_.get(), SIGNAL(TrackEnded()), SLOT(TrackEnded()));
connect(engine_.get(), SIGNAL(MetaData(Engine::SimpleMetaBundle)), connect(engine_.get(), SIGNAL(MetaData(Engine::SimpleMetaBundle)),
SLOT(EngineMetadataReceived(Engine::SimpleMetaBundle))); SLOT(EngineMetadataReceived(Engine::SimpleMetaBundle)));
engine_->SetVolume(settings_.value("volume", 50).toInt()); engine_->SetVolume(settings_.value("volume", 50).toInt());

View File

@ -797,7 +797,7 @@ void Song::BindToQuery(QSqlQuery *query) const {
&& Utilities::UrlOnSameDriveAsClementine(d->url_)) { && Utilities::UrlOnSameDriveAsClementine(d->url_)) {
query->bindValue(":filename", Utilities::GetRelativePathToClementineBin(d->url_)); query->bindValue(":filename", Utilities::GetRelativePathToClementineBin(d->url_));
} else { } else {
query->bindValue(":filename", d->url_); query->bindValue(":filename", d->url_.toEncoded());
} }
query->bindValue(":mtime", notnullintval(d->mtime_)); query->bindValue(":mtime", notnullintval(d->mtime_));

View File

@ -33,16 +33,17 @@
#include "config.h" #include "config.h"
#include "core/concurrentrun.h" #include "core/concurrentrun.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/song.h" #include "core/player.h"
#include "core/signalchecker.h" #include "core/signalchecker.h"
#include "core/song.h"
#include "core/tagreaderclient.h" #include "core/tagreaderclient.h"
#include "core/timeconstants.h" #include "core/timeconstants.h"
#include "internet/fixlastfm.h" #include "internet/fixlastfm.h"
#include "internet/internetmodel.h" #include "internet/internetmodel.h"
#include "library/librarybackend.h" #include "library/librarybackend.h"
#include "library/sqlrow.h" #include "library/sqlrow.h"
#include "playlistparsers/parserbase.h"
#include "playlistparsers/cueparser.h" #include "playlistparsers/cueparser.h"
#include "playlistparsers/parserbase.h"
#include "playlistparsers/playlistparser.h" #include "playlistparsers/playlistparser.h"
#include "podcasts/podcastparser.h" #include "podcasts/podcastparser.h"
#include "podcasts/podcastservice.h" #include "podcasts/podcastservice.h"
@ -52,7 +53,9 @@
QSet<QString> SongLoader::sRawUriSchemes; QSet<QString> SongLoader::sRawUriSchemes;
const int SongLoader::kDefaultTimeout = 5000; const int SongLoader::kDefaultTimeout = 5000;
SongLoader::SongLoader(LibraryBackendInterface* library, QObject *parent) SongLoader::SongLoader(LibraryBackendInterface* library,
const Player* player,
QObject *parent)
: QObject(parent), : QObject(parent),
timeout_timer_(new QTimer(this)), timeout_timer_(new QTimer(this)),
playlist_parser_(new PlaylistParser(library, this)), playlist_parser_(new PlaylistParser(library, this)),
@ -63,7 +66,8 @@ SongLoader::SongLoader(LibraryBackendInterface* library, QObject *parent)
success_(false), success_(false),
parser_(NULL), parser_(NULL),
is_podcast_(false), is_podcast_(false),
library_(library) library_(library),
player_(player)
{ {
if (sRawUriSchemes.isEmpty()) { if (sRawUriSchemes.isEmpty()) {
sRawUriSchemes << "udp" << "mms" << "mmsh" << "mmst" << "mmsu" << "rtsp" sRawUriSchemes << "udp" << "mms" << "mmsh" << "mmst" << "mmsu" << "rtsp"
@ -91,9 +95,10 @@ SongLoader::Result SongLoader::Load(const QUrl& url) {
return LoadLocal(url_.toLocalFile()); return LoadLocal(url_.toLocalFile());
} }
if (sRawUriSchemes.contains(url_.scheme())) { if (sRawUriSchemes.contains(url_.scheme()) ||
// The URI scheme indicates that it can't possibly be a playlist, so add player_->HandlerForUrl(url) != nullptr) {
// it as a raw stream. // The URI scheme indicates that it can't possibly be a playlist, or we have
// a custom handler for the URL, so add it as a raw stream.
AddAsRawStream(); AddAsRawStream();
return Success; return Success;
} }

View File

@ -33,13 +33,15 @@
class CueParser; class CueParser;
class LibraryBackendInterface; class LibraryBackendInterface;
class ParserBase; class ParserBase;
class Player;
class PlaylistParser; class PlaylistParser;
class PodcastParser; class PodcastParser;
class SongLoader : public QObject { class SongLoader : public QObject {
Q_OBJECT Q_OBJECT
public: public:
SongLoader(LibraryBackendInterface* library, QObject* parent = 0); SongLoader(LibraryBackendInterface* library, const Player* player,
QObject* parent = 0);
~SongLoader(); ~SongLoader();
enum Result { enum Result {
@ -130,6 +132,7 @@ private:
bool is_podcast_; bool is_podcast_;
QByteArray buffer_; QByteArray buffer_;
LibraryBackendInterface* library_; LibraryBackendInterface* library_;
const Player* player_;
boost::shared_ptr<GstElement> pipeline_; boost::shared_ptr<GstElement> pipeline_;

View File

@ -457,9 +457,9 @@ QByteArray Sha256(const QByteArray& data) {
} }
// File must not be open and will be closed afterwards! // File must not be open and will be closed afterwards!
QByteArray Md5File(QFile &file) { QByteArray Sha1File(QFile &file) {
file.open(QIODevice::ReadOnly); file.open(QIODevice::ReadOnly);
QCryptographicHash hash(QCryptographicHash::Md5); QCryptographicHash hash(QCryptographicHash::Sha1);
QByteArray data; QByteArray data;
while(!file.atEnd()) { while(!file.atEnd()) {

View File

@ -67,7 +67,7 @@ namespace Utilities {
QByteArray HmacSha256(const QByteArray& key, const QByteArray& data); QByteArray HmacSha256(const QByteArray& key, const QByteArray& data);
QByteArray HmacSha1(const QByteArray& key, const QByteArray& data); QByteArray HmacSha1(const QByteArray& key, const QByteArray& data);
QByteArray Sha256(const QByteArray& data); QByteArray Sha256(const QByteArray& data);
QByteArray Md5File(QFile& file); QByteArray Sha1File(QFile& file);
QByteArray Sha1CoverHash(const QString& artist, const QString& album); QByteArray Sha1CoverHash(const QString& artist, const QString& album);

View File

@ -13,7 +13,7 @@ using boost::scoped_ptr;
namespace { namespace {
static const char* kServiceName = "Skydrive"; static const char* kServiceName = "OneDrive";
static const char* kServiceId = "skydrive"; static const char* kServiceId = "skydrive";
static const char* kSettingsGroup = "Skydrive"; static const char* kSettingsGroup = "Skydrive";

View File

@ -212,6 +212,10 @@ void LibraryFilterWidget::SetQueryMode(QueryOptions::QueryMode query_mode) {
model_->SetFilterQueryMode(query_mode); model_->SetFilterQueryMode(query_mode);
} }
void LibraryFilterWidget::ShowInLibrary(const QString& search) {
ui_->filter->setText(search);
}
void LibraryFilterWidget::SetAgeFilterEnabled(bool enabled) { void LibraryFilterWidget::SetAgeFilterEnabled(bool enabled) {
filter_age_menu_->setEnabled(enabled); filter_age_menu_->setEnabled(enabled);
} }

View File

@ -56,6 +56,7 @@ class LibraryFilterWidget : public QWidget {
void SetDelayBehaviour(DelayBehaviour behaviour) { delay_behaviour_ = behaviour; } void SetDelayBehaviour(DelayBehaviour behaviour) { delay_behaviour_ = behaviour; }
void SetAgeFilterEnabled(bool enabled); void SetAgeFilterEnabled(bool enabled);
void SetGroupByEnabled(bool enabled); void SetGroupByEnabled(bool enabled);
void ShowInLibrary(const QString& search);
QMenu* menu() const { return library_menu_; } QMenu* menu() const { return library_menu_; }
void AddMenuAction(QAction* action); void AddMenuAction(QAction* action);

View File

@ -39,19 +39,37 @@ LibraryQuery::LibraryQuery(const QueryOptions& options)
// expected with sqlite's FTS3: // expected with sqlite's FTS3:
// 1) Append * to all tokens. // 1) Append * to all tokens.
// 2) Prefix "fts" to column names. // 2) Prefix "fts" to column names.
// 3) Remove colons which don't correspond to column names.
// Split on whitespace // Split on whitespace
QStringList tokens(options.filter().split(QRegExp("\\s+"))); QStringList tokens(options.filter().split(
QRegExp("\\s+"), QString::SkipEmptyParts));
QString query; QString query;
foreach (QString token, tokens) { foreach (QString token, tokens) {
token.remove('('); token.remove('(');
token.remove(')'); token.remove(')');
token.remove('"'); token.remove('"');
token.replace('-', ' ');
if (token.contains(':')) if (token.contains(':')) {
query += "fts" + token + "* "; // Only prefix fts if the token is a valid column name.
else if (Song::kFtsColumns.contains("fts" + token.section(':', 0, 0),
Qt::CaseInsensitive)) {
// Account for multiple colons.
QString columntoken = token.section(
':', 0, 0, QString::SectionIncludeTrailingSep);
QString subtoken = token.section(':', 1, -1);
subtoken.replace(":", " ");
subtoken = subtoken.trimmed();
query += "fts" + columntoken + subtoken + "* ";
} else {
token.replace(":", " ");
token = token.trimmed();
query += token + "* ";
}
} else {
query += token + "* "; query += token + "* ";
}
} }
where_clauses_ << "fts.%fts_table_noprefix MATCH ?"; where_clauses_ << "fts.%fts_table_noprefix MATCH ?";

View File

@ -642,9 +642,9 @@ void OutgoingDataCreator::SendSingleSong(RemoteClient* client, const Song &song,
// Open the file // Open the file
QFile file(song.url().toLocalFile()); QFile file(song.url().toLocalFile());
// Get md5 for file // Get sha1 for file
QByteArray md5 = Utilities::Md5File(file).toHex(); QByteArray sha1 = Utilities::Sha1File(file).toHex();
qLog(Debug) << "md5 for file" << song.url().toLocalFile() << "=" << md5; qLog(Debug) << "sha1 for file" << song.url().toLocalFile() << "=" << sha1;
file.open(QIODevice::ReadOnly); file.open(QIODevice::ReadOnly);
@ -670,7 +670,7 @@ void OutgoingDataCreator::SendSingleSong(RemoteClient* client, const Song &song,
chunk->set_file_number(song_no); chunk->set_file_number(song_no);
chunk->set_size(file.size()); chunk->set_size(file.size());
chunk->set_data(data.data(), data.size()); chunk->set_data(data.data(), data.size());
chunk->set_file_hash(md5.data(), md5.size()); chunk->set_file_hash(sha1.data(), sha1.size());
// On the first chunk send the metadata, so the client knows // On the first chunk send the metadata, so the client knows
// what file it receives. // what file it receives.
@ -753,9 +753,9 @@ void OutgoingDataCreator::SendLibrary(RemoteClient *client) {
// Open the file // Open the file
QFile file(temp_file_name); QFile file(temp_file_name);
// Get the md5 hash // Get the sha1 hash
QByteArray md5 = Utilities::Md5File(file).toHex(); QByteArray sha1 = Utilities::Sha1File(file).toHex();
qLog(Debug) << "Library md5" << md5; qLog(Debug) << "Library sha1" << sha1;
file.open(QIODevice::ReadOnly); file.open(QIODevice::ReadOnly);
@ -777,7 +777,7 @@ void OutgoingDataCreator::SendLibrary(RemoteClient *client) {
chunk->set_chunk_number(chunk_number); chunk->set_chunk_number(chunk_number);
chunk->set_size(file.size()); chunk->set_size(file.size());
chunk->set_data(data.data(), data.size()); chunk->set_data(data.data(), data.size());
chunk->set_file_hash(md5.data(), md5.size()); chunk->set_file_hash(sha1.data(), sha1.size());
// Send data directly to the client // Send data directly to the client
client->SendData(&msg); client->SendData(&msg);

View File

@ -25,6 +25,7 @@
#include "songloaderinserter.h" #include "songloaderinserter.h"
#include "songmimedata.h" #include "songmimedata.h"
#include "songplaylistitem.h" #include "songplaylistitem.h"
#include "core/application.h"
#include "core/closure.h" #include "core/closure.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/modelfuturewatcher.h" #include "core/modelfuturewatcher.h"
@ -761,7 +762,8 @@ bool Playlist::dropMimeData(const QMimeData* data, Qt::DropAction action, int ro
} }
} }
} else if (data->hasFormat(kCddaMimeType)) { } else if (data->hasFormat(kCddaMimeType)) {
SongLoaderInserter* inserter = new SongLoaderInserter(task_manager_, library_); SongLoaderInserter* inserter = new SongLoaderInserter(
task_manager_, library_, backend_->app()->player());
connect(inserter, SIGNAL(Error(QString)), SIGNAL(LoadTracksError(QString))); connect(inserter, SIGNAL(Error(QString)), SIGNAL(LoadTracksError(QString)));
inserter->LoadAudioCD(this, row, play_now, enqueue_now); inserter->LoadAudioCD(this, row, play_now, enqueue_now);
} else if (data->hasUrls()) { } else if (data->hasUrls()) {
@ -773,7 +775,8 @@ bool Playlist::dropMimeData(const QMimeData* data, Qt::DropAction action, int ro
} }
void Playlist::InsertUrls(const QList<QUrl> &urls, int pos, bool play_now, bool enqueue) { void Playlist::InsertUrls(const QList<QUrl> &urls, int pos, bool play_now, bool enqueue) {
SongLoaderInserter* inserter = new SongLoaderInserter(task_manager_, library_); SongLoaderInserter* inserter = new SongLoaderInserter(
task_manager_, library_, backend_->app()->player());
connect(inserter, SIGNAL(Error(QString)), SIGNAL(LoadTracksError(QString))); connect(inserter, SIGNAL(Error(QString)), SIGNAL(LoadTracksError(QString)));
inserter->Load(this, pos, play_now, enqueue, urls); inserter->Load(this, pos, play_now, enqueue, urls);

View File

@ -78,6 +78,8 @@ class PlaylistBackend : public QObject {
void FavoritePlaylist(int id, bool is_favorite); void FavoritePlaylist(int id, bool is_favorite);
void RemovePlaylist(int id); void RemovePlaylist(int id);
Application* app() const { return app_; }
public slots: public slots:
void SavePlaylist(int playlist, const PlaylistItemList& items, void SavePlaylist(int playlist, const PlaylistItemList& items,
int last_played, smart_playlists::GeneratorPtr dynamic); int last_played, smart_playlists::GeneratorPtr dynamic);

View File

@ -16,13 +16,6 @@
*/ */
#include "playlistdelegates.h" #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 <QDateTime>
#include <QDir> #include <QDir>
@ -39,6 +32,18 @@
#include <QWhatsThis> #include <QWhatsThis>
#include <QtConcurrentRun> #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::kQueueBoxBorder = 1;
const int QueuedItemDelegate::kQueueBoxCornerRadius = 3; const int QueuedItemDelegate::kQueueBoxCornerRadius = 3;
const int QueuedItemDelegate::kQueueBoxLength = 30; const int QueuedItemDelegate::kQueueBoxLength = 30;
@ -492,8 +497,14 @@ void SongSourceDelegate::paint(
const QUrl& url = index.data().toUrl(); const QUrl& url = index.data().toUrl();
QPixmap pixmap = LookupPixmap(url, option_copy.decorationSize); 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 // 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()); draw_rect.moveCenter(option_copy.rect.center());
painter->drawPixmap(draw_rect, pixmap); painter->drawPixmap(draw_rect, pixmap);

View File

@ -156,7 +156,7 @@ void PlaylistManager::New(const QString& name, const SongList& songs,
void PlaylistManager::Load(const QString& filename) { void PlaylistManager::Load(const QString& filename) {
QUrl url = QUrl::fromLocalFile(filename); QUrl url = QUrl::fromLocalFile(filename);
SongLoader* loader = new SongLoader(library_backend_, this); SongLoader* loader = new SongLoader(library_backend_, app_->player(), this);
connect(loader, SIGNAL(LoadFinished(bool)), SLOT(LoadFinished(bool))); connect(loader, SIGNAL(LoadFinished(bool)), SLOT(LoadFinished(bool)));
SongLoader::Result result = loader->Load(url); SongLoader::Result result = loader->Load(url);
QFileInfo info(filename); QFileInfo info(filename);

View File

@ -1254,3 +1254,24 @@ void PlaylistView::FadePreviousBackgroundImage(qreal value) {
void PlaylistView::PlayerStopped() { void PlaylistView::PlayerStopped() {
CurrentSongChanged(Song(), QString(), QImage()); CurrentSongChanged(Song(), QString(), QImage());
} }
void PlaylistView::focusInEvent(QFocusEvent* event) {
QTreeView::focusInEvent(event);
if (event->reason() == Qt::TabFocusReason ||
event->reason() == Qt::BacktabFocusReason) {
// If there's a current item but no selection it probably means the list was
// filtered, and the selected item does not match the filter. If there's
// only 1 item in the view it is now impossible to select that item without
// using the mouse.
const QModelIndex& current = selectionModel()->currentIndex();
if (current.isValid() &&
selectionModel()->selectedIndexes().isEmpty()) {
QItemSelection new_selection(
current.sibling(current.row(), 0),
current.sibling(current.row(),
current.model()->columnCount(current.parent()) - 1));
selectionModel()->select(new_selection, QItemSelectionModel::Select);
}
}
}

View File

@ -134,6 +134,7 @@ class PlaylistView : public QTreeView {
void dropEvent(QDropEvent *event); void dropEvent(QDropEvent *event);
void resizeEvent(QResizeEvent* event); void resizeEvent(QResizeEvent* event);
bool eventFilter(QObject* object, QEvent* event); bool eventFilter(QObject* object, QEvent* event);
void focusInEvent(QFocusEvent* event);
// QAbstractScrollArea // QAbstractScrollArea
void scrollContentsBy(int dx, int dy); void scrollContentsBy(int dx, int dy);

View File

@ -23,8 +23,9 @@
#include "core/songloader.h" #include "core/songloader.h"
#include "core/taskmanager.h" #include "core/taskmanager.h"
SongLoaderInserter::SongLoaderInserter( SongLoaderInserter::SongLoaderInserter(TaskManager* task_manager,
TaskManager* task_manager, LibraryBackendInterface* library) LibraryBackendInterface* library,
const Player* player)
: task_manager_(task_manager), : task_manager_(task_manager),
destination_(NULL), destination_(NULL),
row_(-1), row_(-1),
@ -32,7 +33,8 @@ SongLoaderInserter::SongLoaderInserter(
enqueue_(false), enqueue_(false),
async_load_id_(0), async_load_id_(0),
async_progress_(0), async_progress_(0),
library_(library) { library_(library),
player_(player) {
} }
SongLoaderInserter::~SongLoaderInserter() { SongLoaderInserter::~SongLoaderInserter() {
@ -53,7 +55,7 @@ void SongLoaderInserter::Load(Playlist *destination,
destination, SLOT(UpdateItems(const SongList&))); destination, SLOT(UpdateItems(const SongList&)));
foreach (const QUrl& url, urls) { foreach (const QUrl& url, urls) {
SongLoader* loader = new SongLoader(library_, this); SongLoader* loader = new SongLoader(library_, player_, this);
// we're connecting this before we're even sure if this is an async load // we're connecting this before we're even sure if this is an async load
// to avoid race conditions (signal emission before we're listening to it) // to avoid race conditions (signal emission before we're listening to it)
@ -92,7 +94,7 @@ void SongLoaderInserter::LoadAudioCD(Playlist *destination,
play_now_ = play_now; play_now_ = play_now;
enqueue_ = enqueue; enqueue_ = enqueue;
SongLoader *loader = new SongLoader(library_, this); SongLoader* loader = new SongLoader(library_, player_, this);
connect(loader, SIGNAL(LoadFinished(bool)), SLOT(AudioCDTagsLoaded(bool))); connect(loader, SIGNAL(LoadFinished(bool)), SLOT(AudioCDTagsLoaded(bool)));
qLog(Info) << "Loading audio CD..."; qLog(Info) << "Loading audio CD...";
SongLoader::Result ret = loader->LoadAudioCD(); SongLoader::Result ret = loader->LoadAudioCD();

View File

@ -25,6 +25,7 @@
#include "core/song.h" #include "core/song.h"
class LibraryBackendInterface; class LibraryBackendInterface;
class Player;
class Playlist; class Playlist;
class SongLoader; class SongLoader;
class TaskManager; class TaskManager;
@ -34,7 +35,9 @@ class QModelIndex;
class SongLoaderInserter : public QObject { class SongLoaderInserter : public QObject {
Q_OBJECT Q_OBJECT
public: public:
SongLoaderInserter(TaskManager* task_manager, LibraryBackendInterface* library); SongLoaderInserter(TaskManager* task_manager,
LibraryBackendInterface* library,
const Player* player);
~SongLoaderInserter(); ~SongLoaderInserter();
void Load(Playlist* destination, int row, bool play_now, bool enqueue, void Load(Playlist* destination, int row, bool play_now, bool enqueue,
@ -70,6 +73,7 @@ private:
int async_load_id_; int async_load_id_;
int async_progress_; int async_progress_;
LibraryBackendInterface* library_; LibraryBackendInterface* library_;
const Player* player_;
}; };
#endif // SONGLOADERINSERTER_H #endif // SONGLOADERINSERTER_H

View File

@ -1,16 +1,16 @@
/* This file is part of Clementine. /* This file is part of Clementine.
Copyright 2012, David Sansome <me@davidsansome.com> Copyright 2012, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
Clementine is distributed in the hope that it will be useful, Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>. along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/ */
@ -27,6 +27,12 @@
#include <QRegExp> #include <QRegExp>
#include <QSet> #include <QSet>
#ifdef Q_OS_WIN
#include <time.h>
#else
#include <sys/time.h>
#endif
class Application; class Application;
class PodcastBackend; class PodcastBackend;

View File

@ -138,7 +138,7 @@ void UltimateLyricsProvider::LyricsFetched() {
// Apply exclude rules // Apply exclude rules
foreach (const Rule& rule, exclude_rules_) { foreach (const Rule& rule, exclude_rules_) {
ApplyExcludeRule(rule, &lyrics); ApplyExcludeRule(rule, &content);
} }
if (!content.isEmpty() and HTMLHasAlphaNumeric(content)) { if (!content.isEmpty() and HTMLHasAlphaNumeric(content)) {

View File

@ -20,6 +20,7 @@
#include "transcoderoptionsdialog.h" #include "transcoderoptionsdialog.h"
#include "ui_transcodedialog.h" #include "ui_transcodedialog.h"
#include "ui_transcodelogdialog.h" #include "ui_transcodelogdialog.h"
#include "ui/iconloader.h"
#include "ui/mainwindow.h" #include "ui/mainwindow.h"
#include "widgets/fileview.h" #include "widgets/fileview.h"
@ -35,6 +36,7 @@
const char* TranscodeDialog::kSettingsGroup = "Transcoder"; const char* TranscodeDialog::kSettingsGroup = "Transcoder";
const int TranscodeDialog::kProgressInterval = 500; const int TranscodeDialog::kProgressInterval = 500;
const int TranscodeDialog::kMaxDestinationItems = 10;
static bool ComparePresetsByName(const TranscoderPreset& left, static bool ComparePresetsByName(const TranscoderPreset& left,
const TranscoderPreset& right) { const TranscoderPreset& right) {
@ -103,6 +105,8 @@ TranscodeDialog::TranscodeDialog(QWidget *parent)
connect(close_button_, SIGNAL(clicked()), SLOT(hide())); connect(close_button_, SIGNAL(clicked()), SLOT(hide()));
connect(ui_->details, SIGNAL(clicked()), log_dialog_, SLOT(show())); connect(ui_->details, SIGNAL(clicked()), log_dialog_, SLOT(show()));
connect(ui_->options, SIGNAL(clicked()), SLOT(Options())); 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(JobComplete(QString,bool)), SLOT(JobComplete(QString,bool)));
connect(transcoder_, SIGNAL(LogLine(QString)), SLOT(LogLine(QString))); connect(transcoder_, SIGNAL(LogLine(QString)), SLOT(LogLine(QString)));
@ -138,7 +142,8 @@ void TranscodeDialog::Start() {
// Add jobs to the transcoder // Add jobs to the transcoder
for (int i=0 ; i<file_model->rowCount() ; ++i) { for (int i=0 ; i<file_model->rowCount() ; ++i) {
QString filename = file_model->index(i, 0).data(Qt::UserRole).toString(); 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 // Set up the progressbar
@ -265,3 +270,50 @@ void TranscodeDialog::Options() {
dialog.exec(); 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_;
}
}

View File

@ -25,6 +25,8 @@ class Transcoder;
class Ui_TranscodeDialog; class Ui_TranscodeDialog;
class Ui_TranscodeLogDialog; class Ui_TranscodeLogDialog;
struct TranscoderPreset;
class TranscodeDialog : public QDialog { class TranscodeDialog : public QDialog {
Q_OBJECT Q_OBJECT
@ -34,6 +36,7 @@ class TranscodeDialog : public QDialog {
static const char* kSettingsGroup; static const char* kSettingsGroup;
static const int kProgressInterval; static const int kProgressInterval;
static const int kMaxDestinationItems;
void SetFilenames(const QStringList& filenames); void SetFilenames(const QStringList& filenames);
@ -49,11 +52,15 @@ class TranscodeDialog : public QDialog {
void LogLine(const QString& message); void LogLine(const QString& message);
void AllJobsComplete(); void AllJobsComplete();
void Options(); void Options();
void AddDestination();
private: private:
void SetWorking(bool working); void SetWorking(bool working);
void UpdateStatusText(); void UpdateStatusText();
void UpdateProgress(); void UpdateProgress();
QString TrimPath(const QString& path) const;
QString GetOutputFileName(const QString& input,
const TranscoderPreset& preset) const;
private: private:
Ui_TranscodeDialog* ui_; Ui_TranscodeDialog* ui_;

View File

@ -98,7 +98,7 @@
<property name="title"> <property name="title">
<string>Output options</string> <string>Output options</string>
</property> </property>
<layout class="QFormLayout" name="formLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
@ -107,25 +107,21 @@
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3"> <widget class="QComboBox" name="format">
<item> <property name="sizePolicy">
<widget class="QComboBox" name="format"> <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<property name="sizePolicy"> <horstretch>0</horstretch>
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"> <verstretch>0</verstretch>
<horstretch>0</horstretch> </sizepolicy>
<verstretch>0</verstretch> </property>
</sizepolicy> </widget>
</property> </item>
</widget> <item row="0" column="2">
</item> <widget class="QPushButton" name="options">
<item> <property name="text">
<widget class="QPushButton" name="options"> <string>Options...</string>
<property name="text"> </property>
<string>Options...</string> </widget>
</property>
</widget>
</item>
</layout>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
@ -136,6 +132,15 @@
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QComboBox" name="destination"> <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> <item>
<property name="text"> <property name="text">
<string>Alongside the originals</string> <string>Alongside the originals</string>
@ -143,6 +148,13 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="1" column="2">
<widget class="QPushButton" name="select">
<property name="text">
<string>Select...</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -196,7 +208,6 @@
<tabstop>add</tabstop> <tabstop>add</tabstop>
<tabstop>remove</tabstop> <tabstop>remove</tabstop>
<tabstop>format</tabstop> <tabstop>format</tabstop>
<tabstop>destination</tabstop>
<tabstop>button_box</tabstop> <tabstop>button_box</tabstop>
</tabstops> </tabstops>
<resources/> <resources/>

View File

@ -297,7 +297,7 @@ void Transcoder::AddJob(const QString& input,
// Never overwrite existing files // Never overwrite existing files
if (QFile::exists(job.output)) { if (QFile::exists(job.output)) {
for (int i=0 ; ; ++i) { for (int i=0 ; ; ++i) {
QString new_filename = QString("%1.%2").arg(job.output).arg(i); QString new_filename = QString("%1.%2.%3").arg(job.output.section('.',0,-2)).arg(i).arg(preset.extension_);
if (!QFile::exists(new_filename)) { if (!QFile::exists(new_filename)) {
job.output = new_filename; job.output = new_filename;
break; break;
@ -331,8 +331,10 @@ Transcoder::StartJobStatus Transcoder::MaybeStartNextJob() {
} }
Job job = queued_jobs_.takeFirst(); Job job = queued_jobs_.takeFirst();
if (StartJob(job)) if (StartJob(job)) {
emit(JobOutputName(job.output));
return StartedSuccessfully; return StartedSuccessfully;
}
emit JobComplete(job.input, false); emit JobComplete(job.input, false);
return FailedToStart; return FailedToStart;

View File

@ -75,6 +75,7 @@ class Transcoder : public QObject {
void JobComplete(const QString& filename, bool success); void JobComplete(const QString& filename, bool success);
void LogLine(const QString& message); void LogLine(const QString& message);
void AllJobsComplete(); void AllJobsComplete();
void JobOutputName(const QString& filename);
protected: protected:
bool event(QEvent* e); bool event(QEvent* e);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -27,10 +27,11 @@ class AlbumCoverFetcher;
class AlbumCoverSearcher; class AlbumCoverSearcher;
class Application; class Application;
class CoverFromURLDialog; class CoverFromURLDialog;
class CoverSearchStatistics;
class QFileDialog; class QFileDialog;
class Song; class Song;
struct CoverSearchStatistics;
// Controller for the common album cover related menu options. // Controller for the common album cover related menu options.
class AlbumCoverChoiceController : public QWidget { class AlbumCoverChoiceController : public QWidget {
Q_OBJECT Q_OBJECT

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