commit
b04367937a
|
@ -45,3 +45,4 @@ dist/windows/Python27.zip
|
|||
*.swp
|
||||
src/translations/translations.pot
|
||||
*.DS_Store
|
||||
.vscode
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
language: cpp
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- cmake
|
||||
- gettext
|
||||
- libboost-dev
|
||||
- libboost-serialization-dev
|
||||
- libcdio-dev
|
||||
- libchromaprint-dev
|
||||
- libcrypto++-dev
|
||||
- libechonest-dev
|
||||
- libfftw3-dev
|
||||
- libglew1.5-dev
|
||||
- libgpod-dev
|
||||
- libgstreamer1.0-dev
|
||||
- libgstreamer-plugins-base1.0-dev
|
||||
- liblastfm-dev
|
||||
- libmtp-dev
|
||||
- libplist-dev
|
||||
- libprotobuf-dev
|
||||
- libpulse-dev
|
||||
- libqca2-dev
|
||||
- libqca2-plugin-ossl
|
||||
- libqjson-dev
|
||||
- libqt4-dev
|
||||
- libqt4-opengl-dev
|
||||
- libqtwebkit-dev
|
||||
- libsparsehash-dev
|
||||
- libsqlite3-dev
|
||||
- libtag1-dev
|
||||
- libusbmuxd-dev
|
||||
- protobuf-compiler
|
||||
- qt4-dev-tools
|
||||
|
||||
script:
|
||||
- cd bin
|
||||
- cmake ..
|
||||
- make
|
|
@ -26,6 +26,8 @@ set(DISABLE_MILKDROP_PRESETS OFF)
|
|||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++98")
|
||||
|
||||
pkg_check_modules(GLEW glew)
|
||||
|
||||
if(DISABLE_NATIVE_PRESETS)
|
||||
ADD_DEFINITIONS(-DDISABLE_NATIVE_PRESETS)
|
||||
endif(DISABLE_NATIVE_PRESETS)
|
||||
|
|
|
@ -22,6 +22,6 @@ FIND_PACKAGE(X11)
|
|||
INCLUDE_DIRECTORIES(${projectM_SOURCE_DIR} ${X11_INCLUDE_DIR})
|
||||
ADD_LIBRARY(Renderer STATIC ${Renderer_SOURCES})
|
||||
SET_TARGET_PROPERTIES(Renderer PROPERTIES VERSION 2.00 SOVERSION 2)
|
||||
TARGET_LINK_LIBRARIES(Renderer ${MATH_LIBRARIES} projectM)
|
||||
TARGET_LINK_LIBRARIES(Renderer ${MATH_LIBRARIES} ${GLEW_LIBRARIES} projectM)
|
||||
|
||||
set_target_properties(Renderer PROPERTIES COMPILE_FLAGS "-Wno-parentheses")
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
Clementine
|
||||
==========
|
||||
|
||||
Clementine is a modern music player and library organizer for Windows, Linux and Mac OS X.
|
||||
Clementine is a modern music player and library organizer for Windows, Linux and macOS.
|
||||
|
||||
- Website: http://www.clementine-player.org/
|
||||
- Github: https://github.com/clementine-player/Clementine
|
||||
|
|
|
@ -471,6 +471,7 @@
|
|||
<file>schema/schema-4.sql</file>
|
||||
<file>schema/schema-5.sql</file>
|
||||
<file>schema/schema-50.sql</file>
|
||||
<file>schema/schema-51.sql</file>
|
||||
<file>schema/schema-6.sql</file>
|
||||
<file>schema/schema-7.sql</file>
|
||||
<file>schema/schema-8.sql</file>
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
</exclude>
|
||||
<invalidIndicator value="Couldn't find that page."/>
|
||||
</provider>
|
||||
<provider name="letras.mus.br" title="" charset="utf-8" url="http://letras.terra.com.br/winamp.php?musica={title}&artista={artist}">
|
||||
<provider name="letras.mus.br" title="" charset="utf-8" url="https://www.letras.mus.br/winamp.php?musica={title}&artista={artist}">
|
||||
<urlFormat replace="_@,;&\/"" with="_"/>
|
||||
<urlFormat replace=" " with="+"/>
|
||||
<extract>
|
||||
|
@ -105,7 +105,7 @@
|
|||
<urlFormat replace=" _@;\"" with="_"/>
|
||||
<urlFormat replace="?" with="%3F"/>
|
||||
<extract>
|
||||
<item begin="<div class='lyricbox'>" end="<!--"/>
|
||||
<item begin="<div class='lyricbox'>" end="<div class='lyricsbreak'"/>
|
||||
</extract>
|
||||
<exclude>
|
||||
<item tag="<div class='rtMatcher'>"/>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
DELETE FROM %allsongstables_fts;
|
||||
|
||||
DROP TABLE %allsongstables_fts;
|
||||
|
||||
CREATE VIRTUAL TABLE %allsongstables_fts USING fts3( ftstitle, ftsalbum, ftsartist, ftsalbumartist,
|
||||
ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment, ftsyear,
|
||||
tokenize=unicode
|
||||
);
|
||||
|
||||
INSERT INTO %allsongstables_fts ( ROWID, ftstitle, ftsalbum, ftsartist, ftsalbumartist,
|
||||
ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment, ftsyear)
|
||||
SELECT ROWID, title, album, artist, albumartist, composer, performer, grouping, genre, comment, year
|
||||
FROM %allsongstables;
|
||||
|
||||
UPDATE schema_version SET version=51;
|
|
@ -2,6 +2,7 @@
|
|||
Version=1.0
|
||||
Type=Application
|
||||
Name=Clementine
|
||||
Name[hi]=क्लेमेंटैन्
|
||||
Name[sr]=Клементина
|
||||
Name[sr@ijekavian]=Клементина
|
||||
Name[sr@ijekavianlatin]=Klementina
|
||||
|
@ -9,6 +10,7 @@ Name[sr@latin]=Klementina
|
|||
GenericName=Clementine Music Player
|
||||
GenericName[ca]=Reproductor de música Clementine
|
||||
GenericName[es]=Reproductor de música Clementine
|
||||
GenericName[hi]=क्लेमेंटैन् संगीत वादक
|
||||
GenericName[pl]=Odtwarzacz muzyki Clementine
|
||||
GenericName[pt]=Reprodutor de músicas Clementine
|
||||
GenericName[sl]=Predvajalnik glasbe Clementine
|
||||
|
@ -56,6 +58,7 @@ Name[fr]=Lecture
|
|||
Name[ga]=Seinn
|
||||
Name[gl]=Reproducir
|
||||
Name[he]=נגינה
|
||||
Name[hi]=गाना बजाएं
|
||||
Name[hr]=Pokreni reprodukciju
|
||||
Name[hu]=Lejátszás
|
||||
Name[it]=Riproduci
|
||||
|
@ -102,6 +105,7 @@ Name[fi]=Keskeytä
|
|||
Name[ga]=Cuir ar sos
|
||||
Name[gl]=Pausa
|
||||
Name[he]=השהייה
|
||||
Name[hi]=गाना रोकें
|
||||
Name[hr]=Pauza
|
||||
Name[hu]=Szünet
|
||||
Name[it]=Pausa
|
||||
|
@ -148,6 +152,7 @@ Name[fi]=Pysäytä
|
|||
Name[ga]=Stad
|
||||
Name[gl]=Deter
|
||||
Name[he]=הפסקה
|
||||
Name[hi]=गाना बंद करे
|
||||
Name[hr]=Zaustavi reprodukciju
|
||||
Name[hu]=Leállít
|
||||
Name[it]=Ferma
|
||||
|
@ -197,6 +202,7 @@ Name[fr]=Arrêter la lecture après cette piste
|
|||
Name[ga]=Stad i ndiaidh an rian seo
|
||||
Name[gl]=Deter a reprodución despois da pista actual
|
||||
Name[he]=הפסקה אחרי רצועה זו
|
||||
Name[hi]=इस गाने के बाद बंद करे
|
||||
Name[hr]=Zaustavi reprodukciju nakon ove pjesme
|
||||
Name[hu]=Leállítás az aktuális szám után
|
||||
Name[it]=Ferma dopo questa traccia
|
||||
|
@ -215,8 +221,6 @@ Name[ru]=Остановить после этого трека
|
|||
Name[sk]=Zastaviť po tejto skladbe
|
||||
Name[sl]=Zaustavi po tej skladbi
|
||||
Name[sr]=Заустави после ове нумере
|
||||
Name[sr@ijekavian]=
|
||||
Name[sr@ijekavianlatin]=
|
||||
Name[sr@latin]=Zaustavi posle ove numere
|
||||
Name[sv]=Stoppa efter detta spår
|
||||
Name[tr]=Bu parçadan sonra durdur
|
||||
|
@ -245,6 +249,7 @@ Name[fr]=Précédent
|
|||
Name[ga]=Roimhe
|
||||
Name[gl]=Anterior
|
||||
Name[he]=הקודם
|
||||
Name[hi]=पिछला गाना
|
||||
Name[hr]=Prijašnje
|
||||
Name[hu]=Előző
|
||||
Name[it]=Precedente
|
||||
|
@ -292,6 +297,7 @@ Name[fr]=Suivant
|
|||
Name[ga]=Ar aghaidh
|
||||
Name[gl]=Próximo
|
||||
Name[he]=הבא
|
||||
Name[hi]=अगला गाना
|
||||
Name[hr]=Sljedeće
|
||||
Name[hu]=Következő
|
||||
Name[id]=Berikut
|
||||
|
|
|
@ -72,6 +72,7 @@ GSTREAMER_PLUGINS = [
|
|||
'libgstvorbis.so',
|
||||
'libgstwavpack.so',
|
||||
'libgstwavparse.so',
|
||||
'libgstxingmux.so',
|
||||
|
||||
# HTTP src support
|
||||
'libgstsouphttpsrc.so',
|
||||
|
|
|
@ -35,6 +35,7 @@ add_library(libclementine-common STATIC
|
|||
)
|
||||
|
||||
target_link_libraries(libclementine-common
|
||||
${QT_LIBRARIES}
|
||||
${TAGLIB_LIBRARIES}
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
)
|
||||
|
|
|
@ -67,7 +67,8 @@ const char* CommandlineOptions::kHelpText =
|
|||
" --quiet %29\n"
|
||||
" --verbose %30\n"
|
||||
" --log-levels <levels> %31\n"
|
||||
" --version %32\n";
|
||||
" --version %32\n"
|
||||
" -x, --delete-current %33\n";
|
||||
|
||||
const char* CommandlineOptions::kVersionText = "Clementine %1";
|
||||
|
||||
|
@ -81,6 +82,7 @@ CommandlineOptions::CommandlineOptions(int argc, char** argv)
|
|||
seek_to_(-1),
|
||||
seek_by_(0),
|
||||
play_track_at_(-1),
|
||||
delete_current_track_(false),
|
||||
show_osd_(false),
|
||||
toggle_pretty_osd_(false),
|
||||
log_levels_(logging::kDefaultLogLevels) {
|
||||
|
@ -135,12 +137,13 @@ bool CommandlineOptions::Parse() {
|
|||
{"verbose", no_argument, 0, Verbose},
|
||||
{"log-levels", required_argument, 0, LogLevels},
|
||||
{"version", no_argument, 0, Version},
|
||||
{"delete-current", no_argument, 0, 'x'},
|
||||
{0, 0, 0, 0}};
|
||||
|
||||
// Parse the arguments
|
||||
bool ok = false;
|
||||
forever {
|
||||
int c = getopt_long(argc_, argv_, "hptusqrfv:c:alk:oyg:", kOptions, nullptr);
|
||||
int c = getopt_long(argc_, argv_, "xhptusqrfv:c:alk:oyg:", kOptions, nullptr);
|
||||
|
||||
// End of the options
|
||||
if (c == -1) break;
|
||||
|
@ -158,8 +161,8 @@ bool CommandlineOptions::Parse() {
|
|||
.arg(tr("Skip backwards in playlist"),
|
||||
tr("Skip forwards in playlist"),
|
||||
tr("Set the volume to <value> percent"),
|
||||
tr("Increase the volume by 4%"),
|
||||
tr("Decrease the volume by 4%"),
|
||||
tr("Increase the volume by 4 percent"),
|
||||
tr("Decrease the volume by 4 percent"),
|
||||
tr("Increase the volume by <value> percent"),
|
||||
tr("Decrease the volume by <value> percent"))
|
||||
.arg(tr("Seek the currently playing track to an absolute "
|
||||
|
@ -179,7 +182,8 @@ bool CommandlineOptions::Parse() {
|
|||
tr("Equivalent to --log-levels *:1"),
|
||||
tr("Equivalent to --log-levels *:3"),
|
||||
tr("Comma separated list of class:level, level is 0-3"))
|
||||
.arg(tr("Print out version information"));
|
||||
.arg(tr("Print out version information"),
|
||||
tr("Delete the currently playing song"));
|
||||
|
||||
std::cout << translated_help_text.toLocal8Bit().constData();
|
||||
return false;
|
||||
|
@ -280,6 +284,10 @@ bool CommandlineOptions::Parse() {
|
|||
if (!ok) play_track_at_ = -1;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
delete_current_track_ = true;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
default:
|
||||
return false;
|
||||
|
@ -303,7 +311,8 @@ bool CommandlineOptions::is_empty() const {
|
|||
return player_action_ == Player_None && set_volume_ == -1 &&
|
||||
volume_modifier_ == 0 && seek_to_ == -1 && seek_by_ == 0 &&
|
||||
play_track_at_ == -1 && show_osd_ == false &&
|
||||
toggle_pretty_osd_ == false && urls_.isEmpty();
|
||||
toggle_pretty_osd_ == false && urls_.isEmpty() &&
|
||||
delete_current_track_ == false;
|
||||
}
|
||||
|
||||
bool CommandlineOptions::contains_play_options() const {
|
||||
|
@ -338,7 +347,8 @@ QString CommandlineOptions::tr(const char* source_text) {
|
|||
QDataStream& operator<<(QDataStream& s, const CommandlineOptions& a) {
|
||||
s << qint32(a.player_action_) << qint32(a.url_list_action_) << a.set_volume_
|
||||
<< a.volume_modifier_ << a.seek_to_ << a.seek_by_ << a.play_track_at_
|
||||
<< a.show_osd_ << a.urls_ << a.log_levels_ << a.toggle_pretty_osd_;
|
||||
<< a.show_osd_ << a.urls_ << a.log_levels_ << a.toggle_pretty_osd_
|
||||
<< a.delete_current_track_;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
@ -348,7 +358,8 @@ QDataStream& operator>>(QDataStream& s, CommandlineOptions& a) {
|
|||
quint32 url_list_action = 0;
|
||||
s >> player_action >> url_list_action >> a.set_volume_ >>
|
||||
a.volume_modifier_ >> a.seek_to_ >> a.seek_by_ >> a.play_track_at_ >>
|
||||
a.show_osd_ >> a.urls_ >> a.log_levels_ >> a.toggle_pretty_osd_;
|
||||
a.show_osd_ >> a.urls_ >> a.log_levels_ >> a.toggle_pretty_osd_ >>
|
||||
a.delete_current_track_;
|
||||
a.player_action_ = CommandlineOptions::PlayerAction(player_action);
|
||||
a.url_list_action_ = CommandlineOptions::UrlListAction(url_list_action);
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@ class CommandlineOptions {
|
|||
int seek_to() const { return seek_to_; }
|
||||
int seek_by() const { return seek_by_; }
|
||||
int play_track_at() const { return play_track_at_; }
|
||||
bool delete_current_track() const { return delete_current_track_; }
|
||||
bool show_osd() const { return show_osd_; }
|
||||
bool toggle_pretty_osd() const { return toggle_pretty_osd_; }
|
||||
QList<QUrl> urls() const { return urls_; }
|
||||
|
@ -113,6 +114,7 @@ class CommandlineOptions {
|
|||
int seek_to_;
|
||||
int seek_by_;
|
||||
int play_track_at_;
|
||||
bool delete_current_track_;
|
||||
bool show_osd_;
|
||||
bool toggle_pretty_osd_;
|
||||
QString language_;
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
#include <QVariant>
|
||||
|
||||
const char* Database::kDatabaseFilename = "clementine.db";
|
||||
const int Database::kSchemaVersion = 50;
|
||||
const int Database::kSchemaVersion = 51;
|
||||
const char* Database::kMagicAllSongsTables = "%allsongstables";
|
||||
|
||||
int Database::sNextConnectionId = 1;
|
||||
|
|
|
@ -57,6 +57,15 @@ void DeleteFiles::Start(const SongList& songs) {
|
|||
thread_->start();
|
||||
}
|
||||
|
||||
void DeleteFiles::Start(const QUrl& url) {
|
||||
SongList songs;
|
||||
Song song;
|
||||
song.set_url(url);
|
||||
songs << song;
|
||||
|
||||
Start(songs);
|
||||
}
|
||||
|
||||
void DeleteFiles::Start(const QStringList& filenames) {
|
||||
SongList songs;
|
||||
for (const QString& filename : filenames) {
|
||||
|
|
|
@ -40,6 +40,7 @@ class DeleteFiles : public QObject {
|
|||
|
||||
void Start(const SongList& songs);
|
||||
void Start(const QStringList& filenames);
|
||||
void Start(const QUrl& url);
|
||||
|
||||
signals:
|
||||
void Finished(const SongList& songs_with_errors);
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include "metatypes.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QMetaType>
|
||||
#include <QNetworkCookie>
|
||||
|
||||
|
@ -110,6 +111,7 @@ void RegisterMetaTypes() {
|
|||
qRegisterMetaType<Subdirectory>("Subdirectory");
|
||||
qRegisterMetaType<QList<QUrl>>("QList<QUrl>");
|
||||
qRegisterMetaType<QAbstractSocket::SocketState>();
|
||||
qRegisterMetaType<QFileInfo>("QFileInfo");
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
qDBusRegisterMetaType<QImage>();
|
||||
|
|
|
@ -320,7 +320,7 @@ void Mpris2::SetShuffle(bool enable) {
|
|||
QVariantMap Mpris2::Metadata() const { return last_metadata_; }
|
||||
|
||||
QString Mpris2::current_track_id() const {
|
||||
return QString("/org/mpris/MediaPlayer2/Track/%1")
|
||||
return QString("/org/clementineplayer/Clementine/Track/%1")
|
||||
.arg(QString::number(app_->playlist_manager()->active()->current_row()));
|
||||
}
|
||||
|
||||
|
@ -495,7 +495,7 @@ namespace {
|
|||
|
||||
QDBusObjectPath MakePlaylistPath(int id) {
|
||||
return QDBusObjectPath(
|
||||
QString("/org/mpris/MediaPlayer2/Playlists/%1").arg(id));
|
||||
QString("/org/clementineplayer/clementine/PlaylistId/%1").arg(id));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -194,6 +194,13 @@ void Organise::ProcessSomeFiles() {
|
|||
if (!destination_->CopyToStorage(job)) {
|
||||
files_with_errors_ << task.song_info_.song_.basefilename();
|
||||
} else {
|
||||
if (job.remove_original_) {
|
||||
// Notify other aspects of system that song has been invalidated
|
||||
QString root = destination_->LocalPath();
|
||||
QFileInfo new_file = QFileInfo(
|
||||
root + "/" + task.song_info_.new_filename_);
|
||||
emit SongPathChanged(song, new_file);
|
||||
}
|
||||
if (job.mark_as_listened_) {
|
||||
emit FileCopied(job.metadata_.id());
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QBasicTimer>
|
||||
#include <QObject>
|
||||
#include <QTemporaryFile>
|
||||
|
@ -60,6 +61,7 @@ class Organise : public QObject {
|
|||
signals:
|
||||
void Finished(const QStringList& files_with_errors);
|
||||
void FileCopied(int database_id);
|
||||
void SongPathChanged(const Song& song, const QFileInfo& new_file);
|
||||
|
||||
protected:
|
||||
void timerEvent(QTimerEvent* e);
|
||||
|
|
|
@ -613,7 +613,16 @@ void Player::InvalidSongRequested(const QUrl& url) {
|
|||
emit SongChangeRequestProcessed(url, false);
|
||||
// ... and now when our listeners have completed their processing of the
|
||||
// current item we can change the current item by skipping to the next song
|
||||
NextItem(Engine::Auto);
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
|
||||
bool stop_playback = s.value("stop_play_if_fail", 0).toBool();
|
||||
s.endGroup();
|
||||
|
||||
if (!stop_playback) {
|
||||
NextItem(Engine::Auto);
|
||||
}
|
||||
}
|
||||
|
||||
void Player::RegisterUrlHandler(UrlHandler* handler) {
|
||||
|
|
|
@ -130,7 +130,8 @@ const QStringList Song::kFtsColumns = QStringList() << "ftstitle"
|
|||
<< "ftsperformer"
|
||||
<< "ftsgrouping"
|
||||
<< "ftsgenre"
|
||||
<< "ftscomment";
|
||||
<< "ftscomment"
|
||||
<< "ftsyear";
|
||||
|
||||
const QString Song::kFtsColumnSpec = Song::kFtsColumns.join(", ");
|
||||
const QString Song::kFtsBindSpec =
|
||||
|
@ -407,7 +408,7 @@ QString Song::TextForFiletype(FileType type) {
|
|||
case Song::Type_Asf:
|
||||
return QObject::tr("Windows Media audio");
|
||||
case Song::Type_Flac:
|
||||
return QObject::tr("Flac");
|
||||
return QObject::tr("FLAC");
|
||||
case Song::Type_Mp4:
|
||||
return QObject::tr("MP4 AAC");
|
||||
case Song::Type_Mpc:
|
||||
|
@ -415,7 +416,7 @@ QString Song::TextForFiletype(FileType type) {
|
|||
case Song::Type_Mpeg:
|
||||
return QObject::tr("MP3"); // Not technically correct
|
||||
case Song::Type_OggFlac:
|
||||
return QObject::tr("Ogg Flac");
|
||||
return QObject::tr("Ogg FLAC");
|
||||
case Song::Type_OggSpeex:
|
||||
return QObject::tr("Ogg Speex");
|
||||
case Song::Type_OggVorbis:
|
||||
|
@ -996,6 +997,7 @@ void Song::BindToFtsQuery(QSqlQuery* query) const {
|
|||
query->bindValue(":ftsgrouping", d->grouping_);
|
||||
query->bindValue(":ftsgenre", d->genre_);
|
||||
query->bindValue(":ftscomment", d->comment_);
|
||||
query->bindValue(":ftsyear", d->year_);
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBLASTFM
|
||||
|
|
|
@ -113,13 +113,18 @@ QString PrettyTimeNanosec(qint64 nanoseconds) {
|
|||
}
|
||||
|
||||
QString WordyTime(quint64 seconds) {
|
||||
quint64 days = seconds / (60 * 60 * 24);
|
||||
quint64 days = seconds / (kSecsPerDay);
|
||||
quint64 remaining_hours = (seconds - days * kSecsPerDay) / (60 * 60);
|
||||
|
||||
// TODO(David Sansome): Make the plural rules translatable
|
||||
QStringList parts;
|
||||
|
||||
if (days) parts << (days == 1 ? tr("1 day") : tr("%1 days").arg(days));
|
||||
parts << PrettyTime(seconds - days * 60 * 60 * 24);
|
||||
|
||||
// Since PrettyTime does not return the hour if it is 0, we need to add it
|
||||
// explicitly for durations longer than 1 day.
|
||||
parts << (days && !remaining_hours ? QString("0:") : QString()) +
|
||||
PrettyTime(seconds - days * kSecsPerDay);
|
||||
|
||||
return parts.join(" ");
|
||||
}
|
||||
|
|
|
@ -122,7 +122,7 @@ QVariantMap DeviceKitLister::DeviceHardwareInfo(const QString& id) {
|
|||
if (!device_data_.contains(id)) return ret;
|
||||
const DeviceData& data = device_data_[id];
|
||||
|
||||
ret[QT_TR_NOOP("DBus path")] = data.dbus_path;
|
||||
ret[QT_TR_NOOP("D-Bus path")] = data.dbus_path;
|
||||
ret[QT_TR_NOOP("Serial number")] = data.drive_serial;
|
||||
ret[QT_TR_NOOP("Mount points")] = data.device_mount_paths.join(", ");
|
||||
ret[QT_TR_NOOP("Device")] = data.device_file;
|
||||
|
|
|
@ -73,7 +73,7 @@ QVariantMap Udisks2Lister::DeviceHardwareInfo(const QString& id) {
|
|||
QVariantMap result;
|
||||
|
||||
const auto& data = device_data_[id];
|
||||
result[QT_TR_NOOP("DBus path")] = data.dbus_path;
|
||||
result[QT_TR_NOOP("D-Bus path")] = data.dbus_path;
|
||||
result[QT_TR_NOOP("Serial number")] = data.serial;
|
||||
result[QT_TR_NOOP("Mount points")] = data.mount_paths.join(", ");
|
||||
result[QT_TR_NOOP("Partition label")] = data.label;
|
||||
|
|
|
@ -692,6 +692,18 @@ void GstEngine::HandlePipelineError(int pipeline_id, const QString& message,
|
|||
|
||||
qLog(Warning) << "Gstreamer error:" << message;
|
||||
|
||||
// try to reload the URL in case of a drop of the connection
|
||||
if (domain == GST_RESOURCE_ERROR && error_code == GST_RESOURCE_ERROR_SEEK) {
|
||||
if (Load(url_, 0, false, 0, 0)) {
|
||||
|
||||
current_pipeline_->SetState(GST_STATE_PLAYING);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
qLog(Warning) << "Attempt to reload " << url_ << " failed";
|
||||
}
|
||||
|
||||
current_pipeline_.reset();
|
||||
|
||||
BufferingFinished();
|
||||
|
|
|
@ -36,9 +36,11 @@
|
|||
#include "core/logging.h"
|
||||
#include "core/mimedata.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "internet/core/internetsongmimedata.h"
|
||||
#include "library/libraryfilterwidget.h"
|
||||
#include "library/librarymodel.h"
|
||||
#include "library/groupbydialog.h"
|
||||
#include "playlist/songmimedata.h"
|
||||
|
||||
using std::placeholders::_1;
|
||||
using std::placeholders::_2;
|
||||
|
@ -446,34 +448,38 @@ bool GlobalSearchView::SearchKeyEvent(QKeyEvent* event) {
|
|||
}
|
||||
|
||||
bool GlobalSearchView::ResultsContextMenuEvent(QContextMenuEvent* event) {
|
||||
if (!context_menu_) {
|
||||
context_menu_ = new QMenu(this);
|
||||
context_actions_ << context_menu_->addAction(
|
||||
IconLoader::Load("media-playback-start", IconLoader::Base),
|
||||
tr("Append to current playlist"), this,
|
||||
SLOT(AddSelectedToPlaylist()));
|
||||
context_actions_ << context_menu_->addAction(
|
||||
IconLoader::Load("media-playback-start", IconLoader::Base),
|
||||
tr("Replace current playlist"), this,
|
||||
SLOT(LoadSelected()));
|
||||
context_actions_ << context_menu_->addAction(
|
||||
IconLoader::Load("document-new", IconLoader::Base),
|
||||
tr("Open in new playlist"), this,
|
||||
SLOT(OpenSelectedInNewPlaylist()));
|
||||
context_menu_ = new QMenu(this);
|
||||
context_actions_ << context_menu_->addAction(
|
||||
IconLoader::Load("media-playback-start", IconLoader::Base),
|
||||
tr("Append to current playlist"), this, SLOT(AddSelectedToPlaylist()));
|
||||
context_actions_ << context_menu_->addAction(
|
||||
IconLoader::Load("media-playback-start", IconLoader::Base),
|
||||
tr("Replace current playlist"), this, SLOT(LoadSelected()));
|
||||
context_actions_ << context_menu_->addAction(
|
||||
IconLoader::Load("document-new", IconLoader::Base),
|
||||
tr("Open in new playlist"), this, SLOT(OpenSelectedInNewPlaylist()));
|
||||
|
||||
context_menu_->addSeparator();
|
||||
context_actions_ << context_menu_->addAction(
|
||||
IconLoader::Load("go-next", IconLoader::Base), tr("Queue track"),
|
||||
this, SLOT(AddSelectedToPlaylistEnqueue()));
|
||||
context_menu_->addSeparator();
|
||||
context_actions_ << context_menu_->addAction(
|
||||
IconLoader::Load("go-next", IconLoader::Base), tr("Queue track"), this,
|
||||
SLOT(AddSelectedToPlaylistEnqueue()));
|
||||
|
||||
context_menu_->addSeparator();
|
||||
context_menu_->addMenu(tr("Group by"))
|
||||
->addActions(group_by_actions_->actions());
|
||||
context_menu_->addAction(IconLoader::Load("configure", IconLoader::Base),
|
||||
tr("Configure global search..."), this,
|
||||
SLOT(OpenSettingsDialog()));
|
||||
context_menu_->addSeparator();
|
||||
|
||||
if (ui_->results->selectionModel() &&
|
||||
ui_->results->selectionModel()->selectedRows().length() == 1) {
|
||||
context_actions_ << context_menu_->addAction(
|
||||
IconLoader::Load("system-search", IconLoader::Base),
|
||||
tr("Search for this"), this, SLOT(SearchForThis()));
|
||||
}
|
||||
|
||||
context_menu_->addSeparator();
|
||||
context_menu_->addMenu(tr("Group by"))
|
||||
->addActions(group_by_actions_->actions());
|
||||
context_menu_->addAction(IconLoader::Load("configure", IconLoader::Base),
|
||||
tr("Configure global search..."), this,
|
||||
SLOT(OpenSettingsDialog()));
|
||||
|
||||
const bool enable_context_actions =
|
||||
ui_->results->selectionModel() &&
|
||||
ui_->results->selectionModel()->hasSelection();
|
||||
|
@ -515,6 +521,11 @@ void GlobalSearchView::OpenSelectedInNewPlaylist() {
|
|||
emit AddToPlaylist(data);
|
||||
}
|
||||
|
||||
void GlobalSearchView::SearchForThis() {
|
||||
StartSearch(
|
||||
ui_->results->selectionModel()->selectedRows().first().data().toString());
|
||||
}
|
||||
|
||||
void GlobalSearchView::showEvent(QShowEvent* e) {
|
||||
if (show_suggestions_) {
|
||||
UpdateSuggestions();
|
||||
|
@ -560,9 +571,12 @@ void GlobalSearchView::GroupByClicked(QAction* action) {
|
|||
}
|
||||
|
||||
void GlobalSearchView::SetGroupBy(const LibraryModel::Grouping& g) {
|
||||
// Clear requests: changing "group by" on the models will cause all the items to be removed/added
|
||||
// again, so all the QModelIndex here will become invalid. New requests will be created for those
|
||||
// songs when they will be displayed again anyway (when GlobalSearchItemDelegate::paint will call
|
||||
// Clear requests: changing "group by" on the models will cause all the items
|
||||
// to be removed/added
|
||||
// again, so all the QModelIndex here will become invalid. New requests will
|
||||
// be created for those
|
||||
// songs when they will be displayed again anyway (when
|
||||
// GlobalSearchItemDelegate::paint will call
|
||||
// LazyLoadArt)
|
||||
art_requests_.clear();
|
||||
// Update the models
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "searchprovider.h"
|
||||
#include "library/librarymodel.h"
|
||||
#include "ui/settingsdialog.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
|
@ -82,6 +83,8 @@ signals:
|
|||
void OpenSelectedInNewPlaylist();
|
||||
void AddSelectedToPlaylistEnqueue();
|
||||
|
||||
void SearchForThis();
|
||||
|
||||
void GroupByClicked(QAction* action);
|
||||
void SetGroupBy(const LibraryModel::Grouping& grouping);
|
||||
|
||||
|
|
|
@ -32,8 +32,12 @@
|
|||
|
||||
#include "lastfmservice.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QDesktopServices>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QSettings>
|
||||
#include <QUrlQuery>
|
||||
|
||||
#ifdef HAVE_LIBLASTFM1
|
||||
#include <lastfm5/RadioStation.h>
|
||||
|
@ -42,14 +46,16 @@
|
|||
#endif
|
||||
|
||||
#include "lastfmcompat.h"
|
||||
#include "internet/core/internetmodel.h"
|
||||
#include "internet/core/internetplaylistitem.h"
|
||||
#include "core/application.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
#include "core/player.h"
|
||||
#include "core/song.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "internet/core/internetmodel.h"
|
||||
#include "internet/core/internetplaylistitem.h"
|
||||
#include "internet/core/localredirectserver.h"
|
||||
#include "covers/coverproviders.h"
|
||||
#include "covers/lastfmcoverprovider.h"
|
||||
#include "ui/iconloader.h"
|
||||
|
@ -75,7 +81,8 @@ LastFMService::LastFMService(Application* app, QObject* parent)
|
|||
already_scrobbled_(false),
|
||||
scrobbling_enabled_(false),
|
||||
connection_problems_(false),
|
||||
app_(app) {
|
||||
app_(app),
|
||||
network_(new NetworkAccessManager) {
|
||||
#ifdef HAVE_LIBLASTFM1
|
||||
lastfm::ws::setScheme(lastfm::ws::Https);
|
||||
#endif
|
||||
|
@ -123,37 +130,53 @@ bool LastFMService::IsSubscriber() const {
|
|||
return settings.value("Subscriber", false).toBool();
|
||||
}
|
||||
|
||||
void LastFMService::GetToken() {
|
||||
QMap<QString, QString> params;
|
||||
params["method"] = "auth.getToken";
|
||||
QNetworkReply* reply = lastfm::ws::post(params);
|
||||
NewClosure(reply, SIGNAL(finished()), this,
|
||||
SLOT(GetTokenReplyFinished(QNetworkReply*)), reply);
|
||||
}
|
||||
|
||||
void LastFMService::GetTokenReplyFinished(QNetworkReply* reply) {
|
||||
reply->deleteLater();
|
||||
|
||||
// Parse the reply
|
||||
lastfm::XmlQuery lfm(lastfm::compat::EmptyXmlQuery());
|
||||
if (lastfm::compat::ParseQuery(reply->readAll(), &lfm)) {
|
||||
QString token = lfm["token"].text();
|
||||
|
||||
emit TokenReceived(true, token);
|
||||
} else {
|
||||
emit TokenReceived(false, lfm["error"].text().trimmed());
|
||||
namespace {
|
||||
QByteArray SignApiRequest(QList<QPair<QString, QString>> params) {
|
||||
qSort(params);
|
||||
QString to_sign;
|
||||
for (const auto& p : params) {
|
||||
to_sign += p.first;
|
||||
to_sign += p.second;
|
||||
}
|
||||
to_sign += LastFMService::kSecret;
|
||||
return QCryptographicHash::hash(to_sign.toUtf8(), QCryptographicHash::Md5).toHex();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void LastFMService::Authenticate(const QString& token) {
|
||||
QMap<QString, QString> params;
|
||||
params["method"] = "auth.getSession";
|
||||
params["token"] = token;
|
||||
void LastFMService::Authenticate() {
|
||||
QUrl url("https://www.last.fm/api/auth/");
|
||||
QUrlQuery url_query;
|
||||
url_query.addQueryItem("api_key", kApiKey);
|
||||
|
||||
QNetworkReply* reply = lastfm::ws::post(params);
|
||||
NewClosure(reply, SIGNAL(finished()), this,
|
||||
SLOT(AuthenticateReplyFinished(QNetworkReply*)), reply);
|
||||
// If we need more detailed error reporting, handle error(NetworkError) signal
|
||||
LocalRedirectServer* server = new LocalRedirectServer(this);
|
||||
server->Listen();
|
||||
|
||||
url_query.addQueryItem("cb", server->url().toString());
|
||||
|
||||
NewClosure(server, SIGNAL(Finished()), [this, server]() {
|
||||
server->deleteLater();
|
||||
|
||||
const QUrl& url = server->request_url();
|
||||
QString token = QUrlQuery(url).queryItemValue("token");
|
||||
|
||||
QUrl session_url("https://ws.audioscrobbler.com/2.0/");
|
||||
QUrlQuery session_url_query;
|
||||
session_url_query.addQueryItem("api_key", kApiKey);
|
||||
session_url_query.addQueryItem("method", "auth.getSession");
|
||||
session_url_query.addQueryItem("token", token);
|
||||
session_url_query.addQueryItem("api_sig", SignApiRequest(session_url_query.queryItems()));
|
||||
session_url.setQuery(session_url_query);
|
||||
|
||||
QNetworkReply* reply = network_->get(QNetworkRequest(session_url));
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(AuthenticateReplyFinished(QNetworkReply*)), reply);
|
||||
});
|
||||
|
||||
if (!QDesktopServices::openUrl(url)) {
|
||||
QMessageBox box(QMessageBox::NoIcon, tr("Last.fm Authentication"), tr("Please open this URL in your browser: <a href=\"%1\">%1</a>").arg(url.toString()), QMessageBox::Ok);
|
||||
box.setTextFormat(Qt::RichText);
|
||||
qLog(Debug) << "Last.fm authentication URL: " << url.toString();
|
||||
box.exec();
|
||||
}
|
||||
}
|
||||
|
||||
void LastFMService::AuthenticateReplyFinished(QNetworkReply* reply) {
|
||||
|
@ -174,14 +197,14 @@ void LastFMService::AuthenticateReplyFinished(QNetworkReply* reply) {
|
|||
settings.setValue("Session", lastfm::ws::SessionKey);
|
||||
settings.setValue("Subscriber", is_subscriber);
|
||||
} else {
|
||||
emit AuthenticationComplete(false, lfm["error"].text().trimmed());
|
||||
emit AuthenticationComplete(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Invalidate the scrobbler - it will get recreated later
|
||||
scrobbler_.reset(nullptr);
|
||||
|
||||
emit AuthenticationComplete(true, QString());
|
||||
emit AuthenticationComplete(true);
|
||||
}
|
||||
|
||||
void LastFMService::SignOut() {
|
||||
|
|
|
@ -38,8 +38,8 @@ uint qHash(const lastfm::Track& track);
|
|||
|
||||
class Application;
|
||||
class LastFMUrlHandler;
|
||||
class NetworkAccessManager;
|
||||
class QAction;
|
||||
class QNetworkAccessManager;
|
||||
class Song;
|
||||
|
||||
class LastFMService : public Scrobbler {
|
||||
|
@ -69,8 +69,7 @@ class LastFMService : public Scrobbler {
|
|||
bool PreferAlbumArtist() const { return prefer_albumartist_; }
|
||||
bool HasConnectionProblems() const { return connection_problems_; }
|
||||
|
||||
void GetToken();
|
||||
void Authenticate(const QString& token);
|
||||
void Authenticate();
|
||||
void SignOut();
|
||||
void UpdateSubscriberStatus();
|
||||
|
||||
|
@ -83,8 +82,7 @@ class LastFMService : public Scrobbler {
|
|||
void ToggleScrobbling();
|
||||
|
||||
signals:
|
||||
void TokenReceived(bool success, const QString& token);
|
||||
void AuthenticationComplete(bool success, const QString& error_message);
|
||||
void AuthenticationComplete(bool success);
|
||||
void ScrobblingEnabledChanged(bool value);
|
||||
void ButtonVisibilityChanged(bool value);
|
||||
void ScrobbleButtonVisibilityChanged(bool value);
|
||||
|
@ -97,7 +95,6 @@ signals:
|
|||
void SavedItemsChanged();
|
||||
|
||||
private slots:
|
||||
void GetTokenReplyFinished(QNetworkReply* reply);
|
||||
void AuthenticateReplyFinished(QNetworkReply* reply);
|
||||
void UpdateSubscriberStatusFinished(QNetworkReply* reply);
|
||||
|
||||
|
@ -129,6 +126,7 @@ signals:
|
|||
bool connection_problems_;
|
||||
|
||||
Application* app_;
|
||||
std::unique_ptr<NetworkAccessManager> network_;
|
||||
};
|
||||
|
||||
#endif // INTERNET_LASTFM_LASTFMSERVICE_H_
|
||||
|
|
|
@ -43,10 +43,8 @@ LastFMSettingsPage::LastFMSettingsPage(SettingsDialog* dialog)
|
|||
// Icons
|
||||
setWindowIcon(IconLoader::Load("lastfm", IconLoader::Provider));
|
||||
|
||||
connect(service_, SIGNAL(TokenReceived(bool,QString)),
|
||||
SLOT(TokenReceived(bool,QString)));
|
||||
connect(service_, SIGNAL(AuthenticationComplete(bool, QString)),
|
||||
SLOT(AuthenticationComplete(bool, QString)));
|
||||
connect(service_, SIGNAL(AuthenticationComplete(bool)),
|
||||
SLOT(AuthenticationComplete(bool)));
|
||||
connect(ui_->login_state, SIGNAL(LogoutClicked()), SLOT(Logout()));
|
||||
connect(ui_->login_state, SIGNAL(LoginClicked()), SLOT(Login()));
|
||||
connect(ui_->login, SIGNAL(clicked()), SLOT(Login()));
|
||||
|
@ -62,26 +60,10 @@ void LastFMSettingsPage::Login() {
|
|||
waiting_for_auth_ = true;
|
||||
|
||||
ui_->login_state->SetLoggedIn(LoginStateWidget::LoginInProgress);
|
||||
service_->GetToken();
|
||||
service_->Authenticate();
|
||||
}
|
||||
|
||||
void LastFMSettingsPage::TokenReceived(bool success, const QString &token) {
|
||||
if (!success) {
|
||||
QMessageBox::warning(this, tr("Last.fm authentication failed"), token);
|
||||
return;
|
||||
}
|
||||
|
||||
QString url = QString(LastFMService::kAuthLoginUrl).arg(LastFMService::kApiKey, token);
|
||||
QDesktopServices::openUrl(QUrl(url));
|
||||
|
||||
QMessageBox::information(this, tr("Last.fm authentication"),
|
||||
tr("Click Ok once you authenticated Clementine in your last.fm account."));
|
||||
|
||||
service_->Authenticate(token);
|
||||
}
|
||||
|
||||
void LastFMSettingsPage::AuthenticationComplete(bool success,
|
||||
const QString& message) {
|
||||
void LastFMSettingsPage::AuthenticationComplete(bool success) {
|
||||
if (!waiting_for_auth_) return; // Wasn't us that was waiting for auth
|
||||
|
||||
waiting_for_auth_ = false;
|
||||
|
@ -90,10 +72,7 @@ void LastFMSettingsPage::AuthenticationComplete(bool success,
|
|||
// Save settings
|
||||
Save();
|
||||
} else {
|
||||
QString dialog_text = tr("Your Last.fm credentials were incorrect");
|
||||
if (!message.isEmpty()) {
|
||||
dialog_text = message;
|
||||
}
|
||||
QString dialog_text = tr("Could not log in to Last.fm. Please try again.");
|
||||
QMessageBox::warning(this, tr("Authentication failed"), dialog_text);
|
||||
}
|
||||
|
||||
|
|
|
@ -38,8 +38,7 @@ class LastFMSettingsPage : public SettingsPage {
|
|||
|
||||
private slots:
|
||||
void Login();
|
||||
void TokenReceived(bool success, const QString& token);
|
||||
void AuthenticationComplete(bool success, const QString& error_message);
|
||||
void AuthenticationComplete(bool success);
|
||||
void Logout();
|
||||
|
||||
private:
|
||||
|
|
|
@ -229,9 +229,16 @@ void PodcastParser::ParseItem(QXmlStreamReader* reader, Podcast* ret) const {
|
|||
}
|
||||
} else if (name == "enclosure") {
|
||||
const QString type = reader->attributes().value("type").toString();
|
||||
const QUrl url = QUrl::fromEncoded(reader->attributes().value("url").toString().toLatin1());
|
||||
if (type.startsWith("audio/") || type.startsWith("x-audio/")) {
|
||||
episode.set_url(QUrl::fromEncoded(
|
||||
reader->attributes().value("url").toString().toLatin1()));
|
||||
episode.set_url(url);
|
||||
}
|
||||
// If the URL doesn't have a type, see if it's one of the obvious types
|
||||
else if (type.isEmpty() && (
|
||||
url.path().endsWith(".mp3", Qt::CaseInsensitive) ||
|
||||
url.path().endsWith(".m4a", Qt::CaseInsensitive) ||
|
||||
url.path().endsWith(".wav", Qt::CaseInsensitive))) {
|
||||
episode.set_url(url);
|
||||
}
|
||||
Utilities::ConsumeCurrentElement(reader);
|
||||
} else if (name == "author" && lower_namespace == kItunesNamespace) {
|
||||
|
|
|
@ -67,7 +67,7 @@ PodcastService::PodcastService(Application* app, InternetModel* parent)
|
|||
proxy_(new PodcastSortProxyModel(this)),
|
||||
context_menu_(nullptr),
|
||||
root_(nullptr),
|
||||
organise_dialog_(new OrganiseDialog(app_->task_manager(), nullptr)) {
|
||||
organise_dialog_(new OrganiseDialog(app_->task_manager())) {
|
||||
icon_loader_->SetModel(model_);
|
||||
proxy_->setSourceModel(model_);
|
||||
proxy_->setDynamicSortFilter(true);
|
||||
|
@ -128,7 +128,7 @@ bool PodcastSortProxyModel::lessThan(const QModelIndex& left,
|
|||
}
|
||||
|
||||
QStandardItem* PodcastService::CreateRootItem() {
|
||||
root_ = new QStandardItem(IconLoader::Load("podcast", IconLoader::Provider),
|
||||
root_ = new QStandardItem(IconLoader::Load("podcast", IconLoader::Provider),
|
||||
tr("Podcasts"));
|
||||
root_->setData(true, InternetModel::Role_CanLazyLoad);
|
||||
return root_;
|
||||
|
@ -414,7 +414,7 @@ QStandardItem* PodcastService::CreatePodcastEpisodeItem(
|
|||
void PodcastService::ShowContextMenu(const QPoint& global_pos) {
|
||||
if (!context_menu_) {
|
||||
context_menu_ = new QMenu;
|
||||
context_menu_->addAction(IconLoader::Load("list-add", IconLoader::Base),
|
||||
context_menu_->addAction(IconLoader::Load("list-add", IconLoader::Base),
|
||||
tr("Add podcast..."), this, SLOT(AddPodcast()));
|
||||
context_menu_->addAction(IconLoader::Load("view-refresh", IconLoader::Base),
|
||||
tr("Update all podcasts"), app_->podcast_updater(),
|
||||
|
@ -425,23 +425,23 @@ void PodcastService::ShowContextMenu(const QPoint& global_pos) {
|
|||
|
||||
context_menu_->addSeparator();
|
||||
update_selected_action_ = context_menu_->addAction(
|
||||
IconLoader::Load("view-refresh", IconLoader::Base),
|
||||
IconLoader::Load("view-refresh", IconLoader::Base),
|
||||
tr("Update this podcast"), this, SLOT(UpdateSelectedPodcast()));
|
||||
download_selected_action_ =
|
||||
context_menu_->addAction(IconLoader::Load("download", IconLoader::Base),
|
||||
context_menu_->addAction(IconLoader::Load("download", IconLoader::Base),
|
||||
"", this, SLOT(DownloadSelectedEpisode()));
|
||||
delete_downloaded_action_ = context_menu_->addAction(
|
||||
IconLoader::Load("edit-delete", IconLoader::Base),
|
||||
IconLoader::Load("edit-delete", IconLoader::Base),
|
||||
tr("Delete downloaded data"), this, SLOT(DeleteDownloadedData()));
|
||||
copy_to_device_ = context_menu_->addAction(
|
||||
IconLoader::Load("multimedia-player-ipod-mini-blue", IconLoader::Base),
|
||||
tr("Copy to device..."), this, SLOT(CopyToDevice()));
|
||||
cancel_download_ = context_menu_->addAction(IconLoader::Load("cancel",
|
||||
cancel_download_ = context_menu_->addAction(IconLoader::Load("cancel",
|
||||
IconLoader::Base),
|
||||
tr("Cancel download"), this,
|
||||
SLOT(CancelDownload()));
|
||||
remove_selected_action_ = context_menu_->addAction(
|
||||
IconLoader::Load("list-remove", IconLoader::Base), tr("Unsubscribe"),
|
||||
IconLoader::Load("list-remove", IconLoader::Base), tr("Unsubscribe"),
|
||||
this, SLOT(RemoveSelectedPodcast()));
|
||||
|
||||
context_menu_->addSeparator();
|
||||
|
|
|
@ -42,6 +42,11 @@
|
|||
#ifdef HAVE_CRYPTOPP
|
||||
#include <cryptopp/pkcspad.h>
|
||||
#include <cryptopp/rsa.h>
|
||||
|
||||
// Compatibility with cryptocpp >= 6.0.0
|
||||
namespace CryptoPP {
|
||||
typedef unsigned char byte;
|
||||
}
|
||||
#endif // HAVE_CRYPTOPP
|
||||
|
||||
const char* SpotifyBlobDownloader::kSignatureSuffix = ".sha512";
|
||||
|
@ -189,7 +194,7 @@ bool SpotifyBlobDownloader::CheckSignature(
|
|||
|
||||
try {
|
||||
CryptoPP::ByteQueue bytes;
|
||||
bytes.Put(reinterpret_cast<const byte*>(public_key_data.constData()),
|
||||
bytes.Put(reinterpret_cast<const CryptoPP::byte*>(public_key_data.constData()),
|
||||
public_key_data.size());
|
||||
bytes.MessageEnd();
|
||||
|
||||
|
@ -204,9 +209,9 @@ bool SpotifyBlobDownloader::CheckSignature(
|
|||
actual_filename.remove(kSignatureSuffix);
|
||||
|
||||
const bool result = verifier.VerifyMessage(
|
||||
reinterpret_cast<const byte*>(file_data[actual_filename].constData()),
|
||||
reinterpret_cast<const CryptoPP::byte*>(file_data[actual_filename].constData()),
|
||||
file_data[actual_filename].size(),
|
||||
reinterpret_cast<const byte*>(
|
||||
reinterpret_cast<const CryptoPP::byte*>(
|
||||
file_data[signature_filename].constData()),
|
||||
file_data[signature_filename].size());
|
||||
qLog(Debug) << "Verifying" << actual_filename << "against"
|
||||
|
|
|
@ -220,6 +220,11 @@ void SubsonicDynamicPlaylist::GetAlbum(SubsonicService* service,
|
|||
length *= kNsecPerSec;
|
||||
song.set_length_nanosec(length);
|
||||
QUrl url = QUrl(QString("subsonic://%1").arg(id));
|
||||
QUrl cover_url = service->BuildRequestUrl("getCoverArt");
|
||||
QUrlQuery cover_url_query(cover_url.query());
|
||||
cover_url_query.addQueryItem("id", id);
|
||||
cover_url.setQuery(cover_url_query);
|
||||
song.set_art_automatic(cover_url.toEncoded());
|
||||
song.set_url(url);
|
||||
song.set_filesize(reader.attributes().value("size").toString().toInt());
|
||||
QFileInfo fi(reader.attributes().value("path").toString());
|
||||
|
|
|
@ -542,6 +542,11 @@ void SubsonicLibraryScanner::OnGetAlbumFinished(QNetworkReply* reply) {
|
|||
length *= kNsecPerSec;
|
||||
song.set_length_nanosec(length);
|
||||
QUrl url = QUrl(QString("subsonic://%1").arg(id));
|
||||
QUrl cover_url = service_->BuildRequestUrl("getCoverArt");
|
||||
QUrlQuery cover_url_query(url.query());
|
||||
cover_url_query.addQueryItem("id", id);
|
||||
cover_url.setQuery(cover_url_query);
|
||||
song.set_art_automatic(cover_url.toEncoded());
|
||||
song.set_url(url);
|
||||
song.set_filesize(reader.attributes().value("size").toString().toInt());
|
||||
// We need to set these to satisfy the database constraints
|
||||
|
|
|
@ -290,6 +290,16 @@ SongList LibraryBackend::FindSongsInDirectory(int id) {
|
|||
return ret;
|
||||
}
|
||||
|
||||
void LibraryBackend::SongPathChanged(const Song& song,
|
||||
const QFileInfo& new_file) {
|
||||
// Take a song and update its path
|
||||
Song updated_song = song;
|
||||
updated_song.InitFromFilePartial(new_file.absoluteFilePath());
|
||||
SongList updated_songs;
|
||||
updated_songs << updated_song;
|
||||
AddOrUpdateSongs(updated_songs);
|
||||
}
|
||||
|
||||
void LibraryBackend::AddOrUpdateSubdirs(const SubdirectoryList& subdirs) {
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <QObject>
|
||||
#include <QSet>
|
||||
#include <QUrl>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "directory.h"
|
||||
#include "libraryquery.h"
|
||||
|
@ -218,6 +219,8 @@ class LibraryBackend : public LibraryBackendInterface {
|
|||
void ResetStatistics(int id);
|
||||
void UpdateSongRating(int id, float rating);
|
||||
void UpdateSongsRating(const QList<int>& id_list, float rating);
|
||||
// Tells the library model that a song path has changed
|
||||
void SongPathChanged(const Song& song, const QFileInfo& new_file);
|
||||
|
||||
signals:
|
||||
void DirectoryDiscovered(const Directory& dir,
|
||||
|
|
|
@ -109,7 +109,7 @@ LibraryModel::LibraryModel(LibraryBackend* backend, Application* app,
|
|||
|
||||
QIcon nocover = IconLoader::Load("nocover", IconLoader::Other);
|
||||
no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(
|
||||
kPrettyCoverSize, kPrettyCoverSize,
|
||||
kPrettyCoverSize, kPrettyCoverSize,
|
||||
Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
|
||||
|
@ -657,6 +657,9 @@ QVariant LibraryModel::data(const LibraryItem* item, int role) const {
|
|||
|
||||
case Role_SortText:
|
||||
return item->SortText();
|
||||
|
||||
case Role_DisplayText:
|
||||
return item->DisplayText();
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ class LibraryModel : public SimpleTreeModel<LibraryItem> {
|
|||
Role_Type = Qt::UserRole + 1,
|
||||
Role_ContainerType,
|
||||
Role_SortText,
|
||||
Role_DisplayText,
|
||||
Role_Key,
|
||||
Role_Artist,
|
||||
Role_IsDivider,
|
||||
|
|
|
@ -103,7 +103,7 @@ void LibraryItemDelegate::paint(QPainter* painter,
|
|||
// Draw the line under the item
|
||||
QColor line_color = opt.palette.color(QPalette::Text);
|
||||
QLinearGradient grad_color(opt.rect.bottomLeft(), opt.rect.bottomRight());
|
||||
const double fade_start_end = (opt.rect.width()/3.0)/opt.rect.width();
|
||||
const double fade_start_end = (opt.rect.width() / 3.0) / opt.rect.width();
|
||||
line_color.setAlphaF(0.0);
|
||||
grad_color.setColorAt(0, line_color);
|
||||
line_color.setAlphaF(0.5);
|
||||
|
@ -175,7 +175,7 @@ LibraryView::LibraryView(QWidget* parent)
|
|||
total_song_count_(-1),
|
||||
context_menu_(nullptr),
|
||||
is_in_keyboard_search_(false) {
|
||||
QIcon nocover = IconLoader::Load("nocover", IconLoader::Other);
|
||||
QIcon nocover = IconLoader::Load("nocover", IconLoader::Other);
|
||||
nomusic_ = nocover.pixmap(nocover.availableSizes().last());
|
||||
setItemDelegate(new LibraryItemDelegate(this));
|
||||
setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
|
@ -193,15 +193,17 @@ LibraryView::~LibraryView() {}
|
|||
void LibraryView::SaveFocus() {
|
||||
QModelIndex current = currentIndex();
|
||||
QVariant type = model()->data(current, LibraryModel::Role_Type);
|
||||
if (!type.isValid() || !(type.toInt() == LibraryItem::Type_Song ||
|
||||
type.toInt() == LibraryItem::Type_Container ||
|
||||
type.toInt() == LibraryItem::Type_Divider)) {
|
||||
if (!type.isValid() ||
|
||||
!(type.toInt() == LibraryItem::Type_Song ||
|
||||
type.toInt() == LibraryItem::Type_Container ||
|
||||
type.toInt() == LibraryItem::Type_Divider)) {
|
||||
return;
|
||||
}
|
||||
|
||||
last_selected_path_.clear();
|
||||
last_selected_song_ = Song();
|
||||
last_selected_container_ = QString();
|
||||
last_selected_text_ = QString();
|
||||
|
||||
switch (type.toInt()) {
|
||||
case LibraryItem::Type_Song: {
|
||||
|
@ -210,6 +212,7 @@ void LibraryView::SaveFocus() {
|
|||
SongList songs = app_->library_model()->GetChildSongs(index);
|
||||
if (!songs.isEmpty()) {
|
||||
last_selected_song_ = songs.last();
|
||||
last_selected_text_ = songs.last().title();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -217,8 +220,9 @@ void LibraryView::SaveFocus() {
|
|||
case LibraryItem::Type_Container:
|
||||
case LibraryItem::Type_Divider: {
|
||||
QString text =
|
||||
model()->data(current, LibraryModel::Role_SortText).toString();
|
||||
model()->data(current, LibraryModel::Role_Key).toString();
|
||||
last_selected_container_ = text;
|
||||
last_selected_text_ = model()->data(current, LibraryModel::Role_DisplayText).toString();
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -232,8 +236,9 @@ void LibraryView::SaveFocus() {
|
|||
void LibraryView::SaveContainerPath(const QModelIndex& child) {
|
||||
QModelIndex current = model()->parent(child);
|
||||
QVariant type = model()->data(current, LibraryModel::Role_Type);
|
||||
if (!type.isValid() || !(type.toInt() == LibraryItem::Type_Container ||
|
||||
type.toInt() == LibraryItem::Type_Divider)) {
|
||||
if (!type.isValid() ||
|
||||
!(type.toInt() == LibraryItem::Type_Container ||
|
||||
type.toInt() == LibraryItem::Type_Divider)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -276,7 +281,7 @@ bool LibraryView::RestoreLevelFocus(const QModelIndex& parent) {
|
|||
case LibraryItem::Type_Container:
|
||||
case LibraryItem::Type_Divider: {
|
||||
QString text =
|
||||
model()->data(current, LibraryModel::Role_SortText).toString();
|
||||
model()->data(current, LibraryModel::Role_Key).toString();
|
||||
if (!last_selected_container_.isEmpty() &&
|
||||
last_selected_container_ == text) {
|
||||
emit expand(current);
|
||||
|
@ -380,46 +385,48 @@ void LibraryView::contextMenuEvent(QContextMenuEvent* e) {
|
|||
IconLoader::Load("media-playback-start", IconLoader::Base),
|
||||
tr("Replace current playlist"), this, SLOT(Load()));
|
||||
open_in_new_playlist_ = context_menu_->addAction(
|
||||
IconLoader::Load("document-new", IconLoader::Base),
|
||||
IconLoader::Load("document-new", IconLoader::Base),
|
||||
tr("Open in new playlist"), this, SLOT(OpenInNewPlaylist()));
|
||||
|
||||
context_menu_->addSeparator();
|
||||
add_to_playlist_enqueue_ =
|
||||
context_menu_->addAction(IconLoader::Load("go-next", IconLoader::Base),
|
||||
tr("Queue track"), this,
|
||||
SLOT(AddToPlaylistEnqueue()));
|
||||
|
||||
add_to_playlist_enqueue_ = context_menu_->addAction(
|
||||
IconLoader::Load("go-next", IconLoader::Base), tr("Queue track"), this,
|
||||
SLOT(AddToPlaylistEnqueue()));
|
||||
context_menu_->addSeparator();
|
||||
search_for_this_ = context_menu_->addAction(
|
||||
IconLoader::Load("system-search", IconLoader::Base),
|
||||
tr("Search for this"), this, SLOT(SearchForThis()));
|
||||
context_menu_->addSeparator();
|
||||
new_smart_playlist_ = context_menu_->addAction(
|
||||
IconLoader::Load("document-new", IconLoader::Base),
|
||||
IconLoader::Load("document-new", IconLoader::Base),
|
||||
tr("New smart playlist..."), this, SLOT(NewSmartPlaylist()));
|
||||
edit_smart_playlist_ = context_menu_->addAction(
|
||||
IconLoader::Load("edit-rename", IconLoader::Base),
|
||||
IconLoader::Load("edit-rename", IconLoader::Base),
|
||||
tr("Edit smart playlist..."), this, SLOT(EditSmartPlaylist()));
|
||||
delete_smart_playlist_ = context_menu_->addAction(
|
||||
IconLoader::Load("edit-delete", IconLoader::Base),
|
||||
IconLoader::Load("edit-delete", IconLoader::Base),
|
||||
tr("Delete smart playlist"), this, SLOT(DeleteSmartPlaylist()));
|
||||
|
||||
context_menu_->addSeparator();
|
||||
organise_ = context_menu_->addAction(IconLoader::Load("edit-copy", IconLoader::Base),
|
||||
tr("Organise files..."), this,
|
||||
SLOT(Organise()));
|
||||
organise_ = context_menu_->addAction(
|
||||
IconLoader::Load("edit-copy", IconLoader::Base),
|
||||
tr("Organise files..."), this, SLOT(Organise()));
|
||||
copy_to_device_ = context_menu_->addAction(
|
||||
IconLoader::Load("multimedia-player-ipod-mini-blue", IconLoader::Base),
|
||||
tr("Copy to device..."), this, SLOT(CopyToDevice()));
|
||||
delete_ = context_menu_->addAction(IconLoader::Load("edit-delete", IconLoader::Base),
|
||||
tr("Delete from disk..."), this,
|
||||
SLOT(Delete()));
|
||||
delete_ = context_menu_->addAction(
|
||||
IconLoader::Load("edit-delete", IconLoader::Base),
|
||||
tr("Delete from disk..."), this, SLOT(Delete()));
|
||||
|
||||
context_menu_->addSeparator();
|
||||
edit_track_ = context_menu_->addAction(IconLoader::Load("edit-rename", IconLoader::Base),
|
||||
tr("Edit track information..."),
|
||||
this, SLOT(EditTracks()));
|
||||
edit_tracks_ = context_menu_->addAction(IconLoader::Load("edit-rename", IconLoader::Base),
|
||||
tr("Edit tracks information..."),
|
||||
this, SLOT(EditTracks()));
|
||||
edit_track_ = context_menu_->addAction(
|
||||
IconLoader::Load("edit-rename", IconLoader::Base),
|
||||
tr("Edit track information..."), this, SLOT(EditTracks()));
|
||||
edit_tracks_ = context_menu_->addAction(
|
||||
IconLoader::Load("edit-rename", IconLoader::Base),
|
||||
tr("Edit tracks information..."), this, SLOT(EditTracks()));
|
||||
show_in_browser_ = context_menu_->addAction(
|
||||
IconLoader::Load("document-open-folder", IconLoader::Base),
|
||||
IconLoader::Load("document-open-folder", IconLoader::Base),
|
||||
tr("Show in file browser..."), this, SLOT(ShowInBrowser()));
|
||||
|
||||
context_menu_->addSeparator();
|
||||
|
@ -458,6 +465,8 @@ void LibraryView::contextMenuEvent(QContextMenuEvent* e) {
|
|||
int regular_elements = 0;
|
||||
// number of editable non smart playlists selected
|
||||
int regular_editable = 0;
|
||||
// number of container elements selected
|
||||
int container_elements = 0;
|
||||
|
||||
for (const QModelIndex& index : selected_indexes) {
|
||||
int type =
|
||||
|
@ -467,6 +476,10 @@ void LibraryView::contextMenuEvent(QContextMenuEvent* e) {
|
|||
smart_playlists++;
|
||||
} else if (type == LibraryItem::Type_PlaylistContainer) {
|
||||
smart_playlists_header++;
|
||||
} else if (type == LibraryItem::Type_Container) {
|
||||
container_elements++;
|
||||
// To preserve expected behavior, since a container is "regular"
|
||||
regular_elements++;
|
||||
} else {
|
||||
regular_elements++;
|
||||
}
|
||||
|
@ -487,6 +500,10 @@ void LibraryView::contextMenuEvent(QContextMenuEvent* e) {
|
|||
songs_selected == smart_playlists + smart_playlists_header;
|
||||
const bool only_smart_playlist_selected =
|
||||
smart_playlists == 1 && songs_selected == 1;
|
||||
const bool one_regular_song_only =
|
||||
regular_elements_only && container_elements == 0 && regular_elements == 1;
|
||||
const bool one_container_only =
|
||||
container_elements == 1 && songs_selected == 1;
|
||||
|
||||
// in all modes
|
||||
load_->setEnabled(songs_selected);
|
||||
|
@ -509,6 +526,9 @@ void LibraryView::contextMenuEvent(QContextMenuEvent* e) {
|
|||
show_in_various_->setVisible(regular_elements_only);
|
||||
no_show_in_various_->setVisible(regular_elements_only);
|
||||
|
||||
// only when a single container or one song is selected exclusively
|
||||
search_for_this_->setVisible(one_container_only || one_regular_song_only);
|
||||
|
||||
// only when all selected items are editable
|
||||
organise_->setEnabled(regular_elements == regular_editable);
|
||||
copy_to_device_->setEnabled(regular_elements == regular_editable);
|
||||
|
@ -617,6 +637,7 @@ void LibraryView::scrollTo(const QModelIndex& index, ScrollHint hint) {
|
|||
QTreeView::scrollTo(index, hint);
|
||||
}
|
||||
|
||||
// get selected songs
|
||||
SongList LibraryView::GetSelectedSongs() const {
|
||||
QModelIndexList selected_indexes =
|
||||
qobject_cast<QSortFilterProxyModel*>(model())
|
||||
|
@ -627,7 +648,8 @@ SongList LibraryView::GetSelectedSongs() const {
|
|||
|
||||
void LibraryView::Organise() {
|
||||
if (!organise_dialog_)
|
||||
organise_dialog_.reset(new OrganiseDialog(app_->task_manager()));
|
||||
organise_dialog_.reset(new OrganiseDialog(app_->task_manager(),
|
||||
app_->library_backend()));
|
||||
|
||||
organise_dialog_->SetDestinationModel(
|
||||
app_->library_model()->directory_model());
|
||||
|
@ -675,6 +697,10 @@ void LibraryView::EditTracks() {
|
|||
|
||||
void LibraryView::CopyToDevice() {
|
||||
if (!organise_dialog_)
|
||||
// Don't notify song has been replaced if copying to device, so
|
||||
// don't associate the organise dialog with the library backend.
|
||||
// Could improve this behavior if the device has a separate set of saved
|
||||
// playlists that are somehow in sync with the library.
|
||||
organise_dialog_.reset(new OrganiseDialog(app_->task_manager()));
|
||||
|
||||
organise_dialog_->SetDestinationModel(
|
||||
|
@ -709,6 +735,13 @@ void LibraryView::FilterReturnPressed() {
|
|||
emit doubleClicked(currentIndex());
|
||||
}
|
||||
|
||||
void LibraryView::SearchForThis() {
|
||||
SaveFocus();
|
||||
if (!last_selected_text_.isEmpty()) {
|
||||
filter_->ShowInLibrary(last_selected_text_.simplified());
|
||||
}
|
||||
}
|
||||
|
||||
void LibraryView::NewSmartPlaylist() {
|
||||
Wizard* wizard = new Wizard(app_, app_->library_backend(), this);
|
||||
wizard->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
|
|
@ -101,6 +101,8 @@ signals:
|
|||
void ShowInVarious();
|
||||
void NoShowInVarious();
|
||||
|
||||
void SearchForThis();
|
||||
|
||||
void NewSmartPlaylist();
|
||||
void EditSmartPlaylist();
|
||||
void DeleteSmartPlaylist();
|
||||
|
@ -139,6 +141,8 @@ signals:
|
|||
QAction* show_in_various_;
|
||||
QAction* no_show_in_various_;
|
||||
|
||||
QAction* search_for_this_;
|
||||
|
||||
QAction* new_smart_playlist_;
|
||||
QAction* edit_smart_playlist_;
|
||||
QAction* delete_smart_playlist_;
|
||||
|
@ -151,6 +155,7 @@ signals:
|
|||
// Save focus
|
||||
Song last_selected_song_;
|
||||
QString last_selected_container_;
|
||||
QString last_selected_text_;
|
||||
QSet<QString> last_selected_path_;
|
||||
};
|
||||
|
||||
|
|
|
@ -247,7 +247,7 @@ int main(int argc, char* argv[]) {
|
|||
|
||||
if (QSysInfo::MacintoshVersion > QSysInfo::MV_10_8) {
|
||||
// Work around 10.9 issue.
|
||||
// https://bugreports.qt-project.org/browse/QTBUG-32789
|
||||
// https://bugreports.qt.io/browse/QTBUG-32789
|
||||
QFont::insertSubstitution(".Lucida Grande UI", "Lucida Grande");
|
||||
}
|
||||
#endif
|
||||
|
@ -292,7 +292,10 @@ int main(int argc, char* argv[]) {
|
|||
qLog(Info)
|
||||
<< "Clementine is already running - activating existing window";
|
||||
}
|
||||
if (a.sendMessage(options.Serialize(), 5000)) {
|
||||
|
||||
QByteArray serializedOptions = options.Serialize();
|
||||
if (a.sendMessage(serializedOptions, 5000)) {
|
||||
qLog(Info) << "Options found, sent message to running instance";
|
||||
return 0;
|
||||
}
|
||||
// Couldn't send the message so start anyway
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "playlistmanager.h"
|
||||
#include "ui_playlistcontainer.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/appearance.h"
|
||||
#include "playlistparsers/playlistparser.h"
|
||||
#include "ui/iconloader.h"
|
||||
|
||||
|
@ -71,6 +72,11 @@ PlaylistContainer::PlaylistContainer(QWidget* parent)
|
|||
// Remove QFrame border
|
||||
ui_->toolbar->setStyleSheet("QFrame { border: 0px; }");
|
||||
|
||||
QSettings settings;
|
||||
settings.beginGroup(Appearance::kSettingsGroup);
|
||||
bool hide_toolbar = settings.value("b_hide_filter_toolbar", false).toBool();
|
||||
ui_->toolbar->setVisible(!hide_toolbar);
|
||||
|
||||
// Make it bold
|
||||
QFont no_matches_font = no_matches_label_->font();
|
||||
no_matches_font.setBold(true);
|
||||
|
@ -452,3 +458,10 @@ bool PlaylistContainer::eventFilter(QObject* objectWatched, QEvent* event) {
|
|||
}
|
||||
return QWidget::eventFilter(objectWatched, event);
|
||||
}
|
||||
|
||||
void PlaylistContainer::ReloadSettings() {
|
||||
QSettings settings;
|
||||
settings.beginGroup(Appearance::kSettingsGroup);
|
||||
bool hide_toolbar = settings.value("b_hide_filter_toolbar", false).toBool();
|
||||
ui_->toolbar->setVisible(!hide_toolbar);
|
||||
}
|
||||
|
|
|
@ -61,6 +61,9 @@ signals:
|
|||
// QWidget
|
||||
void resizeEvent(QResizeEvent*);
|
||||
|
||||
public slots:
|
||||
void ReloadSettings();
|
||||
|
||||
private slots:
|
||||
void NewPlaylist();
|
||||
void LoadPlaylist();
|
||||
|
|
|
@ -617,9 +617,6 @@ void PlaylistView::keyPressEvent(QKeyEvent* event) {
|
|||
event->key() == Qt::Key_Space) {
|
||||
emit PlayPause();
|
||||
event->accept();
|
||||
} else if (event->key() == Qt::Key_Up) {
|
||||
app_->player()->SeekTo(0);
|
||||
event->accept();
|
||||
} else if (event->key() == Qt::Key_Left) {
|
||||
emit SeekBackward();
|
||||
event->accept();
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
</size>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Up</string>
|
||||
<string>Ctrl+↑</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -90,7 +90,7 @@
|
|||
</size>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Down</string>
|
||||
<string>Ctrl+↓</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
|
|
|
@ -230,7 +230,7 @@ QList<TranscoderPreset> Transcoder::GetAllPresets() {
|
|||
TranscoderPreset Transcoder::PresetForFileType(Song::FileType type) {
|
||||
switch (type) {
|
||||
case Song::Type_Flac:
|
||||
return TranscoderPreset(type, tr("Flac"), "flac", "audio/x-flac");
|
||||
return TranscoderPreset(type, tr("FLAC"), "flac", "audio/x-flac");
|
||||
case Song::Type_Mp4:
|
||||
return TranscoderPreset(type, tr("M4A AAC"), "mp4",
|
||||
"audio/mpeg, mpegversion=(int)4", "audio/mp4");
|
||||
|
|
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
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue