Use common classes for Tidal and Deezer
This commit is contained in:
parent
9349ad9383
commit
a8a714c820
|
@ -264,22 +264,16 @@ set(SOURCES
|
|||
internet/internetmodel.cpp
|
||||
internet/internetservice.cpp
|
||||
internet/internetplaylistitem.cpp
|
||||
internet/internetsearch.cpp
|
||||
internet/internetsearchview.cpp
|
||||
internet/internetsearchmodel.cpp
|
||||
internet/internetsearchsortmodel.cpp
|
||||
internet/internetsearchitemdelegate.cpp
|
||||
internet/localredirectserver.cpp
|
||||
|
||||
tidal/tidalservice.cpp
|
||||
tidal/tidalsearch.cpp
|
||||
tidal/tidalsearchview.cpp
|
||||
tidal/tidalsearchmodel.cpp
|
||||
tidal/tidalsearchsortmodel.cpp
|
||||
tidal/tidalsearchitemdelegate.cpp
|
||||
tidal/tidalurlhandler.cpp
|
||||
|
||||
deezer/deezerservice.cpp
|
||||
deezer/deezersearch.cpp
|
||||
deezer/deezersearchview.cpp
|
||||
deezer/deezersearchmodel.cpp
|
||||
deezer/deezersearchsortmodel.cpp
|
||||
deezer/deezersearchitemdelegate.cpp
|
||||
deezer/deezerurlhandler.cpp
|
||||
|
||||
)
|
||||
|
@ -439,18 +433,14 @@ set(HEADERS
|
|||
internet/internetservice.h
|
||||
internet/internetmimedata.h
|
||||
internet/internetsongmimedata.h
|
||||
internet/internetsearch.h
|
||||
internet/internetsearchview.h
|
||||
internet/internetsearchmodel.h
|
||||
internet/localredirectserver.h
|
||||
|
||||
tidal/tidalservice.h
|
||||
tidal/tidalsearch.h
|
||||
tidal/tidalsearchview.h
|
||||
tidal/tidalsearchmodel.h
|
||||
tidal/tidalurlhandler.h
|
||||
|
||||
deezer/deezerservice.h
|
||||
deezer/deezersearch.h
|
||||
deezer/deezersearchview.h
|
||||
deezer/deezersearchmodel.h
|
||||
deezer/deezerurlhandler.h
|
||||
|
||||
)
|
||||
|
@ -507,8 +497,7 @@ set(UI
|
|||
|
||||
globalshortcuts/globalshortcutgrabber.ui
|
||||
|
||||
tidal/tidalsearchview.ui
|
||||
deezer/deezersearchview.ui
|
||||
internet/internetsearchview.ui
|
||||
|
||||
)
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "core/closure.h"
|
||||
#include "core/lazy.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/song.h"
|
||||
|
||||
#include "database.h"
|
||||
#include "taskmanager.h"
|
||||
|
@ -59,8 +60,7 @@
|
|||
#include "lyrics/apiseedslyricsprovider.h"
|
||||
|
||||
#include "internet/internetmodel.h"
|
||||
#include "tidal/tidalsearch.h"
|
||||
#include "deezer/deezersearch.h"
|
||||
#include "internet/internetsearch.h"
|
||||
|
||||
bool Application::kIsPortable = false;
|
||||
|
||||
|
@ -116,8 +116,8 @@ class ApplicationImpl {
|
|||
return lyrics_providers;
|
||||
}),
|
||||
internet_model_([=]() { return new InternetModel(app, app); }),
|
||||
tidal_search_([=]() { return new TidalSearch(app, app); }),
|
||||
deezer_search_([=]() { return new DeezerSearch(app, app); })
|
||||
tidal_search_([=]() { return new InternetSearch(app, Song::Source_Tidal, app); }),
|
||||
deezer_search_([=]() { return new InternetSearch(app, Song::Source_Deezer, app); })
|
||||
{}
|
||||
|
||||
Lazy<TagReaderClient> tag_reader_client_;
|
||||
|
@ -137,8 +137,8 @@ class ApplicationImpl {
|
|||
Lazy<CurrentArtLoader> current_art_loader_;
|
||||
Lazy<LyricsProviders> lyrics_providers_;
|
||||
Lazy<InternetModel> internet_model_;
|
||||
Lazy<TidalSearch> tidal_search_;
|
||||
Lazy<DeezerSearch> deezer_search_;
|
||||
Lazy<InternetSearch> tidal_search_;
|
||||
Lazy<InternetSearch> deezer_search_;
|
||||
|
||||
};
|
||||
|
||||
|
@ -206,5 +206,5 @@ LyricsProviders *Application::lyrics_providers() const { return p_->lyrics_provi
|
|||
PlaylistBackend *Application::playlist_backend() const { return p_->playlist_backend_.get(); }
|
||||
PlaylistManager *Application::playlist_manager() const { return p_->playlist_manager_.get(); }
|
||||
InternetModel *Application::internet_model() const { return p_->internet_model_.get(); }
|
||||
TidalSearch *Application::tidal_search() const { return p_->tidal_search_.get(); }
|
||||
DeezerSearch *Application::deezer_search() const { return p_->deezer_search_.get(); }
|
||||
InternetSearch *Application::tidal_search() const { return p_->tidal_search_.get(); }
|
||||
InternetSearch *Application::deezer_search() const { return p_->deezer_search_.get(); }
|
||||
|
|
|
@ -53,8 +53,7 @@ class AlbumCoverLoader;
|
|||
class CurrentArtLoader;
|
||||
class LyricsProviders;
|
||||
class InternetModel;
|
||||
class TidalSearch;
|
||||
class DeezerSearch;
|
||||
class InternetSearch;
|
||||
|
||||
class Application : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -89,8 +88,8 @@ class Application : public QObject {
|
|||
LyricsProviders *lyrics_providers() const;
|
||||
|
||||
InternetModel *internet_model() const;
|
||||
TidalSearch *tidal_search() const;
|
||||
DeezerSearch *deezer_search() const;
|
||||
InternetSearch *tidal_search() const;
|
||||
InternetSearch *deezer_search() const;
|
||||
|
||||
void MoveToNewThread(QObject *object);
|
||||
void MoveToThread(QObject *object, QThread *thread);
|
||||
|
|
|
@ -129,16 +129,16 @@
|
|||
# include "device/deviceviewcontainer.h"
|
||||
#endif
|
||||
#include "transcoder/transcodedialog.h"
|
||||
#include "settings/settingsdialog.h"
|
||||
#include "settings/behavioursettingspage.h"
|
||||
#include "settings/playbacksettingspage.h"
|
||||
#include "settings/playlistsettingspage.h"
|
||||
#include "settings/settingsdialog.h"
|
||||
#include "settings/tidalsettingspage.h"
|
||||
#include "settings/deezersettingspage.h"
|
||||
|
||||
#include "internet/internetmodel.h"
|
||||
#include "internet/internetservice.h"
|
||||
|
||||
#include "tidal/tidalsearchview.h"
|
||||
#include "deezer/deezersearchview.h"
|
||||
#include "internet/internetsearchview.h"
|
||||
|
||||
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
|
||||
# include "musicbrainz/tagfetcher.h"
|
||||
|
@ -203,8 +203,8 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
|||
manager->SetPlaylistManager(app->playlist_manager());
|
||||
return manager;
|
||||
}),
|
||||
tidal_search_view_(new TidalSearchView(app_, this)),
|
||||
deezer_search_view_(new DeezerSearchView(app_, this)),
|
||||
tidal_search_view_(new InternetSearchView(app_, app_->tidal_search(), TidalSettingsPage::kSettingsGroup, SettingsDialog::Page_Tidal, this)),
|
||||
deezer_search_view_(new InternetSearchView(app_, app_->deezer_search(), DeezerSettingsPage::kSettingsGroup, SettingsDialog::Page_Deezer, this)),
|
||||
playlist_menu_(new QMenu(this)),
|
||||
playlist_add_to_another_(nullptr),
|
||||
playlistitem_actions_separator_(nullptr),
|
||||
|
|
|
@ -88,8 +88,7 @@ class TranscodeDialog;
|
|||
#endif
|
||||
class Ui_MainWindow;
|
||||
class Windows7ThumbBar;
|
||||
class TidalSearchView;
|
||||
class DeezerSearchView;
|
||||
class InternetSearchView;
|
||||
|
||||
class MainWindow : public QMainWindow, public PlatformInterface {
|
||||
Q_OBJECT
|
||||
|
@ -337,8 +336,8 @@ signals:
|
|||
PlaylistItemList autocomplete_tag_items_;
|
||||
#endif
|
||||
|
||||
TidalSearchView *tidal_search_view_;
|
||||
DeezerSearchView *deezer_search_view_;
|
||||
InternetSearchView *tidal_search_view_;
|
||||
InternetSearchView *deezer_search_view_;
|
||||
|
||||
QAction *collection_show_all_;
|
||||
QAction *collection_show_duplicates_;
|
||||
|
|
|
@ -60,8 +60,7 @@
|
|||
# include "dbus/metatypes.h"
|
||||
#endif
|
||||
|
||||
#include "tidal/tidalsearch.h"
|
||||
#include "deezer/deezersearch.h"
|
||||
#include "internet/internetsearch.h"
|
||||
|
||||
void RegisterMetaTypes() {
|
||||
|
||||
|
@ -116,10 +115,7 @@ void RegisterMetaTypes() {
|
|||
#endif
|
||||
#endif
|
||||
|
||||
qRegisterMetaType<TidalSearch::ResultList>("TidalSearch::ResultList");
|
||||
qRegisterMetaType<TidalSearch::Result>("TidalSearch::Result");
|
||||
|
||||
qRegisterMetaType<DeezerSearch::ResultList>("DeezerSearch::ResultList");
|
||||
qRegisterMetaType<DeezerSearch::Result>("DeezerSearch::Result");
|
||||
qRegisterMetaType<InternetSearch::ResultList>("InternetSearch::ResultList");
|
||||
qRegisterMetaType<InternetSearch::Result>("InternetSearch::Result");
|
||||
|
||||
}
|
||||
|
|
|
@ -1,329 +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 <algorithm>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QStringBuilder>
|
||||
#include <QUrl>
|
||||
#include <QImage>
|
||||
#include <QPixmap>
|
||||
#include <QIcon>
|
||||
#include <QPainter>
|
||||
#include <QTimerEvent>
|
||||
#include <QSettings>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "internet/internetsongmimedata.h"
|
||||
#include "playlist/songmimedata.h"
|
||||
#include "deezersearch.h"
|
||||
#include "deezerservice.h"
|
||||
#include "settings/deezersettingspage.h"
|
||||
|
||||
const int DeezerSearch::kDelayedSearchTimeoutMs = 200;
|
||||
const int DeezerSearch::kMaxResultsPerEmission = 2000;
|
||||
const int DeezerSearch::kArtHeight = 32;
|
||||
|
||||
DeezerSearch::DeezerSearch(Application *app, QObject *parent)
|
||||
: QObject(parent),
|
||||
app_(app),
|
||||
service_(app->internet_model()->Service<DeezerService>()),
|
||||
name_("Deezer"),
|
||||
id_("deezer"),
|
||||
icon_(IconLoader::Load("deezer")),
|
||||
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, QImage)), SLOT(AlbumArtLoaded(quint64, QImage)));
|
||||
connect(this, SIGNAL(SearchAsyncSig(int, QString, DeezerSettingsPage::SearchBy)), this, SLOT(DoSearchAsync(int, QString, DeezerSettingsPage::SearchBy)));
|
||||
connect(this, SIGNAL(ResultsAvailable(int, DeezerSearch::ResultList)), SLOT(ResultsAvailableSlot(int, DeezerSearch::ResultList)));
|
||||
connect(this, SIGNAL(ArtLoaded(int, QImage)), SLOT(ArtLoadedSlot(int, QImage)));
|
||||
connect(service_, SIGNAL(UpdateStatus(QString)), SLOT(UpdateStatusSlot(QString)));
|
||||
connect(service_, SIGNAL(ProgressSetMaximum(int)), SLOT(ProgressSetMaximumSlot(int)));
|
||||
connect(service_, SIGNAL(UpdateProgress(int)), SLOT(UpdateProgressSlot(int)));
|
||||
connect(service_, SIGNAL(SearchResults(int, SongList)), SLOT(SearchDone(int, SongList)));
|
||||
connect(service_, SIGNAL(SearchError(int, QString)), SLOT(HandleError(int, QString)));
|
||||
|
||||
icon_as_image_ = QImage(icon_.pixmap(48, 48).toImage());
|
||||
|
||||
}
|
||||
|
||||
DeezerSearch::~DeezerSearch() {}
|
||||
|
||||
QStringList DeezerSearch::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 DeezerSearch::Matches(const QStringList &tokens, const QString &string) {
|
||||
|
||||
for (const QString &token : tokens) {
|
||||
if (!string.contains(token, Qt::CaseInsensitive)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
int DeezerSearch::SearchAsync(const QString &query, DeezerSettingsPage::SearchBy searchby) {
|
||||
|
||||
const int id = searches_next_id_++;
|
||||
|
||||
emit SearchAsyncSig(id, query, searchby);
|
||||
|
||||
return id;
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearch::SearchAsync(int id, const QString &query, DeezerSettingsPage::SearchBy searchby) {
|
||||
|
||||
const int service_id = service_->Search(query, searchby);
|
||||
pending_searches_[service_id] = PendingState(id, TokenizeQuery(query));
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearch::DoSearchAsync(int id, const QString &query, DeezerSettingsPage::SearchBy searchby) {
|
||||
|
||||
int timer_id = startTimer(kDelayedSearchTimeoutMs);
|
||||
delayed_searches_[timer_id].id_ = id;
|
||||
delayed_searches_[timer_id].query_ = query;
|
||||
delayed_searches_[timer_id].searchby_ = searchby;
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearch::SearchDone(int service_id, const SongList &songs) {
|
||||
|
||||
// Map back to the original id.
|
||||
const PendingState state = pending_searches_.take(service_id);
|
||||
const int search_id = state.orig_id_;
|
||||
|
||||
ResultList ret;
|
||||
for (const Song &song : songs) {
|
||||
Result result;
|
||||
result.metadata_ = song;
|
||||
ret << result;
|
||||
}
|
||||
|
||||
emit ResultsAvailable(search_id, ret);
|
||||
MaybeSearchFinished(search_id);
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearch::HandleError(const int id, const QString error) {
|
||||
|
||||
emit SearchError(id, error);
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearch::MaybeSearchFinished(int id) {
|
||||
|
||||
if (pending_searches_.keys(PendingState(id, QStringList())).isEmpty()) {
|
||||
emit SearchFinished(id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearch::CancelSearch(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 DeezerSearch::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().searchby_);
|
||||
delayed_searches_.erase(it);
|
||||
return;
|
||||
}
|
||||
|
||||
QObject::timerEvent(e);
|
||||
}
|
||||
|
||||
void DeezerSearch::ResultsAvailableSlot(int id, DeezerSearch::ResultList results) {
|
||||
|
||||
if (results.isEmpty()) return;
|
||||
|
||||
// Limit the number of results that are used from each emission.
|
||||
if (results.count() > kMaxResultsPerEmission) {
|
||||
DeezerSearch::ResultList::iterator begin = results.begin();
|
||||
std::advance(begin, kMaxResultsPerEmission);
|
||||
results.erase(begin, results.end());
|
||||
}
|
||||
|
||||
// Load cached pixmaps into the results
|
||||
for (DeezerSearch::ResultList::iterator it = results.begin(); it != results.end(); ++it) {
|
||||
it->pixmap_cache_key_ = PixmapCacheKey(*it);
|
||||
}
|
||||
|
||||
emit AddResults(id, results);
|
||||
|
||||
}
|
||||
|
||||
QString DeezerSearch::PixmapCacheKey(const DeezerSearch::Result &result) const {
|
||||
return "deezer:" % result.metadata_.url().toString();
|
||||
}
|
||||
|
||||
bool DeezerSearch::FindCachedPixmap(const DeezerSearch::Result &result, QPixmap *pixmap) const {
|
||||
return pixmap_cache_.find(result.pixmap_cache_key_, pixmap);
|
||||
}
|
||||
|
||||
int DeezerSearch::LoadArtAsync(const DeezerSearch::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 DeezerSearch::ArtLoadedSlot(int id, const QImage &image) {
|
||||
HandleLoadedArt(id, image);
|
||||
}
|
||||
|
||||
void DeezerSearch::AlbumArtLoaded(quint64 id, const QImage &image) {
|
||||
|
||||
if (!cover_loader_tasks_.contains(id)) return;
|
||||
int orig_id = cover_loader_tasks_.take(id);
|
||||
|
||||
HandleLoadedArt(orig_id, image);
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearch::HandleLoadedArt(int id, const QImage &image) {
|
||||
|
||||
const QString key = pending_art_searches_.take(id);
|
||||
|
||||
QPixmap pixmap = QPixmap::fromImage(image);
|
||||
pixmap_cache_.insert(key, pixmap);
|
||||
|
||||
emit ArtLoaded(id, pixmap);
|
||||
|
||||
}
|
||||
|
||||
QImage DeezerSearch::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 *DeezerSearch::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 DeezerSearch::UpdateStatusSlot(QString text) {
|
||||
emit UpdateStatus(text);
|
||||
}
|
||||
|
||||
void DeezerSearch::ProgressSetMaximumSlot(int max) {
|
||||
emit ProgressSetMaximum(max);
|
||||
}
|
||||
|
||||
void DeezerSearch::UpdateProgressSlot(int progress) {
|
||||
emit UpdateProgress(progress);
|
||||
}
|
|
@ -1,164 +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 DEEZERSEARCH_H
|
||||
#define DEEZERSEARCH_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QFuture>
|
||||
#include <QIcon>
|
||||
#include <QMetaType>
|
||||
#include <QPixmapCache>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "covermanager/albumcoverloaderoptions.h"
|
||||
#include "settings/deezersettingspage.h"
|
||||
|
||||
class Application;
|
||||
class MimeData;
|
||||
class AlbumCoverLoader;
|
||||
class InternetService;
|
||||
class DeezerService;
|
||||
|
||||
class DeezerSearch : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DeezerSearch(Application *app, QObject *parent = nullptr);
|
||||
~DeezerSearch();
|
||||
|
||||
struct Result {
|
||||
Song metadata_;
|
||||
QString pixmap_cache_key_;
|
||||
};
|
||||
typedef QList<Result> ResultList;
|
||||
|
||||
static const int kDelayedSearchTimeoutMs;
|
||||
static const int kMaxResultsPerEmission;
|
||||
|
||||
Application *application() const { return app_; }
|
||||
DeezerService *service() const { return service_; }
|
||||
|
||||
int SearchAsync(const QString &query, DeezerSettingsPage::SearchBy searchby);
|
||||
int LoadArtAsync(const DeezerSearch::Result &result);
|
||||
|
||||
void CancelSearch(int id);
|
||||
void CancelArt(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(int id, const QString &query, DeezerSettingsPage::SearchBy searchby);
|
||||
void ResultsAvailable(int id, const DeezerSearch::ResultList &results);
|
||||
void AddResults(int id, const DeezerSearch::ResultList &results);
|
||||
void SearchError(const int id, const QString error);
|
||||
void SearchFinished(int id);
|
||||
void UpdateStatus(QString text);
|
||||
void ProgressSetMaximum(int progress);
|
||||
void UpdateProgress(int max);
|
||||
|
||||
void ArtLoaded(int id, const QPixmap &pixmap);
|
||||
void ArtLoaded(int id, const QImage &image);
|
||||
|
||||
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 LibraryQuery.
|
||||
// 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(int id, const QString &query, DeezerSettingsPage::SearchBy searchby);
|
||||
void SearchDone(int id, const SongList &songs);
|
||||
void HandleError(const int id, const QString error);
|
||||
void ResultsAvailableSlot(int id, DeezerSearch::ResultList results);
|
||||
|
||||
void ArtLoadedSlot(int id, const QImage &image);
|
||||
void AlbumArtLoaded(quint64 id, const QImage &image);
|
||||
|
||||
void UpdateStatusSlot(QString text);
|
||||
void ProgressSetMaximumSlot(int progress);
|
||||
void UpdateProgressSlot(int max);
|
||||
|
||||
private:
|
||||
void SearchAsync(int id, const QString &query, DeezerSettingsPage::SearchBy searchby);
|
||||
void HandleLoadedArt(int id, const QImage &image);
|
||||
bool FindCachedPixmap(const DeezerSearch::Result &result, QPixmap *pixmap) const;
|
||||
QString PixmapCacheKey(const DeezerSearch::Result &result) const;
|
||||
void MaybeSearchFinished(int id);
|
||||
void ShowConfig() {}
|
||||
static QImage ScaleAndPad(const QImage &image);
|
||||
|
||||
private:
|
||||
struct DelayedSearch {
|
||||
int id_;
|
||||
QString query_;
|
||||
DeezerSettingsPage::SearchBy searchby_;
|
||||
};
|
||||
|
||||
static const int kArtHeight;
|
||||
|
||||
Application *app_;
|
||||
DeezerService *service_;
|
||||
Song::Source source_;
|
||||
QString name_;
|
||||
QString id_;
|
||||
QIcon icon_;
|
||||
QImage icon_as_image_;
|
||||
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(DeezerSearch::Result)
|
||||
Q_DECLARE_METATYPE(DeezerSearch::ResultList)
|
||||
|
||||
#endif // DEEZERSEARCH_H
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* 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 <QPainter>
|
||||
#include <QStyleOptionViewItem>
|
||||
|
||||
#include "deezersearchitemdelegate.h"
|
||||
#include "deezersearchview.h"
|
||||
|
||||
DeezerSearchItemDelegate::DeezerSearchItemDelegate(DeezerSearchView* view)
|
||||
: CollectionItemDelegate(view), view_(view) {}
|
||||
|
||||
void DeezerSearchItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||
// Tell the view we painted this item so it can lazy load some art.
|
||||
const_cast<DeezerSearchView*>(view_)->LazyLoadArt(index);
|
||||
|
||||
CollectionItemDelegate::paint(painter, option, index);
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* 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 DEEZERSEARCHITEMDELEGATE_H
|
||||
#define DEEZERSEARCHITEMDELEGATE_H
|
||||
|
||||
#include <QPainter>
|
||||
#include <QStyleOptionViewItem>
|
||||
|
||||
#include "collection/collectionview.h"
|
||||
|
||||
class DeezerSearchView;
|
||||
|
||||
class DeezerSearchItemDelegate : public CollectionItemDelegate {
|
||||
public:
|
||||
DeezerSearchItemDelegate(DeezerSearchView *view);
|
||||
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
|
||||
|
||||
private:
|
||||
DeezerSearchView* view_;
|
||||
};
|
||||
|
||||
#endif // DEEZERSEARCHITEMDELEGATE_H
|
|
@ -1,574 +0,0 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2012, 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 <functional>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QWidget>
|
||||
#include <QTimer>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QPixmap>
|
||||
#include <QPalette>
|
||||
#include <QColor>
|
||||
#include <QFont>
|
||||
#include <QMenu>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QStandardItem>
|
||||
#include <QSettings>
|
||||
#include <QAction>
|
||||
#include <QtEvents>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/mimedata.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "internet/internetsongmimedata.h"
|
||||
#include "collection/collectionfilterwidget.h"
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "collection/groupbydialog.h"
|
||||
#include "playlist/songmimedata.h"
|
||||
#include "deezersearch.h"
|
||||
#include "deezersearchitemdelegate.h"
|
||||
#include "deezersearchmodel.h"
|
||||
#include "deezersearchsortmodel.h"
|
||||
#include "deezersearchview.h"
|
||||
#include "ui_deezersearchview.h"
|
||||
#include "settings/deezersettingspage.h"
|
||||
|
||||
using std::placeholders::_1;
|
||||
using std::placeholders::_2;
|
||||
using std::swap;
|
||||
|
||||
const int DeezerSearchView::kSwapModelsTimeoutMsec = 250;
|
||||
|
||||
DeezerSearchView::DeezerSearchView(Application *app, QWidget *parent)
|
||||
: QWidget(parent),
|
||||
app_(app),
|
||||
engine_(app_->deezer_search()),
|
||||
ui_(new Ui_DeezerSearchView),
|
||||
context_menu_(nullptr),
|
||||
last_search_id_(0),
|
||||
front_model_(new DeezerSearchModel(engine_, this)),
|
||||
back_model_(new DeezerSearchModel(engine_, this)),
|
||||
current_model_(front_model_),
|
||||
front_proxy_(new DeezerSearchSortModel(this)),
|
||||
back_proxy_(new DeezerSearchSortModel(this)),
|
||||
current_proxy_(front_proxy_),
|
||||
swap_models_timer_(new QTimer(this)),
|
||||
search_icon_(IconLoader::Load("search")),
|
||||
warning_icon_(IconLoader::Load("dialog-warning")),
|
||||
error_(false) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
ui_->progressbar->hide();
|
||||
ui_->progressbar->reset();
|
||||
|
||||
front_model_->set_proxy(front_proxy_);
|
||||
back_model_->set_proxy(back_proxy_);
|
||||
|
||||
ui_->search->installEventFilter(this);
|
||||
ui_->results_stack->installEventFilter(this);
|
||||
|
||||
ui_->settings->setIcon(IconLoader::Load("configure"));
|
||||
|
||||
// Must be a queued connection to ensure the DeezerSearch handles it first.
|
||||
connect(app_, SIGNAL(SettingsChanged()), SLOT(ReloadSettings()), Qt::QueuedConnection);
|
||||
|
||||
connect(ui_->search, SIGNAL(textChanged(QString)), SLOT(TextEdited(QString)));
|
||||
connect(ui_->results, SIGNAL(AddToPlaylistSignal(QMimeData*)), SIGNAL(AddToPlaylist(QMimeData*)));
|
||||
connect(ui_->results, SIGNAL(FocusOnFilterSignal(QKeyEvent*)), SLOT(FocusOnFilter(QKeyEvent*)));
|
||||
|
||||
// Set the appearance of the results list
|
||||
ui_->results->setItemDelegate(new DeezerSearchItemDelegate(this));
|
||||
ui_->results->setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
ui_->results->setStyleSheet("QTreeView::item{padding-top:1px;}");
|
||||
|
||||
// Show the help page initially
|
||||
ui_->results_stack->setCurrentWidget(ui_->help_page);
|
||||
ui_->help_frame->setBackgroundRole(QPalette::Base);
|
||||
|
||||
// Set the colour of the help text to the disabled window text colour
|
||||
QPalette help_palette = ui_->label_helptext->palette();
|
||||
const QColor help_color = help_palette.color(QPalette::Disabled, QPalette::WindowText);
|
||||
help_palette.setColor(QPalette::Normal, QPalette::WindowText, help_color);
|
||||
help_palette.setColor(QPalette::Inactive, QPalette::WindowText, help_color);
|
||||
ui_->label_helptext->setPalette(help_palette);
|
||||
|
||||
// Make it bold
|
||||
QFont help_font = ui_->label_helptext->font();
|
||||
help_font.setBold(true);
|
||||
ui_->label_helptext->setFont(help_font);
|
||||
|
||||
// Set up the sorting proxy model
|
||||
front_proxy_->setSourceModel(front_model_);
|
||||
front_proxy_->setDynamicSortFilter(true);
|
||||
front_proxy_->sort(0);
|
||||
|
||||
back_proxy_->setSourceModel(back_model_);
|
||||
back_proxy_->setDynamicSortFilter(true);
|
||||
back_proxy_->sort(0);
|
||||
|
||||
swap_models_timer_->setSingleShot(true);
|
||||
swap_models_timer_->setInterval(kSwapModelsTimeoutMsec);
|
||||
connect(swap_models_timer_, SIGNAL(timeout()), SLOT(SwapModels()));
|
||||
|
||||
// Add actions to the settings menu
|
||||
group_by_actions_ = CollectionFilterWidget::CreateGroupByActions(this);
|
||||
QMenu *settings_menu = new QMenu(this);
|
||||
settings_menu->addActions(group_by_actions_->actions());
|
||||
settings_menu->addSeparator();
|
||||
settings_menu->addAction(IconLoader::Load("configure"), tr("Configure Deezer..."), this, SLOT(OpenSettingsDialog()));
|
||||
ui_->settings->setMenu(settings_menu);
|
||||
|
||||
connect(ui_->radiobutton_searchbyalbums, SIGNAL(clicked(bool)), SLOT(SearchByAlbumsClicked(bool)));
|
||||
connect(ui_->radiobutton_searchbysongs, SIGNAL(clicked(bool)), SLOT(SearchBySongsClicked(bool)));
|
||||
|
||||
connect(group_by_actions_, SIGNAL(triggered(QAction*)), SLOT(GroupByClicked(QAction*)));
|
||||
|
||||
// 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(engine_, SIGNAL(UpdateStatus(QString)), SLOT(UpdateStatus(QString)));
|
||||
connect(engine_, SIGNAL(ProgressSetMaximum(int)), SLOT(ProgressSetMaximum(int)), Qt::QueuedConnection);
|
||||
connect(engine_, SIGNAL(UpdateProgress(int)), SLOT(UpdateProgress(int)), Qt::QueuedConnection);
|
||||
|
||||
connect(engine_, SIGNAL(AddResults(int, DeezerSearch::ResultList)), SLOT(AddResults(int, DeezerSearch::ResultList)), Qt::QueuedConnection);
|
||||
connect(engine_, SIGNAL(SearchError(int, QString)), SLOT(SearchError(int, QString)), Qt::QueuedConnection);
|
||||
connect(engine_, SIGNAL(ArtLoaded(int, QPixmap)), SLOT(ArtLoaded(int, QPixmap)), Qt::QueuedConnection);
|
||||
|
||||
ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
DeezerSearchView::~DeezerSearchView() { delete ui_; }
|
||||
|
||||
void DeezerSearchView::ReloadSettings() {
|
||||
|
||||
QSettings s;
|
||||
|
||||
// Collection settings
|
||||
|
||||
s.beginGroup(DeezerSettingsPage::kSettingsGroup);
|
||||
const bool pretty = s.value("pretty_covers", true).toBool();
|
||||
front_model_->set_use_pretty_covers(pretty);
|
||||
back_model_->set_use_pretty_covers(pretty);
|
||||
s.endGroup();
|
||||
|
||||
// Deezer search settings
|
||||
|
||||
s.beginGroup(DeezerSettingsPage::kSettingsGroup);
|
||||
searchby_ = DeezerSettingsPage::SearchBy(s.value("searchby", int(DeezerSettingsPage::SearchBy_Songs)).toInt());
|
||||
switch (searchby_) {
|
||||
case DeezerSettingsPage::SearchBy_Songs:
|
||||
ui_->radiobutton_searchbysongs->setChecked(true);
|
||||
break;
|
||||
case DeezerSettingsPage::SearchBy_Albums:
|
||||
ui_->radiobutton_searchbyalbums->setChecked(true);
|
||||
break;
|
||||
}
|
||||
|
||||
SetGroupBy(CollectionModel::Grouping(
|
||||
CollectionModel::GroupBy(s.value("group_by1", int(CollectionModel::GroupBy_Artist)).toInt()),
|
||||
CollectionModel::GroupBy(s.value("group_by2", int(CollectionModel::GroupBy_Album)).toInt()),
|
||||
CollectionModel::GroupBy(s.value("group_by3", int(CollectionModel::GroupBy_None)).toInt())));
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearchView::StartSearch(const QString &query) {
|
||||
|
||||
ui_->search->setText(query);
|
||||
TextEdited(query);
|
||||
|
||||
// Swap models immediately
|
||||
swap_models_timer_->stop();
|
||||
SwapModels();
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearchView::TextEdited(const QString &text) {
|
||||
|
||||
const QString trimmed(text.trimmed());
|
||||
|
||||
error_ = false;
|
||||
|
||||
// Add results to the back model, switch models after some delay.
|
||||
back_model_->Clear();
|
||||
current_model_ = back_model_;
|
||||
current_proxy_ = back_proxy_;
|
||||
swap_models_timer_->start();
|
||||
|
||||
// Cancel the last search (if any) and start the new one.
|
||||
engine_->CancelSearch(last_search_id_);
|
||||
// If text query is empty, don't start a new search
|
||||
if (trimmed.isEmpty()) {
|
||||
last_search_id_ = -1;
|
||||
ui_->label_helptext->setText("Enter search terms above to find music");
|
||||
ui_->label_status->clear();
|
||||
ui_->progressbar->hide();
|
||||
ui_->progressbar->reset();
|
||||
}
|
||||
else {
|
||||
ui_->progressbar->reset();
|
||||
last_search_id_ = engine_->SearchAsync(trimmed, searchby_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearchView::AddResults(int id, const DeezerSearch::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 DeezerSearchView::SearchError(const int id, const QString error) {
|
||||
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 DeezerSearchView::SwapModels() {
|
||||
|
||||
art_requests_.clear();
|
||||
|
||||
std::swap(front_model_, back_model_);
|
||||
std::swap(front_proxy_, back_proxy_);
|
||||
|
||||
ui_->results->setModel(front_proxy_);
|
||||
|
||||
if (ui_->search->text().trimmed().isEmpty() || error_) {
|
||||
ui_->results_stack->setCurrentWidget(ui_->help_page);
|
||||
}
|
||||
else {
|
||||
ui_->results_stack->setCurrentWidget(ui_->results_page);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearchView::LazyLoadArt(const QModelIndex &proxy_index) {
|
||||
|
||||
if (!proxy_index.isValid() || proxy_index.model() != front_proxy_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Already loading art for this item?
|
||||
if (proxy_index.data(DeezerSearchModel::Role_LazyLoadingArt).isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Should we even load art at all?
|
||||
if (!app_->collection_model()->use_pretty_covers()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Is this an album?
|
||||
const CollectionModel::GroupBy container_type = CollectionModel::GroupBy(proxy_index.data(CollectionModel::Role_ContainerType).toInt());
|
||||
if (container_type != CollectionModel::GroupBy_Album &&
|
||||
container_type != CollectionModel::GroupBy_AlbumArtist &&
|
||||
container_type != CollectionModel::GroupBy_YearAlbum &&
|
||||
container_type != CollectionModel::GroupBy_OriginalYearAlbum) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark the item as loading art
|
||||
const QModelIndex source_index = front_proxy_->mapToSource(proxy_index);
|
||||
QStandardItem *item = front_model_->itemFromIndex(source_index);
|
||||
item->setData(true, DeezerSearchModel::Role_LazyLoadingArt);
|
||||
|
||||
// Walk down the item's children until we find a track
|
||||
while (item->rowCount()) {
|
||||
item = item->child(0);
|
||||
}
|
||||
|
||||
// Get the track's Result
|
||||
const DeezerSearch::Result result = item->data(DeezerSearchModel::Role_Result).value<DeezerSearch::Result>();
|
||||
|
||||
// Load the art.
|
||||
int id = engine_->LoadArtAsync(result);
|
||||
art_requests_[id] = source_index;
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearchView::ArtLoaded(int id, const QPixmap &pixmap) {
|
||||
|
||||
if (!art_requests_.contains(id)) return;
|
||||
QModelIndex index = art_requests_.take(id);
|
||||
|
||||
if (!pixmap.isNull()) {
|
||||
front_model_->itemFromIndex(index)->setData(pixmap, Qt::DecorationRole);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MimeData *DeezerSearchView::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<QStandardItem*> items;
|
||||
for (const QModelIndex &index : indexes) {
|
||||
items << (front_model_->itemFromIndex(front_proxy_->mapToSource(index)));
|
||||
}
|
||||
|
||||
// Get a MimeData for these items
|
||||
return engine_->LoadTracks(front_model_->GetChildResults(items));
|
||||
|
||||
}
|
||||
|
||||
bool DeezerSearchView::eventFilter(QObject *object, QEvent *event) {
|
||||
|
||||
if (object == ui_->search && event->type() == QEvent::KeyRelease) {
|
||||
if (SearchKeyEvent(static_cast<QKeyEvent*>(event))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (object == ui_->results_stack && event->type() == QEvent::ContextMenu) {
|
||||
if (ResultsContextMenuEvent(static_cast<QContextMenuEvent*>(event))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return QWidget::eventFilter(object, event);
|
||||
|
||||
}
|
||||
|
||||
bool DeezerSearchView::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 DeezerSearchView::ResultsContextMenuEvent(QContextMenuEvent *event) {
|
||||
|
||||
context_menu_ = new QMenu(this);
|
||||
context_actions_ << context_menu_->addAction( IconLoader::Load("media-play"), tr("Append to current playlist"), this, SLOT(AddSelectedToPlaylist()));
|
||||
context_actions_ << context_menu_->addAction( IconLoader::Load("media-play"), 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 (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 Deezer..."), 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 DeezerSearchView::AddSelectedToPlaylist() {
|
||||
emit AddToPlaylist(SelectedMimeData());
|
||||
}
|
||||
|
||||
void DeezerSearchView::LoadSelected() {
|
||||
MimeData *data = SelectedMimeData();
|
||||
if (!data) return;
|
||||
|
||||
data->clear_first_ = true;
|
||||
emit AddToPlaylist(data);
|
||||
}
|
||||
|
||||
void DeezerSearchView::AddSelectedToPlaylistEnqueue() {
|
||||
MimeData *data = SelectedMimeData();
|
||||
if (!data) return;
|
||||
|
||||
data->enqueue_now_ = true;
|
||||
emit AddToPlaylist(data);
|
||||
}
|
||||
|
||||
void DeezerSearchView::OpenSelectedInNewPlaylist() {
|
||||
MimeData *data = SelectedMimeData();
|
||||
if (!data) return;
|
||||
|
||||
data->open_in_new_playlist_ = true;
|
||||
emit AddToPlaylist(data);
|
||||
}
|
||||
|
||||
void DeezerSearchView::SearchForThis() {
|
||||
StartSearch(ui_->results->selectionModel()->selectedRows().first().data().toString());
|
||||
}
|
||||
|
||||
void DeezerSearchView::showEvent(QShowEvent *e) {
|
||||
QWidget::showEvent(e);
|
||||
FocusSearchField();
|
||||
}
|
||||
|
||||
void DeezerSearchView::FocusSearchField() {
|
||||
ui_->search->setFocus();
|
||||
ui_->search->selectAll();
|
||||
}
|
||||
|
||||
void DeezerSearchView::hideEvent(QHideEvent *e) {
|
||||
QWidget::hideEvent(e);
|
||||
}
|
||||
|
||||
void DeezerSearchView::FocusOnFilter(QKeyEvent *event) {
|
||||
ui_->search->setFocus();
|
||||
QApplication::sendEvent(ui_->search, event);
|
||||
}
|
||||
|
||||
void DeezerSearchView::OpenSettingsDialog() {
|
||||
app_->OpenSettingsDialogAtPage(SettingsDialog::Page_Deezer);
|
||||
}
|
||||
|
||||
void DeezerSearchView::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<CollectionModel::Grouping>());
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearchView::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 DeezerSearchItemDelegate::paint will call LazyLoadArt)
|
||||
art_requests_.clear();
|
||||
// Update the models
|
||||
front_model_->SetGroupBy(g, true);
|
||||
back_model_->SetGroupBy(g, false);
|
||||
|
||||
// Save the setting
|
||||
QSettings s;
|
||||
s.beginGroup(DeezerSettingsPage::kSettingsGroup);
|
||||
s.setValue("group_by1", int(g.first));
|
||||
s.setValue("group_by2", int(g.second));
|
||||
s.setValue("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<CollectionModel::Grouping>()) {
|
||||
action->setChecked(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the advanced action
|
||||
group_by_actions_->actions().last()->setChecked(true);
|
||||
|
||||
}
|
||||
|
||||
void DeezerSearchView::SearchBySongsClicked(bool checked) {
|
||||
SetSearchBy(DeezerSettingsPage::SearchBy_Songs);
|
||||
}
|
||||
|
||||
void DeezerSearchView::SearchByAlbumsClicked(bool checked) {
|
||||
SetSearchBy(DeezerSettingsPage::SearchBy_Albums);
|
||||
}
|
||||
|
||||
void DeezerSearchView::SetSearchBy(DeezerSettingsPage::SearchBy searchby) {
|
||||
searchby_ = searchby;
|
||||
QSettings s;
|
||||
s.beginGroup(DeezerSettingsPage::kSettingsGroup);
|
||||
s.setValue("searchby", int(searchby));
|
||||
s.endGroup();
|
||||
TextEdited(ui_->search->text());
|
||||
}
|
||||
|
||||
void DeezerSearchView::UpdateStatus(QString text) {
|
||||
ui_->progressbar->show();
|
||||
ui_->label_status->setText(text);
|
||||
}
|
||||
|
||||
void DeezerSearchView::ProgressSetMaximum(int max) {
|
||||
ui_->progressbar->setMaximum(max);
|
||||
}
|
||||
|
||||
void DeezerSearchView::UpdateProgress(int progress) {
|
||||
ui_->progressbar->setValue(progress);
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2012, 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 DEEZERSEARCHVIEW_H
|
||||
#define DEEZERSEARCHVIEW_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
#include <QMap>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QIcon>
|
||||
#include <QPixmap>
|
||||
#include <QMimeData>
|
||||
#include <QMenu>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QAction>
|
||||
#include <QActionGroup>
|
||||
#include <QtEvents>
|
||||
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "settings/settingsdialog.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
#include "deezersearch.h"
|
||||
#include "settings/deezersettingspage.h"
|
||||
|
||||
class Application;
|
||||
class GroupByDialog;
|
||||
class DeezerSearchModel;
|
||||
class Ui_DeezerSearchView;
|
||||
|
||||
class DeezerSearchView : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DeezerSearchView(Application *app, QWidget *parent = nullptr);
|
||||
~DeezerSearchView();
|
||||
|
||||
static const int kSwapModelsTimeoutMsec;
|
||||
|
||||
void LazyLoadArt(const QModelIndex &index);
|
||||
|
||||
void showEvent(QShowEvent *e);
|
||||
void hideEvent(QHideEvent *e);
|
||||
bool eventFilter(QObject *object, QEvent *event);
|
||||
|
||||
public slots:
|
||||
void ReloadSettings();
|
||||
void StartSearch(const QString &query);
|
||||
void FocusSearchField();
|
||||
void OpenSettingsDialog();
|
||||
|
||||
signals:
|
||||
void AddToPlaylist(QMimeData *data);
|
||||
|
||||
private slots:
|
||||
void SwapModels();
|
||||
void TextEdited(const QString &text);
|
||||
void UpdateStatus(QString text);
|
||||
void ProgressSetMaximum(int progress);
|
||||
void UpdateProgress(int max);
|
||||
void AddResults(int id, const DeezerSearch::ResultList &results);
|
||||
void SearchError(const int id, const QString error);
|
||||
void ArtLoaded(int id, const QPixmap &pixmap);
|
||||
|
||||
void FocusOnFilter(QKeyEvent *event);
|
||||
|
||||
void AddSelectedToPlaylist();
|
||||
void LoadSelected();
|
||||
void OpenSelectedInNewPlaylist();
|
||||
void AddSelectedToPlaylistEnqueue();
|
||||
|
||||
void SearchForThis();
|
||||
|
||||
void SearchBySongsClicked(bool);
|
||||
void SearchByAlbumsClicked(bool);
|
||||
void GroupByClicked(QAction *action);
|
||||
void SetSearchBy(DeezerSettingsPage::SearchBy searchby);
|
||||
void SetGroupBy(const CollectionModel::Grouping &g);
|
||||
|
||||
private:
|
||||
MimeData *SelectedMimeData();
|
||||
|
||||
bool SearchKeyEvent(QKeyEvent *event);
|
||||
bool ResultsContextMenuEvent(QContextMenuEvent *event);
|
||||
|
||||
Application *app_;
|
||||
DeezerSearch *engine_;
|
||||
Ui_DeezerSearchView *ui_;
|
||||
QScopedPointer<GroupByDialog> group_by_dialog_;
|
||||
|
||||
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.
|
||||
DeezerSearchModel *front_model_;
|
||||
DeezerSearchModel *back_model_;
|
||||
DeezerSearchModel *current_model_;
|
||||
|
||||
QSortFilterProxyModel *front_proxy_;
|
||||
QSortFilterProxyModel *back_proxy_;
|
||||
QSortFilterProxyModel *current_proxy_;
|
||||
|
||||
QMap<int, QModelIndex> art_requests_;
|
||||
|
||||
QTimer *swap_models_timer_;
|
||||
|
||||
QIcon search_icon_;
|
||||
QIcon warning_icon_;
|
||||
|
||||
DeezerSettingsPage::SearchBy searchby_;
|
||||
bool error_;
|
||||
|
||||
};
|
||||
|
||||
#endif // DEEZERSEARCHVIEW_H
|
|
@ -1,283 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DeezerSearchView</class>
|
||||
<widget class="QWidget" name="DeezerSearchView">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>660</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</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="QWidget" name="widget_search" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="layout_top" stretch="0,0,0">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="layout_search">
|
||||
<item>
|
||||
<widget class="QSearchField" name="search" native="true">
|
||||
<property name="placeholderText" stdset="0">
|
||||
<string>Search for anything</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="settings">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::InstantPopup</enum>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="layout_searchby">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_searchby">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Search by</string>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radiobutton_searchbyalbums">
|
||||
<property name="text">
|
||||
<string>a&lbums</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radiobutton_searchbysongs">
|
||||
<property name="text">
|
||||
<string>son&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>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="layout_progress">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_status">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressbar">
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="results_stack">
|
||||
<property name="currentIndex">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="results_page">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>0</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="AutoExpandingTreeView" name="results">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="dragEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="dragDropMode">
|
||||
<enum>QAbstractItemView::DragOnly</enum>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="allColumnsShowFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="help_page">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<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="QScrollArea" name="help_frame">
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="help_frame_contents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>398</width>
|
||||
<height>502</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<property name="leftMargin">
|
||||
<number>32</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>16</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>32</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>64</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_helptext">
|
||||
<property name="text">
|
||||
<string>Enter search terms above to find music</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_helptext">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QSearchField</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>3rdparty/qocoa/qsearchfield.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>AutoExpandingTreeView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>widgets/autoexpandingtreeview.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -55,9 +55,9 @@
|
|||
#include "core/timeconstants.h"
|
||||
#include "core/utilities.h"
|
||||
#include "internet/internetmodel.h"
|
||||
#include "internet/internetsearch.h"
|
||||
#include "internet/localredirectserver.h"
|
||||
#include "deezerservice.h"
|
||||
#include "deezersearch.h"
|
||||
#include "deezerurlhandler.h"
|
||||
#include "settings/deezersettingspage.h"
|
||||
|
||||
|
@ -408,7 +408,7 @@ QJsonValue DeezerService::ExtractData(QByteArray &data) {
|
|||
|
||||
}
|
||||
|
||||
int DeezerService::Search(const QString &text, DeezerSettingsPage::SearchBy searchby) {
|
||||
int DeezerService::Search(const QString &text, InternetSearch::SearchBy searchby) {
|
||||
|
||||
pending_search_id_ = next_pending_search_id_;
|
||||
pending_search_text_ = text;
|
||||
|
@ -466,11 +466,11 @@ void DeezerService::SendSearch() {
|
|||
parameters << Param("q", search_text_);
|
||||
QString searchparam;
|
||||
switch (pending_searchby_) {
|
||||
case DeezerSettingsPage::SearchBy_Songs:
|
||||
case InternetSearch::SearchBy_Songs:
|
||||
searchparam = "search/track";
|
||||
parameters << Param("limit", QString::number(songssearchlimit_));
|
||||
break;
|
||||
case DeezerSettingsPage::SearchBy_Albums:
|
||||
case InternetSearch::SearchBy_Albums:
|
||||
default:
|
||||
searchparam = "search/album";
|
||||
parameters << Param("limit", QString::number(albumssearchlimit_));
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
#include "core/song.h"
|
||||
#include "internet/internetmodel.h"
|
||||
#include "internet/internetservice.h"
|
||||
#include "settings/deezersettingspage.h"
|
||||
#include "internet/internetsearch.h"
|
||||
|
||||
class NetworkAccessManager;
|
||||
class LocalRedirectServer;
|
||||
|
@ -70,7 +70,7 @@ class DeezerService : public InternetService {
|
|||
void ReloadSettings();
|
||||
|
||||
void Logout();
|
||||
int Search(const QString &query, DeezerSettingsPage::SearchBy searchby);
|
||||
int Search(const QString &query, InternetSearch::SearchBy searchby);
|
||||
void CancelSearch();
|
||||
|
||||
const bool app_id() { return kAppID; }
|
||||
|
@ -146,7 +146,7 @@ class DeezerService : public InternetService {
|
|||
int pending_search_id_;
|
||||
int next_pending_search_id_;
|
||||
QString pending_search_text_;
|
||||
DeezerSettingsPage::SearchBy pending_searchby_;
|
||||
InternetSearch::SearchBy pending_searchby_;
|
||||
|
||||
int search_id_;
|
||||
QString search_text_;
|
||||
|
|
|
@ -124,7 +124,6 @@ class InternetModel : public QStandardItemModel {
|
|||
void ServiceDeleted();
|
||||
|
||||
private:
|
||||
//static QMap<QString, InternetService*> *sServices;
|
||||
static QMap<Song::Source, InternetService*> *sServices;
|
||||
Application *app_;
|
||||
|
||||
|
|
|
@ -42,24 +42,23 @@
|
|||
#include "core/logging.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/song.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "internet/internetsongmimedata.h"
|
||||
#include "playlist/songmimedata.h"
|
||||
#include "tidalsearch.h"
|
||||
#include "tidalservice.h"
|
||||
#include "settings/tidalsettingspage.h"
|
||||
#include "internetsearch.h"
|
||||
#include "internetservice.h"
|
||||
#include "internetmodel.h"
|
||||
|
||||
const int TidalSearch::kDelayedSearchTimeoutMs = 200;
|
||||
const int TidalSearch::kMaxResultsPerEmission = 2000;
|
||||
const int TidalSearch::kArtHeight = 32;
|
||||
const int InternetSearch::kDelayedSearchTimeoutMs = 200;
|
||||
const int InternetSearch::kMaxResultsPerEmission = 2000;
|
||||
const int InternetSearch::kArtHeight = 32;
|
||||
|
||||
TidalSearch::TidalSearch(Application *app, QObject *parent)
|
||||
InternetSearch::InternetSearch(Application *app, Song::Source source, QObject *parent)
|
||||
: QObject(parent),
|
||||
app_(app),
|
||||
service_(app->internet_model()->Service<TidalService>()),
|
||||
name_("Tidal"),
|
||||
id_("tidal"),
|
||||
icon_(IconLoader::Load("tidal")),
|
||||
source_(source),
|
||||
service_(app->internet_model()->ServiceBySource(source)),
|
||||
searches_next_id_(1),
|
||||
art_searches_next_id_(1) {
|
||||
|
||||
|
@ -68,8 +67,8 @@ TidalSearch::TidalSearch(Application *app, QObject *parent)
|
|||
cover_loader_options_.scale_output_image_ = true;
|
||||
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(AlbumArtLoaded(quint64, QImage)));
|
||||
connect(this, SIGNAL(SearchAsyncSig(int, QString, TidalSettingsPage::SearchBy)), this, SLOT(DoSearchAsync(int, QString, TidalSettingsPage::SearchBy)));
|
||||
connect(this, SIGNAL(ResultsAvailable(int, TidalSearch::ResultList)), SLOT(ResultsAvailableSlot(int, TidalSearch::ResultList)));
|
||||
connect(this, SIGNAL(SearchAsyncSig(int, QString, SearchBy)), this, SLOT(DoSearchAsync(int, QString, SearchBy)));
|
||||
connect(this, SIGNAL(ResultsAvailable(int, InternetSearch::ResultList)), SLOT(ResultsAvailableSlot(int, InternetSearch::ResultList)));
|
||||
connect(this, SIGNAL(ArtLoaded(int, QImage)), SLOT(ArtLoadedSlot(int, QImage)));
|
||||
connect(service_, SIGNAL(UpdateStatus(QString)), SLOT(UpdateStatusSlot(QString)));
|
||||
connect(service_, SIGNAL(ProgressSetMaximum(int)), SLOT(ProgressSetMaximumSlot(int)));
|
||||
|
@ -77,13 +76,11 @@ TidalSearch::TidalSearch(Application *app, QObject *parent)
|
|||
connect(service_, SIGNAL(SearchResults(int, SongList)), SLOT(SearchDone(int, SongList)));
|
||||
connect(service_, SIGNAL(SearchError(int, QString)), SLOT(HandleError(int, QString)));
|
||||
|
||||
icon_as_image_ = QImage(icon_.pixmap(48, 48).toImage());
|
||||
|
||||
}
|
||||
|
||||
TidalSearch::~TidalSearch() {}
|
||||
InternetSearch::~InternetSearch() {}
|
||||
|
||||
QStringList TidalSearch::TokenizeQuery(const QString &query) {
|
||||
QStringList InternetSearch::TokenizeQuery(const QString &query) {
|
||||
|
||||
QStringList tokens(query.split(QRegExp("\\s+")));
|
||||
|
||||
|
@ -102,7 +99,7 @@ QStringList TidalSearch::TokenizeQuery(const QString &query) {
|
|||
|
||||
}
|
||||
|
||||
bool TidalSearch::Matches(const QStringList &tokens, const QString &string) {
|
||||
bool InternetSearch::Matches(const QStringList &tokens, const QString &string) {
|
||||
|
||||
for (const QString &token : tokens) {
|
||||
if (!string.contains(token, Qt::CaseInsensitive)) {
|
||||
|
@ -114,7 +111,7 @@ bool TidalSearch::Matches(const QStringList &tokens, const QString &string) {
|
|||
|
||||
}
|
||||
|
||||
int TidalSearch::SearchAsync(const QString &query, TidalSettingsPage::SearchBy searchby) {
|
||||
int InternetSearch::SearchAsync(const QString &query, SearchBy searchby) {
|
||||
|
||||
const int id = searches_next_id_++;
|
||||
|
||||
|
@ -124,14 +121,14 @@ int TidalSearch::SearchAsync(const QString &query, TidalSettingsPage::SearchBy s
|
|||
|
||||
}
|
||||
|
||||
void TidalSearch::SearchAsync(int id, const QString &query, TidalSettingsPage::SearchBy searchby) {
|
||||
void InternetSearch::SearchAsync(int id, const QString &query, SearchBy searchby) {
|
||||
|
||||
const int service_id = service_->Search(query, searchby);
|
||||
pending_searches_[service_id] = PendingState(id, TokenizeQuery(query));
|
||||
|
||||
}
|
||||
|
||||
void TidalSearch::DoSearchAsync(int id, const QString &query, TidalSettingsPage::SearchBy searchby) {
|
||||
void InternetSearch::DoSearchAsync(int id, const QString &query, SearchBy searchby) {
|
||||
|
||||
int timer_id = startTimer(kDelayedSearchTimeoutMs);
|
||||
delayed_searches_[timer_id].id_ = id;
|
||||
|
@ -140,7 +137,7 @@ void TidalSearch::DoSearchAsync(int id, const QString &query, TidalSettingsPage:
|
|||
|
||||
}
|
||||
|
||||
void TidalSearch::SearchDone(int service_id, const SongList &songs) {
|
||||
void InternetSearch::SearchDone(int service_id, const SongList &songs) {
|
||||
|
||||
// Map back to the original id.
|
||||
const PendingState state = pending_searches_.take(service_id);
|
||||
|
@ -158,13 +155,13 @@ void TidalSearch::SearchDone(int service_id, const SongList &songs) {
|
|||
|
||||
}
|
||||
|
||||
void TidalSearch::HandleError(const int id, const QString error) {
|
||||
void InternetSearch::HandleError(const int id, const QString error) {
|
||||
|
||||
emit SearchError(id, error);
|
||||
|
||||
}
|
||||
|
||||
void TidalSearch::MaybeSearchFinished(int id) {
|
||||
void InternetSearch::MaybeSearchFinished(int id) {
|
||||
|
||||
if (pending_searches_.keys(PendingState(id, QStringList())).isEmpty()) {
|
||||
emit SearchFinished(id);
|
||||
|
@ -172,7 +169,7 @@ void TidalSearch::MaybeSearchFinished(int id) {
|
|||
|
||||
}
|
||||
|
||||
void TidalSearch::CancelSearch(int id) {
|
||||
void InternetSearch::CancelSearch(int id) {
|
||||
QMap<int, DelayedSearch>::iterator it;
|
||||
for (it = delayed_searches_.begin(); it != delayed_searches_.end(); ++it) {
|
||||
if (it.value().id_ == id) {
|
||||
|
@ -184,7 +181,7 @@ void TidalSearch::CancelSearch(int id) {
|
|||
service_->CancelSearch();
|
||||
}
|
||||
|
||||
void TidalSearch::timerEvent(QTimerEvent *e) {
|
||||
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().searchby_);
|
||||
|
@ -195,19 +192,19 @@ void TidalSearch::timerEvent(QTimerEvent *e) {
|
|||
QObject::timerEvent(e);
|
||||
}
|
||||
|
||||
void TidalSearch::ResultsAvailableSlot(int id, TidalSearch::ResultList results) {
|
||||
void InternetSearch::ResultsAvailableSlot(int id, InternetSearch::ResultList results) {
|
||||
|
||||
if (results.isEmpty()) return;
|
||||
|
||||
// Limit the number of results that are used from each emission.
|
||||
if (results.count() > kMaxResultsPerEmission) {
|
||||
TidalSearch::ResultList::iterator begin = results.begin();
|
||||
InternetSearch::ResultList::iterator begin = results.begin();
|
||||
std::advance(begin, kMaxResultsPerEmission);
|
||||
results.erase(begin, results.end());
|
||||
}
|
||||
|
||||
// Load cached pixmaps into the results
|
||||
for (TidalSearch::ResultList::iterator it = results.begin(); it != results.end(); ++it) {
|
||||
for (InternetSearch::ResultList::iterator it = results.begin(); it != results.end(); ++it) {
|
||||
it->pixmap_cache_key_ = PixmapCacheKey(*it);
|
||||
}
|
||||
|
||||
|
@ -215,15 +212,15 @@ void TidalSearch::ResultsAvailableSlot(int id, TidalSearch::ResultList results)
|
|||
|
||||
}
|
||||
|
||||
QString TidalSearch::PixmapCacheKey(const TidalSearch::Result &result) const {
|
||||
return "tidal:" % result.metadata_.url().toString();
|
||||
QString InternetSearch::PixmapCacheKey(const InternetSearch::Result &result) const {
|
||||
return "internet:" % result.metadata_.url().toString();
|
||||
}
|
||||
|
||||
bool TidalSearch::FindCachedPixmap(const TidalSearch::Result &result, QPixmap *pixmap) const {
|
||||
bool InternetSearch::FindCachedPixmap(const InternetSearch::Result &result, QPixmap *pixmap) const {
|
||||
return pixmap_cache_.find(result.pixmap_cache_key_, pixmap);
|
||||
}
|
||||
|
||||
int TidalSearch::LoadArtAsync(const TidalSearch::Result &result) {
|
||||
int InternetSearch::LoadArtAsync(const InternetSearch::Result &result) {
|
||||
|
||||
const int id = art_searches_next_id_++;
|
||||
|
||||
|
@ -236,11 +233,11 @@ int TidalSearch::LoadArtAsync(const TidalSearch::Result &result) {
|
|||
|
||||
}
|
||||
|
||||
void TidalSearch::ArtLoadedSlot(int id, const QImage &image) {
|
||||
void InternetSearch::ArtLoadedSlot(int id, const QImage &image) {
|
||||
HandleLoadedArt(id, image);
|
||||
}
|
||||
|
||||
void TidalSearch::AlbumArtLoaded(quint64 id, const QImage &image) {
|
||||
void InternetSearch::AlbumArtLoaded(quint64 id, const QImage &image) {
|
||||
|
||||
if (!cover_loader_tasks_.contains(id)) return;
|
||||
int orig_id = cover_loader_tasks_.take(id);
|
||||
|
@ -249,7 +246,7 @@ void TidalSearch::AlbumArtLoaded(quint64 id, const QImage &image) {
|
|||
|
||||
}
|
||||
|
||||
void TidalSearch::HandleLoadedArt(int id, const QImage &image) {
|
||||
void InternetSearch::HandleLoadedArt(int id, const QImage &image) {
|
||||
|
||||
const QString key = pending_art_searches_.take(id);
|
||||
|
||||
|
@ -260,7 +257,7 @@ void TidalSearch::HandleLoadedArt(int id, const QImage &image) {
|
|||
|
||||
}
|
||||
|
||||
QImage TidalSearch::ScaleAndPad(const QImage &image) {
|
||||
QImage InternetSearch::ScaleAndPad(const QImage &image) {
|
||||
|
||||
if (image.isNull()) return QImage();
|
||||
|
||||
|
@ -286,7 +283,7 @@ QImage TidalSearch::ScaleAndPad(const QImage &image) {
|
|||
|
||||
}
|
||||
|
||||
MimeData *TidalSearch::LoadTracks(const ResultList &results) {
|
||||
MimeData *InternetSearch::LoadTracks(const ResultList &results) {
|
||||
|
||||
if (results.isEmpty()) {
|
||||
return nullptr;
|
||||
|
@ -316,14 +313,14 @@ MimeData *TidalSearch::LoadTracks(const ResultList &results) {
|
|||
|
||||
}
|
||||
|
||||
void TidalSearch::UpdateStatusSlot(QString text) {
|
||||
void InternetSearch::UpdateStatusSlot(QString text) {
|
||||
emit UpdateStatus(text);
|
||||
}
|
||||
|
||||
void TidalSearch::ProgressSetMaximumSlot(int max) {
|
||||
void InternetSearch::ProgressSetMaximumSlot(int max) {
|
||||
emit ProgressSetMaximum(max);
|
||||
}
|
||||
|
||||
void TidalSearch::UpdateProgressSlot(int progress) {
|
||||
void InternetSearch::UpdateProgressSlot(int progress) {
|
||||
emit UpdateProgress(progress);
|
||||
}
|
|
@ -19,8 +19,8 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#ifndef TIDALSEARCH_H
|
||||
#define TIDALSEARCH_H
|
||||
#ifndef INTERNETSEARCH_H
|
||||
#define INTERNETSEARCH_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
|
@ -32,20 +32,23 @@
|
|||
|
||||
#include "core/song.h"
|
||||
#include "covermanager/albumcoverloaderoptions.h"
|
||||
#include "settings/tidalsettingspage.h"
|
||||
|
||||
class Application;
|
||||
class MimeData;
|
||||
class AlbumCoverLoader;
|
||||
class InternetService;
|
||||
class TidalService;
|
||||
|
||||
class TidalSearch : public QObject {
|
||||
class InternetSearch : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TidalSearch(Application *app, QObject *parent = nullptr);
|
||||
~TidalSearch();
|
||||
InternetSearch(Application *app, Song::Source source, QObject *parent = nullptr);
|
||||
~InternetSearch();
|
||||
|
||||
enum SearchBy {
|
||||
SearchBy_Songs = 1,
|
||||
SearchBy_Albums = 2,
|
||||
};
|
||||
|
||||
struct Result {
|
||||
Song metadata_;
|
||||
|
@ -57,10 +60,11 @@ class TidalSearch : public QObject {
|
|||
static const int kMaxResultsPerEmission;
|
||||
|
||||
Application *application() const { return app_; }
|
||||
TidalService *service() const { return service_; }
|
||||
Song::Source source() const { return source_; }
|
||||
InternetService *service() const { return service_; }
|
||||
|
||||
int SearchAsync(const QString &query, TidalSettingsPage::SearchBy searchby);
|
||||
int LoadArtAsync(const TidalSearch::Result &result);
|
||||
int SearchAsync(const QString &query, SearchBy searchby);
|
||||
int LoadArtAsync(const InternetSearch::Result &result);
|
||||
|
||||
void CancelSearch(int id);
|
||||
void CancelArt(int id);
|
||||
|
@ -70,9 +74,9 @@ class TidalSearch : public QObject {
|
|||
MimeData *LoadTracks(const ResultList &results);
|
||||
|
||||
signals:
|
||||
void SearchAsyncSig(int id, const QString &query, TidalSettingsPage::SearchBy searchby);
|
||||
void ResultsAvailable(int id, const TidalSearch::ResultList &results);
|
||||
void AddResults(int id, const TidalSearch::ResultList &results);
|
||||
void SearchAsyncSig(int id, const QString &query, SearchBy searchby);
|
||||
void ResultsAvailable(int id, const InternetSearch::ResultList &results);
|
||||
void AddResults(int id, const InternetSearch::ResultList &results);
|
||||
void SearchError(const int id, const QString error);
|
||||
void SearchFinished(int id);
|
||||
void UpdateStatus(QString text);
|
||||
|
@ -108,10 +112,10 @@ class TidalSearch : public QObject {
|
|||
static bool Matches(const QStringList &tokens, const QString &string);
|
||||
|
||||
private slots:
|
||||
void DoSearchAsync(int id, const QString &query, TidalSettingsPage::SearchBy searchby);
|
||||
void DoSearchAsync(int id, const QString &query, SearchBy searchby);
|
||||
void SearchDone(int id, const SongList &songs);
|
||||
void HandleError(const int id, const QString error);
|
||||
void ResultsAvailableSlot(int id, TidalSearch::ResultList results);
|
||||
void ResultsAvailableSlot(int id, InternetSearch::ResultList results);
|
||||
|
||||
void ArtLoadedSlot(int id, const QImage &image);
|
||||
void AlbumArtLoaded(quint64 id, const QImage &image);
|
||||
|
@ -121,10 +125,10 @@ class TidalSearch : public QObject {
|
|||
void UpdateProgressSlot(int max);
|
||||
|
||||
private:
|
||||
void SearchAsync(int id, const QString &query, TidalSettingsPage::SearchBy searchby);
|
||||
void SearchAsync(int id, const QString &query, SearchBy searchby);
|
||||
void HandleLoadedArt(int id, const QImage &image);
|
||||
bool FindCachedPixmap(const TidalSearch::Result &result, QPixmap *pixmap) const;
|
||||
QString PixmapCacheKey(const TidalSearch::Result &result) const;
|
||||
bool FindCachedPixmap(const InternetSearch::Result &result, QPixmap *pixmap) const;
|
||||
QString PixmapCacheKey(const InternetSearch::Result &result) const;
|
||||
void MaybeSearchFinished(int id);
|
||||
void ShowConfig() {}
|
||||
static QImage ScaleAndPad(const QImage &image);
|
||||
|
@ -133,18 +137,14 @@ class TidalSearch : public QObject {
|
|||
struct DelayedSearch {
|
||||
int id_;
|
||||
QString query_;
|
||||
TidalSettingsPage::SearchBy searchby_;
|
||||
SearchBy searchby_;
|
||||
};
|
||||
|
||||
static const int kArtHeight;
|
||||
|
||||
Application *app_;
|
||||
TidalService *service_;
|
||||
Song::Source source_;
|
||||
QString name_;
|
||||
QString id_;
|
||||
QIcon icon_;
|
||||
QImage icon_as_image_;
|
||||
InternetService *service_;
|
||||
int searches_next_id_;
|
||||
int art_searches_next_id_;
|
||||
|
||||
|
@ -158,7 +158,7 @@ class TidalSearch : public QObject {
|
|||
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(TidalSearch::Result)
|
||||
Q_DECLARE_METATYPE(TidalSearch::ResultList)
|
||||
Q_DECLARE_METATYPE(InternetSearch::Result)
|
||||
Q_DECLARE_METATYPE(InternetSearch::ResultList)
|
||||
|
||||
#endif // TIDALSEARCH_H
|
||||
#endif // INTERNETSEARCH_H
|
|
@ -21,15 +21,15 @@
|
|||
#include <QPainter>
|
||||
#include <QStyleOptionViewItem>
|
||||
|
||||
#include "tidalsearchitemdelegate.h"
|
||||
#include "tidalsearchview.h"
|
||||
#include "internetsearchitemdelegate.h"
|
||||
#include "internetsearchview.h"
|
||||
|
||||
TidalSearchItemDelegate::TidalSearchItemDelegate(TidalSearchView* view)
|
||||
InternetSearchItemDelegate::InternetSearchItemDelegate(InternetSearchView* view)
|
||||
: CollectionItemDelegate(view), view_(view) {}
|
||||
|
||||
void TidalSearchItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||
void InternetSearchItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||
// Tell the view we painted this item so it can lazy load some art.
|
||||
const_cast<TidalSearchView*>(view_)->LazyLoadArt(index);
|
||||
const_cast<InternetSearchView*>(view_)->LazyLoadArt(index);
|
||||
|
||||
CollectionItemDelegate::paint(painter, option, index);
|
||||
}
|
|
@ -18,24 +18,24 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#ifndef TIDALSEARCHITEMDELEGATE_H
|
||||
#define TIDALSEARCHITEMDELEGATE_H
|
||||
#ifndef INTERNETSEARCHITEMDELEGATE_H
|
||||
#define INTERNETSEARCHITEMDELEGATE_H
|
||||
|
||||
#include <QPainter>
|
||||
#include <QStyleOptionViewItem>
|
||||
|
||||
#include "collection/collectionview.h"
|
||||
|
||||
class TidalSearchView;
|
||||
class InternetSearchView;
|
||||
|
||||
class TidalSearchItemDelegate : public CollectionItemDelegate {
|
||||
class InternetSearchItemDelegate : public CollectionItemDelegate {
|
||||
public:
|
||||
TidalSearchItemDelegate(TidalSearchView *view);
|
||||
InternetSearchItemDelegate(InternetSearchView *view);
|
||||
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
|
||||
|
||||
private:
|
||||
TidalSearchView* view_;
|
||||
InternetSearchView* view_;
|
||||
};
|
||||
|
||||
#endif // TIDALSEARCHITEMDELEGATE_H
|
||||
#endif // INTERNETSEARCHITEMDELEGATE_H
|
|
@ -33,10 +33,10 @@
|
|||
#include "core/mimedata.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/logging.h"
|
||||
#include "deezersearch.h"
|
||||
#include "deezersearchmodel.h"
|
||||
#include "internetsearch.h"
|
||||
#include "internetsearchmodel.h"
|
||||
|
||||
DeezerSearchModel::DeezerSearchModel(DeezerSearch *engine, QObject *parent)
|
||||
InternetSearchModel::InternetSearchModel(InternetSearch *engine, QObject *parent)
|
||||
: QStandardItemModel(parent),
|
||||
engine_(engine),
|
||||
proxy_(nullptr),
|
||||
|
@ -55,11 +55,11 @@ DeezerSearchModel::DeezerSearchModel(DeezerSearch *engine, QObject *parent)
|
|||
|
||||
}
|
||||
|
||||
void DeezerSearchModel::AddResults(const DeezerSearch::ResultList &results) {
|
||||
void InternetSearchModel::AddResults(const InternetSearch::ResultList &results) {
|
||||
|
||||
int sort_index = 0;
|
||||
|
||||
for (const DeezerSearch::Result &result : results) {
|
||||
for (const InternetSearch::Result &result : results) {
|
||||
QStandardItem *parent = invisibleRootItem();
|
||||
|
||||
// Find (or create) the container nodes for this result if we can.
|
||||
|
@ -79,7 +79,7 @@ void DeezerSearchModel::AddResults(const DeezerSearch::ResultList &results) {
|
|||
|
||||
}
|
||||
|
||||
QStandardItem *DeezerSearchModel::BuildContainers(const Song &s, QStandardItem *parent, ContainerKey *key, int level) {
|
||||
QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem *parent, ContainerKey *key, int level) {
|
||||
|
||||
if (level >= 3) {
|
||||
return parent;
|
||||
|
@ -210,12 +210,12 @@ QStandardItem *DeezerSearchModel::BuildContainers(const Song &s, QStandardItem *
|
|||
|
||||
}
|
||||
|
||||
void DeezerSearchModel::Clear() {
|
||||
void InternetSearchModel::Clear() {
|
||||
containers_.clear();
|
||||
clear();
|
||||
}
|
||||
|
||||
DeezerSearch::ResultList DeezerSearchModel::GetChildResults(const QModelIndexList &indexes) const {
|
||||
InternetSearch::ResultList InternetSearchModel::GetChildResults(const QModelIndexList &indexes) const {
|
||||
|
||||
QList<QStandardItem*> items;
|
||||
for (const QModelIndex &index : indexes) {
|
||||
|
@ -225,9 +225,9 @@ DeezerSearch::ResultList DeezerSearchModel::GetChildResults(const QModelIndexLis
|
|||
|
||||
}
|
||||
|
||||
DeezerSearch::ResultList DeezerSearchModel::GetChildResults(const QList<QStandardItem*> &items) const {
|
||||
InternetSearch::ResultList InternetSearchModel::GetChildResults(const QList<QStandardItem*> &items) const {
|
||||
|
||||
DeezerSearch::ResultList results;
|
||||
InternetSearch::ResultList results;
|
||||
QSet<const QStandardItem*> visited;
|
||||
|
||||
for (QStandardItem *item : items) {
|
||||
|
@ -238,7 +238,7 @@ DeezerSearch::ResultList DeezerSearchModel::GetChildResults(const QList<QStandar
|
|||
|
||||
}
|
||||
|
||||
void DeezerSearchModel::GetChildResults(const QStandardItem *item, DeezerSearch::ResultList *results, QSet<const QStandardItem*> *visited) const {
|
||||
void InternetSearchModel::GetChildResults(const QStandardItem *item, InternetSearch::ResultList *results, QSet<const QStandardItem*> *visited) const {
|
||||
|
||||
if (visited->contains(item)) {
|
||||
return;
|
||||
|
@ -261,7 +261,7 @@ void DeezerSearchModel::GetChildResults(const QStandardItem *item, DeezerSearch:
|
|||
// No - maybe it's a song, add its result if valid
|
||||
QVariant result = item->data(Role_Result);
|
||||
if (result.isValid()) {
|
||||
results->append(result.value<DeezerSearch::Result>());
|
||||
results->append(result.value<InternetSearch::Result>());
|
||||
}
|
||||
else {
|
||||
// Maybe it's a provider then?
|
||||
|
@ -282,16 +282,16 @@ void DeezerSearchModel::GetChildResults(const QStandardItem *item, DeezerSearch:
|
|||
|
||||
}
|
||||
|
||||
QMimeData *DeezerSearchModel::mimeData(const QModelIndexList &indexes) const {
|
||||
QMimeData *InternetSearchModel::mimeData(const QModelIndexList &indexes) const {
|
||||
return engine_->LoadTracks(GetChildResults(indexes));
|
||||
}
|
||||
|
||||
namespace {
|
||||
void GatherResults(const QStandardItem *parent, DeezerSearch::ResultList *results) {
|
||||
void GatherResults(const QStandardItem *parent, InternetSearch::ResultList *results) {
|
||||
|
||||
QVariant result_variant = parent->data(DeezerSearchModel::Role_Result);
|
||||
QVariant result_variant = parent->data(InternetSearchModel::Role_Result);
|
||||
if (result_variant.isValid()) {
|
||||
DeezerSearch::Result result = result_variant.value<DeezerSearch::Result>();
|
||||
InternetSearch::Result result = result_variant.value<InternetSearch::Result>();
|
||||
(*results).append(result);
|
||||
}
|
||||
|
||||
|
@ -301,14 +301,14 @@ void GatherResults(const QStandardItem *parent, DeezerSearch::ResultList *result
|
|||
}
|
||||
}
|
||||
|
||||
void DeezerSearchModel::SetGroupBy(const CollectionModel::Grouping &grouping, bool regroup_now) {
|
||||
void InternetSearchModel::SetGroupBy(const CollectionModel::Grouping &grouping, bool regroup_now) {
|
||||
|
||||
const CollectionModel::Grouping old_group_by = group_by_;
|
||||
group_by_ = grouping;
|
||||
|
||||
if (regroup_now && group_by_ != old_group_by) {
|
||||
// Walk the tree gathering the results we have already
|
||||
DeezerSearch::ResultList results;
|
||||
InternetSearch::ResultList results;
|
||||
GatherResults(invisibleRootItem(), &results);
|
||||
|
||||
// Reset the model and re-add all the results using the new grouping.
|
|
@ -18,8 +18,8 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#ifndef DEEZERSEARCHMODEL_H
|
||||
#define DEEZERSEARCHMODEL_H
|
||||
#ifndef INTERNETSEARCHMODEL_H
|
||||
#define INTERNETSEARCHMODEL_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
|
@ -38,13 +38,13 @@
|
|||
#include <QPixmap>
|
||||
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "deezersearch.h"
|
||||
#include "internetsearch.h"
|
||||
|
||||
class DeezerSearchModel : public QStandardItemModel {
|
||||
class InternetSearchModel : public QStandardItemModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DeezerSearchModel(DeezerSearch *engine, QObject *parent = nullptr);
|
||||
InternetSearchModel(InternetSearch *engine, QObject *parent = nullptr);
|
||||
|
||||
enum Role {
|
||||
Role_Result = CollectionModel::LastRole,
|
||||
|
@ -64,20 +64,20 @@ class DeezerSearchModel : public QStandardItemModel {
|
|||
|
||||
void Clear();
|
||||
|
||||
DeezerSearch::ResultList GetChildResults(const QModelIndexList &indexes) const;
|
||||
DeezerSearch::ResultList GetChildResults(const QList<QStandardItem*> &items) const;
|
||||
InternetSearch::ResultList GetChildResults(const QModelIndexList &indexes) const;
|
||||
InternetSearch::ResultList GetChildResults(const QList<QStandardItem*> &items) const;
|
||||
|
||||
QMimeData *mimeData(const QModelIndexList &indexes) const;
|
||||
|
||||
public slots:
|
||||
void AddResults(const DeezerSearch::ResultList &results);
|
||||
void AddResults(const InternetSearch::ResultList &results);
|
||||
|
||||
private:
|
||||
QStandardItem *BuildContainers(const Song &metadata, QStandardItem *parent, ContainerKey *key, int level = 0);
|
||||
void GetChildResults(const QStandardItem *item, DeezerSearch::ResultList *results, QSet<const QStandardItem*> *visited) const;
|
||||
void GetChildResults(const QStandardItem *item, InternetSearch::ResultList *results, QSet<const QStandardItem*> *visited) const;
|
||||
|
||||
private:
|
||||
DeezerSearch *engine_;
|
||||
InternetSearch *engine_;
|
||||
QSortFilterProxyModel *proxy_;
|
||||
bool use_pretty_covers_;
|
||||
QIcon artist_icon_;
|
||||
|
@ -88,11 +88,11 @@ class DeezerSearchModel : public QStandardItemModel {
|
|||
|
||||
};
|
||||
|
||||
inline uint qHash(const DeezerSearchModel::ContainerKey &key) {
|
||||
inline uint qHash(const InternetSearchModel::ContainerKey &key) {
|
||||
return qHash(key.provider_index_) ^ qHash(key.group_[0]) ^ qHash(key.group_[1]) ^ qHash(key.group_[2]);
|
||||
}
|
||||
|
||||
inline bool operator<(const DeezerSearchModel::ContainerKey &left, const DeezerSearchModel::ContainerKey &right) {
|
||||
inline bool operator<(const InternetSearchModel::ContainerKey &left, const InternetSearchModel::ContainerKey &right) {
|
||||
#define CMP(field) \
|
||||
if (left.field < right.field) return true; \
|
||||
if (left.field > right.field) return false
|
||||
|
@ -106,4 +106,4 @@ inline bool operator<(const DeezerSearchModel::ContainerKey &left, const DeezerS
|
|||
#undef CMP
|
||||
}
|
||||
|
||||
#endif // DEEZERSEARCHMODEL_H
|
||||
#endif // INTERNETSEARCHMODEL_H
|
|
@ -25,16 +25,16 @@
|
|||
#include <QString>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "deezersearchmodel.h"
|
||||
#include "deezersearchsortmodel.h"
|
||||
#include "internetsearchmodel.h"
|
||||
#include "internetsearchsortmodel.h"
|
||||
|
||||
DeezerSearchSortModel::DeezerSearchSortModel(QObject *parent)
|
||||
InternetSearchSortModel::InternetSearchSortModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent) {}
|
||||
|
||||
bool DeezerSearchSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const {
|
||||
bool InternetSearchSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const {
|
||||
// Compare the provider sort index first.
|
||||
const int index_left = left.data(DeezerSearchModel::Role_ProviderIndex).toInt();
|
||||
const int index_right = right.data(DeezerSearchModel::Role_ProviderIndex).toInt();
|
||||
const int index_left = left.data(InternetSearchModel::Role_ProviderIndex).toInt();
|
||||
const int index_right = right.data(InternetSearchModel::Role_ProviderIndex).toInt();
|
||||
if (index_left < index_right) return true;
|
||||
if (index_left > index_right) return false;
|
||||
|
||||
|
@ -54,8 +54,8 @@ bool DeezerSearchSortModel::lessThan(const QModelIndex &left, const QModelIndex
|
|||
}
|
||||
|
||||
// Otherwise we're comparing songs. Sort by disc, track, then title.
|
||||
const DeezerSearch::Result r1 = left.data(DeezerSearchModel::Role_Result).value<DeezerSearch::Result>();
|
||||
const DeezerSearch::Result r2 = right.data(DeezerSearchModel::Role_Result).value<DeezerSearch::Result>();
|
||||
const InternetSearch::Result r1 = left.data(InternetSearchModel::Role_Result).value<InternetSearch::Result>();
|
||||
const InternetSearch::Result r2 = right.data(InternetSearchModel::Role_Result).value<InternetSearch::Result>();
|
||||
|
||||
#define CompareInt(field) \
|
||||
if (r1.metadata_.field() < r2.metadata_.field()) return true; \
|
|
@ -18,18 +18,18 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#ifndef DEEZERSEARCHSORTMODEL_H
|
||||
#define DEEZERSEARCHSORTMODEL_H
|
||||
#ifndef INTERNETSEARCHSORTMODEL_H
|
||||
#define INTERNETSEARCHSORTMODEL_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
class DeezerSearchSortModel : public QSortFilterProxyModel {
|
||||
class InternetSearchSortModel : public QSortFilterProxyModel {
|
||||
public:
|
||||
DeezerSearchSortModel(QObject *parent = nullptr);
|
||||
InternetSearchSortModel(QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
|
||||
};
|
||||
|
||||
#endif // DEEZERSEARCHSORTMODEL_H
|
||||
#endif // INTERNETSEARCHSORTMODEL_H
|
|
@ -50,37 +50,37 @@
|
|||
#include "collection/collectionmodel.h"
|
||||
#include "collection/groupbydialog.h"
|
||||
#include "playlist/songmimedata.h"
|
||||
#include "tidalsearch.h"
|
||||
#include "tidalsearchitemdelegate.h"
|
||||
#include "tidalsearchmodel.h"
|
||||
#include "tidalsearchsortmodel.h"
|
||||
#include "tidalsearchview.h"
|
||||
#include "ui_tidalsearchview.h"
|
||||
#include "settings/tidalsettingspage.h"
|
||||
#include "internetsearch.h"
|
||||
#include "internetsearchitemdelegate.h"
|
||||
#include "internetsearchmodel.h"
|
||||
#include "internetsearchsortmodel.h"
|
||||
#include "internetsearchview.h"
|
||||
#include "ui_internetsearchview.h"
|
||||
|
||||
using std::placeholders::_1;
|
||||
using std::placeholders::_2;
|
||||
using std::swap;
|
||||
|
||||
const int TidalSearchView::kSwapModelsTimeoutMsec = 250;
|
||||
const int InternetSearchView::kSwapModelsTimeoutMsec = 250;
|
||||
|
||||
TidalSearchView::TidalSearchView(Application *app, QWidget *parent)
|
||||
InternetSearchView::InternetSearchView(Application *app, InternetSearch *engine, QString settings_group, SettingsDialog::Page settings_page, QWidget *parent)
|
||||
: QWidget(parent),
|
||||
app_(app),
|
||||
engine_(app_->tidal_search()),
|
||||
ui_(new Ui_TidalSearchView),
|
||||
engine_(engine),
|
||||
settings_group_(settings_group),
|
||||
settings_page_(settings_page),
|
||||
ui_(new Ui_InternetSearchView),
|
||||
context_menu_(nullptr),
|
||||
last_search_id_(0),
|
||||
front_model_(new TidalSearchModel(engine_, this)),
|
||||
back_model_(new TidalSearchModel(engine_, this)),
|
||||
front_model_(new InternetSearchModel(engine_, this)),
|
||||
back_model_(new InternetSearchModel(engine_, this)),
|
||||
current_model_(front_model_),
|
||||
front_proxy_(new TidalSearchSortModel(this)),
|
||||
back_proxy_(new TidalSearchSortModel(this)),
|
||||
front_proxy_(new InternetSearchSortModel(this)),
|
||||
back_proxy_(new InternetSearchSortModel(this)),
|
||||
current_proxy_(front_proxy_),
|
||||
swap_models_timer_(new QTimer(this)),
|
||||
search_icon_(IconLoader::Load("search")),
|
||||
warning_icon_(IconLoader::Load("dialog-warning")),
|
||||
error_(false) {
|
||||
error_(false)
|
||||
{
|
||||
|
||||
ui_->setupUi(this);
|
||||
ui_->progressbar->hide();
|
||||
|
@ -94,7 +94,7 @@ TidalSearchView::TidalSearchView(Application *app, QWidget *parent)
|
|||
|
||||
ui_->settings->setIcon(IconLoader::Load("configure"));
|
||||
|
||||
// Must be a queued connection to ensure the TidalSearch handles it first.
|
||||
// Must be a queued connection to ensure the InternetSearch handles it first.
|
||||
connect(app_, SIGNAL(SettingsChanged()), SLOT(ReloadSettings()), Qt::QueuedConnection);
|
||||
|
||||
connect(ui_->search, SIGNAL(textChanged(QString)), SLOT(TextEdited(QString)));
|
||||
|
@ -102,7 +102,7 @@ TidalSearchView::TidalSearchView(Application *app, QWidget *parent)
|
|||
connect(ui_->results, SIGNAL(FocusOnFilterSignal(QKeyEvent*)), SLOT(FocusOnFilter(QKeyEvent*)));
|
||||
|
||||
// Set the appearance of the results list
|
||||
ui_->results->setItemDelegate(new TidalSearchItemDelegate(this));
|
||||
ui_->results->setItemDelegate(new InternetSearchItemDelegate(this));
|
||||
ui_->results->setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
ui_->results->setStyleSheet("QTreeView::item{padding-top:1px;}");
|
||||
|
||||
|
@ -140,7 +140,7 @@ TidalSearchView::TidalSearchView(Application *app, QWidget *parent)
|
|||
QMenu *settings_menu = new QMenu(this);
|
||||
settings_menu->addActions(group_by_actions_->actions());
|
||||
settings_menu->addSeparator();
|
||||
settings_menu->addAction(IconLoader::Load("configure"), tr("Configure Tidal..."), this, SLOT(OpenSettingsDialog()));
|
||||
settings_menu->addAction(IconLoader::Load("configure"), QString("Configure %1...").arg(Song::TextForSource(engine->source())), this, SLOT(OpenSettingsDialog()));
|
||||
ui_->settings->setMenu(settings_menu);
|
||||
|
||||
connect(ui_->radiobutton_searchbyalbums, SIGNAL(clicked(bool)), SLOT(SearchByAlbumsClicked(bool)));
|
||||
|
@ -154,7 +154,7 @@ TidalSearchView::TidalSearchView(Application *app, QWidget *parent)
|
|||
connect(engine_, SIGNAL(ProgressSetMaximum(int)), SLOT(ProgressSetMaximum(int)), Qt::QueuedConnection);
|
||||
connect(engine_, SIGNAL(UpdateProgress(int)), SLOT(UpdateProgress(int)), Qt::QueuedConnection);
|
||||
|
||||
connect(engine_, SIGNAL(AddResults(int, TidalSearch::ResultList)), SLOT(AddResults(int, TidalSearch::ResultList)), Qt::QueuedConnection);
|
||||
connect(engine_, SIGNAL(AddResults(int, InternetSearch::ResultList)), SLOT(AddResults(int, InternetSearch::ResultList)), Qt::QueuedConnection);
|
||||
connect(engine_, SIGNAL(SearchError(int, QString)), SLOT(SearchError(int, QString)), Qt::QueuedConnection);
|
||||
connect(engine_, SIGNAL(ArtLoaded(int, QPixmap)), SLOT(ArtLoaded(int, QPixmap)), Qt::QueuedConnection);
|
||||
|
||||
|
@ -162,29 +162,29 @@ TidalSearchView::TidalSearchView(Application *app, QWidget *parent)
|
|||
|
||||
}
|
||||
|
||||
TidalSearchView::~TidalSearchView() { delete ui_; }
|
||||
InternetSearchView::~InternetSearchView() { delete ui_; }
|
||||
|
||||
void TidalSearchView::ReloadSettings() {
|
||||
void InternetSearchView::ReloadSettings() {
|
||||
|
||||
QSettings s;
|
||||
|
||||
// Collection settings
|
||||
|
||||
s.beginGroup(TidalSettingsPage::kSettingsGroup);
|
||||
s.beginGroup(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);
|
||||
s.endGroup();
|
||||
|
||||
// Tidal search settings
|
||||
// Internet search settings
|
||||
|
||||
s.beginGroup(TidalSettingsPage::kSettingsGroup);
|
||||
searchby_ = TidalSettingsPage::SearchBy(s.value("searchby", int(TidalSettingsPage::SearchBy_Songs)).toInt());
|
||||
s.beginGroup(settings_group_);
|
||||
searchby_ = InternetSearch::SearchBy(s.value("searchby", int(InternetSearch::SearchBy_Songs)).toInt());
|
||||
switch (searchby_) {
|
||||
case TidalSettingsPage::SearchBy_Songs:
|
||||
case InternetSearch::SearchBy_Songs:
|
||||
ui_->radiobutton_searchbysongs->setChecked(true);
|
||||
break;
|
||||
case TidalSettingsPage::SearchBy_Albums:
|
||||
case InternetSearch::SearchBy_Albums:
|
||||
ui_->radiobutton_searchbyalbums->setChecked(true);
|
||||
break;
|
||||
}
|
||||
|
@ -197,7 +197,7 @@ void TidalSearchView::ReloadSettings() {
|
|||
|
||||
}
|
||||
|
||||
void TidalSearchView::StartSearch(const QString &query) {
|
||||
void InternetSearchView::StartSearch(const QString &query) {
|
||||
|
||||
ui_->search->setText(query);
|
||||
TextEdited(query);
|
||||
|
@ -208,7 +208,7 @@ void TidalSearchView::StartSearch(const QString &query) {
|
|||
|
||||
}
|
||||
|
||||
void TidalSearchView::TextEdited(const QString &text) {
|
||||
void InternetSearchView::TextEdited(const QString &text) {
|
||||
|
||||
const QString trimmed(text.trimmed());
|
||||
|
||||
|
@ -237,7 +237,7 @@ void TidalSearchView::TextEdited(const QString &text) {
|
|||
|
||||
}
|
||||
|
||||
void TidalSearchView::AddResults(int id, const TidalSearch::ResultList &results) {
|
||||
void InternetSearchView::AddResults(int id, const InternetSearch::ResultList &results) {
|
||||
if (id != last_search_id_) return;
|
||||
if (results.isEmpty()) return;
|
||||
ui_->label_status->clear();
|
||||
|
@ -246,7 +246,7 @@ void TidalSearchView::AddResults(int id, const TidalSearch::ResultList &results)
|
|||
current_model_->AddResults(results);
|
||||
}
|
||||
|
||||
void TidalSearchView::SearchError(const int id, const QString error) {
|
||||
void InternetSearchView::SearchError(const int id, const QString error) {
|
||||
error_ = true;
|
||||
ui_->label_helptext->setText(error);
|
||||
ui_->label_status->clear();
|
||||
|
@ -255,7 +255,7 @@ void TidalSearchView::SearchError(const int id, const QString error) {
|
|||
ui_->results_stack->setCurrentWidget(ui_->help_page);
|
||||
}
|
||||
|
||||
void TidalSearchView::SwapModels() {
|
||||
void InternetSearchView::SwapModels() {
|
||||
|
||||
art_requests_.clear();
|
||||
|
||||
|
@ -273,14 +273,14 @@ void TidalSearchView::SwapModels() {
|
|||
|
||||
}
|
||||
|
||||
void TidalSearchView::LazyLoadArt(const QModelIndex &proxy_index) {
|
||||
void InternetSearchView::LazyLoadArt(const QModelIndex &proxy_index) {
|
||||
|
||||
if (!proxy_index.isValid() || proxy_index.model() != front_proxy_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Already loading art for this item?
|
||||
if (proxy_index.data(TidalSearchModel::Role_LazyLoadingArt).isValid()) {
|
||||
if (proxy_index.data(InternetSearchModel::Role_LazyLoadingArt).isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -301,7 +301,7 @@ void TidalSearchView::LazyLoadArt(const QModelIndex &proxy_index) {
|
|||
// Mark the item as loading art
|
||||
const QModelIndex source_index = front_proxy_->mapToSource(proxy_index);
|
||||
QStandardItem *item = front_model_->itemFromIndex(source_index);
|
||||
item->setData(true, TidalSearchModel::Role_LazyLoadingArt);
|
||||
item->setData(true, InternetSearchModel::Role_LazyLoadingArt);
|
||||
|
||||
// Walk down the item's children until we find a track
|
||||
while (item->rowCount()) {
|
||||
|
@ -309,7 +309,7 @@ void TidalSearchView::LazyLoadArt(const QModelIndex &proxy_index) {
|
|||
}
|
||||
|
||||
// Get the track's Result
|
||||
const TidalSearch::Result result = item->data(TidalSearchModel::Role_Result).value<TidalSearch::Result>();
|
||||
const InternetSearch::Result result = item->data(InternetSearchModel::Role_Result).value<InternetSearch::Result>();
|
||||
|
||||
// Load the art.
|
||||
int id = engine_->LoadArtAsync(result);
|
||||
|
@ -317,7 +317,7 @@ void TidalSearchView::LazyLoadArt(const QModelIndex &proxy_index) {
|
|||
|
||||
}
|
||||
|
||||
void TidalSearchView::ArtLoaded(int id, const QPixmap &pixmap) {
|
||||
void InternetSearchView::ArtLoaded(int id, const QPixmap &pixmap) {
|
||||
|
||||
if (!art_requests_.contains(id)) return;
|
||||
QModelIndex index = art_requests_.take(id);
|
||||
|
@ -328,7 +328,7 @@ void TidalSearchView::ArtLoaded(int id, const QPixmap &pixmap) {
|
|||
|
||||
}
|
||||
|
||||
MimeData *TidalSearchView::SelectedMimeData() {
|
||||
MimeData *InternetSearchView::SelectedMimeData() {
|
||||
|
||||
if (!ui_->results->selectionModel()) return nullptr;
|
||||
|
||||
|
@ -362,7 +362,7 @@ MimeData *TidalSearchView::SelectedMimeData() {
|
|||
|
||||
}
|
||||
|
||||
bool TidalSearchView::eventFilter(QObject *object, QEvent *event) {
|
||||
bool InternetSearchView::eventFilter(QObject *object, QEvent *event) {
|
||||
|
||||
if (object == ui_->search && event->type() == QEvent::KeyRelease) {
|
||||
if (SearchKeyEvent(static_cast<QKeyEvent*>(event))) {
|
||||
|
@ -379,7 +379,7 @@ bool TidalSearchView::eventFilter(QObject *object, QEvent *event) {
|
|||
|
||||
}
|
||||
|
||||
bool TidalSearchView::SearchKeyEvent(QKeyEvent *event) {
|
||||
bool InternetSearchView::SearchKeyEvent(QKeyEvent *event) {
|
||||
|
||||
switch (event->key()) {
|
||||
case Qt::Key_Up:
|
||||
|
@ -407,7 +407,7 @@ bool TidalSearchView::SearchKeyEvent(QKeyEvent *event) {
|
|||
|
||||
}
|
||||
|
||||
bool TidalSearchView::ResultsContextMenuEvent(QContextMenuEvent *event) {
|
||||
bool InternetSearchView::ResultsContextMenuEvent(QContextMenuEvent *event) {
|
||||
|
||||
context_menu_ = new QMenu(this);
|
||||
context_actions_ << context_menu_->addAction( IconLoader::Load("media-play"), tr("Append to current playlist"), this, SLOT(AddSelectedToPlaylist()));
|
||||
|
@ -425,7 +425,7 @@ bool TidalSearchView::ResultsContextMenuEvent(QContextMenuEvent *event) {
|
|||
|
||||
context_menu_->addSeparator();
|
||||
context_menu_->addMenu(tr("Group by"))->addActions(group_by_actions_->actions());
|
||||
context_menu_->addAction(IconLoader::Load("configure"), tr("Configure Tidal..."), this, SLOT(OpenSettingsDialog()));
|
||||
context_menu_->addAction(IconLoader::Load("configure"), tr("Configure Internet..."), this, SLOT(OpenSettingsDialog()));
|
||||
|
||||
const bool enable_context_actions = ui_->results->selectionModel() && ui_->results->selectionModel()->hasSelection();
|
||||
|
||||
|
@ -439,11 +439,11 @@ bool TidalSearchView::ResultsContextMenuEvent(QContextMenuEvent *event) {
|
|||
|
||||
}
|
||||
|
||||
void TidalSearchView::AddSelectedToPlaylist() {
|
||||
void InternetSearchView::AddSelectedToPlaylist() {
|
||||
emit AddToPlaylist(SelectedMimeData());
|
||||
}
|
||||
|
||||
void TidalSearchView::LoadSelected() {
|
||||
void InternetSearchView::LoadSelected() {
|
||||
MimeData *data = SelectedMimeData();
|
||||
if (!data) return;
|
||||
|
||||
|
@ -451,7 +451,7 @@ void TidalSearchView::LoadSelected() {
|
|||
emit AddToPlaylist(data);
|
||||
}
|
||||
|
||||
void TidalSearchView::AddSelectedToPlaylistEnqueue() {
|
||||
void InternetSearchView::AddSelectedToPlaylistEnqueue() {
|
||||
MimeData *data = SelectedMimeData();
|
||||
if (!data) return;
|
||||
|
||||
|
@ -459,7 +459,7 @@ void TidalSearchView::AddSelectedToPlaylistEnqueue() {
|
|||
emit AddToPlaylist(data);
|
||||
}
|
||||
|
||||
void TidalSearchView::OpenSelectedInNewPlaylist() {
|
||||
void InternetSearchView::OpenSelectedInNewPlaylist() {
|
||||
MimeData *data = SelectedMimeData();
|
||||
if (!data) return;
|
||||
|
||||
|
@ -467,34 +467,34 @@ void TidalSearchView::OpenSelectedInNewPlaylist() {
|
|||
emit AddToPlaylist(data);
|
||||
}
|
||||
|
||||
void TidalSearchView::SearchForThis() {
|
||||
void InternetSearchView::SearchForThis() {
|
||||
StartSearch(ui_->results->selectionModel()->selectedRows().first().data().toString());
|
||||
}
|
||||
|
||||
void TidalSearchView::showEvent(QShowEvent *e) {
|
||||
void InternetSearchView::showEvent(QShowEvent *e) {
|
||||
QWidget::showEvent(e);
|
||||
FocusSearchField();
|
||||
}
|
||||
|
||||
void TidalSearchView::FocusSearchField() {
|
||||
void InternetSearchView::FocusSearchField() {
|
||||
ui_->search->setFocus();
|
||||
ui_->search->selectAll();
|
||||
}
|
||||
|
||||
void TidalSearchView::hideEvent(QHideEvent *e) {
|
||||
void InternetSearchView::hideEvent(QHideEvent *e) {
|
||||
QWidget::hideEvent(e);
|
||||
}
|
||||
|
||||
void TidalSearchView::FocusOnFilter(QKeyEvent *event) {
|
||||
void InternetSearchView::FocusOnFilter(QKeyEvent *event) {
|
||||
ui_->search->setFocus();
|
||||
QApplication::sendEvent(ui_->search, event);
|
||||
}
|
||||
|
||||
void TidalSearchView::OpenSettingsDialog() {
|
||||
app_->OpenSettingsDialogAtPage(SettingsDialog::Page_Tidal);
|
||||
void InternetSearchView::OpenSettingsDialog() {
|
||||
app_->OpenSettingsDialogAtPage(settings_page_);
|
||||
}
|
||||
|
||||
void TidalSearchView::GroupByClicked(QAction *action) {
|
||||
void InternetSearchView::GroupByClicked(QAction *action) {
|
||||
|
||||
if (action->property("group_by").isNull()) {
|
||||
if (!group_by_dialog_) {
|
||||
|
@ -510,11 +510,11 @@ void TidalSearchView::GroupByClicked(QAction *action) {
|
|||
|
||||
}
|
||||
|
||||
void TidalSearchView::SetGroupBy(const CollectionModel::Grouping &g) {
|
||||
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 TidalSearchItemDelegate::paint will call LazyLoadArt)
|
||||
// songs when they will be displayed again anyway (when InternetSearchItemDelegate::paint will call LazyLoadArt)
|
||||
art_requests_.clear();
|
||||
// Update the models
|
||||
front_model_->SetGroupBy(g, true);
|
||||
|
@ -522,7 +522,7 @@ void TidalSearchView::SetGroupBy(const CollectionModel::Grouping &g) {
|
|||
|
||||
// Save the setting
|
||||
QSettings s;
|
||||
s.beginGroup(TidalSettingsPage::kSettingsGroup);
|
||||
s.beginGroup(settings_group_);
|
||||
s.setValue("group_by1", int(g.first));
|
||||
s.setValue("group_by2", int(g.second));
|
||||
s.setValue("group_by3", int(g.third));
|
||||
|
@ -543,32 +543,32 @@ void TidalSearchView::SetGroupBy(const CollectionModel::Grouping &g) {
|
|||
|
||||
}
|
||||
|
||||
void TidalSearchView::SearchBySongsClicked(bool checked) {
|
||||
SetSearchBy(TidalSettingsPage::SearchBy_Songs);
|
||||
void InternetSearchView::SearchBySongsClicked(bool checked) {
|
||||
SetSearchBy(InternetSearch::SearchBy_Songs);
|
||||
}
|
||||
|
||||
void TidalSearchView::SearchByAlbumsClicked(bool checked) {
|
||||
SetSearchBy(TidalSettingsPage::SearchBy_Albums);
|
||||
void InternetSearchView::SearchByAlbumsClicked(bool checked) {
|
||||
SetSearchBy(InternetSearch::SearchBy_Albums);
|
||||
}
|
||||
|
||||
void TidalSearchView::SetSearchBy(TidalSettingsPage::SearchBy searchby) {
|
||||
void InternetSearchView::SetSearchBy(InternetSearch::SearchBy searchby) {
|
||||
searchby_ = searchby;
|
||||
QSettings s;
|
||||
s.beginGroup(TidalSettingsPage::kSettingsGroup);
|
||||
s.beginGroup(settings_group_);
|
||||
s.setValue("searchby", int(searchby));
|
||||
s.endGroup();
|
||||
TextEdited(ui_->search->text());
|
||||
}
|
||||
|
||||
void TidalSearchView::UpdateStatus(QString text) {
|
||||
void InternetSearchView::UpdateStatus(QString text) {
|
||||
ui_->progressbar->show();
|
||||
ui_->label_status->setText(text);
|
||||
}
|
||||
|
||||
void TidalSearchView::ProgressSetMaximum(int max) {
|
||||
void InternetSearchView::ProgressSetMaximum(int max) {
|
||||
ui_->progressbar->setMaximum(max);
|
||||
}
|
||||
|
||||
void TidalSearchView::UpdateProgress(int progress) {
|
||||
void InternetSearchView::UpdateProgress(int progress) {
|
||||
ui_->progressbar->setValue(progress);
|
||||
}
|
|
@ -19,8 +19,8 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#ifndef TIDALSEARCHVIEW_H
|
||||
#define TIDALSEARCHVIEW_H
|
||||
#ifndef INTERNETSEARCHVIEW_H
|
||||
#define INTERNETSEARCHVIEW_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
|
@ -42,20 +42,20 @@
|
|||
#include "collection/collectionmodel.h"
|
||||
#include "settings/settingsdialog.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
#include "tidalsearch.h"
|
||||
#include "settings/tidalsettingspage.h"
|
||||
#include "internetsearch.h"
|
||||
//#include "settings/internetsettingspage.h"
|
||||
|
||||
class Application;
|
||||
class GroupByDialog;
|
||||
class TidalSearchModel;
|
||||
class Ui_TidalSearchView;
|
||||
class InternetSearchModel;
|
||||
class Ui_InternetSearchView;
|
||||
|
||||
class TidalSearchView : public QWidget {
|
||||
class InternetSearchView : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TidalSearchView(Application *app, QWidget *parent = nullptr);
|
||||
~TidalSearchView();
|
||||
InternetSearchView(Application *app, InternetSearch *engine, QString settings_group, SettingsDialog::Page settings_page, QWidget *parent = nullptr);
|
||||
~InternetSearchView();
|
||||
|
||||
static const int kSwapModelsTimeoutMsec;
|
||||
|
||||
|
@ -80,7 +80,7 @@ signals:
|
|||
void UpdateStatus(QString text);
|
||||
void ProgressSetMaximum(int progress);
|
||||
void UpdateProgress(int max);
|
||||
void AddResults(int id, const TidalSearch::ResultList &results);
|
||||
void AddResults(int id, const InternetSearch::ResultList &results);
|
||||
void SearchError(const int id, const QString error);
|
||||
void ArtLoaded(int id, const QPixmap &pixmap);
|
||||
|
||||
|
@ -96,7 +96,7 @@ signals:
|
|||
void SearchBySongsClicked(bool);
|
||||
void SearchByAlbumsClicked(bool);
|
||||
void GroupByClicked(QAction *action);
|
||||
void SetSearchBy(TidalSettingsPage::SearchBy searchby);
|
||||
void SetSearchBy(InternetSearch::SearchBy searchby);
|
||||
void SetGroupBy(const CollectionModel::Grouping &g);
|
||||
|
||||
private:
|
||||
|
@ -106,8 +106,10 @@ signals:
|
|||
bool ResultsContextMenuEvent(QContextMenuEvent *event);
|
||||
|
||||
Application *app_;
|
||||
TidalSearch *engine_;
|
||||
Ui_TidalSearchView *ui_;
|
||||
InternetSearch *engine_;
|
||||
QString settings_group_;
|
||||
SettingsDialog::Page settings_page_;
|
||||
Ui_InternetSearchView *ui_;
|
||||
QScopedPointer<GroupByDialog> group_by_dialog_;
|
||||
|
||||
QMenu *context_menu_;
|
||||
|
@ -119,9 +121,9 @@ signals:
|
|||
// 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.
|
||||
TidalSearchModel *front_model_;
|
||||
TidalSearchModel *back_model_;
|
||||
TidalSearchModel *current_model_;
|
||||
InternetSearchModel *front_model_;
|
||||
InternetSearchModel *back_model_;
|
||||
InternetSearchModel *current_model_;
|
||||
|
||||
QSortFilterProxyModel *front_proxy_;
|
||||
QSortFilterProxyModel *back_proxy_;
|
||||
|
@ -131,12 +133,9 @@ signals:
|
|||
|
||||
QTimer *swap_models_timer_;
|
||||
|
||||
QIcon search_icon_;
|
||||
QIcon warning_icon_;
|
||||
|
||||
TidalSettingsPage::SearchBy searchby_;
|
||||
InternetSearch::SearchBy searchby_;
|
||||
bool error_;
|
||||
|
||||
};
|
||||
|
||||
#endif // TIDALSEARCHVIEW_H
|
||||
#endif // INTERNETSEARCHVIEW_H
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TidalSearchView</class>
|
||||
<widget class="QWidget" name="TidalSearchView">
|
||||
<class>InternetSearchView</class>
|
||||
<widget class="QWidget" name="InternetSearchView">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
|
@ -33,6 +33,7 @@
|
|||
#include "core/iconloader.h"
|
||||
#include "playlist/playlistitem.h"
|
||||
#include "settings/settingsdialog.h"
|
||||
#include "internetsearch.h"
|
||||
|
||||
class Application;
|
||||
class InternetModel;
|
||||
|
@ -44,14 +45,16 @@ class InternetService : public QObject {
|
|||
public:
|
||||
InternetService(Song::Source source, const QString &name, const QString &url_scheme, Application *app, InternetModel *model, QObject *parent = nullptr);
|
||||
virtual ~InternetService() {}
|
||||
Song::Source source() const { return source_; }
|
||||
QString name() const { return name_; }
|
||||
QString url_scheme() const { return url_scheme_; }
|
||||
InternetModel *model() const { return model_; }
|
||||
virtual Song::Source source() const { return source_; }
|
||||
virtual QString name() const { return name_; }
|
||||
virtual QString url_scheme() const { return url_scheme_; }
|
||||
virtual InternetModel *model() const { return model_; }
|
||||
virtual bool has_initial_load_settings() const { return false; }
|
||||
virtual void InitialLoadSettings() {}
|
||||
virtual void ReloadSettings() {}
|
||||
virtual QIcon Icon() { return Song::IconForSource(source_); }
|
||||
virtual int Search(const QString &query, InternetSearch::SearchBy searchby) = 0;
|
||||
virtual void CancelSearch() = 0;
|
||||
|
||||
public slots:
|
||||
virtual void ShowConfig() {}
|
||||
|
|
|
@ -36,11 +36,6 @@ class DeezerSettingsPage : public SettingsPage {
|
|||
explicit DeezerSettingsPage(SettingsDialog* parent = nullptr);
|
||||
~DeezerSettingsPage();
|
||||
|
||||
enum SearchBy {
|
||||
SearchBy_Songs = 1,
|
||||
SearchBy_Albums = 2,
|
||||
};
|
||||
|
||||
static const char *kSettingsGroup;
|
||||
|
||||
void Load();
|
||||
|
@ -50,7 +45,6 @@ class DeezerSettingsPage : public SettingsPage {
|
|||
|
||||
signals:
|
||||
void Login();
|
||||
void Login(const QString &username, const QString &password);
|
||||
|
||||
private slots:
|
||||
void LoginClicked();
|
||||
|
|
|
@ -37,6 +37,7 @@ const char *TidalSettingsPage::kSettingsGroup = "Tidal";
|
|||
TidalSettingsPage::TidalSettingsPage(SettingsDialog *parent)
|
||||
: SettingsPage(parent),
|
||||
ui_(new Ui::TidalSettingsPage),
|
||||
//service_(dialog()->app()->internet_model()->Service<TidalService>()) {
|
||||
service_(dialog()->app()->internet_model()->Service<TidalService>()) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
|
|
|
@ -36,11 +36,6 @@ class TidalSettingsPage : public SettingsPage {
|
|||
explicit TidalSettingsPage(SettingsDialog* parent = nullptr);
|
||||
~TidalSettingsPage();
|
||||
|
||||
enum SearchBy {
|
||||
SearchBy_Songs = 1,
|
||||
SearchBy_Albums = 2,
|
||||
};
|
||||
|
||||
static const char *kSettingsGroup;
|
||||
|
||||
void Load();
|
||||
|
|
|
@ -1,319 +0,0 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* 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 <QObject>
|
||||
#include <QStandardItem>
|
||||
#include <QStandardItemModel>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QPixmap>
|
||||
#include <QMimeData>
|
||||
|
||||
#include "core/mimedata.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/logging.h"
|
||||
#include "tidalsearch.h"
|
||||
#include "tidalsearchmodel.h"
|
||||
|
||||
TidalSearchModel::TidalSearchModel(TidalSearch *engine, QObject *parent)
|
||||
: QStandardItemModel(parent),
|
||||
engine_(engine),
|
||||
proxy_(nullptr),
|
||||
use_pretty_covers_(true),
|
||||
artist_icon_(IconLoader::Load("folder-sound")) {
|
||||
|
||||
group_by_[0] = CollectionModel::GroupBy_Artist;
|
||||
group_by_[1] = CollectionModel::GroupBy_Album;
|
||||
group_by_[2] = CollectionModel::GroupBy_None;
|
||||
|
||||
QIcon nocover = IconLoader::Load("cdcase");
|
||||
no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(CollectionModel::kPrettyCoverSize, CollectionModel::kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
//no_cover_icon_ = QPixmap(":/pictures/noalbumart.png").scaled(CollectionModel::kPrettyCoverSize, CollectionModel::kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
album_icon_ = no_cover_icon_;
|
||||
|
||||
}
|
||||
|
||||
void TidalSearchModel::AddResults(const TidalSearch::ResultList &results) {
|
||||
|
||||
int sort_index = 0;
|
||||
|
||||
for (const TidalSearch::Result &result : results) {
|
||||
QStandardItem *parent = invisibleRootItem();
|
||||
|
||||
// Find (or create) the container nodes for this result if we can.
|
||||
ContainerKey key;
|
||||
key.provider_index_ = sort_index;
|
||||
parent = BuildContainers(result.metadata_, parent, &key);
|
||||
|
||||
// Create the item
|
||||
QStandardItem *item = new QStandardItem;
|
||||
item->setText(result.metadata_.TitleWithCompilationArtist());
|
||||
item->setData(QVariant::fromValue(result), Role_Result);
|
||||
item->setData(sort_index, Role_ProviderIndex);
|
||||
|
||||
parent->appendRow(item);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QStandardItem *TidalSearchModel::BuildContainers(const Song &s, QStandardItem *parent, ContainerKey *key, int level) {
|
||||
|
||||
if (level >= 3) {
|
||||
return parent;
|
||||
}
|
||||
|
||||
bool has_artist_icon = false;
|
||||
bool has_album_icon = false;
|
||||
QString display_text;
|
||||
QString sort_text;
|
||||
int unique_tag = -1;
|
||||
int year = 0;
|
||||
|
||||
switch (group_by_[level]) {
|
||||
case CollectionModel::GroupBy_Artist:
|
||||
if (s.is_compilation()) {
|
||||
display_text = tr("Various artists");
|
||||
sort_text = "aaaaaa";
|
||||
}
|
||||
else {
|
||||
display_text = CollectionModel::TextOrUnknown(s.artist());
|
||||
sort_text = CollectionModel::SortTextForArtist(s.artist());
|
||||
}
|
||||
has_artist_icon = true;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy_YearAlbum:
|
||||
year = qMax(0, s.year());
|
||||
display_text = CollectionModel::PrettyYearAlbum(year, s.album());
|
||||
sort_text = CollectionModel::SortTextForNumber(year) + s.album();
|
||||
unique_tag = s.album_id();
|
||||
has_album_icon = true;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy_OriginalYearAlbum:
|
||||
year = qMax(0, s.effective_originalyear());
|
||||
display_text = CollectionModel::PrettyYearAlbum(year, s.album());
|
||||
sort_text = CollectionModel::SortTextForNumber(year) + s.album();
|
||||
unique_tag = s.album_id();
|
||||
has_album_icon = true;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy_Year:
|
||||
year = qMax(0, s.year());
|
||||
display_text = QString::number(year);
|
||||
sort_text = CollectionModel::SortTextForNumber(year) + " ";
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy_OriginalYear:
|
||||
year = qMax(0, s.effective_originalyear());
|
||||
display_text = QString::number(year);
|
||||
sort_text = CollectionModel::SortTextForNumber(year) + " ";
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy_Composer:
|
||||
display_text = s.composer();
|
||||
case CollectionModel::GroupBy_Performer:
|
||||
display_text = s.performer();
|
||||
case CollectionModel::GroupBy_Disc:
|
||||
display_text = s.disc();
|
||||
case CollectionModel::GroupBy_Grouping:
|
||||
display_text = s.grouping();
|
||||
case CollectionModel::GroupBy_Genre:
|
||||
if (display_text.isNull()) display_text = s.genre();
|
||||
case CollectionModel::GroupBy_Album:
|
||||
unique_tag = s.album_id();
|
||||
if (display_text.isNull()) {
|
||||
display_text = s.album();
|
||||
}
|
||||
// fallthrough
|
||||
case CollectionModel::GroupBy_AlbumArtist:
|
||||
if (display_text.isNull()) display_text = s.effective_albumartist();
|
||||
display_text = CollectionModel::TextOrUnknown(display_text);
|
||||
sort_text = CollectionModel::SortTextForArtist(display_text);
|
||||
has_album_icon = true;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy_FileType:
|
||||
display_text = s.TextForFiletype();
|
||||
sort_text = display_text;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy_Bitrate:
|
||||
display_text = QString(s.bitrate(), 1);
|
||||
sort_text = display_text;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy_Samplerate:
|
||||
display_text = QString(s.samplerate(), 1);
|
||||
sort_text = display_text;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy_Bitdepth:
|
||||
display_text = QString(s.bitdepth(), 1);
|
||||
sort_text = display_text;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy_None:
|
||||
return parent;
|
||||
}
|
||||
|
||||
// Find a container for this level
|
||||
key->group_[level] = display_text + QString::number(unique_tag);
|
||||
QStandardItem *container = containers_[*key];
|
||||
if (!container) {
|
||||
container = new QStandardItem(display_text);
|
||||
container->setData(key->provider_index_, Role_ProviderIndex);
|
||||
container->setData(sort_text, CollectionModel::Role_SortText);
|
||||
container->setData(group_by_[level], CollectionModel::Role_ContainerType);
|
||||
|
||||
if (has_artist_icon) {
|
||||
container->setIcon(artist_icon_);
|
||||
}
|
||||
else if (has_album_icon) {
|
||||
if (use_pretty_covers_) {
|
||||
container->setData(no_cover_icon_, Qt::DecorationRole);
|
||||
}
|
||||
else {
|
||||
container->setIcon(album_icon_);
|
||||
}
|
||||
}
|
||||
|
||||
parent->appendRow(container);
|
||||
containers_[*key] = container;
|
||||
}
|
||||
|
||||
// Create the container for the next level.
|
||||
return BuildContainers(s, container, key, level + 1);
|
||||
|
||||
}
|
||||
|
||||
void TidalSearchModel::Clear() {
|
||||
containers_.clear();
|
||||
clear();
|
||||
}
|
||||
|
||||
TidalSearch::ResultList TidalSearchModel::GetChildResults(const QModelIndexList &indexes) const {
|
||||
|
||||
QList<QStandardItem*> items;
|
||||
for (const QModelIndex &index : indexes) {
|
||||
items << itemFromIndex(index);
|
||||
}
|
||||
return GetChildResults(items);
|
||||
|
||||
}
|
||||
|
||||
TidalSearch::ResultList TidalSearchModel::GetChildResults(const QList<QStandardItem*> &items) const {
|
||||
|
||||
TidalSearch::ResultList results;
|
||||
QSet<const QStandardItem*> visited;
|
||||
|
||||
for (QStandardItem *item : items) {
|
||||
GetChildResults(item, &results, &visited);
|
||||
}
|
||||
|
||||
return results;
|
||||
|
||||
}
|
||||
|
||||
void TidalSearchModel::GetChildResults(const QStandardItem *item, TidalSearch::ResultList *results, QSet<const QStandardItem*> *visited) const {
|
||||
|
||||
if (visited->contains(item)) {
|
||||
return;
|
||||
}
|
||||
visited->insert(item);
|
||||
|
||||
// Does this item have children?
|
||||
if (item->rowCount()) {
|
||||
const QModelIndex parent_proxy_index = proxy_->mapFromSource(item->index());
|
||||
|
||||
// Yes - visit all the children, but do so through the proxy so we get them
|
||||
// in the right order.
|
||||
for (int i = 0; i < item->rowCount(); ++i) {
|
||||
const QModelIndex proxy_index = parent_proxy_index.child(i, 0);
|
||||
const QModelIndex index = proxy_->mapToSource(proxy_index);
|
||||
GetChildResults(itemFromIndex(index), results, visited);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No - maybe it's a song, add its result if valid
|
||||
QVariant result = item->data(Role_Result);
|
||||
if (result.isValid()) {
|
||||
results->append(result.value<TidalSearch::Result>());
|
||||
}
|
||||
else {
|
||||
// Maybe it's a provider then?
|
||||
bool is_provider;
|
||||
const int sort_index = item->data(Role_ProviderIndex).toInt(&is_provider);
|
||||
if (is_provider) {
|
||||
// Go through all the items (through the proxy to keep them ordered) and add the ones belonging to this provider to our list
|
||||
for (int i = 0; i < proxy_->rowCount(invisibleRootItem()->index()); ++i) {
|
||||
QModelIndex child_index = proxy_->index(i, 0, invisibleRootItem()->index());
|
||||
const QStandardItem *child_item = itemFromIndex(proxy_->mapToSource(child_index));
|
||||
if (child_item->data(Role_ProviderIndex).toInt() == sort_index) {
|
||||
GetChildResults(child_item, results, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QMimeData *TidalSearchModel::mimeData(const QModelIndexList &indexes) const {
|
||||
return engine_->LoadTracks(GetChildResults(indexes));
|
||||
}
|
||||
|
||||
namespace {
|
||||
void GatherResults(const QStandardItem *parent, TidalSearch::ResultList *results) {
|
||||
|
||||
QVariant result_variant = parent->data(TidalSearchModel::Role_Result);
|
||||
if (result_variant.isValid()) {
|
||||
TidalSearch::Result result = result_variant.value<TidalSearch::Result>();
|
||||
(*results).append(result);
|
||||
}
|
||||
|
||||
for (int i = 0; i < parent->rowCount(); ++i) {
|
||||
GatherResults(parent->child(i), results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TidalSearchModel::SetGroupBy(const CollectionModel::Grouping &grouping, bool regroup_now) {
|
||||
|
||||
const CollectionModel::Grouping old_group_by = group_by_;
|
||||
group_by_ = grouping;
|
||||
|
||||
if (regroup_now && group_by_ != old_group_by) {
|
||||
// Walk the tree gathering the results we have already
|
||||
TidalSearch::ResultList results;
|
||||
GatherResults(invisibleRootItem(), &results);
|
||||
|
||||
// Reset the model and re-add all the results using the new grouping.
|
||||
Clear();
|
||||
AddResults(results);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* 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 TIDALSEARCHMODEL_H
|
||||
#define TIDALSEARCHMODEL_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QMimeData>
|
||||
#include <QStandardItemModel>
|
||||
#include <QStandardItem>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QMap>
|
||||
#include <QSet>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QIcon>
|
||||
#include <QPixmap>
|
||||
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "tidalsearch.h"
|
||||
|
||||
class TidalSearchModel : public QStandardItemModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TidalSearchModel(TidalSearch *engine, QObject *parent = nullptr);
|
||||
|
||||
enum Role {
|
||||
Role_Result = CollectionModel::LastRole,
|
||||
Role_LazyLoadingArt,
|
||||
Role_ProviderIndex,
|
||||
LastRole
|
||||
};
|
||||
|
||||
struct ContainerKey {
|
||||
int provider_index_;
|
||||
QString group_[3];
|
||||
};
|
||||
|
||||
void set_proxy(QSortFilterProxyModel *proxy) { proxy_ = proxy; }
|
||||
void set_use_pretty_covers(bool pretty) { use_pretty_covers_ = pretty; }
|
||||
void SetGroupBy(const CollectionModel::Grouping &grouping, bool regroup_now);
|
||||
|
||||
void Clear();
|
||||
|
||||
TidalSearch::ResultList GetChildResults(const QModelIndexList &indexes) const;
|
||||
TidalSearch::ResultList GetChildResults(const QList<QStandardItem*> &items) const;
|
||||
|
||||
QMimeData *mimeData(const QModelIndexList &indexes) const;
|
||||
|
||||
public slots:
|
||||
void AddResults(const TidalSearch::ResultList &results);
|
||||
|
||||
private:
|
||||
QStandardItem *BuildContainers(const Song &metadata, QStandardItem *parent, ContainerKey *key, int level = 0);
|
||||
void GetChildResults(const QStandardItem *item, TidalSearch::ResultList *results, QSet<const QStandardItem*> *visited) const;
|
||||
|
||||
private:
|
||||
TidalSearch *engine_;
|
||||
QSortFilterProxyModel *proxy_;
|
||||
bool use_pretty_covers_;
|
||||
QIcon artist_icon_;
|
||||
QPixmap no_cover_icon_;
|
||||
QIcon album_icon_;
|
||||
CollectionModel::Grouping group_by_;
|
||||
QMap<ContainerKey, QStandardItem*> containers_;
|
||||
|
||||
};
|
||||
|
||||
inline uint qHash(const TidalSearchModel::ContainerKey &key) {
|
||||
return qHash(key.provider_index_) ^ qHash(key.group_[0]) ^ qHash(key.group_[1]) ^ qHash(key.group_[2]);
|
||||
}
|
||||
|
||||
inline bool operator<(const TidalSearchModel::ContainerKey &left, const TidalSearchModel::ContainerKey &right) {
|
||||
#define CMP(field) \
|
||||
if (left.field < right.field) return true; \
|
||||
if (left.field > right.field) return false
|
||||
|
||||
CMP(provider_index_);
|
||||
CMP(group_[0]);
|
||||
CMP(group_[1]);
|
||||
CMP(group_[2]);
|
||||
return false;
|
||||
|
||||
#undef CMP
|
||||
}
|
||||
|
||||
#endif // TIDALSEARCHMODEL_H
|
|
@ -1,79 +0,0 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* 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 <QObject>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QString>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "tidalsearchmodel.h"
|
||||
#include "tidalsearchsortmodel.h"
|
||||
|
||||
TidalSearchSortModel::TidalSearchSortModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent) {}
|
||||
|
||||
bool TidalSearchSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const {
|
||||
// Compare the provider sort index first.
|
||||
const int index_left = left.data(TidalSearchModel::Role_ProviderIndex).toInt();
|
||||
const int index_right = right.data(TidalSearchModel::Role_ProviderIndex).toInt();
|
||||
if (index_left < index_right) return true;
|
||||
if (index_left > index_right) return false;
|
||||
|
||||
// Dividers always go first
|
||||
if (left.data(CollectionModel::Role_IsDivider).toBool()) return true;
|
||||
if (right.data(CollectionModel::Role_IsDivider).toBool()) return false;
|
||||
|
||||
// Containers go before songs if they're at the same level
|
||||
const bool left_is_container = left.data(CollectionModel::Role_ContainerType).isValid();
|
||||
const bool right_is_container = right.data(CollectionModel::Role_ContainerType).isValid();
|
||||
if (left_is_container && !right_is_container) return true;
|
||||
if (right_is_container && !left_is_container) return false;
|
||||
|
||||
// Containers get sorted on their sort text.
|
||||
if (left_is_container) {
|
||||
return QString::localeAwareCompare(left.data(CollectionModel::Role_SortText).toString(), right.data(CollectionModel::Role_SortText).toString()) < 0;
|
||||
}
|
||||
|
||||
// Otherwise we're comparing songs. Sort by disc, track, then title.
|
||||
const TidalSearch::Result r1 = left.data(TidalSearchModel::Role_Result).value<TidalSearch::Result>();
|
||||
const TidalSearch::Result r2 = right.data(TidalSearchModel::Role_Result).value<TidalSearch::Result>();
|
||||
|
||||
#define CompareInt(field) \
|
||||
if (r1.metadata_.field() < r2.metadata_.field()) return true; \
|
||||
if (r1.metadata_.field() > r2.metadata_.field()) return false
|
||||
|
||||
int ret = 0;
|
||||
|
||||
#define CompareString(field) \
|
||||
ret = QString::localeAwareCompare(r1.metadata_.field(), r2.metadata_.field()); \
|
||||
if (ret < 0) return true; \
|
||||
if (ret > 0) return false
|
||||
|
||||
CompareInt(disc);
|
||||
CompareInt(track);
|
||||
CompareString(title);
|
||||
|
||||
return false;
|
||||
|
||||
#undef CompareInt
|
||||
#undef CompareString
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* 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 TIDALSEARCHSORTMODEL_H
|
||||
#define TIDALSEARCHSORTMODEL_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
class TidalSearchSortModel : public QSortFilterProxyModel {
|
||||
public:
|
||||
TidalSearchSortModel(QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
|
||||
};
|
||||
|
||||
#endif // TIDALSEARCHSORTMODEL_H
|
|
@ -50,8 +50,8 @@
|
|||
#include "core/timeconstants.h"
|
||||
#include "core/utilities.h"
|
||||
#include "internet/internetmodel.h"
|
||||
#include "internet/internetsearch.h"
|
||||
#include "tidalservice.h"
|
||||
#include "tidalsearch.h"
|
||||
#include "tidalurlhandler.h"
|
||||
#include "settings/tidalsettingspage.h"
|
||||
|
||||
|
@ -446,7 +446,7 @@ QJsonValue TidalService::ExtractItems(QByteArray &data) {
|
|||
|
||||
}
|
||||
|
||||
int TidalService::Search(const QString &text, TidalSettingsPage::SearchBy searchby) {
|
||||
int TidalService::Search(const QString &text, InternetSearch::SearchBy searchby) {
|
||||
|
||||
pending_search_id_ = next_pending_search_id_;
|
||||
pending_search_text_ = text;
|
||||
|
@ -506,11 +506,11 @@ void TidalService::SendSearch() {
|
|||
|
||||
QString searchparam;
|
||||
switch (pending_searchby_) {
|
||||
case TidalSettingsPage::SearchBy_Songs:
|
||||
case InternetSearch::SearchBy_Songs:
|
||||
searchparam = "search/tracks";
|
||||
parameters << Param("limit", QString::number(songssearchlimit_));
|
||||
break;
|
||||
case TidalSettingsPage::SearchBy_Albums:
|
||||
case InternetSearch::SearchBy_Albums:
|
||||
default:
|
||||
searchparam = "search/albums";
|
||||
parameters << Param("limit", QString::number(albumssearchlimit_));
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
#include "core/song.h"
|
||||
#include "internet/internetmodel.h"
|
||||
#include "internet/internetservice.h"
|
||||
#include "settings/tidalsettingspage.h"
|
||||
#include "internet/internetsearch.h"
|
||||
|
||||
class NetworkAccessManager;
|
||||
class TidalUrlHandler;
|
||||
|
@ -54,7 +54,7 @@ class TidalService : public InternetService {
|
|||
void ReloadSettings();
|
||||
|
||||
void Logout();
|
||||
int Search(const QString &query, TidalSettingsPage::SearchBy searchby);
|
||||
int Search(const QString &query, InternetSearch::SearchBy searchby);
|
||||
void CancelSearch();
|
||||
|
||||
const bool login_sent() { return login_sent_; }
|
||||
|
@ -130,7 +130,7 @@ class TidalService : public InternetService {
|
|||
int pending_search_id_;
|
||||
int next_pending_search_id_;
|
||||
QString pending_search_text_;
|
||||
TidalSettingsPage::SearchBy pending_searchby_;
|
||||
InternetSearch::SearchBy pending_searchby_;
|
||||
|
||||
int search_id_;
|
||||
QString search_text_;
|
||||
|
|
Loading…
Reference in New Issue