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>
License: GPL-2+
Files: src/widgets/fancytabwidget.*
src/widgets/stylehelper.*
Files: src/widgets/stylehelper.*
Copyright: 2010, Nokia Corporation
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 <tag.h>
#include <QByteArray>
#include <QChar>
#include <QFile>
#include <QFileInfo>
#include <QString>
@ -191,7 +192,7 @@ void GME::VGM::Read(const QFileInfo& file_info,
QTextStream fileTagStream(gd3Data, QIODevice::ReadOnly);
// Stored as 16 bit UTF string, two bytes per letter.
fileTagStream.setCodec("UTF-16");
QStringList strings = fileTagStream.readLine(0).split('\0');
QStringList strings = fileTagStream.readLine(0).split(QChar('\0'));
if (strings.count() < 10) return;
/* VGM standard dictates string tag data exist in specific order.

View File

@ -265,6 +265,22 @@ QSqlDatabase Database::Connect() {
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);
set_fts_tokenizer.prepare("SELECT fts3_tokenizer(:name, :pointer)");
set_fts_tokenizer.bindValue(":name", "unicode");

View File

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

View File

@ -118,6 +118,23 @@ const QStringList Song::kColumns = QStringList() << "title"
<< "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::kBindSpec =
Utilities::Prepend(":", Song::kColumns).join(", ");

View File

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

View File

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

View File

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

View File

@ -255,6 +255,10 @@ bool GstEnginePipeline::Init() {
case QVariant::Int:
g_object_set(G_OBJECT(audiosink_), "device", device_.toInt(), nullptr);
break;
case QVariant::LongLong:
g_object_set(G_OBJECT(audiosink_), "device", device_.toLongLong(),
nullptr);
break;
case QVariant::String:
g_object_set(G_OBJECT(audiosink_), "device",
device_.toString().toUtf8().constData(), nullptr);
@ -279,7 +283,7 @@ bool GstEnginePipeline::Init() {
audioconvert_ = engine_->CreateElement("audioconvert", 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_sink = engine_->CreateElement("fakesink", audiobin_);
@ -903,7 +907,8 @@ GstPadProbeReturn GstEnginePipeline::HandoffCallback(GstPad*,
if (instance->emit_track_ended_on_time_discontinuity_) {
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";
instance->emit_track_ended_on_time_discontinuity_ = false;
emit instance->EndOfStreamReached(instance->id(), true);

View File

@ -107,6 +107,7 @@ QList<DeviceFinder::Device> OsxDeviceFinder::ListDevices() {
Device dev;
dev.description = QString::fromUtf8(
CFStringGetCStringPtr(*device_name, CFStringGetSystemEncoding()));
if (dev.description.isEmpty()) dev.description = QString("Unknown device");
dev.device_property_value = id;
dev.icon_name = GuessIconName(dev.description);
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
// file to prevent that they get translated by mistake.
QString available_fields =
Song::kFtsColumns.join(", ").replace(QRegExp("\\bfts"), "");
QString available_fields = (Song::kFtsColumns + Song::kIntColumns +
Song::kFloatColumns + Song::kDateColumns)
.join(", ")
.replace(QRegExp("\\bfts"), "");
ui_->filter->setToolTip(ui_->filter->toolTip().arg(available_fields));
connect(ui_->filter, SIGNAL(returnPressed()), SIGNAL(ReturnPressed()));

View File

@ -17,13 +17,22 @@
<property name="spacing">
<number>0</number>
</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>
</property>
<item>
<widget class="QSearchField" name="filter" native="true">
<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 name="placeholderText" stdset="0">
<string>Enter search terms here</string>
@ -99,14 +108,14 @@
</property>
</action>
<action name="save_grouping">
<property name="text">
<string>Save current grouping</string>
</property>
<property name="text">
<string>Save current grouping</string>
</property>
</action>
<action name="manage_groupings">
<property name="text">
<string>Manage saved groupings</string>
</property>
<property name="text">
<string>Manage saved groupings</string>
</property>
</action>
</widget>
<customwidgets>

View File

@ -24,6 +24,31 @@
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)
: include_unavailable_(false), join_with_fts_(false), limit_(-1) {
if (!options.filter().isEmpty()) {
@ -32,6 +57,9 @@ LibraryQuery::LibraryQuery(const QueryOptions& options)
// 1) Append * to all tokens.
// 2) Prefix "fts" 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
QStringList tokens(
@ -44,17 +72,72 @@ LibraryQuery::LibraryQuery(const QueryOptions& options)
token.replace('-', ' ');
if (token.contains(':')) {
// Only prefix fts if the token is a valid column name.
if (Song::kFtsColumns.contains("fts" + token.section(':', 0, 0),
Qt::CaseInsensitive)) {
// Account for multiple colons.
QString columntoken =
token.section(':', 0, 0, QString::SectionIncludeTrailingSep);
QString subtoken = token.section(':', 1, -1);
subtoken.replace(":", " ");
subtoken = subtoken.trimmed();
QString columntoken = token.section(':', 0, 0);
QString subtoken = token.section(':', 1, -1);
subtoken.replace(":", " ");
subtoken = subtoken.trimmed();
if (Song::kFtsColumns.contains(
"fts" + columntoken,
Qt::CaseInsensitive)) { // Is it a FTS column?
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 = token.trimmed();
query += token + "* ";
@ -64,9 +147,11 @@ LibraryQuery::LibraryQuery(const QueryOptions& options)
}
}
where_clauses_ << "fts.%fts_table_noprefix MATCH ?";
bound_values_ << query;
join_with_fts_ = true;
if (!query.isEmpty()) {
where_clauses_ << "fts.%fts_table_noprefix MATCH ?";
bound_values_ << query;
join_with_fts_ = true;
}
}
if (options.max_age() != -1) {

View File

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

View File

@ -123,7 +123,7 @@ PlaylistView::PlaylistView(QWidget* parent)
last_height_(-1),
last_width_(-1),
force_background_redraw_(false),
glow_enabled_(true),
glow_enabled_(false),
currently_glowing_(false),
glow_intensity_step_(0),
rating_delegate_(nullptr),
@ -843,6 +843,10 @@ void PlaylistView::mousePressEvent(QMouseEvent* event) {
// Update only this item 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 {
QTreeView::mousePressEvent(event);
}
@ -1074,7 +1078,7 @@ void PlaylistView::PlaylistDestroyed() {
void PlaylistView::ReloadSettings() {
QSettings s;
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_) {
header_->SetStretchEnabled(s.value("stretch", true).toBool());

View File

@ -18,21 +18,23 @@
#include "ripper/ripcddialog.h"
#include <QCheckBox>
#include <QCloseEvent>
#include <QFileDialog>
#include <QFileInfo>
#include <QLineEdit>
#include <QMessageBox>
#include <QSettings>
#include <QUrl>
#include "config.h"
#include "core/logging.h"
#include "core/tagreaderclient.h"
#include "core/utilities.h"
#include "devices/cddasongloader.h"
#include "ripper/ripper.h"
#include "ui_ripcddialog.h"
#include "transcoder/transcoder.h"
#include "transcoder/transcoderoptionsdialog.h"
#include "ui/iconloader.h"
#include "ui_ripcddialog.h"
namespace {
bool ComparePresetsByName(const TranscoderPreset& left,
@ -44,7 +46,7 @@ const int kCheckboxColumn = 0;
const int kTrackNumberColumn = 1;
const int kTrackTitleColumn = 2;
const int kTrackDurationColumn = 3;
}
} // namespace
const char* RipCDDialog::kSettingsGroup = "Transcoder";
const int RipCDDialog::kMaxDestinationItems = 10;
@ -53,7 +55,8 @@ RipCDDialog::RipCDDialog(QWidget* parent)
: QDialog(parent),
ui_(new Ui_RipCDDialog),
ripper_(new Ripper(this)),
working_(false) {
working_(false),
loader_(new CddaSongLoader(QUrl(), this)) {
// Init
ui_->setupUi(this);
@ -86,6 +89,13 @@ RipCDDialog::RipCDDialog(QWidget* parent)
connect(ui_->options, SIGNAL(clicked()), SLOT(Options()));
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(Cancelled()), SLOT(Cancelled()));
connect(ripper_, SIGNAL(ProgressInterval(int, int)),
@ -123,8 +133,15 @@ RipCDDialog::~RipCDDialog() {}
bool RipCDDialog::CheckCDIOIsValid() { return ripper_->CheckCDIOIsValid(); }
void RipCDDialog::closeEvent(QCloseEvent* event) {
if (working_) {
event->ignore();
}
}
void RipCDDialog::showEvent(QShowEvent* event) {
BuildTrackListTable();
ResetDialog();
loader_->LoadSongs();
if (!working_) {
ui_->progress_group->hide();
}
@ -135,10 +152,9 @@ void RipCDDialog::ClickedRipButton() {
QMessageBox cdio_fail(QMessageBox::Critical, tr("Error Ripping CD"),
tr("Media has changed. Reloading"));
cdio_fail.exec();
ResetDialog();
if (CheckCDIOIsValid()) {
BuildTrackListTable();
} else {
ui_->tableWidget->clearContents();
loader_->LoadSongs();
}
return;
}
@ -241,6 +257,39 @@ void RipCDDialog::UpdateProgressBar(int 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) {
working_ = working;
rip_button_->setVisible(!working);
@ -251,33 +300,6 @@ void RipCDDialog::SetWorking(bool working) {
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 {
QFileInfo path(
ui_->destination->itemData(ui_->destination->currentIndex()).toString());
@ -301,3 +323,12 @@ QString RipCDDialog::ParseFileFormatString(const QString& file_format,
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_
#include <memory>
#include <QDialog>
#include <QFile>
@ -26,8 +27,11 @@
#include "core/tagreaderclient.h"
class QCheckBox;
class QCloseEvent;
class QLineEdit;
class QShowEvent;
class CddaSongLoader;
class Ripper;
class Ui_RipCDDialog;
@ -40,6 +44,7 @@ class RipCDDialog : public QDialog {
bool CheckCDIOIsValid();
protected:
void closeEvent(QCloseEvent* event);
void showEvent(QShowEvent* event);
private slots:
@ -53,6 +58,8 @@ class RipCDDialog : public QDialog {
void Cancelled();
void SetupProgressBarLimits(int min, int max);
void UpdateProgressBar(int progress);
void BuildTrackListTable(const SongList& songs);
void AddAlbumMetadataFromMusicBrainz(const SongList& songs);
private:
static const char* kSettingsGroup;
@ -62,10 +69,10 @@ class RipCDDialog : public QDialog {
// from the ui dialog and an extension that corresponds to the audio
// format chosen in the ui.
void AddDestinationDirectory(QString dir);
void BuildTrackListTable();
QString GetOutputFileName(const QString& basename) const;
QString ParseFileFormatString(const QString& file_format, int track_no) const;
void SetWorking(bool working);
void ResetDialog();
QList<QCheckBox*> checkboxes_;
QList<QLineEdit*> track_names_;
@ -76,5 +83,6 @@ class RipCDDialog : public QDialog {
std::unique_ptr<Ui_RipCDDialog> ui_;
Ripper* ripper_;
bool working_;
CddaSongLoader* loader_;
};
#endif // SRC_RIPPER_RIPCDDIALOG_H_

View File

@ -86,19 +86,6 @@ int Ripper::TracksOnDisc() const {
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(); }
void Ripper::ClearTracks() { tracks_.clear(); }
@ -219,6 +206,11 @@ void Ripper::WriteWAVHeader(QFile* stream, int32_t i_bytecount) {
}
void Ripper::Rip() {
if (tracks_.isEmpty()) {
emit Finished();
return;
}
temporary_directory_ = Utilities::MakeTempDir() + "/";
finished_success_ = 0;
finished_failed_ = 0;

View File

@ -55,8 +55,6 @@ class Ripper : public QObject {
Song::FileType type);
// Returns the number of audio tracks on the disc.
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.
int AddedTracks() const;
// 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 (*)");
namespace {
const int kTrackSliderUpdateTimeMs = 40;
const int kTrackSliderUpdateTimeMs = 500;
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(),
SLOT(SeekBackward()));
connect(ui_->track_slider, SIGNAL(Previous()), app_->player(),
SLOT(Previous()));
connect(ui_->track_slider, SIGNAL(Next()), app_->player(), SLOT(Next()));
// Library connections
connect(library_view_->view(), SIGNAL(AddToPlaylistSignal(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(SeekForward()), SIGNAL(SeekForward()));
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()));
}

View File

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

View File

@ -32,6 +32,7 @@ TrackSliderSlider::TrackSliderSlider(QWidget* parent)
mouse_hover_seconds_(0) {
setMouseTracking(true);
popup_->hide();
connect(this, SIGNAL(valueChanged(int)), SLOT(UpdateDeltaTime()));
}
@ -60,6 +61,12 @@ void TrackSliderSlider::mousePressEvent(QMouseEvent* e) {
void TrackSliderSlider::mouseReleaseEvent(QMouseEvent* 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) {

View File

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