Fixed merge conflicts

This commit is contained in:
Jonas Kvinge 2018-09-30 15:11:06 +02:00
commit c29c2e95cc
99 changed files with 28878 additions and 24752 deletions

0
.gcloudignore Normal file
View File

View File

@ -1,22 +0,0 @@
cmake_minimum_required(VERSION 2.8.11)
set(SOURCES
fancytabwidget.cpp
stylehelper.cpp
)
set(HEADERS
fancytabwidget.h
)
qt5_wrap_cpp(MOC ${HEADERS})
include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
add_library(fancytabwidget STATIC
${SOURCES}
${MOC}
)
target_link_libraries(fancytabwidget
${QT_LIBRARIES}
)

3
debian/copyright vendored
View File

@ -22,8 +22,7 @@ Copyright: 2003, Mark Kretschmann <markey@web.de>
2005, Gábor Lehel <illissius@gmail.com> 2005, Gábor Lehel <illissius@gmail.com>
License: GPL-2+ License: GPL-2+
Files: src/widgets/fancytabwidget.* Files: src/widgets/stylehelper.*
src/widgets/stylehelper.*
Copyright: 2010, Nokia Corporation Copyright: 2010, Nokia Corporation
License: Qt Commercial or LGPL-2.1 License: Qt Commercial or LGPL-2.1

72
dist/translations/cloudbuild-pull.yaml vendored Normal file
View File

@ -0,0 +1,72 @@
steps:
- name: 'gcr.io/cloud-builders/gcloud'
args:
- kms
- decrypt
- --ciphertext-file=dist/translations/id_rsa.enc
- --plaintext-file=/root/.ssh/id_rsa
- --location=global
- --keyring=translations
- --key=transifex
volumes:
- name: 'ssh'
path: /root/.ssh
- name: 'gcr.io/cloud-builders/git'
entrypoint: 'bash'
args:
- -c
- |
chmod 600 /root/.ssh/id_rsa
cat <<EOF >/root/.ssh/config
Hostname github.com
IdentityFile /root/.ssh/id_rsa
EOF
mv dist/translations/known_hosts /root/.ssh/known_hosts
volumes:
- name: 'ssh'
path: /root/.ssh
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'transifex', '.']
dir: 'dist/translations/transifex'
- name: 'transifex'
secretEnv: ['TX_TOKEN']
args: ['init', '--no-interactive', '--force']
- name: 'transifex'
secretEnv: ['TX_TOKEN']
args: ['config', 'mapping', '--execute', '-r', 'clementine.clementineplayer', '-s', 'en', '-t', 'PO', 'src/translations/<lang>.po']
- name: 'transifex'
secretEnv: ['TX_TOKEN']
args: ['pull', '--all', '-f', '--no-interactive']
- name: 'gcr.io/cloud-builders/git'
entrypoint: 'bash'
args:
- -c
- |
git add src/translations/*.po
- name: 'gcr.io/cloud-builders/git'
args:
- -c
- user.name=Clementine Buildbot
- -c
- user.email=buildbot@clementine-player.org
- commit
- --message
- Automatic merge of translations from Transifex (https://www.transifex.com/projects/p/clementine/resource/clementineplayer)
- name: 'gcr.io/cloud-builders/git'
args: ['push', 'git@github.com:clementine-player/Clementine.git', 'master']
volumes:
- name: 'ssh'
path: /root/.ssh
secrets:
- kmsKeyName: projects/clementine-data/locations/global/keyRings/translations/cryptoKeys/transifex
secretEnv:
TX_TOKEN: CiQAmOiGiwceV26v7vX/yvQQXkMJ7+zwH9Y2zy+B4FtwM1iVdj8SVAD+AEzLJXJ6d+hGZlJPYjbbxL6/wiOhQIZLc+yvFznLSIn6dtCAhFecNqYX+cj+nxuZ/uHR9p72kj7PPsqy54WkWRvbG1Xl4CQX67wy3cqnlRHsqQ==

32
dist/translations/cloudbuild-push.yaml vendored Normal file
View File

@ -0,0 +1,32 @@
steps:
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'transifex', '.']
dir: 'dist/translations/transifex'
- name: 'transifex'
secretEnv: ['TX_TOKEN']
args: ['init', '--no-interactive', '--force']
- name: 'gcr.io/clementine-data/slave-ubuntu-zesty-64'
entrypoint: 'cmake'
dir: 'bin'
args: ['..']
- name: 'gcr.io/clementine-data/slave-ubuntu-zesty-64'
entrypoint: 'make'
dir: 'bin'
- name: 'transifex'
secretEnv: ['TX_TOKEN']
args: ['config', 'mapping', '--execute', '-r', 'clementine.clementineplayer', '-f', 'src/translations/translations.pot', '-s', 'en', '-t', 'PO', 'src/translations/<lang>.po']
- name: 'transifex'
secretEnv: ['TX_TOKEN']
args: ['push', '-s']
secrets:
- kmsKeyName: projects/clementine-data/locations/global/keyRings/translations/cryptoKeys/transifex
secretEnv:
TX_TOKEN: CiQAmOiGiwceV26v7vX/yvQQXkMJ7+zwH9Y2zy+B4FtwM1iVdj8SVAD+AEzLJXJ6d+hGZlJPYjbbxL6/wiOhQIZLc+yvFznLSIn6dtCAhFecNqYX+cj+nxuZ/uHR9p72kj7PPsqy54WkWRvbG1Xl4CQX67wy3cqnlRHsqQ==
timeout: 1200s

BIN
dist/translations/id_rsa.enc vendored Normal file

Binary file not shown.

1
dist/translations/id_rsa.pub vendored Normal file
View File

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmdCNCDDSWyj9aw9CkAeBb8dtsO4d7qhK15Pv8WCMCJ8p7iErJC9OVE649mbgZKMW42+5D9xkZI0Sh/v8og+Khvxpv53REdW5T5VHSPejXZlA0+VDZYr5ubaUn2bQlFsu2KkMmCOY+gC2QNTDi83FDdcYOxnpr4XoGLUgCGDAiRxVcEV22EsT/5FQRjE2D4te6pUOMYZMrVoZx/GVHoy/Pf1DuhAsPEtrQBH3o56GaGZ7dVNRjPkuJDjQjCnAkar4fmMMKKX+nw+rzlWE7248uaRJCApn/+LAxKuB+Ol3Wtuye/FQFvO7S7aBRFoqWRwRmjL0hptAQmhVf7/wwKZbT john@zem

1
dist/translations/known_hosts vendored Normal file
View File

@ -0,0 +1 @@
github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==

View File

@ -0,0 +1,3 @@
FROM alpine
RUN apk --no-cache add py-pip && pip install transifex-client
ENTRYPOINT ["tx"]

View File

@ -6,6 +6,7 @@
#include <apefile.h> #include <apefile.h>
#include <tag.h> #include <tag.h>
#include <QByteArray> #include <QByteArray>
#include <QChar>
#include <QFile> #include <QFile>
#include <QFileInfo> #include <QFileInfo>
#include <QString> #include <QString>
@ -191,7 +192,7 @@ void GME::VGM::Read(const QFileInfo& file_info,
QTextStream fileTagStream(gd3Data, QIODevice::ReadOnly); QTextStream fileTagStream(gd3Data, QIODevice::ReadOnly);
// Stored as 16 bit UTF string, two bytes per letter. // Stored as 16 bit UTF string, two bytes per letter.
fileTagStream.setCodec("UTF-16"); fileTagStream.setCodec("UTF-16");
QStringList strings = fileTagStream.readLine(0).split('\0'); QStringList strings = fileTagStream.readLine(0).split(QChar('\0'));
if (strings.count() < 10) return; if (strings.count() < 10) return;
/* VGM standard dictates string tag data exist in specific order. /* VGM standard dictates string tag data exist in specific order.

View File

@ -265,6 +265,22 @@ QSqlDatabase Database::Connect() {
StaticInit(); StaticInit();
{ {
#ifdef SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER
// In case sqlite>=3.12 is compiled without -DSQLITE_ENABLE_FTS3_TOKENIZER (generally a good idea
// due to security reasons) the fts3 support should be enabled explicitly.
// see https://github.com/clementine-player/Clementine/issues/5297
//
// See https://www.sqlite.org/fts3.html#custom_application_defined_tokenizers
QVariant v = db.driver()->handle();
if (v.isValid() && qstrcmp(v.typeName(), "sqlite3*") == 0) {
sqlite3* handle = *static_cast<sqlite3**>(v.data());
if (!handle || sqlite3_db_config(handle, SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, nullptr) != SQLITE_OK) {
qLog(Fatal) << "Failed to enable FTS3 tokenizer";
}
}
#endif
QSqlQuery set_fts_tokenizer(db); QSqlQuery set_fts_tokenizer(db);
set_fts_tokenizer.prepare("SELECT fts3_tokenizer(:name, :pointer)"); set_fts_tokenizer.prepare("SELECT fts3_tokenizer(:name, :pointer)");
set_fts_tokenizer.bindValue(":name", "unicode"); set_fts_tokenizer.bindValue(":name", "unicode");

View File

@ -131,6 +131,8 @@ void Mpris2::EngineStateChanged(Engine::State newState) {
EmitNotification("Metadata"); EmitNotification("Metadata");
} }
EmitNotification("CanPlay");
EmitNotification("CanPause");
EmitNotification("PlaybackStatus", PlaybackStatus(newState)); EmitNotification("PlaybackStatus", PlaybackStatus(newState));
if (newState == Engine::Playing) if (newState == Engine::Playing)
EmitNotification("CanSeek", CanSeek(newState)); EmitNotification("CanSeek", CanSeek(newState));
@ -181,6 +183,10 @@ void Mpris2::EmitNotification(const QString& name) {
value = CanGoPrevious(); value = CanGoPrevious();
else if (name == "CanSeek") else if (name == "CanSeek")
value = CanSeek(); value = CanSeek();
else if (name == "CanPlay")
value = CanPlay();
else if (name == "CanPause")
value = CanPause();
if (value.isValid()) EmitNotification(name, value); if (value.isValid()) EmitNotification(name, value);
} }
@ -328,6 +334,8 @@ QString Mpris2::current_track_id() const {
// changing song starts... // changing song starts...
void Mpris2::CurrentSongChanged(const Song& song) { void Mpris2::CurrentSongChanged(const Song& song) {
ArtLoaded(song, ""); ArtLoaded(song, "");
EmitNotification("CanPlay");
EmitNotification("CanPause");
EmitNotification("CanGoNext", CanGoNext()); EmitNotification("CanGoNext", CanGoNext());
EmitNotification("CanGoPrevious", CanGoPrevious()); EmitNotification("CanGoPrevious", CanGoPrevious());
EmitNotification("CanSeek", CanSeek()); EmitNotification("CanSeek", CanSeek());

View File

@ -118,6 +118,23 @@ const QStringList Song::kColumns = QStringList() << "title"
<< "originalyear" << "originalyear"
<< "effective_originalyear"; << "effective_originalyear";
const QStringList Song::kIntColumns = QStringList() << "track"
<< "disc"
<< "year"
<< "originalyear"
<< "playcount"
<< "skipcount"
<< "score"
<< "bitrate"
<< "samplerate";
const QStringList Song::kFloatColumns = QStringList() << "rating"
<< "bpm";
const QStringList Song::kDateColumns = QStringList() << "lastplayed"
<< "mtime"
<< "ctime";
const QString Song::kColumnSpec = Song::kColumns.join(", "); const QString Song::kColumnSpec = Song::kColumns.join(", ");
const QString Song::kBindSpec = const QString Song::kBindSpec =
Utilities::Prepend(":", Song::kColumns).join(", "); Utilities::Prepend(":", Song::kColumns).join(", ");

View File

@ -73,6 +73,10 @@ class Song {
static const QString kBindSpec; static const QString kBindSpec;
static const QString kUpdateSpec; static const QString kUpdateSpec;
static const QStringList kIntColumns;
static const QStringList kFloatColumns;
static const QStringList kDateColumns;
static const QStringList kFtsColumns; static const QStringList kFtsColumns;
static const QString kFtsColumnSpec; static const QString kFtsColumnSpec;
static const QString kFtsBindSpec; static const QString kFtsBindSpec;

View File

@ -15,6 +15,8 @@
along with Clementine. If not, see <http://www.gnu.org/licenses/>. along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <QtConcurrentRun>
#include <gst/gst.h> #include <gst/gst.h>
#include <gst/tag/tag.h> #include <gst/tag/tag.h>
@ -24,10 +26,10 @@
#include "cddasongloader.h" #include "cddasongloader.h"
CddaSongLoader::CddaSongLoader(const QUrl& url, QObject* parent) CddaSongLoader::CddaSongLoader(const QUrl& url, QObject* parent)
: QObject(parent), : QObject(parent), url_(url), cdda_(nullptr), cdio_(nullptr) {
url_(url), connect(this, SIGNAL(MusicBrainzDiscIdLoaded(const QString&)),
cdda_(nullptr), SLOT(LoadAudioCDTags(const QString&)));
cdio_(nullptr) {} }
CddaSongLoader::~CddaSongLoader() { CddaSongLoader::~CddaSongLoader() {
if (cdio_) cdio_destroy(cdio_); if (cdio_) cdio_destroy(cdio_);
@ -42,6 +44,9 @@ QUrl CddaSongLoader::GetUrlFromTrack(int track_number) const {
} }
void CddaSongLoader::LoadSongs() { void CddaSongLoader::LoadSongs() {
QtConcurrent::run(this, &CddaSongLoader::LoadSongsFromCdda);
}
void CddaSongLoader::LoadSongsFromCdda() {
QMutexLocker locker(&mutex_load_); QMutexLocker locker(&mutex_load_);
cdio_ = cdio_open(url_.path().toLocal8Bit().constData(), DRIVER_DEVICE); cdio_ = cdio_open(url_.path().toLocal8Bit().constData(), DRIVER_DEVICE);
if (cdio_ == nullptr) { if (cdio_ == nullptr) {
@ -101,7 +106,6 @@ void CddaSongLoader::LoadSongs() {
} }
emit SongsLoaded(songs); emit SongsLoaded(songs);
gst_tag_register_musicbrainz_tags(); gst_tag_register_musicbrainz_tags();
GstElement* pipeline = gst_pipeline_new("pipeline"); GstElement* pipeline = gst_pipeline_new("pipeline");
@ -115,8 +119,9 @@ void CddaSongLoader::LoadSongs() {
GstMessage* msg = nullptr; GstMessage* msg = nullptr;
GstMessage* msg_toc = nullptr; GstMessage* msg_toc = nullptr;
GstMessage* msg_tag = nullptr; GstMessage* msg_tag = nullptr;
while ((msg = gst_bus_timed_pop_filtered(GST_ELEMENT_BUS(pipeline), while ((msg = gst_bus_timed_pop_filtered(
GST_SECOND, (GstMessageType)(GST_MESSAGE_TOC | GST_MESSAGE_TAG)))) { GST_ELEMENT_BUS(pipeline), 2 * GST_SECOND,
(GstMessageType)(GST_MESSAGE_TOC | GST_MESSAGE_TAG)))) {
if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_TOC) { if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_TOC) {
if (msg_toc) gst_message_unref(msg_toc); // Shouldn't happen, but just in case if (msg_toc) gst_message_unref(msg_toc); // Shouldn't happen, but just in case
msg_toc = msg; msg_toc = msg;
@ -157,13 +162,8 @@ void CddaSongLoader::LoadSongs() {
&string_mb)) { &string_mb)) {
QString musicbrainz_discid(string_mb); QString musicbrainz_discid(string_mb);
qLog(Info) << "MusicBrainz discid: " << musicbrainz_discid; qLog(Info) << "MusicBrainz discid: " << musicbrainz_discid;
emit MusicBrainzDiscIdLoaded(musicbrainz_discid);
MusicBrainzClient* musicbrainz_client = new MusicBrainzClient;
connect(musicbrainz_client, SIGNAL(Finished(const QString&, const QString&,
MusicBrainzClient::ResultList)),
SLOT(AudioCDTagsLoaded(const QString&, const QString&,
MusicBrainzClient::ResultList)));
musicbrainz_client->StartDiscIdRequest(musicbrainz_discid);
g_free(string_mb); g_free(string_mb);
gst_message_unref(msg_tag); gst_message_unref(msg_tag);
gst_tag_list_free(tags); gst_tag_list_free(tags);
@ -175,6 +175,17 @@ void CddaSongLoader::LoadSongs() {
gst_object_unref(pipeline); gst_object_unref(pipeline);
} }
void CddaSongLoader::LoadAudioCDTags(const QString& musicbrainz_discid) const {
MusicBrainzClient* musicbrainz_client = new MusicBrainzClient;
connect(musicbrainz_client,
SIGNAL(Finished(const QString&, const QString&,
MusicBrainzClient::ResultList)),
SLOT(AudioCDTagsLoaded(const QString&, const QString&,
MusicBrainzClient::ResultList)));
musicbrainz_client->StartDiscIdRequest(musicbrainz_discid);
}
void CddaSongLoader::AudioCDTagsLoaded( void CddaSongLoader::AudioCDTagsLoaded(
const QString& artist, const QString& album, const QString& artist, const QString& album,
const MusicBrainzClient::ResultList& results) { const MusicBrainzClient::ResultList& results) {

View File

@ -50,13 +50,16 @@ class CddaSongLoader : public QObject {
void SongsLoaded(const SongList& songs); void SongsLoaded(const SongList& songs);
void SongsDurationLoaded(const SongList& songs); void SongsDurationLoaded(const SongList& songs);
void SongsMetadataLoaded(const SongList& songs); void SongsMetadataLoaded(const SongList& songs);
void MusicBrainzDiscIdLoaded(const QString& musicbrainz_discid);
private slots: private slots:
void LoadAudioCDTags(const QString& musicbrainz_discid) const;
void AudioCDTagsLoaded(const QString& artist, const QString& album, void AudioCDTagsLoaded(const QString& artist, const QString& album,
const MusicBrainzClient::ResultList& results); const MusicBrainzClient::ResultList& results);
private: private:
QUrl GetUrlFromTrack(int track_number) const; QUrl GetUrlFromTrack(int track_number) const;
void LoadSongsFromCdda();
QUrl url_; QUrl url_;
GstElement* cdda_; GstElement* cdda_;

View File

@ -255,6 +255,10 @@ bool GstEnginePipeline::Init() {
case QVariant::Int: case QVariant::Int:
g_object_set(G_OBJECT(audiosink_), "device", device_.toInt(), nullptr); g_object_set(G_OBJECT(audiosink_), "device", device_.toInt(), nullptr);
break; break;
case QVariant::LongLong:
g_object_set(G_OBJECT(audiosink_), "device", device_.toLongLong(),
nullptr);
break;
case QVariant::String: case QVariant::String:
g_object_set(G_OBJECT(audiosink_), "device", g_object_set(G_OBJECT(audiosink_), "device",
device_.toString().toUtf8().constData(), nullptr); device_.toString().toUtf8().constData(), nullptr);
@ -279,7 +283,7 @@ bool GstEnginePipeline::Init() {
audioconvert_ = engine_->CreateElement("audioconvert", audiobin_); audioconvert_ = engine_->CreateElement("audioconvert", audiobin_);
tee = engine_->CreateElement("tee", audiobin_); tee = engine_->CreateElement("tee", audiobin_);
probe_queue = engine_->CreateElement("queue", audiobin_); probe_queue = engine_->CreateElement("queue2", audiobin_);
probe_converter = engine_->CreateElement("audioconvert", audiobin_); probe_converter = engine_->CreateElement("audioconvert", audiobin_);
probe_sink = engine_->CreateElement("fakesink", audiobin_); probe_sink = engine_->CreateElement("fakesink", audiobin_);
@ -903,7 +907,8 @@ GstPadProbeReturn GstEnginePipeline::HandoffCallback(GstPad*,
if (instance->emit_track_ended_on_time_discontinuity_) { if (instance->emit_track_ended_on_time_discontinuity_) {
if (GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DISCONT) || if (GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DISCONT) ||
GST_BUFFER_OFFSET(buf) < instance->last_buffer_offset_) { GST_BUFFER_OFFSET(buf) < instance->last_buffer_offset_ ||
!GST_BUFFER_OFFSET_IS_VALID(buf)) {
qLog(Debug) << "Buffer discontinuity - emitting EOS"; qLog(Debug) << "Buffer discontinuity - emitting EOS";
instance->emit_track_ended_on_time_discontinuity_ = false; instance->emit_track_ended_on_time_discontinuity_ = false;
emit instance->EndOfStreamReached(instance->id(), true); emit instance->EndOfStreamReached(instance->id(), true);

View File

@ -107,6 +107,7 @@ QList<DeviceFinder::Device> OsxDeviceFinder::ListDevices() {
Device dev; Device dev;
dev.description = QString::fromUtf8( dev.description = QString::fromUtf8(
CFStringGetCStringPtr(*device_name, CFStringGetSystemEncoding())); CFStringGetCStringPtr(*device_name, CFStringGetSystemEncoding()));
if (dev.description.isEmpty()) dev.description = QString("Unknown device");
dev.device_property_value = id; dev.device_property_value = id;
dev.icon_name = GuessIconName(dev.description); dev.icon_name = GuessIconName(dev.description);
ret.append(dev); ret.append(dev);

View File

@ -45,8 +45,10 @@ LibraryFilterWidget::LibraryFilterWidget(QWidget* parent)
// Add the available fields to the tooltip here instead of the ui // Add the available fields to the tooltip here instead of the ui
// file to prevent that they get translated by mistake. // file to prevent that they get translated by mistake.
QString available_fields = QString available_fields = (Song::kFtsColumns + Song::kIntColumns +
Song::kFtsColumns.join(", ").replace(QRegExp("\\bfts"), ""); Song::kFloatColumns + Song::kDateColumns)
.join(", ")
.replace(QRegExp("\\bfts"), "");
ui_->filter->setToolTip(ui_->filter->toolTip().arg(available_fields)); ui_->filter->setToolTip(ui_->filter->toolTip().arg(available_fields));
connect(ui_->filter, SIGNAL(returnPressed()), SIGNAL(ReturnPressed())); connect(ui_->filter, SIGNAL(returnPressed()), SIGNAL(ReturnPressed()));

View File

@ -17,13 +17,22 @@
<property name="spacing"> <property name="spacing">
<number>0</number> <number>0</number>
</property> </property>
<property name="margin"> <property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item> <item>
<widget class="QSearchField" name="filter" native="true"> <widget class="QSearchField" name="filter" native="true">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Prefix a word with a field name to limit the search to that field, e.g. &lt;span style=&quot; font-weight:600;&quot;&gt;artist:&lt;/span&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;Bode&lt;/span&gt; searches the library for all artists that contain the word Bode.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Available fields: &lt;/span&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;%1&lt;/span&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Prefix a word with a field name to limit the search to that field, e.g. &lt;span style=&quot; font-weight:600;&quot;&gt;artist:&lt;/span&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;Bode&lt;/span&gt; searches the library for all artists that contain the word Bode, &lt;span style=&quot; font-weight:600;&quot;&gt;playcount:&lt;/span&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;&amp;gt;=2&lt;/span&gt; searches the library for songs played at least twice, &lt;span style=&quot; font-weight:600;&quot;&gt;lastplayed:&lt;/span&gt;&amp;lt;&lt;span style=&quot; font-style:italic;&quot;&gt;1h30m&lt;/span&gt; searches the library for songs played in the last 180 minutes.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Available fields: &lt;/span&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;%1&lt;/span&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="placeholderText" stdset="0"> <property name="placeholderText" stdset="0">
<string>Enter search terms here</string> <string>Enter search terms here</string>
@ -99,14 +108,14 @@
</property> </property>
</action> </action>
<action name="save_grouping"> <action name="save_grouping">
<property name="text"> <property name="text">
<string>Save current grouping</string> <string>Save current grouping</string>
</property> </property>
</action> </action>
<action name="manage_groupings"> <action name="manage_groupings">
<property name="text"> <property name="text">
<string>Manage saved groupings</string> <string>Manage saved groupings</string>
</property> </property>
</action> </action>
</widget> </widget>
<customwidgets> <customwidgets>

View File

@ -24,6 +24,31 @@
QueryOptions::QueryOptions() : max_age_(-1), query_mode_(QueryMode_All) {} QueryOptions::QueryOptions() : max_age_(-1), query_mode_(QueryMode_All) {}
const QStringList LibraryQuery::kNumericCompOperators = QStringList() << "<="
<< ">="
<< "<"
<< ">"
<< "=";
const QMap<QString, Song::FileType> kFiletypeId = QMap<QString, Song::FileType>(
std::map<QString, Song::FileType>{{"asf", Song::Type_Asf},
{"flac", Song::Type_Flac},
{"mp4", Song::Type_Mp4},
{"mpc", Song::Type_Mpc},
{"mp3", Song::Type_Mpeg},
{"oggflac", Song::Type_OggFlac},
{"oggspeex", Song::Type_OggSpeex},
{"oggvorbis", Song::Type_OggVorbis},
{"oggopus", Song::Type_OggOpus},
{"aiff", Song::Type_Aiff},
{"wav", Song::Type_Wav},
{"wavpack", Song::Type_WavPack},
{"trueaudio", Song::Type_TrueAudio},
{"cdda", Song::Type_Cdda},
{"spc700", Song::Type_Spc},
{"vgm", Song::Type_VGM},
{"stream", Song::Type_Stream},
{"unknown", Song::Type_Unknown}});
LibraryQuery::LibraryQuery(const QueryOptions& options) LibraryQuery::LibraryQuery(const QueryOptions& options)
: include_unavailable_(false), join_with_fts_(false), limit_(-1) { : include_unavailable_(false), join_with_fts_(false), limit_(-1) {
if (!options.filter().isEmpty()) { if (!options.filter().isEmpty()) {
@ -32,6 +57,9 @@ LibraryQuery::LibraryQuery(const QueryOptions& options)
// 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. // 3) Remove colons which don't correspond to column names.
//
// We also allow to search on non-FTS columns, but FTS columns
// are higher priority. Non-FTS query parts go to where_clauses
// Split on whitespace // Split on whitespace
QStringList tokens( QStringList tokens(
@ -44,17 +72,72 @@ LibraryQuery::LibraryQuery(const QueryOptions& options)
token.replace('-', ' '); token.replace('-', ' ');
if (token.contains(':')) { if (token.contains(':')) {
// Only prefix fts if the token is a valid column name. QString columntoken = token.section(':', 0, 0);
if (Song::kFtsColumns.contains("fts" + token.section(':', 0, 0), QString subtoken = token.section(':', 1, -1);
Qt::CaseInsensitive)) { subtoken.replace(":", " ");
// Account for multiple colons. subtoken = subtoken.trimmed();
QString columntoken =
token.section(':', 0, 0, QString::SectionIncludeTrailingSep); if (Song::kFtsColumns.contains(
QString subtoken = token.section(':', 1, -1); "fts" + columntoken,
subtoken.replace(":", " "); Qt::CaseInsensitive)) { // Is it a FTS column?
subtoken = subtoken.trimmed();
query += "fts" + columntoken + subtoken + "* "; query += "fts" + columntoken + subtoken + "* ";
} else { } else if (Song::kColumns.contains(columntoken, Qt::CaseInsensitive)) {
// We need to extract the operator and the value from the subtoken
QRegExp operatorRe("^(" + kNumericCompOperators.join("|") + ")(.*)");
QString op = "="; // default if no operator given
QString val = subtoken; // whole subtoken is the value if no operator
if (operatorRe.indexIn(subtoken) != -1) {
op = operatorRe.cap(1);
val = operatorRe.cap(2);
}
if (Song::kIntColumns.contains(columntoken)) {
bool ok;
int intVal = val.toInt(&ok);
if (ok) {
AddWhere(columntoken, intVal, op);
}
} else if (Song::kFloatColumns.contains(columntoken)) {
bool ok;
double doubleVal = val.toDouble(&ok);
if (ok) {
AddWhere(columntoken, doubleVal, op);
}
} else if (columntoken == "filetype") {
AddWhere(columntoken, kFiletypeId[val]);
} else if (Song::kDateColumns.contains(columntoken)) {
int seconds = 0;
QString tmp = "";
QString allowedChars = "smhd";
for (QChar c : val) {
if (c.isDigit()) {
tmp.append(c);
} else if (allowedChars.contains(c)) {
bool ok;
int intVal = tmp.toInt(&ok);
tmp = "";
if (ok) {
if (c == 's') {
seconds += intVal;
} else if (c == 'm') {
seconds += intVal * 60;
} else if (c == 'h') {
seconds += intVal * 60 * 60;
} else if (c == 'd') {
seconds += intVal * 60 * 60 * 24;
}
}
}
}
if (seconds > 0) {
int now = QDateTime::currentDateTime().toTime_t();
QString dt = QString("(%1-%2)").arg(now).arg(columntoken);
AddWhere(dt, seconds, op);
}
} else {
AddWhere(columntoken, val, op);
}
} else { // We did't recognize this as a column
token.replace(":", " "); token.replace(":", " ");
token = token.trimmed(); token = token.trimmed();
query += token + "* "; query += token + "* ";
@ -64,9 +147,11 @@ LibraryQuery::LibraryQuery(const QueryOptions& options)
} }
} }
where_clauses_ << "fts.%fts_table_noprefix MATCH ?"; if (!query.isEmpty()) {
bound_values_ << query; where_clauses_ << "fts.%fts_table_noprefix MATCH ?";
join_with_fts_ = true; bound_values_ << query;
join_with_fts_ = true;
}
} }
if (options.max_age() != -1) { if (options.max_age() != -1) {

View File

@ -93,6 +93,8 @@ class LibraryQuery {
operator const QSqlQuery&() const { return query_; } operator const QSqlQuery&() const { return query_; }
static const QStringList kNumericCompOperators;
private: private:
QString GetInnerQuery(); QString GetInnerQuery();

View File

@ -123,7 +123,7 @@ PlaylistView::PlaylistView(QWidget* parent)
last_height_(-1), last_height_(-1),
last_width_(-1), last_width_(-1),
force_background_redraw_(false), force_background_redraw_(false),
glow_enabled_(true), glow_enabled_(false),
currently_glowing_(false), currently_glowing_(false),
glow_intensity_step_(0), glow_intensity_step_(0),
rating_delegate_(nullptr), rating_delegate_(nullptr),
@ -843,6 +843,10 @@ void PlaylistView::mousePressEvent(QMouseEvent* event) {
// Update only this item rating // Update only this item rating
playlist_->RateSong(playlist_->proxy()->mapToSource(index), new_rating); playlist_->RateSong(playlist_->proxy()->mapToSource(index), new_rating);
} }
} else if (event->button() == Qt::XButton1 && index.isValid()) {
app_->player()->Previous();
} else if (event->button() == Qt::XButton2 && index.isValid()) {
app_->player()->Next();
} else { } else {
QTreeView::mousePressEvent(event); QTreeView::mousePressEvent(event);
} }
@ -1074,7 +1078,7 @@ void PlaylistView::PlaylistDestroyed() {
void PlaylistView::ReloadSettings() { void PlaylistView::ReloadSettings() {
QSettings s; QSettings s;
s.beginGroup(Playlist::kSettingsGroup); s.beginGroup(Playlist::kSettingsGroup);
glow_enabled_ = s.value("glow_effect", true).toBool(); glow_enabled_ = s.value("glow_effect", false).toBool();
if (setting_initial_header_layout_ || upgrading_from_qheaderview_) { if (setting_initial_header_layout_ || upgrading_from_qheaderview_) {
header_->SetStretchEnabled(s.value("stretch", true).toBool()); header_->SetStretchEnabled(s.value("stretch", true).toBool());

View File

@ -18,21 +18,23 @@
#include "ripper/ripcddialog.h" #include "ripper/ripcddialog.h"
#include <QCheckBox> #include <QCheckBox>
#include <QCloseEvent>
#include <QFileDialog> #include <QFileDialog>
#include <QFileInfo> #include <QFileInfo>
#include <QLineEdit> #include <QLineEdit>
#include <QMessageBox> #include <QMessageBox>
#include <QSettings> #include <QSettings>
#include <QUrl>
#include "config.h" #include "config.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/tagreaderclient.h" #include "core/tagreaderclient.h"
#include "core/utilities.h" #include "devices/cddasongloader.h"
#include "ripper/ripper.h" #include "ripper/ripper.h"
#include "ui_ripcddialog.h"
#include "transcoder/transcoder.h" #include "transcoder/transcoder.h"
#include "transcoder/transcoderoptionsdialog.h" #include "transcoder/transcoderoptionsdialog.h"
#include "ui/iconloader.h" #include "ui/iconloader.h"
#include "ui_ripcddialog.h"
namespace { namespace {
bool ComparePresetsByName(const TranscoderPreset& left, bool ComparePresetsByName(const TranscoderPreset& left,
@ -44,7 +46,7 @@ const int kCheckboxColumn = 0;
const int kTrackNumberColumn = 1; const int kTrackNumberColumn = 1;
const int kTrackTitleColumn = 2; const int kTrackTitleColumn = 2;
const int kTrackDurationColumn = 3; const int kTrackDurationColumn = 3;
} } // namespace
const char* RipCDDialog::kSettingsGroup = "Transcoder"; const char* RipCDDialog::kSettingsGroup = "Transcoder";
const int RipCDDialog::kMaxDestinationItems = 10; const int RipCDDialog::kMaxDestinationItems = 10;
@ -53,7 +55,8 @@ RipCDDialog::RipCDDialog(QWidget* parent)
: QDialog(parent), : QDialog(parent),
ui_(new Ui_RipCDDialog), ui_(new Ui_RipCDDialog),
ripper_(new Ripper(this)), ripper_(new Ripper(this)),
working_(false) { working_(false),
loader_(new CddaSongLoader(QUrl(), this)) {
// Init // Init
ui_->setupUi(this); ui_->setupUi(this);
@ -86,6 +89,13 @@ RipCDDialog::RipCDDialog(QWidget* parent)
connect(ui_->options, SIGNAL(clicked()), SLOT(Options())); connect(ui_->options, SIGNAL(clicked()), SLOT(Options()));
connect(ui_->select, SIGNAL(clicked()), SLOT(AddDestination())); connect(ui_->select, SIGNAL(clicked()), SLOT(AddDestination()));
connect(loader_, SIGNAL(SongsDurationLoaded(SongList)),
SLOT(BuildTrackListTable(SongList)));
connect(loader_, SIGNAL(SongsMetadataLoaded(SongList)),
SLOT(BuildTrackListTable(SongList)));
connect(loader_, SIGNAL(SongsMetadataLoaded(SongList)),
SLOT(AddAlbumMetadataFromMusicBrainz(SongList)));
connect(ripper_, SIGNAL(Finished()), SLOT(Finished())); connect(ripper_, SIGNAL(Finished()), SLOT(Finished()));
connect(ripper_, SIGNAL(Cancelled()), SLOT(Cancelled())); connect(ripper_, SIGNAL(Cancelled()), SLOT(Cancelled()));
connect(ripper_, SIGNAL(ProgressInterval(int, int)), connect(ripper_, SIGNAL(ProgressInterval(int, int)),
@ -123,8 +133,15 @@ RipCDDialog::~RipCDDialog() {}
bool RipCDDialog::CheckCDIOIsValid() { return ripper_->CheckCDIOIsValid(); } bool RipCDDialog::CheckCDIOIsValid() { return ripper_->CheckCDIOIsValid(); }
void RipCDDialog::closeEvent(QCloseEvent* event) {
if (working_) {
event->ignore();
}
}
void RipCDDialog::showEvent(QShowEvent* event) { void RipCDDialog::showEvent(QShowEvent* event) {
BuildTrackListTable(); ResetDialog();
loader_->LoadSongs();
if (!working_) { if (!working_) {
ui_->progress_group->hide(); ui_->progress_group->hide();
} }
@ -135,10 +152,9 @@ void RipCDDialog::ClickedRipButton() {
QMessageBox cdio_fail(QMessageBox::Critical, tr("Error Ripping CD"), QMessageBox cdio_fail(QMessageBox::Critical, tr("Error Ripping CD"),
tr("Media has changed. Reloading")); tr("Media has changed. Reloading"));
cdio_fail.exec(); cdio_fail.exec();
ResetDialog();
if (CheckCDIOIsValid()) { if (CheckCDIOIsValid()) {
BuildTrackListTable(); loader_->LoadSongs();
} else {
ui_->tableWidget->clearContents();
} }
return; return;
} }
@ -241,6 +257,39 @@ void RipCDDialog::UpdateProgressBar(int progress) {
ui_->progress_bar->setValue(progress); ui_->progress_bar->setValue(progress);
} }
void RipCDDialog::BuildTrackListTable(const SongList& songs) {
checkboxes_.clear();
track_names_.clear();
ui_->tableWidget->setRowCount(songs.length());
int current_row = 0;
for (const Song& song : songs) {
QCheckBox* checkbox = new QCheckBox(ui_->tableWidget);
checkbox->setCheckState(Qt::Checked);
checkboxes_.append(checkbox);
ui_->tableWidget->setCellWidget(current_row, kCheckboxColumn, checkbox);
ui_->tableWidget->setCellWidget(current_row, kTrackNumberColumn,
new QLabel(QString::number(song.track())));
QLineEdit* line_edit_track_title =
new QLineEdit(song.title(), ui_->tableWidget);
track_names_.append(line_edit_track_title);
ui_->tableWidget->setCellWidget(current_row, kTrackTitleColumn,
line_edit_track_title);
ui_->tableWidget->setCellWidget(current_row, kTrackDurationColumn,
new QLabel(song.PrettyLength()));
current_row++;
}
}
void RipCDDialog::AddAlbumMetadataFromMusicBrainz(const SongList& songs) {
Q_ASSERT(songs.length() > 0);
const Song& song = songs.first();
ui_->albumLineEdit->setText(song.album());
ui_->artistLineEdit->setText(song.artist());
ui_->yearLineEdit->setText(QString::number(song.year()));
}
void RipCDDialog::SetWorking(bool working) { void RipCDDialog::SetWorking(bool working) {
working_ = working; working_ = working;
rip_button_->setVisible(!working); rip_button_->setVisible(!working);
@ -251,33 +300,6 @@ void RipCDDialog::SetWorking(bool working) {
ui_->progress_group->setVisible(true); ui_->progress_group->setVisible(true);
} }
void RipCDDialog::BuildTrackListTable() {
checkboxes_.clear();
track_names_.clear();
int tracks = ripper_->TracksOnDisc();
ui_->tableWidget->setRowCount(tracks);
for (int i = 1; i <= tracks; i++) {
QCheckBox* checkbox_i = new QCheckBox(ui_->tableWidget);
checkbox_i->setCheckState(Qt::Checked);
checkboxes_.append(checkbox_i);
ui_->tableWidget->setCellWidget(i - 1, kCheckboxColumn, checkbox_i);
ui_->tableWidget->setCellWidget(i - 1, kTrackNumberColumn,
new QLabel(QString::number(i)));
QString track_title = QString("Track %1").arg(i);
QLineEdit* line_edit_track_title_i =
new QLineEdit(track_title, ui_->tableWidget);
track_names_.append(line_edit_track_title_i);
ui_->tableWidget->setCellWidget(i - 1, kTrackTitleColumn,
line_edit_track_title_i);
QString track_duration =
Utilities::PrettyTime(ripper_->TrackDurationSecs(i));
ui_->tableWidget->setCellWidget(i - 1, kTrackDurationColumn,
new QLabel(track_duration));
}
}
QString RipCDDialog::GetOutputFileName(const QString& basename) const { QString RipCDDialog::GetOutputFileName(const QString& basename) const {
QFileInfo path( QFileInfo path(
ui_->destination->itemData(ui_->destination->currentIndex()).toString()); ui_->destination->itemData(ui_->destination->currentIndex()).toString());
@ -301,3 +323,12 @@ QString RipCDDialog::ParseFileFormatString(const QString& file_format,
return to_return; return to_return;
} }
void RipCDDialog::ResetDialog() {
ui_->tableWidget->setRowCount(0);
ui_->albumLineEdit->clear();
ui_->artistLineEdit->clear();
ui_->genreLineEdit->clear();
ui_->yearLineEdit->clear();
ui_->discLineEdit->clear();
}

View File

@ -19,6 +19,7 @@
#define SRC_RIPPER_RIPCDDIALOG_H_ #define SRC_RIPPER_RIPCDDIALOG_H_
#include <memory> #include <memory>
#include <QDialog> #include <QDialog>
#include <QFile> #include <QFile>
@ -26,8 +27,11 @@
#include "core/tagreaderclient.h" #include "core/tagreaderclient.h"
class QCheckBox; class QCheckBox;
class QCloseEvent;
class QLineEdit; class QLineEdit;
class QShowEvent;
class CddaSongLoader;
class Ripper; class Ripper;
class Ui_RipCDDialog; class Ui_RipCDDialog;
@ -40,6 +44,7 @@ class RipCDDialog : public QDialog {
bool CheckCDIOIsValid(); bool CheckCDIOIsValid();
protected: protected:
void closeEvent(QCloseEvent* event);
void showEvent(QShowEvent* event); void showEvent(QShowEvent* event);
private slots: private slots:
@ -53,6 +58,8 @@ class RipCDDialog : public QDialog {
void Cancelled(); void Cancelled();
void SetupProgressBarLimits(int min, int max); void SetupProgressBarLimits(int min, int max);
void UpdateProgressBar(int progress); void UpdateProgressBar(int progress);
void BuildTrackListTable(const SongList& songs);
void AddAlbumMetadataFromMusicBrainz(const SongList& songs);
private: private:
static const char* kSettingsGroup; static const char* kSettingsGroup;
@ -62,10 +69,10 @@ class RipCDDialog : public QDialog {
// from the ui dialog and an extension that corresponds to the audio // from the ui dialog and an extension that corresponds to the audio
// format chosen in the ui. // format chosen in the ui.
void AddDestinationDirectory(QString dir); void AddDestinationDirectory(QString dir);
void BuildTrackListTable();
QString GetOutputFileName(const QString& basename) const; QString GetOutputFileName(const QString& basename) const;
QString ParseFileFormatString(const QString& file_format, int track_no) const; QString ParseFileFormatString(const QString& file_format, int track_no) const;
void SetWorking(bool working); void SetWorking(bool working);
void ResetDialog();
QList<QCheckBox*> checkboxes_; QList<QCheckBox*> checkboxes_;
QList<QLineEdit*> track_names_; QList<QLineEdit*> track_names_;
@ -76,5 +83,6 @@ class RipCDDialog : public QDialog {
std::unique_ptr<Ui_RipCDDialog> ui_; std::unique_ptr<Ui_RipCDDialog> ui_;
Ripper* ripper_; Ripper* ripper_;
bool working_; bool working_;
CddaSongLoader* loader_;
}; };
#endif // SRC_RIPPER_RIPCDDIALOG_H_ #endif // SRC_RIPPER_RIPCDDIALOG_H_

View File

@ -86,19 +86,6 @@ int Ripper::TracksOnDisc() const {
return number_of_tracks; return number_of_tracks;
} }
int Ripper::TrackDurationSecs(int track) const {
Q_ASSERT(track <= TracksOnDisc());
int first_frame = cdio_get_track_lsn(cdio_, track);
int last_frame = cdio_get_track_last_lsn(cdio_, track);
if (first_frame != CDIO_INVALID_LSN && last_frame != CDIO_INVALID_LSN) {
return (last_frame - first_frame + 1) / CDIO_CD_FRAMES_PER_SEC;
} else {
qLog(Error) << "Could not compute duration of track" << track;
return 0;
}
}
int Ripper::AddedTracks() const { return tracks_.length(); } int Ripper::AddedTracks() const { return tracks_.length(); }
void Ripper::ClearTracks() { tracks_.clear(); } void Ripper::ClearTracks() { tracks_.clear(); }
@ -219,6 +206,11 @@ void Ripper::WriteWAVHeader(QFile* stream, int32_t i_bytecount) {
} }
void Ripper::Rip() { void Ripper::Rip() {
if (tracks_.isEmpty()) {
emit Finished();
return;
}
temporary_directory_ = Utilities::MakeTempDir() + "/"; temporary_directory_ = Utilities::MakeTempDir() + "/";
finished_success_ = 0; finished_success_ = 0;
finished_failed_ = 0; finished_failed_ = 0;

View File

@ -55,8 +55,6 @@ class Ripper : public QObject {
Song::FileType type); Song::FileType type);
// Returns the number of audio tracks on the disc. // Returns the number of audio tracks on the disc.
int TracksOnDisc() const; int TracksOnDisc() const;
// Returns the duration of the track or 0 on error.
int TrackDurationSecs(int track) const;
// Returns the number of tracks added to the rip list. // Returns the number of tracks added to the rip list.
int AddedTracks() const; int AddedTracks() const;
// Clears the rip list. // Clears the rip list.

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

@ -158,7 +158,7 @@ const char* MainWindow::kSettingsGroup = "MainWindow";
const char* MainWindow::kAllFilesFilterSpec = QT_TR_NOOP("All Files (*)"); const char* MainWindow::kAllFilesFilterSpec = QT_TR_NOOP("All Files (*)");
namespace { namespace {
const int kTrackSliderUpdateTimeMs = 40; const int kTrackSliderUpdateTimeMs = 500;
const int kTrackPositionUpdateTimeMs = 1000; const int kTrackPositionUpdateTimeMs = 1000;
} }
@ -612,6 +612,10 @@ MainWindow::MainWindow(Application* app, SystemTrayIcon* tray_icon, OSD* osd,
connect(ui_->track_slider, SIGNAL(SeekBackward()), app_->player(), connect(ui_->track_slider, SIGNAL(SeekBackward()), app_->player(),
SLOT(SeekBackward())); SLOT(SeekBackward()));
connect(ui_->track_slider, SIGNAL(Previous()), app_->player(),
SLOT(Previous()));
connect(ui_->track_slider, SIGNAL(Next()), app_->player(), SLOT(Next()));
// Library connections // Library connections
connect(library_view_->view(), SIGNAL(AddToPlaylistSignal(QMimeData*)), connect(library_view_->view(), SIGNAL(AddToPlaylistSignal(QMimeData*)),
SLOT(AddToPlaylist(QMimeData*))); SLOT(AddToPlaylist(QMimeData*)));

View File

@ -49,6 +49,8 @@ TrackSlider::TrackSlider(QWidget* parent)
connect(ui_->slider, SIGNAL(valueChanged(int)), SLOT(ValueMaybeChanged(int))); connect(ui_->slider, SIGNAL(valueChanged(int)), SLOT(ValueMaybeChanged(int)));
connect(ui_->slider, SIGNAL(SeekForward()), SIGNAL(SeekForward())); connect(ui_->slider, SIGNAL(SeekForward()), SIGNAL(SeekForward()));
connect(ui_->slider, SIGNAL(SeekBackward()), SIGNAL(SeekBackward())); connect(ui_->slider, SIGNAL(SeekBackward()), SIGNAL(SeekBackward()));
connect(ui_->slider, SIGNAL(Previous()), SIGNAL(Previous()));
connect(ui_->slider, SIGNAL(Next()), SIGNAL(Next()));
connect(ui_->remaining, SIGNAL(Clicked()), SLOT(ToggleTimeDisplay())); connect(ui_->remaining, SIGNAL(Clicked()), SLOT(ToggleTimeDisplay()));
} }

View File

@ -57,6 +57,8 @@ signals:
void SeekForward(); void SeekForward();
void SeekBackward(); void SeekBackward();
void Next();
void Previous();
private slots: private slots:
void ValueMaybeChanged(int value); void ValueMaybeChanged(int value);

View File

@ -32,6 +32,7 @@ TrackSliderSlider::TrackSliderSlider(QWidget* parent)
mouse_hover_seconds_(0) { mouse_hover_seconds_(0) {
setMouseTracking(true); setMouseTracking(true);
popup_->hide();
connect(this, SIGNAL(valueChanged(int)), SLOT(UpdateDeltaTime())); connect(this, SIGNAL(valueChanged(int)), SLOT(UpdateDeltaTime()));
} }
@ -60,6 +61,12 @@ void TrackSliderSlider::mousePressEvent(QMouseEvent* e) {
void TrackSliderSlider::mouseReleaseEvent(QMouseEvent* e) { void TrackSliderSlider::mouseReleaseEvent(QMouseEvent* e) {
QSlider::mouseReleaseEvent(e); QSlider::mouseReleaseEvent(e);
if (e->button() == Qt::XButton1) {
emit Previous();
} else if (e->button() == Qt::XButton2) {
emit Next();
}
e->accept();
} }
void TrackSliderSlider::mouseMoveEvent(QMouseEvent* e) { void TrackSliderSlider::mouseMoveEvent(QMouseEvent* e) {

View File

@ -32,6 +32,8 @@ class TrackSliderSlider : public QSlider {
signals: signals:
void SeekForward(); void SeekForward();
void SeekBackward(); void SeekBackward();
void Previous();
void Next();
protected: protected:
void mousePressEvent(QMouseEvent* e); void mousePressEvent(QMouseEvent* e);