Improve internet classes

This commit is contained in:
Jonas Kvinge 2020-04-13 06:30:40 +02:00
parent aa43d42cdb
commit 2f72c41cda
17 changed files with 880 additions and 1015 deletions

View File

@ -286,7 +286,6 @@ set(SOURCES
internet/internetservices.cpp
internet/internetservice.cpp
internet/internetplaylistitem.cpp
internet/internetsearch.cpp
internet/internetsearchview.cpp
internet/internetsearchmodel.cpp
internet/internetsearchsortmodel.cpp
@ -472,7 +471,6 @@ set(HEADERS
internet/internetservices.h
internet/internetservice.h
internet/internetsongmimedata.h
internet/internetsearch.h
internet/internetsearchview.h
internet/internetsearchmodel.h
internet/localredirectserver.h

View File

@ -61,7 +61,7 @@
# include "dbus/metatypes.h"
#endif
#include "internet/internetsearch.h"
#include "internet/internetsearchview.h"
void RegisterMetaTypes() {
@ -117,7 +117,7 @@ void RegisterMetaTypes() {
#endif
#endif
qRegisterMetaType<InternetSearch::ResultList>("InternetSearch::ResultList");
qRegisterMetaType<InternetSearch::Result>("InternetSearch::Result");
qRegisterMetaType<InternetSearchView::ResultList>("InternetSearchView::ResultList");
qRegisterMetaType<InternetSearchView::Result>("InternetSearchView::Result");
}

View File

@ -1,318 +0,0 @@
/*
* Strawberry Music Player
* This code was part of Clementine (GlobalSearch)
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QtGlobal>
#include <QObject>
#include <QList>
#include <QMap>
#include <QString>
#include <QStringList>
#include <QStringBuilder>
#include <QUrl>
#include <QRegExp>
#include <QImage>
#include <QPixmap>
#include <QPainter>
#include <QSize>
#include <QTimerEvent>
#include "core/application.h"
#include "core/song.h"
#include "core/mimedata.h"
#include "covermanager/albumcoverloader.h"
#include "internet/internetsongmimedata.h"
#include "internetsearch.h"
#include "internetservice.h"
#include "internetservices.h"
const int InternetSearch::kDelayedSearchTimeoutMs = 200;
const int InternetSearch::kArtHeight = 32;
InternetSearch::InternetSearch(Application *app, Song::Source source, QObject *parent)
: QObject(parent),
app_(app),
source_(source),
service_(app->internet_services()->ServiceBySource(source)),
searches_next_id_(1),
art_searches_next_id_(1) {
cover_loader_options_.desired_height_ = kArtHeight;
cover_loader_options_.pad_output_image_ = true;
cover_loader_options_.scale_output_image_ = true;
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(AlbumCoverLoaded(quint64, QUrl, QImage)));
connect(this, SIGNAL(SearchAsyncSig(const int, const QString&, const SearchType)), this, SLOT(DoSearchAsync(const int, const QString&, const SearchType)));
connect(service_, SIGNAL(SearchUpdateStatus(const int, const QString&)), SLOT(UpdateStatusSlot(const int, const QString&)));
connect(service_, SIGNAL(SearchProgressSetMaximum(const int, const int)), SLOT(ProgressSetMaximumSlot(const int, const int)));
connect(service_, SIGNAL(SearchUpdateProgress(const int, const int)), SLOT(UpdateProgressSlot(const int, const int)));
connect(service_, SIGNAL(SearchResults(const int, const SongList&, const QString&)), SLOT(SearchDone(const int, const SongList&, const QString&)));
}
InternetSearch::~InternetSearch() {}
QStringList InternetSearch::TokenizeQuery(const QString &query) {
QStringList tokens(query.split(QRegExp("\\s+")));
for (QStringList::iterator it = tokens.begin(); it != tokens.end(); ++it) {
(*it).remove('(');
(*it).remove(')');
(*it).remove('"');
const int colon = (*it).indexOf(":");
if (colon != -1) {
(*it).remove(0, colon + 1);
}
}
return tokens;
}
bool InternetSearch::Matches(const QStringList &tokens, const QString &string) {
for (const QString &token : tokens) {
if (!string.contains(token, Qt::CaseInsensitive)) {
return false;
}
}
return true;
}
int InternetSearch::SearchAsync(const QString &query, const SearchType type) {
const int id = searches_next_id_++;
emit SearchAsyncSig(id, query, type);
return id;
}
void InternetSearch::SearchAsync(const int id, const QString &query, const SearchType type) {
const int service_id = service_->Search(query, type);
pending_searches_[service_id] = PendingState(id, TokenizeQuery(query));
}
void InternetSearch::DoSearchAsync(const int id, const QString &query, const SearchType type) {
int timer_id = startTimer(kDelayedSearchTimeoutMs);
delayed_searches_[timer_id].id_ = id;
delayed_searches_[timer_id].query_ = query;
delayed_searches_[timer_id].type_ = type;
}
void InternetSearch::SearchDone(const int service_id, const SongList &songs, const QString &error) {
if (!pending_searches_.contains(service_id)) return;
// Map back to the original id.
const PendingState state = pending_searches_.take(service_id);
const int search_id = state.orig_id_;
if (songs.isEmpty()) {
emit SearchError(search_id, error);
return;
}
ResultList results;
for (const Song &song : songs) {
Result result;
result.metadata_ = song;
results << result;
}
if (results.isEmpty()) return;
// Load cached pixmaps into the results
for (InternetSearch::ResultList::iterator it = results.begin(); it != results.end(); ++it) {
it->pixmap_cache_key_ = PixmapCacheKey(*it);
}
emit AddResults(search_id, results);
MaybeSearchFinished(search_id);
}
void InternetSearch::MaybeSearchFinished(const int id) {
if (pending_searches_.keys(PendingState(id, QStringList())).isEmpty()) {
emit SearchFinished(id);
}
}
void InternetSearch::CancelSearch(const int id) {
QMap<int, DelayedSearch>::iterator it;
for (it = delayed_searches_.begin(); it != delayed_searches_.end(); ++it) {
if (it.value().id_ == id) {
killTimer(it.key());
delayed_searches_.erase(it);
return;
}
}
service_->CancelSearch();
}
void InternetSearch::timerEvent(QTimerEvent *e) {
QMap<int, DelayedSearch>::iterator it = delayed_searches_.find(e->timerId());
if (it != delayed_searches_.end()) {
SearchAsync(it.value().id_, it.value().query_, it.value().type_);
delayed_searches_.erase(it);
return;
}
QObject::timerEvent(e);
}
QString InternetSearch::PixmapCacheKey(const InternetSearch::Result &result) const {
return "internet:" % result.metadata_.url().toString();
}
bool InternetSearch::FindCachedPixmap(const InternetSearch::Result &result, QPixmap *pixmap) const {
return pixmap_cache_.find(result.pixmap_cache_key_, pixmap);
}
int InternetSearch::LoadAlbumCoverAsync(const InternetSearch::Result &result) {
const int id = art_searches_next_id_++;
pending_art_searches_[id] = result.pixmap_cache_key_;
quint64 loader_id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, result.metadata_);
cover_loader_tasks_[loader_id] = id;
return id;
}
void InternetSearch::AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) {
Q_UNUSED(cover_url);
if (!cover_loader_tasks_.contains(id)) return;
int orig_id = cover_loader_tasks_.take(id);
const QString key = pending_art_searches_.take(orig_id);
QPixmap pixmap = QPixmap::fromImage(image);
pixmap_cache_.insert(key, pixmap);
emit AlbumCoverLoaded(orig_id, pixmap);
}
QImage InternetSearch::ScaleAndPad(const QImage &image) {
if (image.isNull()) return QImage();
const QSize target_size = QSize(kArtHeight, kArtHeight);
if (image.size() == target_size) return image;
// Scale the image down
QImage copy;
copy = image.scaled(target_size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
// Pad the image to kHeight x kHeight
if (copy.size() == target_size) return copy;
QImage padded_image(kArtHeight, kArtHeight, QImage::Format_ARGB32);
padded_image.fill(0);
QPainter p(&padded_image);
p.drawImage((kArtHeight - copy.width()) / 2, (kArtHeight - copy.height()) / 2, copy);
p.end();
return padded_image;
}
MimeData *InternetSearch::LoadTracks(const ResultList &results) {
if (results.isEmpty()) {
return nullptr;
}
ResultList results_copy;
for (const Result &result : results) {
results_copy << result;
}
SongList songs;
for (const Result &result : results) {
songs << result.metadata_;
}
InternetSongMimeData *internet_song_mime_data = new InternetSongMimeData(service_);
internet_song_mime_data->songs = songs;
MimeData *mime_data = internet_song_mime_data;
QList<QUrl> urls;
for (const Result &result : results) {
urls << result.metadata_.url();
}
mime_data->setUrls(urls);
return mime_data;
}
void InternetSearch::UpdateStatusSlot(const int service_id, const QString &text) {
if (!pending_searches_.contains(service_id)) return;
const PendingState state = pending_searches_[service_id];
const int search_id = state.orig_id_;
emit UpdateStatus(search_id, text);
}
void InternetSearch::ProgressSetMaximumSlot(const int service_id, const int max) {
if (!pending_searches_.contains(service_id)) return;
const PendingState state = pending_searches_[service_id];
const int search_id = state.orig_id_;
emit ProgressSetMaximum(search_id, max);
}
void InternetSearch::UpdateProgressSlot(const int service_id, const int progress) {
if (!pending_searches_.contains(service_id)) return;
const PendingState state = pending_searches_[service_id];
const int search_id = state.orig_id_;
emit UpdateProgress(search_id, progress);
}

View File

@ -1,167 +0,0 @@
/*
* Strawberry Music Player
* This code was part of Clementine (GlobalSearch)
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef INTERNETSEARCH_H
#define INTERNETSEARCH_H
#include "config.h"
#include <QtGlobal>
#include <QObject>
#include <QMetaType>
#include <QSet>
#include <QList>
#include <QMap>
#include <QString>
#include <QStringList>
#include <QUrl>
#include <QImage>
#include <QPixmap>
#include <QPixmapCache>
#include "core/song.h"
#include "covermanager/albumcoverloaderoptions.h"
class QTimerEvent;
class Application;
class MimeData;
class AlbumCoverLoader;
class InternetService;
class InternetSearch : public QObject {
Q_OBJECT
public:
explicit InternetSearch(Application *app, Song::Source source, QObject *parent = nullptr);
~InternetSearch();
enum SearchType {
SearchType_Artists = 1,
SearchType_Albums = 2,
SearchType_Songs = 3,
};
struct Result {
Song metadata_;
QString pixmap_cache_key_;
};
typedef QList<Result> ResultList;
static const int kDelayedSearchTimeoutMs;
Application *application() const { return app_; }
Song::Source source() const { return source_; }
InternetService *service() const { return service_; }
int SearchAsync(const QString &query, SearchType type);
int LoadAlbumCoverAsync(const InternetSearch::Result &result);
void CancelSearch(const int id);
void CancelArt(const int id);
// Loads tracks for results that were previously emitted by ResultsAvailable.
// The implementation creates a SongMimeData with one Song for each Result.
MimeData *LoadTracks(const ResultList &results);
signals:
void SearchAsyncSig(const int id, const QString &query, const SearchType type);
void ResultsAvailable(const int id, const InternetSearch::ResultList &results);
void AddResults(const int id, const InternetSearch::ResultList &results);
void SearchError(const int id, const QString &error);
void SearchFinished(const int id);
void UpdateStatus(const int id, const QString &text);
void ProgressSetMaximum(const int id, const int progress);
void UpdateProgress(const int id, const int max);
void AlbumCoverLoaded(const int id, const QPixmap &pixmap);
protected:
struct PendingState {
PendingState() : orig_id_(-1) {}
PendingState(int orig_id, QStringList tokens)
: orig_id_(orig_id), tokens_(tokens) {}
int orig_id_;
QStringList tokens_;
bool operator<(const PendingState &b) const {
return orig_id_ < b.orig_id_;
}
bool operator==(const PendingState &b) const {
return orig_id_ == b.orig_id_;
}
};
void timerEvent(QTimerEvent *e);
// These functions treat queries in the same way as CollectionQuery.
// They're useful for figuring out whether you got a result because it matched in the song title or the artist/album name.
static QStringList TokenizeQuery(const QString &query);
static bool Matches(const QStringList &tokens, const QString &string);
private slots:
void DoSearchAsync(const int id, const QString &query, const SearchType type);
void SearchDone(const int service_id, const SongList &songs, const QString &error);
void AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
void UpdateStatusSlot(const int id, const QString &text);
void ProgressSetMaximumSlot(const int id, const int progress);
void UpdateProgressSlot(const int id, const int max);
private:
void SearchAsync(const int id, const QString &query, const SearchType type);
bool FindCachedPixmap(const InternetSearch::Result &result, QPixmap *pixmap) const;
QString PixmapCacheKey(const InternetSearch::Result &result) const;
void MaybeSearchFinished(const int id);
void ShowConfig() {}
static QImage ScaleAndPad(const QImage &image);
private:
struct DelayedSearch {
int id_;
QString query_;
SearchType type_;
};
static const int kArtHeight;
Application *app_;
Song::Source source_;
InternetService *service_;
int searches_next_id_;
int art_searches_next_id_;
QMap<int, DelayedSearch> delayed_searches_;
QMap<int, QString> pending_art_searches_;
QPixmapCache pixmap_cache_;
AlbumCoverLoaderOptions cover_loader_options_;
QMap<quint64, int> cover_loader_tasks_;
QMap<int, PendingState> pending_searches_;
};
Q_DECLARE_METATYPE(InternetSearch::Result)
Q_DECLARE_METATYPE(InternetSearch::ResultList)
#endif // INTERNETSEARCH_H

View File

@ -32,12 +32,14 @@
#include "core/mimedata.h"
#include "core/iconloader.h"
#include "internetsearch.h"
#include "internetsongmimedata.h"
#include "internetservice.h"
#include "internetsearchmodel.h"
#include "internetsearchview.h"
InternetSearchModel::InternetSearchModel(InternetSearch *engine, QObject *parent)
InternetSearchModel::InternetSearchModel(InternetService *service, QObject *parent)
: QStandardItemModel(parent),
engine_(engine),
service_(service),
proxy_(nullptr),
use_pretty_covers_(true),
artist_icon_(IconLoader::Load("folder-sound")),
@ -52,11 +54,11 @@ InternetSearchModel::InternetSearchModel(InternetSearch *engine, QObject *parent
}
void InternetSearchModel::AddResults(const InternetSearch::ResultList &results) {
void InternetSearchModel::AddResults(const InternetSearchView::ResultList &results) {
int sort_index = 0;
for (const InternetSearch::Result &result : results) {
for (const InternetSearchView::Result &result : results) {
QStandardItem *parent = invisibleRootItem();
// Find (or create) the container nodes for this result if we can.
@ -277,7 +279,7 @@ void InternetSearchModel::Clear() {
clear();
}
InternetSearch::ResultList InternetSearchModel::GetChildResults(const QModelIndexList &indexes) const {
InternetSearchView::ResultList InternetSearchModel::GetChildResults(const QModelIndexList &indexes) const {
QList<QStandardItem*> items;
for (const QModelIndex &index : indexes) {
@ -287,9 +289,9 @@ InternetSearch::ResultList InternetSearchModel::GetChildResults(const QModelInde
}
InternetSearch::ResultList InternetSearchModel::GetChildResults(const QList<QStandardItem*> &items) const {
InternetSearchView::ResultList InternetSearchModel::GetChildResults(const QList<QStandardItem*> &items) const {
InternetSearch::ResultList results;
InternetSearchView::ResultList results;
QSet<const QStandardItem*> visited;
for (QStandardItem *item : items) {
@ -300,7 +302,7 @@ InternetSearch::ResultList InternetSearchModel::GetChildResults(const QList<QSta
}
void InternetSearchModel::GetChildResults(const QStandardItem *item, InternetSearch::ResultList *results, QSet<const QStandardItem*> *visited) const {
void InternetSearchModel::GetChildResults(const QStandardItem *item, InternetSearchView::ResultList *results, QSet<const QStandardItem*> *visited) const {
if (visited->contains(item)) {
return;
@ -322,7 +324,7 @@ void InternetSearchModel::GetChildResults(const QStandardItem *item, InternetSea
// No - maybe it's a song, add its result if valid
QVariant result = item->data(Role_Result);
if (result.isValid()) {
results->append(result.value<InternetSearch::Result>());
results->append(result.value<InternetSearchView::Result>());
}
else {
// Maybe it's a provider then?
@ -344,15 +346,17 @@ void InternetSearchModel::GetChildResults(const QStandardItem *item, InternetSea
}
QMimeData *InternetSearchModel::mimeData(const QModelIndexList &indexes) const {
return engine_->LoadTracks(GetChildResults(indexes));
return LoadTracks(GetChildResults(indexes));
}
namespace {
void GatherResults(const QStandardItem *parent, InternetSearch::ResultList *results) {
void GatherResults(const QStandardItem *parent, InternetSearchView::ResultList *results) {
QVariant result_variant = parent->data(InternetSearchModel::Role_Result);
if (result_variant.isValid()) {
InternetSearch::Result result = result_variant.value<InternetSearch::Result>();
InternetSearchView::Result result = result_variant.value<InternetSearchView::Result>();
(*results).append(result);
}
@ -369,7 +373,7 @@ void InternetSearchModel::SetGroupBy(const CollectionModel::Grouping &grouping,
if (regroup_now && group_by_ != old_group_by) {
// Walk the tree gathering the results we have already
InternetSearch::ResultList results;
InternetSearchView::ResultList results;
GatherResults(invisibleRootItem(), &results);
// Reset the model and re-add all the results using the new grouping.
@ -378,3 +382,34 @@ void InternetSearchModel::SetGroupBy(const CollectionModel::Grouping &grouping,
}
}
MimeData *InternetSearchModel::LoadTracks(const InternetSearchView::ResultList &results) const {
if (results.isEmpty()) {
return nullptr;
}
InternetSearchView::ResultList results_copy;
for (const InternetSearchView::Result &result : results) {
results_copy << result;
}
SongList songs;
for (const InternetSearchView::Result &result : results) {
songs << result.metadata_;
}
InternetSongMimeData *internet_song_mime_data = new InternetSongMimeData(service_);
internet_song_mime_data->songs = songs;
MimeData *mime_data = internet_song_mime_data;
QList<QUrl> urls;
for (const InternetSearchView::Result &result : results) {
urls << result.metadata_.url();
}
mime_data->setUrls(urls);
return mime_data;
}

View File

@ -37,16 +37,19 @@
#include "core/song.h"
#include "collection/collectionmodel.h"
#include "internetsearch.h"
#include "internetsearchview.h"
class QMimeData;
class QSortFilterProxyModel;
class MimeData;
class InternetService;
class InternetSearchModel : public QStandardItemModel {
Q_OBJECT
public:
explicit InternetSearchModel(InternetSearch *engine, QObject *parent = nullptr);
explicit InternetSearchModel(InternetService *service, QObject *parent = nullptr);
enum Role {
Role_Result = CollectionModel::LastRole,
@ -61,25 +64,29 @@ class InternetSearchModel : public QStandardItemModel {
};
void set_proxy(QSortFilterProxyModel *proxy) { proxy_ = proxy; }
void set_use_pretty_covers(bool pretty) { use_pretty_covers_ = pretty; }
void set_use_pretty_covers(const bool pretty) { use_pretty_covers_ = pretty; }
void SetGroupBy(const CollectionModel::Grouping &grouping, bool regroup_now);
void Clear();
InternetSearch::ResultList GetChildResults(const QModelIndexList &indexes) const;
InternetSearch::ResultList GetChildResults(const QList<QStandardItem*> &items) const;
InternetSearchView::ResultList GetChildResults(const QModelIndexList &indexes) const;
InternetSearchView::ResultList GetChildResults(const QList<QStandardItem*> &items) const;
QMimeData *mimeData(const QModelIndexList &indexes) const;
// Loads tracks for results that were previously emitted by ResultsAvailable.
// The implementation creates a SongMimeData with one Song for each Result.
MimeData *LoadTracks(const InternetSearchView::ResultList &results) const;
public slots:
void AddResults(const InternetSearch::ResultList &results);
void AddResults(const InternetSearchView::ResultList &results);
private:
QStandardItem *BuildContainers(const Song &metadata, QStandardItem *parent, ContainerKey *key, int level = 0);
void GetChildResults(const QStandardItem *item, InternetSearch::ResultList *results, QSet<const QStandardItem*> *visited) const;
void GetChildResults(const QStandardItem *item, InternetSearchView::ResultList *results, QSet<const QStandardItem*> *visited) const;
private:
InternetSearch *engine_;
InternetService *service_;
QSortFilterProxyModel *proxy_;
bool use_pretty_covers_;
QIcon artist_icon_;

View File

@ -28,9 +28,9 @@
#include "core/song.h"
#include "collection/collectionmodel.h"
#include "internetsearch.h"
#include "internetsearchmodel.h"
#include "internetsearchsortmodel.h"
#include "internetsearchview.h"
InternetSearchSortModel::InternetSearchSortModel(QObject *parent)
: QSortFilterProxyModel(parent) {}
@ -58,8 +58,8 @@ bool InternetSearchSortModel::lessThan(const QModelIndex &left, const QModelInde
}
// Otherwise we're comparing songs. Sort by disc, track, then title.
const InternetSearch::Result r1 = left.data(InternetSearchModel::Role_Result).value<InternetSearch::Result>();
const InternetSearch::Result r2 = right.data(InternetSearchModel::Role_Result).value<InternetSearch::Result>();
const InternetSearchView::Result r1 = left.data(InternetSearchModel::Role_Result).value<InternetSearchView::Result>();
const InternetSearchView::Result r2 = right.data(InternetSearchModel::Role_Result).value<InternetSearchView::Result>();
#define CompareInt(field) \
if (r1.metadata_.field() < r2.metadata_.field()) return true; \

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
* Strawberry Music Player
* This code was part of Clementine (GlobalSearch)
* Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2018-2020, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -24,18 +24,25 @@
#include "config.h"
#include <QtGlobal>
#include <QObject>
#include <QWidget>
#include <QMap>
#include <QSet>
#include <QList>
#include <QMap>
#include <QString>
#include <QStringList>
#include <QUrl>
#include <QImage>
#include <QPixmap>
#include <QPixmapCache>
#include <QScopedPointer>
#include <QMetaType>
#include "core/song.h"
#include "collection/collectionmodel.h"
#include "covermanager/albumcoverloaderoptions.h"
#include "settings/settingsdialog.h"
#include "internetsearch.h"
class QSortFilterProxyModel;
class QMimeData;
@ -48,11 +55,13 @@ class QKeyEvent;
class QShowEvent;
class QHideEvent;
class QContextMenuEvent;
class QTimerEvent;
class QModelIndex;
class Application;
class MimeData;
class GroupByDialog;
class AlbumCoverLoader;
class InternetService;
class InternetSearchModel;
class Ui_InternetSearchView;
@ -63,80 +72,127 @@ class InternetSearchView : public QWidget {
explicit InternetSearchView(QWidget *parent = nullptr);
~InternetSearchView();
void Init(Application *app, InternetSearch *engine, const QString &settings_group, const SettingsDialog::Page settings_page, const bool artists = false, const bool albums = false, const bool songs = false);
enum SearchType {
SearchType_Artists = 1,
SearchType_Albums = 2,
SearchType_Songs = 3,
};
struct Result {
Song metadata_;
QString pixmap_cache_key_;
};
typedef QList<Result> ResultList;
static const int kSwapModelsTimeoutMsec;
void Init(Application *app, InternetService *service);
void LazyLoadAlbumCover(const QModelIndex &index);
protected:
struct PendingState {
PendingState() : orig_id_(-1) {}
PendingState(int orig_id, QStringList tokens) : orig_id_(orig_id), tokens_(tokens) {}
int orig_id_;
QStringList tokens_;
bool operator<(const PendingState &b) const {
return orig_id_ < b.orig_id_;
}
bool operator==(const PendingState &b) const {
return orig_id_ == b.orig_id_;
}
};
void showEvent(QShowEvent *e);
void hideEvent(QHideEvent *e);
bool eventFilter(QObject *object, QEvent *event);
bool eventFilter(QObject *object, QEvent *e);
void timerEvent(QTimerEvent *e);
public slots:
void ReloadSettings();
void StartSearch(const QString &query);
// These functions treat queries in the same way as CollectionQuery.
// They're useful for figuring out whether you got a result because it matched in the song title or the artist/album name.
static QStringList TokenizeQuery(const QString &query);
static bool Matches(const QStringList &tokens, const QString &string);
private:
struct DelayedSearch {
int id_;
QString query_;
SearchType type_;
};
bool SearchKeyEvent(QKeyEvent *e);
bool ResultsContextMenuEvent(QContextMenuEvent *e);
void FocusSearchField();
void OpenSettingsDialog();
MimeData *SelectedMimeData();
void SetSearchType(const SearchType type);
int SearchAsync(const QString &query, SearchType type);
void SearchAsync(const int id, const QString &query, const SearchType type);
void SearchError(const int id, const QString &error);
void CancelSearch(const int id);
QString PixmapCacheKey(const Result &result) const;
bool FindCachedPixmap(const Result &result, QPixmap *pixmap) const;
static QImage ScaleAndPad(const QImage &image);
int LoadAlbumCoverAsync(const Result &result);
signals:
void AddToPlaylist(QMimeData *data);
void AddArtistsSignal(SongList songs);
void AddAlbumsSignal(SongList songs);
void AddSongsSignal(SongList songs);
void AddToPlaylist(QMimeData*);
void AddArtistsSignal(SongList);
void AddAlbumsSignal(SongList);
void AddSongsSignal(SongList);
private slots:
void SwapModels();
void TextEdited(const QString &text);
void StartSearch(const QString &query);
void SearchDone(const int service_id, const SongList &songs, const QString &error);
void UpdateStatus(const int id, const QString &text);
void ProgressSetMaximum(const int id, const int progress);
void UpdateProgress(const int id, const int max);
void AddResults(const int id, const InternetSearch::ResultList &results);
void SearchError(const int id, const QString &error);
void AlbumCoverLoaded(const int id, const QPixmap &pixmap);
void AddResults(const int id, const ResultList &results);
void FocusOnFilter(QKeyEvent *event);
void FocusOnFilter(QKeyEvent *e);
void AddSelectedToPlaylist();
void LoadSelected();
void OpenSelectedInNewPlaylist();
void AddSelectedToPlaylistEnqueue();
void SearchForThis();
void SearchArtistsClicked(bool);
void SearchAlbumsClicked(bool);
void SearchSongsClicked(bool);
void GroupByClicked(QAction *action);
void SetSearchType(const InternetSearch::SearchType type);
void SetGroupBy(const CollectionModel::Grouping &g);
void AddArtists();
void AddAlbums();
void AddSongs();
void SearchForThis();
void OpenSettingsDialog();
void SearchArtistsClicked(const bool);
void SearchAlbumsClicked(const bool);
void SearchSongsClicked(const bool);
void GroupByClicked(QAction *action);
void SetGroupBy(const CollectionModel::Grouping &g);
void AlbumCoverLoaded(const quint64 id, const QUrl&, const QImage &image);
public slots:
void ReloadSettings();
private:
MimeData *SelectedMimeData();
bool SearchKeyEvent(QKeyEvent *event);
bool ResultsContextMenuEvent(QContextMenuEvent *event);
static const int kSwapModelsTimeoutMsec;
static const int kDelayedSearchTimeoutMs;
static const int kArtHeight;
private:
Application *app_;
InternetSearch *engine_;
QString settings_group_;
SettingsDialog::Page settings_page_;
InternetService *service_;
Ui_InternetSearchView *ui_;
QScopedPointer<GroupByDialog> group_by_dialog_;
bool artists_;
bool albums_;
bool songs_;
QMenu *context_menu_;
QList<QAction*> context_actions_;
QActionGroup *group_by_actions_;
int last_search_id_;
// Like graphics APIs have a front buffer and a back buffer, there's a front model and a back model
// The front model is the one that's shown in the UI and the back model is the one that lies in wait.
// current_model_ will point to either the front or the back model.
@ -148,13 +204,24 @@ class InternetSearchView : public QWidget {
QSortFilterProxyModel *back_proxy_;
QSortFilterProxyModel *current_proxy_;
QMap<int, QModelIndex> art_requests_;
QTimer *swap_models_timer_;
InternetSearch::SearchType search_type_;
bool error_;
SearchType search_type_;
bool search_error_;
int last_search_id_;
int searches_next_id_;
int art_searches_next_id_;
QMap<int, DelayedSearch> delayed_searches_;
QMap<int, PendingState> pending_searches_;
QMap<int, QString> pending_art_searches_;
QMap<int, QModelIndex> art_requests_;
AlbumCoverLoaderOptions cover_loader_options_;
QMap<quint64, quint64> cover_loader_tasks_;
QPixmapCache pixmap_cache_;
};
Q_DECLARE_METATYPE(InternetSearchView::Result)
Q_DECLARE_METATYPE(InternetSearchView::ResultList)
#endif // INTERNETSEARCHVIEW_H

View File

@ -10,6 +10,9 @@
<height>660</height>
</rect>
</property>
<property name="windowTitle">
<string>Internet Search View</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
@ -39,7 +42,7 @@
<item>
<widget class="QSearchField" name="search" native="true">
<property name="placeholderText" stdset="0">
<string>Search for anything</string>
<string/>
</property>
</widget>
</item>
@ -62,58 +65,65 @@
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="layout_searchby">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
<widget class="QWidget" name="widget_searchby" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
<item>
<widget class="QLabel" name="label_searchby">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Search type</string>
</property>
<property name="margin">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radiobutton_search_artists">
<property name="text">
<string>ar&amp;tists</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radiobutton_search_albums">
<property name="text">
<string>a&amp;lbums</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radiobutton_search_songs">
<property name="text">
<string>son&amp;gs</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>2</number>
</property>
<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="QRadioButton" name="radiobutton_search_artists">
<property name="text">
<string>artists</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radiobutton_search_albums">
<property name="text">
<string>albums</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radiobutton_search_songs">
<property name="text">
<string>songs</string>
</property>
</widget>
</item>
<item>
<spacer name="spacer_searchby">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="layout_progress">
@ -215,7 +225,7 @@
<x>0</x>
<y>0</y>
<width>398</width>
<height>511</height>
<height>528</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">

View File

@ -22,9 +22,10 @@
#include "internetservice.h"
#include "core/song.h"
#include "settings/settingsdialog.h"
class Application;
InternetService::InternetService(Song::Source source, const QString &name, const QString &url_scheme, Application *app, QObject *parent)
: QObject(parent), app_(app), source_(source), name_(name), url_scheme_(url_scheme) {
InternetService::InternetService(Song::Source source, const QString &name, const QString &url_scheme, const QString &settings_group, SettingsDialog::Page settings_page, Application *app, QObject *parent)
: QObject(parent), app_(app), source_(source), name_(name), url_scheme_(url_scheme), settings_group_(settings_group), settings_page_(settings_page) {
}

View File

@ -28,7 +28,8 @@
#include <QIcon>
#include "core/song.h"
#include "internetsearch.h"
#include "settings/settingsdialog.h"
#include "internetsearchview.h"
class QSortFilterProxyModel;
class Application;
@ -39,7 +40,7 @@ class InternetService : public QObject {
Q_OBJECT
public:
explicit InternetService(Song::Source source, const QString &name, const QString &url_scheme, Application *app, QObject *parent = nullptr);
explicit InternetService(Song::Source source, const QString &name, const QString &url_scheme, const QString &settings_group, SettingsDialog::Page settings_page, Application *app, QObject *parent = nullptr);
virtual ~InternetService() {}
virtual void Exit() {}
@ -47,13 +48,15 @@ class InternetService : public QObject {
virtual Song::Source source() const { return source_; }
virtual QString name() const { return name_; }
virtual QString url_scheme() const { return url_scheme_; }
virtual QString settings_group() const { return settings_group_; }
virtual SettingsDialog::Page settings_page() const { return settings_page_; }
virtual bool has_initial_load_settings() const { return false; }
virtual void InitialLoadSettings() {}
virtual void ReloadSettings() {}
virtual QIcon Icon() { return Song::IconForSource(source_); }
virtual bool oauth() { return false; }
virtual bool authenticated() { return false; }
virtual int Search(const QString &query, InternetSearch::SearchType type) { Q_UNUSED(query); Q_UNUSED(type); return 0; }
virtual int Search(const QString &query, InternetSearchView::SearchType type) { Q_UNUSED(query); Q_UNUSED(type); return 0; }
virtual void CancelSearch() {}
virtual CollectionBackend *artists_collection_backend() { return nullptr; }
@ -129,10 +132,13 @@ class InternetService : public QObject {
protected:
Application *app_;
private:
Song::Source source_;
QString name_;
QString url_scheme_;
QString settings_group_;
SettingsDialog::Page settings_page_;
};
Q_DECLARE_METATYPE(InternetService*)

View File

@ -57,15 +57,15 @@ InternetSongsView::InternetSongsView(Application *app, InternetService *service,
ui_->filter->SetCollectionModel(service_->songs_collection_model());
connect(ui_->view, SIGNAL(GetSongs()), SLOT(GetSongs()));
connect(ui_->view, SIGNAL(RemoveSongs(const SongList&)), service_, SIGNAL(RemoveSongs(const SongList&)));
connect(ui_->view, SIGNAL(RemoveSongs(SongList)), service_, SIGNAL(RemoveSongs(SongList)));
connect(ui_->refresh, SIGNAL(clicked()), SLOT(GetSongs()));
connect(ui_->close, SIGNAL(clicked()), SLOT(AbortGetSongs()));
connect(ui_->abort, SIGNAL(clicked()), SLOT(AbortGetSongs()));
connect(service_, SIGNAL(SongsResults(const SongList&, const QString&)), SLOT(SongsFinished(const SongList&, const QString&)));
connect(service_, SIGNAL(SongsUpdateStatus(const QString&)), ui_->status, SLOT(setText(const QString&)));
connect(service_, SIGNAL(SongsProgressSetMaximum(const int)), ui_->progressbar, SLOT(setMaximum(const int)));
connect(service_, SIGNAL(SongsUpdateProgress(const int)), ui_->progressbar, SLOT(setValue(const int)));
connect(service_, SIGNAL(SongsResults(SongList, QString)), SLOT(SongsFinished(SongList, QString)));
connect(service_, SIGNAL(SongsUpdateStatus(QString)), ui_->status, SLOT(setText(QString)));
connect(service_, SIGNAL(SongsProgressSetMaximum(int)), ui_->progressbar, SLOT(setMaximum(int)));
connect(service_, SIGNAL(SongsUpdateProgress(int)), ui_->progressbar, SLOT(setValue(int)));
connect(service_->songs_collection_model(), SIGNAL(TotalArtistCountUpdated(int)), ui_->view, SLOT(TotalArtistCountUpdated(int)));
connect(service_->songs_collection_model(), SIGNAL(TotalAlbumCountUpdated(int)), ui_->view, SLOT(TotalAlbumCountUpdated(int)));

View File

@ -40,14 +40,12 @@
#include "internettabsview.h"
#include "internetcollectionview.h"
#include "internetcollectionviewcontainer.h"
#include "internetsearchview.h"
#include "ui_internettabsview.h"
InternetTabsView::InternetTabsView(Application *app, InternetService *service, InternetSearch *engine, const QString &settings_group, const SettingsDialog::Page settings_page, QWidget *parent)
InternetTabsView::InternetTabsView(Application *app, InternetService *service, const QString &settings_group, const SettingsDialog::Page settings_page, QWidget *parent)
: QWidget(parent),
app_(app),
service_(service),
engine_(engine),
settings_group_(settings_group),
settings_page_(settings_page),
ui_(new Ui_InternetTabsView)
@ -55,10 +53,10 @@ InternetTabsView::InternetTabsView(Application *app, InternetService *service, I
ui_->setupUi(this);
ui_->search_view->Init(app, engine, settings_group, settings_page, service_->artists_collection_model(), service_->albums_collection_model(), service_->songs_collection_model());
connect(ui_->search_view, SIGNAL(AddArtistsSignal(const SongList&)), service_, SIGNAL(AddArtists(const SongList&)));
connect(ui_->search_view, SIGNAL(AddAlbumsSignal(const SongList&)), service_, SIGNAL(AddAlbums(const SongList&)));
connect(ui_->search_view, SIGNAL(AddSongsSignal(const SongList&)), service_, SIGNAL(AddSongs(const SongList&)));
ui_->search_view->Init(app, service);
connect(ui_->search_view, SIGNAL(AddArtistsSignal(SongList)), service_, SIGNAL(AddArtists(SongList)));
connect(ui_->search_view, SIGNAL(AddAlbumsSignal(SongList)), service_, SIGNAL(AddAlbums(SongList)));
connect(ui_->search_view, SIGNAL(AddSongsSignal(SongList)), service_, SIGNAL(AddSongs(SongList)));
if (service_->artists_collection_model()) {
ui_->artists_collection->stacked()->setCurrentWidget(ui_->artists_collection->internetcollection_page());
@ -70,15 +68,15 @@ InternetTabsView::InternetTabsView(Application *app, InternetService *service, I
ui_->artists_collection->filter()->SetCollectionModel(service_->artists_collection_model());
connect(ui_->artists_collection->view(), SIGNAL(GetSongs()), SLOT(GetArtists()));
connect(ui_->artists_collection->view(), SIGNAL(RemoveSongs(const SongList&)), service_, SIGNAL(RemoveArtists(const SongList&)));
connect(ui_->artists_collection->view(), SIGNAL(RemoveSongs(SongList)), service_, SIGNAL(RemoveArtists(SongList)));
connect(ui_->artists_collection->button_refresh(), SIGNAL(clicked()), SLOT(GetArtists()));
connect(ui_->artists_collection->button_close(), SIGNAL(clicked()), SLOT(AbortGetArtists()));
connect(ui_->artists_collection->button_abort(), SIGNAL(clicked()), SLOT(AbortGetArtists()));
connect(service_, SIGNAL(ArtistsResults(const SongList&, const QString&)), SLOT(ArtistsFinished(const SongList&, const QString&)));
connect(service_, SIGNAL(ArtistsUpdateStatus(const QString&)), ui_->artists_collection->status(), SLOT(setText(const QString&)));
connect(service_, SIGNAL(ArtistsProgressSetMaximum(const int)), ui_->artists_collection->progressbar(), SLOT(setMaximum(const int)));
connect(service_, SIGNAL(ArtistsUpdateProgress(const int)), ui_->artists_collection->progressbar(), SLOT(setValue(const int)));
connect(service_, SIGNAL(ArtistsResults(SongList, QString)), SLOT(ArtistsFinished(SongList, QString)));
connect(service_, SIGNAL(ArtistsUpdateStatus(QString)), ui_->artists_collection->status(), SLOT(setText(QString)));
connect(service_, SIGNAL(ArtistsProgressSetMaximum(int)), ui_->artists_collection->progressbar(), SLOT(setMaximum(int)));
connect(service_, SIGNAL(ArtistsUpdateProgress(int)), ui_->artists_collection->progressbar(), SLOT(setValue(int)));
connect(service_->artists_collection_model(), SIGNAL(TotalArtistCountUpdated(int)), ui_->artists_collection->view(), SLOT(TotalArtistCountUpdated(int)));
connect(service_->artists_collection_model(), SIGNAL(TotalAlbumCountUpdated(int)), ui_->artists_collection->view(), SLOT(TotalAlbumCountUpdated(int)));
@ -101,15 +99,15 @@ InternetTabsView::InternetTabsView(Application *app, InternetService *service, I
ui_->albums_collection->filter()->SetCollectionModel(service_->albums_collection_model());
connect(ui_->albums_collection->view(), SIGNAL(GetSongs()), SLOT(GetAlbums()));
connect(ui_->albums_collection->view(), SIGNAL(RemoveSongs(const SongList&)), service_, SIGNAL(RemoveAlbums(const SongList&)));
connect(ui_->albums_collection->view(), SIGNAL(RemoveSongs(SongList)), service_, SIGNAL(RemoveAlbums(SongList)));
connect(ui_->albums_collection->button_refresh(), SIGNAL(clicked()), SLOT(GetAlbums()));
connect(ui_->albums_collection->button_close(), SIGNAL(clicked()), SLOT(AbortGetAlbums()));
connect(ui_->albums_collection->button_abort(), SIGNAL(clicked()), SLOT(AbortGetAlbums()));
connect(service_, SIGNAL(AlbumsResults(const SongList&, const QString&)), SLOT(AlbumsFinished(const SongList&, const QString&)));
connect(service_, SIGNAL(AlbumsUpdateStatus(const QString&)), ui_->albums_collection->status(), SLOT(setText(const QString&)));
connect(service_, SIGNAL(AlbumsProgressSetMaximum(const int)), ui_->albums_collection->progressbar(), SLOT(setMaximum(const int)));
connect(service_, SIGNAL(AlbumsUpdateProgress(const int)), ui_->albums_collection->progressbar(), SLOT(setValue(const int)));
connect(service_, SIGNAL(AlbumsResults(SongList, QString)), SLOT(AlbumsFinished(SongList, QString)));
connect(service_, SIGNAL(AlbumsUpdateStatus(QString)), ui_->albums_collection->status(), SLOT(setText(QString)));
connect(service_, SIGNAL(AlbumsProgressSetMaximum(int)), ui_->albums_collection->progressbar(), SLOT(setMaximum(int)));
connect(service_, SIGNAL(AlbumsUpdateProgress(int)), ui_->albums_collection->progressbar(), SLOT(setValue(int)));
connect(service_->albums_collection_model(), SIGNAL(TotalArtistCountUpdated(int)), ui_->albums_collection->view(), SLOT(TotalArtistCountUpdated(int)));
connect(service_->albums_collection_model(), SIGNAL(TotalAlbumCountUpdated(int)), ui_->albums_collection->view(), SLOT(TotalAlbumCountUpdated(int)));
@ -132,15 +130,15 @@ InternetTabsView::InternetTabsView(Application *app, InternetService *service, I
ui_->songs_collection->filter()->SetCollectionModel(service_->songs_collection_model());
connect(ui_->songs_collection->view(), SIGNAL(GetSongs()), SLOT(GetSongs()));
connect(ui_->songs_collection->view(), SIGNAL(RemoveSongs(const SongList&)), service_, SIGNAL(RemoveSongs(const SongList&)));
connect(ui_->songs_collection->view(), SIGNAL(RemoveSongs(SongList)), service_, SIGNAL(RemoveSongs(SongList)));
connect(ui_->songs_collection->button_refresh(), SIGNAL(clicked()), SLOT(GetSongs()));
connect(ui_->songs_collection->button_close(), SIGNAL(clicked()), SLOT(AbortGetSongs()));
connect(ui_->songs_collection->button_abort(), SIGNAL(clicked()), SLOT(AbortGetSongs()));
connect(service_, SIGNAL(SongsResults(const SongList&, const QString&)), SLOT(SongsFinished(const SongList&, const QString&)));
connect(service_, SIGNAL(SongsUpdateStatus(const QString&)), ui_->songs_collection->status(), SLOT(setText(const QString&)));
connect(service_, SIGNAL(SongsProgressSetMaximum(const int)), ui_->songs_collection->progressbar(), SLOT(setMaximum(const int)));
connect(service_, SIGNAL(SongsUpdateProgress(const int)), ui_->songs_collection->progressbar(), SLOT(setValue(const int)));
connect(service_, SIGNAL(SongsResults(SongList, QString)), SLOT(SongsFinished(SongList, QString)));
connect(service_, SIGNAL(SongsUpdateStatus(QString)), ui_->songs_collection->status(), SLOT(setText(QString)));
connect(service_, SIGNAL(SongsProgressSetMaximum(int)), ui_->songs_collection->progressbar(), SLOT(setMaximum(int)));
connect(service_, SIGNAL(SongsUpdateProgress(int)), ui_->songs_collection->progressbar(), SLOT(setValue(int)));
connect(service_->songs_collection_model(), SIGNAL(TotalArtistCountUpdated(int)), ui_->songs_collection->view(), SLOT(TotalArtistCountUpdated(int)));
connect(service_->songs_collection_model(), SIGNAL(TotalAlbumCountUpdated(int)), ui_->songs_collection->view(), SLOT(TotalAlbumCountUpdated(int)));

View File

@ -35,7 +35,6 @@ class QContextMenuEvent;
class Application;
class InternetService;
class InternetSearch;
class InternetCollectionView;
class InternetSearchView;
@ -43,7 +42,7 @@ class InternetTabsView : public QWidget {
Q_OBJECT
public:
explicit InternetTabsView(Application *app, InternetService *service, InternetSearch *engine, const QString &settings_group, const SettingsDialog::Page settings_page, QWidget *parent = nullptr);
explicit InternetTabsView(Application *app, InternetService *service, const QString &settings_group, const SettingsDialog::Page settings_page, QWidget *parent = nullptr);
~InternetTabsView();
void ReloadSettings();
@ -68,7 +67,6 @@ class InternetTabsView : public QWidget {
private:
Application *app_;
InternetService *service_;
InternetSearch *engine_;
QString settings_group_;
SettingsDialog::Page settings_page_;
Ui_InternetTabsView *ui_;

View File

@ -10,9 +10,18 @@
<height>660</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="windowTitle">
<string>Internet Tabs View</string>
</property>
<layout class="QVBoxLayout" name="layout_internettabsview">
<item>
<widget class="QTabWidget" name="tabs">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
</property>

View File

@ -67,7 +67,7 @@ const char *SubsonicService::kSongsFtsTable = "subsonic_songs_fts";
const int SubsonicService::kMaxRedirects = 3;
SubsonicService::SubsonicService(Application *app, QObject *parent)
: InternetService(Song::Source_Subsonic, "Subsonic", "subsonic", app, parent),
: InternetService(Song::Source_Subsonic, "Subsonic", "subsonic", SubsonicSettingsPage::kSettingsGroup, SettingsDialog::Page_Subsonic, app, parent),
app_(app),
network_(new QNetworkAccessManager),
url_handler_(new SubsonicUrlHandler(app, this)),