From 2f72c41cdae0501e3de307658fbc000d2d3a12f3 Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Mon, 13 Apr 2020 06:30:40 +0200 Subject: [PATCH] Improve internet classes --- src/CMakeLists.txt | 2 - src/core/metatypes.cpp | 6 +- src/internet/internetsearch.cpp | 318 -------- src/internet/internetsearch.h | 167 ---- src/internet/internetsearchmodel.cpp | 63 +- src/internet/internetsearchmodel.h | 23 +- src/internet/internetsearchsortmodel.cpp | 6 +- src/internet/internetsearchview.cpp | 951 ++++++++++++++--------- src/internet/internetsearchview.h | 157 ++-- src/internet/internetsearchview.ui | 116 +-- src/internet/internetservice.cpp | 5 +- src/internet/internetservice.h | 12 +- src/internet/internetsongsview.cpp | 10 +- src/internet/internettabsview.cpp | 42 +- src/internet/internettabsview.h | 4 +- src/internet/internettabsview.ui | 11 +- src/subsonic/subsonicservice.cpp | 2 +- 17 files changed, 880 insertions(+), 1015 deletions(-) delete mode 100644 src/internet/internetsearch.cpp delete mode 100644 src/internet/internetsearch.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 112ddc0c..f1b8f237 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/core/metatypes.cpp b/src/core/metatypes.cpp index e01ad780..0ed47731 100644 --- a/src/core/metatypes.cpp +++ b/src/core/metatypes.cpp @@ -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"); - qRegisterMetaType("InternetSearch::Result"); + qRegisterMetaType("InternetSearchView::ResultList"); + qRegisterMetaType("InternetSearchView::Result"); } diff --git a/src/internet/internetsearch.cpp b/src/internet/internetsearch.cpp deleted file mode 100644 index 0fe10442..00000000 --- a/src/internet/internetsearch.cpp +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Strawberry Music Player - * This code was part of Clementine (GlobalSearch) - * Copyright 2010, David Sansome - * Copyright 2018, Jonas Kvinge - * - * 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 . - * - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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::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::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 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); - -} diff --git a/src/internet/internetsearch.h b/src/internet/internetsearch.h deleted file mode 100644 index 4b2874c0..00000000 --- a/src/internet/internetsearch.h +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Strawberry Music Player - * This code was part of Clementine (GlobalSearch) - * Copyright 2010, David Sansome - * Copyright 2018, Jonas Kvinge - * - * 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 . - * - */ - -#ifndef INTERNETSEARCH_H -#define INTERNETSEARCH_H - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 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 delayed_searches_; - QMap pending_art_searches_; - QPixmapCache pixmap_cache_; - AlbumCoverLoaderOptions cover_loader_options_; - QMap cover_loader_tasks_; - - QMap pending_searches_; - -}; - -Q_DECLARE_METATYPE(InternetSearch::Result) -Q_DECLARE_METATYPE(InternetSearch::ResultList) - -#endif // INTERNETSEARCH_H diff --git a/src/internet/internetsearchmodel.cpp b/src/internet/internetsearchmodel.cpp index f2120b65..c4785e9c 100644 --- a/src/internet/internetsearchmodel.cpp +++ b/src/internet/internetsearchmodel.cpp @@ -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 items; for (const QModelIndex &index : indexes) { @@ -287,9 +289,9 @@ InternetSearch::ResultList InternetSearchModel::GetChildResults(const QModelInde } -InternetSearch::ResultList InternetSearchModel::GetChildResults(const QList &items) const { +InternetSearchView::ResultList InternetSearchModel::GetChildResults(const QList &items) const { - InternetSearch::ResultList results; + InternetSearchView::ResultList results; QSet visited; for (QStandardItem *item : items) { @@ -300,7 +302,7 @@ InternetSearch::ResultList InternetSearchModel::GetChildResults(const QList *visited) const { +void InternetSearchModel::GetChildResults(const QStandardItem *item, InternetSearchView::ResultList *results, QSet *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()); + results->append(result.value()); } 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(); + InternetSearchView::Result result = result_variant.value(); (*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 urls; + for (const InternetSearchView::Result &result : results) { + urls << result.metadata_.url(); + } + mime_data->setUrls(urls); + + return mime_data; + +} + diff --git a/src/internet/internetsearchmodel.h b/src/internet/internetsearchmodel.h index 0628a434..5e8729c2 100644 --- a/src/internet/internetsearchmodel.h +++ b/src/internet/internetsearchmodel.h @@ -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 &items) const; + InternetSearchView::ResultList GetChildResults(const QModelIndexList &indexes) const; + InternetSearchView::ResultList GetChildResults(const QList &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 *visited) const; + void GetChildResults(const QStandardItem *item, InternetSearchView::ResultList *results, QSet *visited) const; private: - InternetSearch *engine_; + InternetService *service_; QSortFilterProxyModel *proxy_; bool use_pretty_covers_; QIcon artist_icon_; diff --git a/src/internet/internetsearchsortmodel.cpp b/src/internet/internetsearchsortmodel.cpp index 7bdae756..29aee512 100644 --- a/src/internet/internetsearchsortmodel.cpp +++ b/src/internet/internetsearchsortmodel.cpp @@ -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(); - const InternetSearch::Result r2 = right.data(InternetSearchModel::Role_Result).value(); + const InternetSearchView::Result r1 = left.data(InternetSearchModel::Role_Result).value(); + const InternetSearchView::Result r2 = right.data(InternetSearchModel::Role_Result).value(); #define CompareInt(field) \ if (r1.metadata_.field() < r2.metadata_.field()) return true; \ diff --git a/src/internet/internetsearchview.cpp b/src/internet/internetsearchview.cpp index d9b7a09a..35fee56d 100644 --- a/src/internet/internetsearchview.cpp +++ b/src/internet/internetsearchview.cpp @@ -2,7 +2,7 @@ * Strawberry Music Player * This code was part of Clementine (GlobalSearch) * Copyright 2012, David Sansome - * Copyright 2018, Jonas Kvinge + * Copyright 2018-2020, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,29 +21,60 @@ #include "config.h" +#include + #include +#include +#include +#include +#include +#include +#include #include #include #include +#include +#include #include +#include +#include +#include +#include #include +#include #include #include #include +#include #include #include #include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "core/application.h" #include "core/mimedata.h" #include "core/iconloader.h" -#include "internet/internetsongmimedata.h" +#include "core/song.h" #include "collection/collectionfilterwidget.h" #include "collection/collectionmodel.h" #include "collection/groupbydialog.h" -#include "internetsearch.h" +#include "covermanager/albumcoverloader.h" +#include "internetsongmimedata.h" +#include "internetservice.h" #include "internetsearchitemdelegate.h" #include "internetsearchmodel.h" #include "internetsearchsortmodel.h" @@ -52,17 +83,18 @@ using std::placeholders::_1; using std::placeholders::_2; -using std::swap; const int InternetSearchView::kSwapModelsTimeoutMsec = 250; +const int InternetSearchView::kDelayedSearchTimeoutMs = 200; +const int InternetSearchView::kArtHeight = 32; InternetSearchView::InternetSearchView(QWidget *parent) : QWidget(parent), app_(nullptr), - engine_(nullptr), + service_(nullptr), ui_(new Ui_InternetSearchView), context_menu_(nullptr), - last_search_id_(0), + group_by_actions_(nullptr), front_model_(nullptr), back_model_(nullptr), current_model_(nullptr), @@ -70,14 +102,14 @@ InternetSearchView::InternetSearchView(QWidget *parent) back_proxy_(new InternetSearchSortModel(this)), current_proxy_(front_proxy_), swap_models_timer_(new QTimer(this)), - error_(false) - { + search_type_(InternetSearchView::SearchType_Artists), + search_error_(false), + last_search_id_(0), + searches_next_id_(1), + art_searches_next_id_(1) { ui_->setupUi(this); - ui_->progressbar->hide(); - ui_->progressbar->reset(); - ui_->search->installEventFilter(this); ui_->results_stack->installEventFilter(this); @@ -104,22 +136,25 @@ InternetSearchView::InternetSearchView(QWidget *parent) help_font.setBold(true); ui_->label_helptext->setFont(help_font); + // Hide progressbar + ui_->progressbar->hide(); + ui_->progressbar->reset(); + + cover_loader_options_.desired_height_ = kArtHeight; + cover_loader_options_.pad_output_image_ = true; + cover_loader_options_.scale_output_image_ = true; + } InternetSearchView::~InternetSearchView() { delete ui_; } -void InternetSearchView::Init(Application *app, InternetSearch *engine, const QString &settings_group, const SettingsDialog::Page settings_page, const bool artists, const bool albums, const bool songs) { +void InternetSearchView::Init(Application *app, InternetService *service) { app_ = app; - engine_ = engine; - settings_group_ = settings_group; - settings_page_ = settings_page; - artists_ = artists; - albums_ = albums; - songs_ = songs; + service_ = service; - front_model_ = new InternetSearchModel(engine_, this); - back_model_ = new InternetSearchModel(engine_, this); + front_model_ = new InternetSearchModel(service, this); + back_model_ = new InternetSearchModel(service, this); front_proxy_ = new InternetSearchSortModel(this); back_proxy_ = new InternetSearchSortModel(this); @@ -129,9 +164,6 @@ void InternetSearchView::Init(Application *app, InternetSearch *engine, const QS current_model_ = front_model_; current_proxy_ = front_proxy_; - - // Must be a queued connection to ensure the InternetSearch handles it first. - connect(app_, SIGNAL(SettingsChanged()), SLOT(ReloadSettings()), Qt::QueuedConnection); // Set up the sorting proxy model front_proxy_->setSourceModel(front_model_); @@ -147,7 +179,7 @@ void InternetSearchView::Init(Application *app, InternetSearch *engine, const QS QMenu *settings_menu = new QMenu(this); settings_menu->addActions(group_by_actions_->actions()); settings_menu->addSeparator(); - settings_menu->addAction(IconLoader::Load("configure"), tr("Configure %1...").arg(Song::TextForSource(engine->source())), this, SLOT(OpenSettingsDialog())); + settings_menu->addAction(IconLoader::Load("configure"), tr("Configure %1...").arg(Song::TextForSource(service_->source())), this, SLOT(OpenSettingsDialog())); ui_->settings->setMenu(settings_menu); swap_models_timer_->setSingleShot(true); @@ -163,15 +195,13 @@ void InternetSearchView::Init(Application *app, InternetSearch *engine, const QS connect(ui_->results, SIGNAL(AddToPlaylistSignal(QMimeData*)), SIGNAL(AddToPlaylist(QMimeData*))); connect(ui_->results, SIGNAL(FocusOnFilterSignal(QKeyEvent*)), SLOT(FocusOnFilter(QKeyEvent*))); - // These have to be queued connections because they may get emitted before our call to Search() (or whatever) returns and we add the ID to the map. + connect(service_, SIGNAL(SearchUpdateStatus(int, QString)), SLOT(UpdateStatus(int, QString))); + connect(service_, SIGNAL(SearchProgressSetMaximum(int, int)), SLOT(ProgressSetMaximum(int, int))); + connect(service_, SIGNAL(SearchUpdateProgress(int, int)), SLOT(UpdateProgress(int, int))); + connect(service_, SIGNAL(SearchResults(int, SongList, QString)), SLOT(SearchDone(int, SongList, QString))); - connect(engine_, SIGNAL(UpdateStatus(const int, const QString&)), SLOT(UpdateStatus(const int, const QString&))); - connect(engine_, SIGNAL(ProgressSetMaximum(const int, const int)), SLOT(ProgressSetMaximum(const int, const int)), Qt::QueuedConnection); - connect(engine_, SIGNAL(UpdateProgress(const int, const int)), SLOT(UpdateProgress(const int, const int)), Qt::QueuedConnection); - - connect(engine_, SIGNAL(AddResults(const int, InternetSearch::ResultList)), SLOT(AddResults(const int, const InternetSearch::ResultList)), Qt::QueuedConnection); - connect(engine_, SIGNAL(SearchError(const int, const QString&)), SLOT(SearchError(const int, const QString&)), Qt::QueuedConnection); - connect(engine_, SIGNAL(AlbumCoverLoaded(const int, const QPixmap&)), SLOT(AlbumCoverLoaded(const int, const QPixmap&)), Qt::QueuedConnection); + connect(app_, SIGNAL(SettingsChanged()), SLOT(ReloadSettings())); + connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(AlbumCoverLoaded(quint64, QUrl, QImage))); ReloadSettings(); @@ -183,22 +213,22 @@ void InternetSearchView::ReloadSettings() { // Collection settings - s.beginGroup(settings_group_); + s.beginGroup(service_->settings_group()); const bool pretty = s.value("pretty_covers", true).toBool(); front_model_->set_use_pretty_covers(pretty); back_model_->set_use_pretty_covers(pretty); // Internet search settings - search_type_ = InternetSearch::SearchType(s.value("type", int(InternetSearch::SearchType_Artists)).toInt()); + search_type_ = InternetSearchView::SearchType(s.value("type", int(InternetSearchView::SearchType_Artists)).toInt()); switch (search_type_) { - case InternetSearch::SearchType_Artists: + case InternetSearchView::SearchType_Artists: ui_->radiobutton_search_artists->setChecked(true); break; - case InternetSearch::SearchType_Albums: + case InternetSearchView::SearchType_Albums: ui_->radiobutton_search_albums->setChecked(true); break; - case InternetSearch::SearchType_Songs: + case InternetSearchView::SearchType_Songs: ui_->radiobutton_search_songs->setChecked(true); break; } @@ -211,6 +241,123 @@ void InternetSearchView::ReloadSettings() { } +void InternetSearchView::showEvent(QShowEvent *e) { + + QWidget::showEvent(e); + FocusSearchField(); + +} + +void InternetSearchView::hideEvent(QHideEvent *e) { + + QWidget::hideEvent(e); + +} + +bool InternetSearchView::eventFilter(QObject *object, QEvent *e) { + + if (object == ui_->search && e->type() == QEvent::KeyRelease) { + if (SearchKeyEvent(static_cast(e))) { + return true; + } + } + else if (object == ui_->results_stack && e->type() == QEvent::ContextMenu) { + if (ResultsContextMenuEvent(static_cast(e))) { + return true; + } + } + + return QWidget::eventFilter(object, e); + +} + +bool InternetSearchView::SearchKeyEvent(QKeyEvent *e) { + + switch (e->key()) { + case Qt::Key_Up: + ui_->results->UpAndFocus(); + break; + + case Qt::Key_Down: + ui_->results->DownAndFocus(); + break; + + case Qt::Key_Escape: + ui_->search->clear(); + break; + + case Qt::Key_Return: + TextEdited(ui_->search->text()); + break; + + default: + return false; + } + + e->accept(); + return true; + +} + +bool InternetSearchView::ResultsContextMenuEvent(QContextMenuEvent *e) { + + context_menu_ = new QMenu(this); + context_actions_ << context_menu_->addAction( IconLoader::Load("media-playback-start"), tr("Append to current playlist"), this, SLOT(AddSelectedToPlaylist())); + context_actions_ << context_menu_->addAction( IconLoader::Load("media-playback-start"), tr("Replace current playlist"), this, SLOT(LoadSelected())); + context_actions_ << context_menu_->addAction( IconLoader::Load("document-new"), tr("Open in new playlist"), this, SLOT(OpenSelectedInNewPlaylist())); + + context_menu_->addSeparator(); + context_actions_ << context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue track"), this, SLOT(AddSelectedToPlaylistEnqueue())); + + context_menu_->addSeparator(); + + if (service_->artists_collection_model() || service_->albums_collection_model() || service_->songs_collection_model()) { + if (service_->artists_collection_model()) { + context_actions_ << context_menu_->addAction(IconLoader::Load("folder-new"), tr("Add to artists"), this, SLOT(AddArtists())); + } + if (service_->albums_collection_model()) { + context_actions_ << context_menu_->addAction(IconLoader::Load("folder-new"), tr("Add to albums"), this, SLOT(AddAlbums())); + } + if (service_->songs_collection_model()) { + context_actions_ << context_menu_->addAction(IconLoader::Load("folder-new"), tr("Add to songs"), this, SLOT(AddSongs())); + } + context_menu_->addSeparator(); + } + + if (ui_->results->selectionModel() && ui_->results->selectionModel()->selectedRows().length() == 1) { + context_actions_ << context_menu_->addAction(IconLoader::Load("search"), tr("Search for this"), this, SLOT(SearchForThis())); + } + + context_menu_->addSeparator(); + context_menu_->addMenu(tr("Group by"))->addActions(group_by_actions_->actions()); + + context_menu_->addAction(IconLoader::Load("configure"), tr("Configure %1...").arg(Song::TextForSource(service_->source())), this, SLOT(OpenSettingsDialog())); + + const bool enable_context_actions = ui_->results->selectionModel() && ui_->results->selectionModel()->hasSelection(); + + for (QAction *action : context_actions_) { + action->setEnabled(enable_context_actions); + } + + context_menu_->popup(e->globalPos()); + + return true; + +} + +void InternetSearchView::timerEvent(QTimerEvent *e) { + + QMap::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); + +} + void InternetSearchView::StartSearch(const QString &query) { ui_->search->setText(query); @@ -226,7 +373,7 @@ void InternetSearchView::TextEdited(const QString &text) { const QString trimmed(text.trimmed()); - error_ = false; + search_error_ = false; // Add results to the back model, switch models after some delay. back_model_->Clear(); @@ -235,7 +382,8 @@ void InternetSearchView::TextEdited(const QString &text) { swap_models_timer_->start(); // Cancel the last search (if any) and start the new one. - engine_->CancelSearch(last_search_id_); + CancelSearch(last_search_id_); + // If text query is empty, don't start a new search if (trimmed.isEmpty()) { last_search_id_ = -1; @@ -246,34 +394,11 @@ void InternetSearchView::TextEdited(const QString &text) { } else { ui_->progressbar->reset(); - last_search_id_ = engine_->SearchAsync(trimmed, search_type_); + last_search_id_ = SearchAsync(trimmed, search_type_); } } -void InternetSearchView::AddResults(const int id, const InternetSearch::ResultList &results) { - - if (id != last_search_id_) return; - if (results.isEmpty()) return; - ui_->label_status->clear(); - ui_->progressbar->reset(); - ui_->progressbar->hide(); - current_model_->AddResults(results); - -} - -void InternetSearchView::SearchError(const int id, const QString &error) { - - if (id != last_search_id_) return; - error_ = true; - ui_->label_helptext->setText(error); - ui_->label_status->clear(); - ui_->progressbar->reset(); - ui_->progressbar->hide(); - ui_->results_stack->setCurrentWidget(ui_->help_page); - -} - void InternetSearchView::SwapModels() { art_requests_.clear(); @@ -283,7 +408,7 @@ void InternetSearchView::SwapModels() { ui_->results->setModel(front_proxy_); - if (ui_->search->text().trimmed().isEmpty() || error_) { + if (ui_->search->text().trimmed().isEmpty() || search_error_) { ui_->results_stack->setCurrentWidget(ui_->help_page); } else { @@ -292,6 +417,370 @@ void InternetSearchView::SwapModels() { } +QStringList InternetSearchView::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 InternetSearchView::Matches(const QStringList &tokens, const QString &string) { + + for (const QString &token : tokens) { + if (!string.contains(token, Qt::CaseInsensitive)) { + return false; + } + } + + return true; + +} + +int InternetSearchView::SearchAsync(const QString &query, const SearchType type) { + + const int id = searches_next_id_++; + + int timer_id = startTimer(kDelayedSearchTimeoutMs); + delayed_searches_[timer_id].id_ = id; + delayed_searches_[timer_id].query_ = query; + delayed_searches_[timer_id].type_ = type; + + return id; + +} + +void InternetSearchView::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 InternetSearchView::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()) { + 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 (InternetSearchView::ResultList::iterator it = results.begin(); it != results.end(); ++it) { + it->pixmap_cache_key_ = PixmapCacheKey(*it); + } + + AddResults(search_id, results); + +} + +void InternetSearchView::CancelSearch(const int id) { + + QMap::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 InternetSearchView::AddResults(const int id, const InternetSearchView::ResultList &results) { + + if (id != last_search_id_ || results.isEmpty()) return; + + ui_->label_status->clear(); + ui_->progressbar->reset(); + ui_->progressbar->hide(); + current_model_->AddResults(results); + +} + +void InternetSearchView::SearchError(const int id, const QString &error) { + + if (id != last_search_id_) return; + + search_error_ = true; + ui_->label_helptext->setText(error); + ui_->label_status->clear(); + ui_->progressbar->reset(); + ui_->progressbar->hide(); + ui_->results_stack->setCurrentWidget(ui_->help_page); + +} + +void InternetSearchView::UpdateStatus(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_; + if (search_id != last_search_id_) return; + ui_->progressbar->show(); + ui_->label_status->setText(text); + +} + +void InternetSearchView::ProgressSetMaximum(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_; + if (search_id != last_search_id_) return; + ui_->progressbar->setMaximum(max); + +} + +void InternetSearchView::UpdateProgress(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_; + if (search_id != last_search_id_) return; + ui_->progressbar->setValue(progress); + +} + +MimeData *InternetSearchView::SelectedMimeData() { + + if (!ui_->results->selectionModel()) return nullptr; + + // Get all selected model indexes + QModelIndexList indexes = ui_->results->selectionModel()->selectedRows(); + if (indexes.isEmpty()) { + // There's nothing selected - take the first thing in the model that isn't a divider. + for (int i = 0; i < front_proxy_->rowCount(); ++i) { + QModelIndex index = front_proxy_->index(i, 0); + if (!index.data(CollectionModel::Role_IsDivider).toBool()) { + indexes << index; + ui_->results->setCurrentIndex(index); + break; + } + } + } + + // Still got nothing? Give up. + if (indexes.isEmpty()) { + return nullptr; + } + + // Get items for these indexes + QList items; + for (const QModelIndex &index : indexes) { + items << (front_model_->itemFromIndex(front_proxy_->mapToSource(index))); + } + + // Get a MimeData for these items + return front_model_->LoadTracks(front_model_->GetChildResults(items)); + +} + +void InternetSearchView::AddSelectedToPlaylist() { + emit AddToPlaylist(SelectedMimeData()); +} + +void InternetSearchView::LoadSelected() { + + MimeData *data = SelectedMimeData(); + if (!data) return; + + data->clear_first_ = true; + emit AddToPlaylist(data); + +} + +void InternetSearchView::AddSelectedToPlaylistEnqueue() { + + MimeData *data = SelectedMimeData(); + if (!data) return; + + data->enqueue_now_ = true; + emit AddToPlaylist(data); + +} + +void InternetSearchView::OpenSelectedInNewPlaylist() { + + MimeData *data = SelectedMimeData(); + if (!data) return; + + data->open_in_new_playlist_ = true; + emit AddToPlaylist(data); + +} + +void InternetSearchView::SearchForThis() { + StartSearch(ui_->results->selectionModel()->selectedRows().first().data().toString()); +} + +void InternetSearchView::FocusSearchField() { + + ui_->search->setFocus(); + ui_->search->selectAll(); + +} + +void InternetSearchView::FocusOnFilter(QKeyEvent *e) { + + ui_->search->setFocus(); + QApplication::sendEvent(ui_->search, e); + +} + +void InternetSearchView::OpenSettingsDialog() { + app_->OpenSettingsDialogAtPage(service_->settings_page()); +} + +void InternetSearchView::GroupByClicked(QAction *action) { + + if (action->property("group_by").isNull()) { + if (!group_by_dialog_) { + group_by_dialog_.reset(new GroupByDialog); + connect(group_by_dialog_.data(), SIGNAL(Accepted(CollectionModel::Grouping)), SLOT(SetGroupBy(CollectionModel::Grouping))); + } + + group_by_dialog_->show(); + return; + } + + SetGroupBy(action->property("group_by").value()); + +} + +void InternetSearchView::SetGroupBy(const CollectionModel::Grouping &g) { + + // Clear requests: changing "group by" on the models will cause all the items to be removed/added again, + // so all the QModelIndex here will become invalid. New requests will be created for those + // songs when they will be displayed again anyway (when InternetSearchItemDelegate::paint will call LazyLoadAlbumCover) + art_requests_.clear(); + // Update the models + front_model_->SetGroupBy(g, true); + back_model_->SetGroupBy(g, false); + + // Save the setting + QSettings s; + s.beginGroup(service_->settings_group()); + s.setValue("search_group_by1", int(g.first)); + s.setValue("search_group_by2", int(g.second)); + s.setValue("search_group_by3", int(g.third)); + s.endGroup(); + + // Make sure the correct action is checked. + for (QAction *action : group_by_actions_->actions()) { + if (action->property("group_by").isNull()) continue; + + if (g == action->property("group_by").value()) { + action->setChecked(true); + return; + } + } + + // Check the advanced action + group_by_actions_->actions().last()->setChecked(true); + +} + +void InternetSearchView::SearchArtistsClicked(const bool) { + SetSearchType(InternetSearchView::SearchType_Artists); +} + +void InternetSearchView::SearchAlbumsClicked(const bool) { + SetSearchType(InternetSearchView::SearchType_Albums); +} + +void InternetSearchView::SearchSongsClicked(const bool) { + SetSearchType(InternetSearchView::SearchType_Songs); +} + +void InternetSearchView::SetSearchType(const InternetSearchView::SearchType type) { + + search_type_ = type; + QSettings s; + s.beginGroup(service_->settings_group()); + s.setValue("type", int(search_type_)); + s.endGroup(); + TextEdited(ui_->search->text()); + +} + +void InternetSearchView::AddArtists() { + + MimeData *data = SelectedMimeData(); + if (!data) return; + if (const InternetSongMimeData *internet_song_data = qobject_cast(data)) { + emit AddArtistsSignal(internet_song_data->songs); + } + +} + +void InternetSearchView::AddAlbums() { + + MimeData *data = SelectedMimeData(); + if (!data) return; + if (const InternetSongMimeData *internet_song_data = qobject_cast(data)) { + emit AddAlbumsSignal(internet_song_data->songs); + } + +} + +void InternetSearchView::AddSongs() { + + MimeData *data = SelectedMimeData(); + if (!data) return; + if (const InternetSongMimeData *internet_song_data = qobject_cast(data)) { + emit AddSongsSignal(internet_song_data->songs); + } + +} + +QString InternetSearchView::PixmapCacheKey(const InternetSearchView::Result &result) const { + return "internet:" % result.metadata_.url().toString(); +} + +bool InternetSearchView::FindCachedPixmap(const InternetSearchView::Result &result, QPixmap *pixmap) const { + return pixmap_cache_.find(result.pixmap_cache_key_, pixmap); +} + +int InternetSearchView::LoadAlbumCoverAsync(const InternetSearchView::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 InternetSearchView::LazyLoadAlbumCover(const QModelIndex &proxy_index) { if (!proxy_index.isValid() || proxy_index.model() != front_proxy_) { @@ -327,323 +816,55 @@ void InternetSearchView::LazyLoadAlbumCover(const QModelIndex &proxy_index) { } // Get the track's Result - const InternetSearch::Result result = item->data(InternetSearchModel::Role_Result).value(); + const InternetSearchView::Result result = item->data(InternetSearchModel::Role_Result).value(); // Load the art. - int id = engine_->LoadAlbumCoverAsync(result); + int id = LoadAlbumCoverAsync(result); art_requests_[id] = source_index; } -void InternetSearchView::AlbumCoverLoaded(const int id, const QPixmap &pixmap) { +void InternetSearchView::AlbumCoverLoaded(const quint64 id, const QUrl&, const QImage &image) { + + 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); if (!art_requests_.contains(id)) return; - QModelIndex index = art_requests_.take(id); + QModelIndex idx = art_requests_.take(id); - if (!pixmap.isNull()) { - front_model_->itemFromIndex(index)->setData(pixmap, Qt::DecorationRole); + if (!pixmap.isNull() && idx.isValid()) { + front_model_->itemFromIndex(idx)->setData(pixmap, Qt::DecorationRole); } } -MimeData *InternetSearchView::SelectedMimeData() { +QImage InternetSearchView::ScaleAndPad(const QImage &image) { - if (!ui_->results->selectionModel()) return nullptr; + if (image.isNull()) return QImage(); - // Get all selected model indexes - QModelIndexList indexes = ui_->results->selectionModel()->selectedRows(); - if (indexes.isEmpty()) { - // There's nothing selected - take the first thing in the model that isn't a divider. - for (int i = 0; i < front_proxy_->rowCount(); ++i) { - QModelIndex index = front_proxy_->index(i, 0); - if (!index.data(CollectionModel::Role_IsDivider).toBool()) { - indexes << index; - ui_->results->setCurrentIndex(index); - break; - } - } - } + const QSize target_size = QSize(kArtHeight, kArtHeight); - // Still got nothing? Give up. - if (indexes.isEmpty()) { - return nullptr; - } + if (image.size() == target_size) return image; - // Get items for these indexes - QList items; - for (const QModelIndex &index : indexes) { - items << (front_model_->itemFromIndex(front_proxy_->mapToSource(index))); - } + // Scale the image down + QImage copy; + copy = image.scaled(target_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); - // Get a MimeData for these items - return engine_->LoadTracks(front_model_->GetChildResults(items)); - -} - -bool InternetSearchView::eventFilter(QObject *object, QEvent *event) { - - if (object == ui_->search && event->type() == QEvent::KeyRelease) { - if (SearchKeyEvent(static_cast(event))) { - return true; - } - } - else if (object == ui_->results_stack && event->type() == QEvent::ContextMenu) { - if (ResultsContextMenuEvent(static_cast(event))) { - return true; - } - } - - return QWidget::eventFilter(object, event); - -} - -bool InternetSearchView::SearchKeyEvent(QKeyEvent *event) { - - switch (event->key()) { - case Qt::Key_Up: - ui_->results->UpAndFocus(); - break; - - case Qt::Key_Down: - ui_->results->DownAndFocus(); - break; - - case Qt::Key_Escape: - ui_->search->clear(); - break; - - case Qt::Key_Return: - TextEdited(ui_->search->text()); - break; - - default: - return false; - } - - event->accept(); - return true; - -} - -bool InternetSearchView::ResultsContextMenuEvent(QContextMenuEvent *event) { - - context_menu_ = new QMenu(this); - context_actions_ << context_menu_->addAction( IconLoader::Load("media-playback-start"), tr("Append to current playlist"), this, SLOT(AddSelectedToPlaylist())); - context_actions_ << context_menu_->addAction( IconLoader::Load("media-playback-start"), tr("Replace current playlist"), this, SLOT(LoadSelected())); - context_actions_ << context_menu_->addAction( IconLoader::Load("document-new"), tr("Open in new playlist"), this, SLOT(OpenSelectedInNewPlaylist())); - - context_menu_->addSeparator(); - context_actions_ << context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue track"), this, SLOT(AddSelectedToPlaylistEnqueue())); - - context_menu_->addSeparator(); - - if (artists_ || albums_ || songs_) { - if (artists_) - context_actions_ << context_menu_->addAction(IconLoader::Load("folder-new"), tr("Add to artists"), this, SLOT(AddArtists())); - if (albums_) - context_actions_ << context_menu_->addAction(IconLoader::Load("folder-new"), tr("Add to albums"), this, SLOT(AddAlbums())); - if (songs_) - context_actions_ << context_menu_->addAction(IconLoader::Load("folder-new"), tr("Add to songs"), this, SLOT(AddSongs())); - context_menu_->addSeparator(); - } - - if (ui_->results->selectionModel() && ui_->results->selectionModel()->selectedRows().length() == 1) { - context_actions_ << context_menu_->addAction(IconLoader::Load("search"), tr("Search for this"), this, SLOT(SearchForThis())); - } - - context_menu_->addSeparator(); - context_menu_->addMenu(tr("Group by"))->addActions(group_by_actions_->actions()); - - context_menu_->addAction(IconLoader::Load("configure"), tr("Configure %1...").arg(Song::TextForSource(engine_->source())), this, SLOT(OpenSettingsDialog())); - - const bool enable_context_actions = ui_->results->selectionModel() && ui_->results->selectionModel()->hasSelection(); - - for (QAction *action : context_actions_) { - action->setEnabled(enable_context_actions); - } - - context_menu_->popup(event->globalPos()); - - return true; - -} - -void InternetSearchView::AddSelectedToPlaylist() { - emit AddToPlaylist(SelectedMimeData()); -} - -void InternetSearchView::LoadSelected() { - MimeData *data = SelectedMimeData(); - if (!data) return; - - data->clear_first_ = true; - emit AddToPlaylist(data); -} - -void InternetSearchView::AddSelectedToPlaylistEnqueue() { - MimeData *data = SelectedMimeData(); - if (!data) return; - - data->enqueue_now_ = true; - emit AddToPlaylist(data); -} - -void InternetSearchView::OpenSelectedInNewPlaylist() { - MimeData *data = SelectedMimeData(); - if (!data) return; - - data->open_in_new_playlist_ = true; - emit AddToPlaylist(data); -} - -void InternetSearchView::SearchForThis() { - StartSearch(ui_->results->selectionModel()->selectedRows().first().data().toString()); -} - -void InternetSearchView::showEvent(QShowEvent *e) { - QWidget::showEvent(e); - FocusSearchField(); -} - -void InternetSearchView::FocusSearchField() { - ui_->search->setFocus(); - ui_->search->selectAll(); -} - -void InternetSearchView::hideEvent(QHideEvent *e) { - QWidget::hideEvent(e); -} - -void InternetSearchView::FocusOnFilter(QKeyEvent *event) { - ui_->search->setFocus(); - QApplication::sendEvent(ui_->search, event); -} - -void InternetSearchView::OpenSettingsDialog() { - app_->OpenSettingsDialogAtPage(settings_page_); -} - -void InternetSearchView::GroupByClicked(QAction *action) { - - if (action->property("group_by").isNull()) { - if (!group_by_dialog_) { - group_by_dialog_.reset(new GroupByDialog); - connect(group_by_dialog_.data(), SIGNAL(Accepted(CollectionModel::Grouping)), SLOT(SetGroupBy(CollectionModel::Grouping))); - } - - group_by_dialog_->show(); - return; - } - - SetGroupBy(action->property("group_by").value()); - -} - -void InternetSearchView::SetGroupBy(const CollectionModel::Grouping &g) { - - // Clear requests: changing "group by" on the models will cause all the items to be removed/added again, - // so all the QModelIndex here will become invalid. New requests will be created for those - // songs when they will be displayed again anyway (when InternetSearchItemDelegate::paint will call LazyLoadAlbumCover) - art_requests_.clear(); - // Update the models - front_model_->SetGroupBy(g, true); - back_model_->SetGroupBy(g, false); - - // Save the setting - QSettings s; - s.beginGroup(settings_group_); - s.setValue("search_group_by1", int(g.first)); - s.setValue("search_group_by2", int(g.second)); - s.setValue("search_group_by3", int(g.third)); - s.endGroup(); - - // Make sure the correct action is checked. - for (QAction *action : group_by_actions_->actions()) { - if (action->property("group_by").isNull()) continue; - - if (g == action->property("group_by").value()) { - action->setChecked(true); - return; - } - } - - // Check the advanced action - group_by_actions_->actions().last()->setChecked(true); - -} - -void InternetSearchView::SearchArtistsClicked(bool checked) { - Q_UNUSED(checked); - SetSearchType(InternetSearch::SearchType_Artists); -} - -void InternetSearchView::SearchAlbumsClicked(bool checked) { - Q_UNUSED(checked); - SetSearchType(InternetSearch::SearchType_Albums); -} - -void InternetSearchView::SearchSongsClicked(bool checked) { - Q_UNUSED(checked); - SetSearchType(InternetSearch::SearchType_Songs); -} - -void InternetSearchView::SetSearchType(const InternetSearch::SearchType type) { - search_type_ = type; - QSettings s; - s.beginGroup(settings_group_); - s.setValue("type", int(search_type_)); - s.endGroup(); - TextEdited(ui_->search->text()); -} - -void InternetSearchView::UpdateStatus(const int id, const QString &text) { - - if (id != last_search_id_) return; - ui_->progressbar->show(); - ui_->label_status->setText(text); - -} - -void InternetSearchView::ProgressSetMaximum(const int id, const int max) { - - if (id != last_search_id_) return; - ui_->progressbar->setMaximum(max); - -} - -void InternetSearchView::UpdateProgress(const int id, const int progress) { - - if (id != last_search_id_) return; - ui_->progressbar->setValue(progress); - -} - -void InternetSearchView::AddArtists() { - - MimeData *data = SelectedMimeData(); - if (!data) return; - if (const InternetSongMimeData *internet_song_data = qobject_cast(data)) { - emit AddArtistsSignal(internet_song_data->songs); - } - -} - -void InternetSearchView::AddAlbums() { - - MimeData *data = SelectedMimeData(); - if (!data) return; - if (const InternetSongMimeData *internet_song_data = qobject_cast(data)) { - emit AddAlbumsSignal(internet_song_data->songs); - } - -} - -void InternetSearchView::AddSongs() { - - MimeData *data = SelectedMimeData(); - if (!data) return; - if (const InternetSongMimeData *internet_song_data = qobject_cast(data)) { - emit AddSongsSignal(internet_song_data->songs); - } + // 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; } diff --git a/src/internet/internetsearchview.h b/src/internet/internetsearchview.h index a43a9b4a..3b7014c1 100644 --- a/src/internet/internetsearchview.h +++ b/src/internet/internetsearchview.h @@ -2,7 +2,7 @@ * Strawberry Music Player * This code was part of Clementine (GlobalSearch) * Copyright 2012, David Sansome - * Copyright 2018, Jonas Kvinge + * Copyright 2018-2020, Jonas Kvinge * * 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 #include #include -#include +#include #include +#include #include +#include +#include +#include #include +#include #include +#include #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 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 group_by_dialog_; - bool artists_; - bool albums_; - bool songs_; QMenu *context_menu_; QList 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 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 delayed_searches_; + QMap pending_searches_; + QMap pending_art_searches_; + QMap art_requests_; + AlbumCoverLoaderOptions cover_loader_options_; + QMap cover_loader_tasks_; + QPixmapCache pixmap_cache_; }; +Q_DECLARE_METATYPE(InternetSearchView::Result) +Q_DECLARE_METATYPE(InternetSearchView::ResultList) #endif // INTERNETSEARCHVIEW_H diff --git a/src/internet/internetsearchview.ui b/src/internet/internetsearchview.ui index 8293df2b..020d7034 100644 --- a/src/internet/internetsearchview.ui +++ b/src/internet/internetsearchview.ui @@ -10,6 +10,9 @@ 660 + + Internet Search View + 0 @@ -39,7 +42,7 @@ - Search for anything + @@ -62,58 +65,65 @@ - - - QLayout::SetFixedSize + + + + 0 + 20 + - - - - true - - - Search type - - - 10 - - - - - - - ar&tists - - - - - - - a&lbums - - - - - - - son&gs - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + + + 2 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + artists + + + + + + + albums + + + + + + + songs + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -215,7 +225,7 @@ 0 0 398 - 511 + 528 diff --git a/src/internet/internetservice.cpp b/src/internet/internetservice.cpp index c36444d5..a0e68d98 100644 --- a/src/internet/internetservice.cpp +++ b/src/internet/internetservice.cpp @@ -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) { } diff --git a/src/internet/internetservice.h b/src/internet/internetservice.h index abb6a146..0e5c50da 100644 --- a/src/internet/internetservice.h +++ b/src/internet/internetservice.h @@ -28,7 +28,8 @@ #include #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*) diff --git a/src/internet/internetsongsview.cpp b/src/internet/internetsongsview.cpp index 258e8010..a42c3d8b 100644 --- a/src/internet/internetsongsview.cpp +++ b/src/internet/internetsongsview.cpp @@ -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))); diff --git a/src/internet/internettabsview.cpp b/src/internet/internettabsview.cpp index 4fcda848..80a8bb48 100644 --- a/src/internet/internettabsview.cpp +++ b/src/internet/internettabsview.cpp @@ -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))); diff --git a/src/internet/internettabsview.h b/src/internet/internettabsview.h index 045178cf..9fd20a53 100644 --- a/src/internet/internettabsview.h +++ b/src/internet/internettabsview.h @@ -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_; diff --git a/src/internet/internettabsview.ui b/src/internet/internettabsview.ui index ac10053d..d1b6e03c 100644 --- a/src/internet/internettabsview.ui +++ b/src/internet/internettabsview.ui @@ -10,9 +10,18 @@ 660 - + + Internet Tabs View + + + + + 0 + 0 + + 0 diff --git a/src/subsonic/subsonicservice.cpp b/src/subsonic/subsonicservice.cpp index d19862c8..2763c4fd 100644 --- a/src/subsonic/subsonicservice.cpp +++ b/src/subsonic/subsonicservice.cpp @@ -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)),