Rename "Internet" to "Streaming"
This commit is contained in:
474
src/streaming/streamingcollectionview.cpp
Normal file
474
src/streaming/streamingcollectionview.cpp
Normal file
@@ -0,0 +1,474 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2021, 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 <utility>
|
||||
|
||||
#include <QWidget>
|
||||
#include <QTreeView>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QAbstractItemView>
|
||||
#include <QItemSelectionModel>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QPainter>
|
||||
#include <QRect>
|
||||
#include <QFont>
|
||||
#include <QFontMetrics>
|
||||
#include <QMimeData>
|
||||
#include <QMenu>
|
||||
#include <QtEvents>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/application.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/mimedata.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "collection/collectionfilterwidget.h"
|
||||
#include "collection/collectionitem.h"
|
||||
#include "collection/collectionitemdelegate.h"
|
||||
#include "streamingcollectionview.h"
|
||||
|
||||
StreamingCollectionView::StreamingCollectionView(QWidget *parent)
|
||||
: AutoExpandingTreeView(parent),
|
||||
app_(nullptr),
|
||||
collection_backend_(nullptr),
|
||||
collection_model_(nullptr),
|
||||
filter_(nullptr),
|
||||
favorite_(false),
|
||||
total_song_count_(0),
|
||||
total_artist_count_(0),
|
||||
total_album_count_(0),
|
||||
nomusic_(QStringLiteral(":/pictures/nomusic.png")),
|
||||
context_menu_(nullptr),
|
||||
load_(nullptr),
|
||||
add_to_playlist_(nullptr),
|
||||
add_to_playlist_enqueue_(nullptr),
|
||||
add_to_playlist_enqueue_next_(nullptr),
|
||||
open_in_new_playlist_(nullptr),
|
||||
remove_songs_(nullptr),
|
||||
is_in_keyboard_search_(false) {
|
||||
|
||||
setItemDelegate(new CollectionItemDelegate(this));
|
||||
setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
setHeaderHidden(true);
|
||||
setAllColumnsShowFocus(true);
|
||||
setDragEnabled(true);
|
||||
setDragDropMode(QAbstractItemView::DragOnly);
|
||||
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
SetAutoOpen(false);
|
||||
|
||||
setStyleSheet(QStringLiteral("QTreeView::item{padding-top:1px;}"));
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::Init(Application *app, SharedPtr<CollectionBackend> collection_backend, CollectionModel *collection_model, const bool favorite) {
|
||||
|
||||
app_ = app;
|
||||
collection_backend_ = collection_backend;
|
||||
collection_model_ = collection_model;
|
||||
favorite_ = favorite;
|
||||
|
||||
ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::SetFilter(CollectionFilterWidget *filter) {
|
||||
|
||||
filter_ = filter;
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::ReloadSettings() {
|
||||
|
||||
if (collection_model_) collection_model_->ReloadSettings();
|
||||
if (filter_) filter_->ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::SaveFocus() {
|
||||
|
||||
const QModelIndex current = currentIndex();
|
||||
const QVariant role_type = model()->data(current, CollectionModel::Role_Type);
|
||||
if (!role_type.isValid()) {
|
||||
return;
|
||||
}
|
||||
const CollectionItem::Type item_type = role_type.value<CollectionItem::Type>();
|
||||
if (item_type != CollectionItem::Type::Song && item_type != CollectionItem::Type::Container && item_type != CollectionItem::Type::Divider) {
|
||||
return;
|
||||
}
|
||||
|
||||
last_selected_path_.clear();
|
||||
last_selected_song_ = Song();
|
||||
last_selected_container_ = QString();
|
||||
|
||||
switch (item_type) {
|
||||
case CollectionItem::Type::Song:{
|
||||
QModelIndex idx = qobject_cast<QSortFilterProxyModel*>(model())->mapToSource(current);
|
||||
SongList songs = collection_model_->GetChildSongs(idx);
|
||||
if (!songs.isEmpty()) {
|
||||
last_selected_song_ = songs.last();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CollectionItem::Type::Container:
|
||||
case CollectionItem::Type::Divider:{
|
||||
QString text = model()->data(current, CollectionModel::Role_SortText).toString();
|
||||
last_selected_container_ = text;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
SaveContainerPath(current);
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::SaveContainerPath(const QModelIndex &child) {
|
||||
|
||||
const QModelIndex current = model()->parent(child);
|
||||
const QVariant role_type = model()->data(current, CollectionModel::Role_Type);
|
||||
if (!role_type.isValid()) {
|
||||
return;
|
||||
}
|
||||
const CollectionItem::Type item_type = role_type.value<CollectionItem::Type>();
|
||||
if (item_type != CollectionItem::Type::Container && item_type != CollectionItem::Type::Divider) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString text = model()->data(current, CollectionModel::Role_SortText).toString();
|
||||
last_selected_path_ << text;
|
||||
SaveContainerPath(current);
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::RestoreFocus() {
|
||||
|
||||
if (last_selected_container_.isEmpty() && last_selected_song_.url().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
RestoreLevelFocus();
|
||||
|
||||
}
|
||||
|
||||
bool StreamingCollectionView::RestoreLevelFocus(const QModelIndex &parent) {
|
||||
|
||||
if (model()->canFetchMore(parent)) {
|
||||
model()->fetchMore(parent);
|
||||
}
|
||||
const int rows = model()->rowCount(parent);
|
||||
for (int i = 0; i < rows; i++) {
|
||||
const QModelIndex current = model()->index(i, 0, parent);
|
||||
if (!current.isValid()) continue;
|
||||
const QVariant role_type = model()->data(current, CollectionModel::Role_Type);
|
||||
if (!role_type.isValid()) continue;
|
||||
const CollectionItem::Type item_type = role_type.value<CollectionItem::Type>();
|
||||
switch (item_type) {
|
||||
case CollectionItem::Type::Root:
|
||||
case CollectionItem::Type::LoadingIndicator:
|
||||
break;
|
||||
case CollectionItem::Type::Song:
|
||||
if (!last_selected_song_.url().isEmpty()) {
|
||||
QModelIndex idx = qobject_cast<QSortFilterProxyModel*>(model())->mapToSource(current);
|
||||
const SongList songs = collection_model_->GetChildSongs(idx);
|
||||
for (const Song &song : songs) {
|
||||
if (song == last_selected_song_) {
|
||||
setCurrentIndex(current);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case CollectionItem::Type::Container:
|
||||
case CollectionItem::Type::Divider:{
|
||||
QString text = model()->data(current, CollectionModel::Role_SortText).toString();
|
||||
if (!last_selected_container_.isEmpty() && last_selected_container_ == text) {
|
||||
expand(current);
|
||||
setCurrentIndex(current);
|
||||
return true;
|
||||
}
|
||||
if (last_selected_path_.contains(text)) {
|
||||
expand(current);
|
||||
// If a selected container or song were not found, we've got into a wrong subtree (happens with "unknown" all the time)
|
||||
if (!RestoreLevelFocus(current)) {
|
||||
collapse(current);
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::TotalSongCountUpdated(int count) {
|
||||
|
||||
int old = total_song_count_;
|
||||
total_song_count_ = count;
|
||||
if (old != total_song_count_) update();
|
||||
|
||||
if (total_song_count_ == 0) {
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
}
|
||||
else {
|
||||
unsetCursor();
|
||||
}
|
||||
|
||||
emit TotalSongCountUpdated_();
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::TotalArtistCountUpdated(int count) {
|
||||
|
||||
int old = total_artist_count_;
|
||||
total_artist_count_ = count;
|
||||
if (old != total_artist_count_) update();
|
||||
|
||||
if (total_artist_count_ == 0) {
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
}
|
||||
else {
|
||||
unsetCursor();
|
||||
}
|
||||
|
||||
emit TotalArtistCountUpdated_();
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::TotalAlbumCountUpdated(int count) {
|
||||
|
||||
int old = total_album_count_;
|
||||
total_album_count_ = count;
|
||||
if (old != total_album_count_) update();
|
||||
|
||||
if (total_album_count_ == 0) {
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
}
|
||||
else {
|
||||
unsetCursor();
|
||||
}
|
||||
|
||||
emit TotalAlbumCountUpdated_();
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::paintEvent(QPaintEvent *event) {
|
||||
|
||||
if (total_song_count_ == 0) {
|
||||
QPainter p(viewport());
|
||||
QRect rect(viewport()->rect());
|
||||
|
||||
// Draw the confused strawberry
|
||||
QRect image_rect((rect.width() - nomusic_.width()) / 2, 50, nomusic_.width(), nomusic_.height());
|
||||
p.drawPixmap(image_rect, nomusic_);
|
||||
|
||||
// Draw the title text
|
||||
QFont bold_font;
|
||||
bold_font.setBold(true);
|
||||
p.setFont(bold_font);
|
||||
|
||||
QFontMetrics metrics(bold_font);
|
||||
|
||||
QRect title_rect(0, image_rect.bottom() + 20, rect.width(), metrics.height());
|
||||
p.drawText(title_rect, Qt::AlignHCenter, tr("The streaming collection is empty!"));
|
||||
|
||||
// Draw the other text
|
||||
p.setFont(QFont());
|
||||
|
||||
QRect text_rect(0, title_rect.bottom() + 5, rect.width(), metrics.height());
|
||||
p.drawText(text_rect, Qt::AlignHCenter, tr("Click here to retrieve music"));
|
||||
}
|
||||
else {
|
||||
QTreeView::paintEvent(event);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::mouseReleaseEvent(QMouseEvent *e) {
|
||||
|
||||
QTreeView::mouseReleaseEvent(e);
|
||||
|
||||
if (total_song_count_ == 0) {
|
||||
emit GetSongs();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
||||
|
||||
if (!context_menu_) {
|
||||
context_menu_ = new QMenu(this);
|
||||
add_to_playlist_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("media-playback-start")), tr("Append to current playlist"), this, &StreamingCollectionView::AddToPlaylist);
|
||||
load_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("media-playback-start")), tr("Replace current playlist"), this, &StreamingCollectionView::Load);
|
||||
open_in_new_playlist_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("document-new")), tr("Open in new playlist"), this, &StreamingCollectionView::OpenInNewPlaylist);
|
||||
|
||||
context_menu_->addSeparator();
|
||||
add_to_playlist_enqueue_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("go-next")), tr("Queue track"), this, &StreamingCollectionView::AddToPlaylistEnqueue);
|
||||
add_to_playlist_enqueue_next_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("go-next")), tr("Queue to play next"), this, &StreamingCollectionView::AddToPlaylistEnqueueNext);
|
||||
|
||||
context_menu_->addSeparator();
|
||||
|
||||
if (favorite_) {
|
||||
remove_songs_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("edit-delete")), tr("Remove from favorites"), this, &StreamingCollectionView::RemoveSelectedSongs);
|
||||
context_menu_->addSeparator();
|
||||
}
|
||||
|
||||
if (filter_) context_menu_->addMenu(filter_->menu());
|
||||
|
||||
}
|
||||
|
||||
context_menu_index_ = indexAt(e->pos());
|
||||
if (!context_menu_index_.isValid()) return;
|
||||
|
||||
context_menu_index_ = qobject_cast<QSortFilterProxyModel*>(model())->mapToSource(context_menu_index_);
|
||||
QModelIndexList selected_indexes = qobject_cast<QSortFilterProxyModel*>(model())->mapSelectionToSource(selectionModel()->selection()).indexes();
|
||||
qint64 songs_selected = selected_indexes.count();
|
||||
|
||||
// In all modes
|
||||
load_->setEnabled(songs_selected > 0);
|
||||
add_to_playlist_->setEnabled(songs_selected > 0);
|
||||
open_in_new_playlist_->setEnabled(songs_selected > 0);
|
||||
add_to_playlist_enqueue_->setEnabled(songs_selected > 0);
|
||||
if (remove_songs_) remove_songs_->setEnabled(songs_selected > 0);
|
||||
|
||||
context_menu_->popup(e->globalPos());
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::Load() {
|
||||
|
||||
QMimeData *q_mimedata = model()->mimeData(selectedIndexes());
|
||||
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
|
||||
mimedata->clear_first_ = true;
|
||||
}
|
||||
emit AddToPlaylistSignal(q_mimedata);
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::AddToPlaylist() {
|
||||
|
||||
emit AddToPlaylistSignal(model()->mimeData(selectedIndexes()));
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::AddToPlaylistEnqueue() {
|
||||
|
||||
QMimeData *q_mimedata = model()->mimeData(selectedIndexes());
|
||||
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
|
||||
mimedata->enqueue_now_ = true;
|
||||
}
|
||||
emit AddToPlaylistSignal(q_mimedata);
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::AddToPlaylistEnqueueNext() {
|
||||
|
||||
QMimeData *q_mimedata = model()->mimeData(selectedIndexes());
|
||||
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
|
||||
mimedata->enqueue_next_now_ = true;
|
||||
}
|
||||
emit AddToPlaylistSignal(q_mimedata);
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::OpenInNewPlaylist() {
|
||||
|
||||
QMimeData *q_mimedata = model()->mimeData(selectedIndexes());
|
||||
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
|
||||
mimedata->open_in_new_playlist_ = true;
|
||||
}
|
||||
emit AddToPlaylistSignal(q_mimedata);
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::RemoveSelectedSongs() {
|
||||
|
||||
emit RemoveSongs(GetSelectedSongs());
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::keyboardSearch(const QString &search) {
|
||||
|
||||
is_in_keyboard_search_ = true;
|
||||
QTreeView::keyboardSearch(search);
|
||||
is_in_keyboard_search_ = false;
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::scrollTo(const QModelIndex &idx, ScrollHint hint) {
|
||||
|
||||
if (is_in_keyboard_search_) {
|
||||
QTreeView::scrollTo(idx, QAbstractItemView::PositionAtTop);
|
||||
}
|
||||
else {
|
||||
QTreeView::scrollTo(idx, hint);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SongList StreamingCollectionView::GetSelectedSongs() const {
|
||||
|
||||
QModelIndexList selected_indexes = qobject_cast<QSortFilterProxyModel*>(model())->mapSelectionToSource(selectionModel()->selection()).indexes();
|
||||
return collection_model_->GetChildSongs(selected_indexes);
|
||||
|
||||
}
|
||||
|
||||
void StreamingCollectionView::FilterReturnPressed() {
|
||||
|
||||
if (!currentIndex().isValid()) {
|
||||
// Pick the first thing that isn't a divider
|
||||
for (int row = 0; row < model()->rowCount(); ++row) {
|
||||
QModelIndex idx = model()->index(row, 0);
|
||||
const QVariant role_type = idx.data(CollectionModel::Role::Role_Type);
|
||||
if (!role_type.isValid()) continue;
|
||||
const CollectionItem::Type item_type = role_type.value<CollectionItem::Type>();
|
||||
if (item_type != CollectionItem::Type::Divider) {
|
||||
setCurrentIndex(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentIndex().isValid()) return;
|
||||
|
||||
emit doubleClicked(currentIndex());
|
||||
|
||||
}
|
||||
|
||||
int StreamingCollectionView::TotalSongs() const {
|
||||
return total_song_count_;
|
||||
}
|
||||
int StreamingCollectionView::TotalArtists() const {
|
||||
return total_artist_count_;
|
||||
}
|
||||
int StreamingCollectionView::TotalAlbums() const {
|
||||
return total_album_count_;
|
||||
}
|
||||
143
src/streaming/streamingcollectionview.h
Normal file
143
src/streaming/streamingcollectionview.h
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2021, 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 STREAMINGCOLLECTIONVIEW_H
|
||||
#define STREAMINGCOLLECTIONVIEW_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QAbstractItemModel>
|
||||
#include <QAbstractItemView>
|
||||
#include <QSet>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QPixmap>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
|
||||
#include "widgets/autoexpandingtreeview.h"
|
||||
|
||||
class QWidget;
|
||||
class QMenu;
|
||||
class QAction;
|
||||
class QContextMenuEvent;
|
||||
class QMouseEvent;
|
||||
class QPaintEvent;
|
||||
|
||||
class Application;
|
||||
class CollectionBackend;
|
||||
class CollectionModel;
|
||||
class CollectionFilterWidget;
|
||||
|
||||
class StreamingCollectionView : public AutoExpandingTreeView {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit StreamingCollectionView(QWidget *parent = nullptr);
|
||||
|
||||
void Init(Application *app, SharedPtr<CollectionBackend> collection_backend, CollectionModel *collection_model, const bool favorite = false);
|
||||
|
||||
// Returns Songs currently selected in the collection view.
|
||||
// Please note that the selection is recursive meaning that if for example an album is selected this will return all of it's songs.
|
||||
SongList GetSelectedSongs() const;
|
||||
|
||||
void SetApplication(Application *app);
|
||||
void SetFilter(CollectionFilterWidget *filter);
|
||||
|
||||
// QTreeView
|
||||
void keyboardSearch(const QString &search) override;
|
||||
void scrollTo(const QModelIndex &idx, ScrollHint hint = EnsureVisible) override;
|
||||
|
||||
int TotalSongs() const;
|
||||
int TotalArtists() const;
|
||||
int TotalAlbums() const;
|
||||
|
||||
public slots:
|
||||
void TotalSongCountUpdated(int count);
|
||||
void TotalArtistCountUpdated(int count);
|
||||
void TotalAlbumCountUpdated(int count);
|
||||
void ReloadSettings();
|
||||
|
||||
void FilterReturnPressed();
|
||||
|
||||
void SaveFocus();
|
||||
void RestoreFocus();
|
||||
|
||||
signals:
|
||||
void GetSongs();
|
||||
void TotalSongCountUpdated_();
|
||||
void TotalArtistCountUpdated_();
|
||||
void TotalAlbumCountUpdated_();
|
||||
void Error(const QString &error);
|
||||
void RemoveSongs(const SongList &songs);
|
||||
|
||||
protected:
|
||||
// QWidget
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void contextMenuEvent(QContextMenuEvent *e) override;
|
||||
|
||||
private slots:
|
||||
void Load();
|
||||
void AddToPlaylist();
|
||||
void AddToPlaylistEnqueue();
|
||||
void AddToPlaylistEnqueueNext();
|
||||
void OpenInNewPlaylist();
|
||||
void RemoveSelectedSongs();
|
||||
|
||||
private:
|
||||
void RecheckIsEmpty();
|
||||
bool RestoreLevelFocus(const QModelIndex &parent = QModelIndex());
|
||||
void SaveContainerPath(const QModelIndex &child);
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
SharedPtr<CollectionBackend> collection_backend_;
|
||||
CollectionModel *collection_model_;
|
||||
CollectionFilterWidget *filter_;
|
||||
bool favorite_;
|
||||
|
||||
int total_song_count_;
|
||||
int total_artist_count_;
|
||||
int total_album_count_;
|
||||
|
||||
QPixmap nomusic_;
|
||||
|
||||
QMenu *context_menu_;
|
||||
QModelIndex context_menu_index_;
|
||||
QAction *load_;
|
||||
QAction *add_to_playlist_;
|
||||
QAction *add_to_playlist_enqueue_;
|
||||
QAction *add_to_playlist_enqueue_next_;
|
||||
QAction *open_in_new_playlist_;
|
||||
QAction *remove_songs_;
|
||||
|
||||
bool is_in_keyboard_search_;
|
||||
|
||||
// Save focus
|
||||
Song last_selected_song_;
|
||||
QString last_selected_container_;
|
||||
QSet<QString> last_selected_path_;
|
||||
};
|
||||
|
||||
#endif // STREAMINGCOLLECTIONVIEW_H
|
||||
68
src/streaming/streamingcollectionviewcontainer.cpp
Normal file
68
src/streaming/streamingcollectionviewcontainer.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QWidget>
|
||||
#include <QProgressBar>
|
||||
#include <QKeyEvent>
|
||||
#include <QContextMenuEvent>
|
||||
|
||||
#include "collection/collectionfilterwidget.h"
|
||||
#include "streamingcollectionview.h"
|
||||
#include "streamingcollectionviewcontainer.h"
|
||||
#include "ui_streamingcollectionviewcontainer.h"
|
||||
|
||||
StreamingCollectionViewContainer::StreamingCollectionViewContainer(QWidget *parent)
|
||||
: QWidget(parent),
|
||||
ui_(new Ui_StreamingCollectionViewContainer) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
ui_->view->SetFilter(ui_->filter_widget);
|
||||
|
||||
QObject::connect(ui_->filter_widget, &CollectionFilterWidget::UpPressed, ui_->view, &StreamingCollectionView::UpAndFocus);
|
||||
QObject::connect(ui_->filter_widget, &CollectionFilterWidget::DownPressed, ui_->view, &StreamingCollectionView::DownAndFocus);
|
||||
QObject::connect(ui_->filter_widget, &CollectionFilterWidget::ReturnPressed, ui_->view, &StreamingCollectionView::FilterReturnPressed);
|
||||
QObject::connect(ui_->view, &StreamingCollectionView::FocusOnFilterSignal, ui_->filter_widget, &CollectionFilterWidget::FocusOnFilter);
|
||||
|
||||
ui_->progressbar->hide();
|
||||
|
||||
ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
StreamingCollectionViewContainer::~StreamingCollectionViewContainer() { delete ui_; }
|
||||
|
||||
void StreamingCollectionViewContainer::ReloadSettings() const {
|
||||
|
||||
ui_->filter_widget->ReloadSettings();
|
||||
ui_->view->ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
bool StreamingCollectionViewContainer::SearchFieldHasFocus() const {
|
||||
return ui_->filter_widget->SearchFieldHasFocus();
|
||||
}
|
||||
|
||||
void StreamingCollectionViewContainer::FocusSearchField() {
|
||||
ui_->filter_widget->FocusSearchField();
|
||||
}
|
||||
|
||||
void StreamingCollectionViewContainer::contextMenuEvent(QContextMenuEvent *e) { Q_UNUSED(e); }
|
||||
69
src/streaming/streamingcollectionviewcontainer.h
Normal file
69
src/streaming/streamingcollectionviewcontainer.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018-2021, 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 STREAMINGCOLLECTIONVIEWCONTAINER_H
|
||||
#define STREAMINGCOLLECTIONVIEWCONTAINER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QString>
|
||||
|
||||
#include "streamingcollectionview.h"
|
||||
#include "ui_streamingcollectionviewcontainer.h"
|
||||
|
||||
class QStackedWidget;
|
||||
class QPushButton;
|
||||
class QLabel;
|
||||
class QProgressBar;
|
||||
class QContextMenuEvent;
|
||||
class CollectionFilterWidget;
|
||||
|
||||
class StreamingCollectionViewContainer : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit StreamingCollectionViewContainer(QWidget *parent = nullptr);
|
||||
~StreamingCollectionViewContainer() override;
|
||||
|
||||
void ReloadSettings() const;
|
||||
bool SearchFieldHasFocus() const;
|
||||
void FocusSearchField();
|
||||
|
||||
QStackedWidget *stacked() const { return ui_->stacked; }
|
||||
QWidget *help_page() const { return ui_->help_page; }
|
||||
QWidget *streamingcollection_page() const { return ui_->streamingcollection_page; }
|
||||
StreamingCollectionView *view() const { return ui_->view; }
|
||||
CollectionFilterWidget *filter_widget() const { return ui_->filter_widget; }
|
||||
QPushButton *button_refresh() const { return ui_->refresh; }
|
||||
QPushButton *button_close() const { return ui_->close; }
|
||||
QPushButton *button_abort() const { return ui_->abort; }
|
||||
QLabel *status() const { return ui_->status; }
|
||||
QProgressBar *progressbar() const { return ui_->progressbar; }
|
||||
|
||||
private slots:
|
||||
void contextMenuEvent(QContextMenuEvent *e) override;
|
||||
|
||||
private:
|
||||
Ui_StreamingCollectionViewContainer *ui_;
|
||||
|
||||
};
|
||||
|
||||
#endif // STREAMINGCOLLECTIONVIEWCONTAINER_H
|
||||
165
src/streaming/streamingcollectionviewcontainer.ui
Normal file
165
src/streaming/streamingcollectionviewcontainer.ui
Normal file
@@ -0,0 +1,165 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>StreamingCollectionViewContainer</class>
|
||||
<widget class="QWidget" name="StreamingCollectionViewContainer">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>300</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</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="QStackedWidget" name="stacked">
|
||||
<widget class="QWidget" name="help_page">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="status">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressbar">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="layout_buttons">
|
||||
<item>
|
||||
<spacer name="spacer_buttons">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="close">
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="abort">
|
||||
<property name="text">
|
||||
<string>Abort</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<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>
|
||||
<widget class="QWidget" name="streamingcollection_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="CollectionFilterWidget" name="filter_widget" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="refresh">
|
||||
<property name="text">
|
||||
<string>Refresh catalogue</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="StreamingCollectionView" name="view"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>CollectionFilterWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>collection/collectionfilterwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>StreamingCollectionView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header location="global">streaming/streamingcollectionview.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
38
src/streaming/streamingsearchitemdelegate.cpp
Normal file
38
src/streaming/streamingsearchitemdelegate.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 <QStyleOptionViewItem>
|
||||
#include <QPainter>
|
||||
|
||||
#include "streamingsearchitemdelegate.h"
|
||||
#include "streamingsearchview.h"
|
||||
|
||||
StreamingSearchItemDelegate::StreamingSearchItemDelegate(StreamingSearchView *view)
|
||||
: CollectionItemDelegate(view),
|
||||
view_(view) {}
|
||||
|
||||
void StreamingSearchItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const {
|
||||
|
||||
// Tell the view we painted this item, so it can lazy load some art.
|
||||
const_cast<StreamingSearchView*>(view_)->LazyLoadAlbumCover(idx);
|
||||
|
||||
CollectionItemDelegate::paint(painter, option, idx);
|
||||
|
||||
}
|
||||
45
src/streaming/streamingsearchitemdelegate.h
Normal file
45
src/streaming/streamingsearchitemdelegate.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 STREAMINGSEARCHITEMDELEGATE_H
|
||||
#define STREAMINGSEARCHITEMDELEGATE_H
|
||||
|
||||
#include <QStyleOptionViewItem>
|
||||
#include <QStyleOption>
|
||||
|
||||
#include "collection/collectionitemdelegate.h"
|
||||
|
||||
class QPainter;
|
||||
class QModelIndex;
|
||||
class StreamingSearchView;
|
||||
|
||||
class StreamingSearchItemDelegate : public CollectionItemDelegate {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit StreamingSearchItemDelegate(StreamingSearchView *view);
|
||||
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const override;
|
||||
|
||||
private:
|
||||
StreamingSearchView *view_;
|
||||
};
|
||||
|
||||
#endif // STREAMINGSEARCHITEMDELEGATE_H
|
||||
412
src/streaming/streamingsearchmodel.cpp
Normal file
412
src/streaming/streamingsearchmodel.cpp
Normal file
@@ -0,0 +1,412 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2021, 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 <QStandardItemModel>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QMimeData>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QSize>
|
||||
|
||||
#include "core/mimedata.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "streamsongmimedata.h"
|
||||
#include "streamingservice.h"
|
||||
#include "streamingsearchmodel.h"
|
||||
#include "streamingsearchview.h"
|
||||
|
||||
StreamingSearchModel::StreamingSearchModel(StreamingServicePtr service, QObject *parent)
|
||||
: QStandardItemModel(parent),
|
||||
service_(service),
|
||||
proxy_(nullptr),
|
||||
use_pretty_covers_(true),
|
||||
artist_icon_(IconLoader::Load(QStringLiteral("folder-sound"))),
|
||||
album_icon_(IconLoader::Load(QStringLiteral("cdcase"))) {
|
||||
|
||||
group_by_[0] = CollectionModel::GroupBy::AlbumArtist;
|
||||
group_by_[1] = CollectionModel::GroupBy::AlbumDisc;
|
||||
group_by_[2] = CollectionModel::GroupBy::None;
|
||||
|
||||
QList<QSize> nocover_sizes = album_icon_.availableSizes();
|
||||
no_cover_icon_ = album_icon_.pixmap(nocover_sizes.last()).scaled(CollectionModel::kPrettyCoverSize, CollectionModel::kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchModel::AddResults(const StreamingSearchView::ResultList &results) {
|
||||
|
||||
for (const StreamingSearchView::Result &result : results) {
|
||||
QStandardItem *parent = invisibleRootItem();
|
||||
|
||||
// Find (or create) the container nodes for this result if we can.
|
||||
ContainerKey key;
|
||||
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);
|
||||
|
||||
parent->appendRow(item);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QStandardItem *StreamingSearchModel::BuildContainers(const Song &s, QStandardItem *parent, ContainerKey *key, const int level) {
|
||||
|
||||
if (level >= 3) {
|
||||
return parent;
|
||||
}
|
||||
|
||||
bool has_artist_icon = false;
|
||||
bool has_album_icon = false;
|
||||
QString display_text;
|
||||
QString sort_text;
|
||||
QString unique_tag;
|
||||
|
||||
switch (group_by_[level]) {
|
||||
|
||||
case CollectionModel::GroupBy::AlbumArtist:
|
||||
if (s.is_compilation()) {
|
||||
display_text = tr("Various artists");
|
||||
sort_text = QLatin1String("aaaaaa");
|
||||
}
|
||||
else {
|
||||
display_text = CollectionModel::TextOrUnknown(s.effective_albumartist());
|
||||
sort_text = CollectionModel::SortTextForArtist(s.effective_albumartist(), true);
|
||||
}
|
||||
has_artist_icon = true;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy::Artist:
|
||||
if (s.is_compilation()) {
|
||||
display_text = tr("Various artists");
|
||||
sort_text = QLatin1String("aaaaaa");
|
||||
}
|
||||
else {
|
||||
display_text = CollectionModel::TextOrUnknown(s.artist());
|
||||
sort_text = CollectionModel::SortTextForArtist(s.artist(), true);
|
||||
}
|
||||
has_artist_icon = true;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy::Album:
|
||||
display_text = CollectionModel::TextOrUnknown(s.album());
|
||||
sort_text = CollectionModel::SortTextForArtist(s.album(), true);
|
||||
unique_tag = s.album_id();
|
||||
has_album_icon = true;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy::AlbumDisc:{
|
||||
int disc = qMax(0, s.disc());
|
||||
display_text = CollectionModel::PrettyAlbumDisc(s.album(), disc);
|
||||
sort_text = s.album() + CollectionModel::SortTextForNumber(disc);
|
||||
unique_tag = s.album_id();
|
||||
has_album_icon = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case CollectionModel::GroupBy::YearAlbum:{
|
||||
int 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::YearAlbumDisc:{
|
||||
int year = qMax(0, s.year());
|
||||
int disc = qMax(0, s.disc());
|
||||
display_text = CollectionModel::PrettyYearAlbumDisc(year, s.album(), disc);
|
||||
sort_text = CollectionModel::SortTextForNumber(year) + s.album() + CollectionModel::SortTextForNumber(disc);
|
||||
unique_tag = s.album_id();
|
||||
has_album_icon = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case CollectionModel::GroupBy::OriginalYearAlbum:{
|
||||
int 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::OriginalYearAlbumDisc:{
|
||||
const int year = qMax(0, s.effective_originalyear());
|
||||
const int disc = qMax(0, s.disc());
|
||||
display_text = CollectionModel::PrettyYearAlbumDisc(year, s.album(), disc);
|
||||
sort_text = CollectionModel::SortTextForNumber(year) + s.album() + CollectionModel::SortTextForNumber(disc);
|
||||
unique_tag = s.album_id();
|
||||
has_album_icon = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case CollectionModel::GroupBy::Disc:
|
||||
display_text = CollectionModel::PrettyDisc(s.disc());
|
||||
sort_text = CollectionModel::SortTextForArtist(display_text, true);
|
||||
has_album_icon = true;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy::Year:{
|
||||
const int year = qMax(0, s.year());
|
||||
display_text = QString::number(year);
|
||||
sort_text = CollectionModel::SortTextForNumber(year) + QLatin1Char(' ');
|
||||
break;
|
||||
}
|
||||
|
||||
case CollectionModel::GroupBy::OriginalYear:{
|
||||
const int year = qMax(0, s.effective_originalyear());
|
||||
display_text = QString::number(year);
|
||||
sort_text = CollectionModel::SortTextForNumber(year) + QLatin1Char(' ');
|
||||
break;
|
||||
}
|
||||
|
||||
case CollectionModel::GroupBy::Genre:
|
||||
display_text = CollectionModel::TextOrUnknown(s.genre());
|
||||
sort_text = CollectionModel::SortTextForArtist(s.genre(), true);
|
||||
has_album_icon = true;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy::Composer:
|
||||
display_text = CollectionModel::TextOrUnknown(s.composer());
|
||||
sort_text = CollectionModel::SortTextForArtist(s.composer(), true);
|
||||
has_album_icon = true;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy::Performer:
|
||||
display_text = CollectionModel::TextOrUnknown(s.performer());
|
||||
sort_text = CollectionModel::SortTextForArtist(s.performer(), true);
|
||||
has_album_icon = true;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy::Grouping:
|
||||
display_text = CollectionModel::TextOrUnknown(s.grouping());
|
||||
sort_text = CollectionModel::SortTextForArtist(s.grouping(), true);
|
||||
has_album_icon = true;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy::FileType:
|
||||
display_text = s.TextForFiletype();
|
||||
sort_text = display_text;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy::Format:
|
||||
if (s.samplerate() <= 0) {
|
||||
display_text = s.TextForFiletype();
|
||||
}
|
||||
else {
|
||||
if (s.bitdepth() <= 0) {
|
||||
display_text = QStringLiteral("%1 (%2)").arg(s.TextForFiletype(), QString::number(s.samplerate() / 1000.0, 'G', 5));
|
||||
}
|
||||
else {
|
||||
display_text = QStringLiteral("%1 (%2/%3)").arg(s.TextForFiletype(), QString::number(s.samplerate() / 1000.0, 'G', 5), QString::number(s.bitdepth()));
|
||||
}
|
||||
}
|
||||
sort_text = display_text;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy::Samplerate:
|
||||
display_text = QString::number(s.samplerate());
|
||||
sort_text = display_text;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy::Bitdepth:
|
||||
display_text = QString::number(s.bitdepth());
|
||||
sort_text = display_text;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy::Bitrate:
|
||||
display_text = QString::number(s.bitrate());
|
||||
sort_text = display_text;
|
||||
break;
|
||||
|
||||
case CollectionModel::GroupBy::None:
|
||||
case CollectionModel::GroupBy::GroupByCount:
|
||||
return parent;
|
||||
}
|
||||
|
||||
if (display_text.isEmpty() || sort_text.isEmpty()) {
|
||||
display_text = QLatin1String("Unknown");
|
||||
}
|
||||
|
||||
// Find a container for this level
|
||||
key->group_[level] = display_text + unique_tag;
|
||||
QStandardItem *container = nullptr;
|
||||
if (containers_.contains(*key)) {
|
||||
container = containers_[*key];
|
||||
}
|
||||
else {
|
||||
container = new QStandardItem(display_text);
|
||||
container->setData(sort_text, CollectionModel::Role_SortText);
|
||||
container->setData(static_cast<int>(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 StreamingSearchModel::Clear() {
|
||||
|
||||
containers_.clear();
|
||||
clear();
|
||||
|
||||
}
|
||||
|
||||
StreamingSearchView::ResultList StreamingSearchModel::GetChildResults(const QModelIndexList &indexes) const {
|
||||
|
||||
QList<QStandardItem*> items;
|
||||
items.reserve(indexes.count());
|
||||
for (const QModelIndex &idx : indexes) {
|
||||
items << itemFromIndex(idx);
|
||||
}
|
||||
return GetChildResults(items);
|
||||
|
||||
}
|
||||
|
||||
StreamingSearchView::ResultList StreamingSearchModel::GetChildResults(const QList<QStandardItem*> &items) const {
|
||||
|
||||
StreamingSearchView::ResultList results;
|
||||
QSet<const QStandardItem*> visited;
|
||||
|
||||
for (QStandardItem *item : items) {
|
||||
GetChildResults(item, &results, &visited);
|
||||
}
|
||||
|
||||
return results;
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchModel::GetChildResults(const QStandardItem *item, StreamingSearchView::ResultList *results, QSet<const QStandardItem*> *visited) const {
|
||||
|
||||
if (visited->contains(item)) {
|
||||
return;
|
||||
}
|
||||
visited->insert(item);
|
||||
|
||||
// Does this item have children?
|
||||
if (item->rowCount() > 0) {
|
||||
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.model()->index(i, 0, parent_proxy_index);
|
||||
const QModelIndex idx = proxy_->mapToSource(proxy_index);
|
||||
GetChildResults(itemFromIndex(idx), 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<StreamingSearchView::Result>());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QMimeData *StreamingSearchModel::mimeData(const QModelIndexList &indexes) const {
|
||||
|
||||
return LoadTracks(GetChildResults(indexes));
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void GatherResults(const QStandardItem *parent, StreamingSearchView::ResultList *results) {
|
||||
|
||||
QVariant result_variant = parent->data(StreamingSearchModel::Role_Result);
|
||||
if (result_variant.isValid()) {
|
||||
StreamingSearchView::Result result = result_variant.value<StreamingSearchView::Result>();
|
||||
(*results).append(result);
|
||||
}
|
||||
|
||||
for (int i = 0; i < parent->rowCount(); ++i) {
|
||||
GatherResults(parent->child(i), results);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void StreamingSearchModel::SetGroupBy(const CollectionModel::Grouping grouping, const 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
|
||||
StreamingSearchView::ResultList results;
|
||||
GatherResults(invisibleRootItem(), &results);
|
||||
|
||||
// Reset the model and re-add all the results using the new grouping.
|
||||
Clear();
|
||||
AddResults(results);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MimeData *StreamingSearchModel::LoadTracks(const StreamingSearchView::ResultList &results) const {
|
||||
|
||||
if (results.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SongList songs;
|
||||
QList<QUrl> urls;
|
||||
songs.reserve(results.count());
|
||||
urls.reserve(results.count());
|
||||
for (const StreamingSearchView::Result &result : results) {
|
||||
songs.append(result.metadata_);
|
||||
urls << result.metadata_.url();
|
||||
}
|
||||
|
||||
StreamSongMimeData *streaming_song_mime_data = new StreamSongMimeData(service_);
|
||||
streaming_song_mime_data->songs = songs;
|
||||
MimeData *mime_data = streaming_song_mime_data;
|
||||
|
||||
mime_data->setUrls(urls);
|
||||
|
||||
return mime_data;
|
||||
|
||||
}
|
||||
120
src/streaming/streamingsearchmodel.h
Normal file
120
src/streaming/streamingsearchmodel.h
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2021, 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 STREAMINGSEARCHMODEL_H
|
||||
#define STREAMINGSEARCHMODEL_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QAbstractItemModel>
|
||||
#include <QStandardItemModel>
|
||||
#include <QStandardItem>
|
||||
#include <QSet>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QIcon>
|
||||
#include <QPixmap>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "streamingsearchview.h"
|
||||
|
||||
class QMimeData;
|
||||
class QSortFilterProxyModel;
|
||||
|
||||
class MimeData;
|
||||
class StreamingService;
|
||||
|
||||
class StreamingSearchModel : public QStandardItemModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit StreamingSearchModel(SharedPtr<StreamingService> service, QObject *parent = nullptr);
|
||||
|
||||
enum Role {
|
||||
Role_Result = CollectionModel::LastRole,
|
||||
Role_LazyLoadingArt,
|
||||
LastRole
|
||||
};
|
||||
|
||||
struct ContainerKey {
|
||||
QString group_[3];
|
||||
};
|
||||
|
||||
void set_proxy(QSortFilterProxyModel *proxy) { proxy_ = proxy; }
|
||||
void set_use_pretty_covers(const bool pretty) { use_pretty_covers_ = pretty; }
|
||||
void SetGroupBy(const CollectionModel::Grouping grouping, const bool regroup_now);
|
||||
|
||||
void Clear();
|
||||
|
||||
StreamingSearchView::ResultList GetChildResults(const QModelIndexList &indexes) const;
|
||||
StreamingSearchView::ResultList GetChildResults(const QList<QStandardItem*> &items) const;
|
||||
|
||||
QMimeData *mimeData(const QModelIndexList &indexes) const override;
|
||||
|
||||
// Loads tracks for results that were previously emitted by ResultsAvailable.
|
||||
// The implementation creates a SongMimeData with one Song for each Result.
|
||||
MimeData *LoadTracks(const StreamingSearchView::ResultList &results) const;
|
||||
|
||||
public slots:
|
||||
void AddResults(const StreamingSearchView::ResultList &results);
|
||||
|
||||
private:
|
||||
QStandardItem *BuildContainers(const Song &s, QStandardItem *parent, ContainerKey *key, const int level = 0);
|
||||
void GetChildResults(const QStandardItem *item, StreamingSearchView::ResultList *results, QSet<const QStandardItem*> *visited) const;
|
||||
|
||||
private:
|
||||
SharedPtr<StreamingService> service_;
|
||||
QSortFilterProxyModel *proxy_;
|
||||
bool use_pretty_covers_;
|
||||
QIcon artist_icon_;
|
||||
QIcon album_icon_;
|
||||
QPixmap no_cover_icon_;
|
||||
CollectionModel::Grouping group_by_;
|
||||
QMap<ContainerKey, QStandardItem*> containers_;
|
||||
};
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
inline size_t qHash(const StreamingSearchModel::ContainerKey &key) {
|
||||
#else
|
||||
inline uint qHash(const StreamingSearchModel::ContainerKey &key) {
|
||||
#endif
|
||||
return qHash(key.group_[0]) ^ qHash(key.group_[1]) ^ qHash(key.group_[2]);
|
||||
}
|
||||
|
||||
inline bool operator<(const StreamingSearchModel::ContainerKey &left, const StreamingSearchModel::ContainerKey &right) {
|
||||
#define CMP(field) \
|
||||
if (left.field < right.field) return true; \
|
||||
if (left.field > right.field) return false
|
||||
|
||||
CMP(group_[0]);
|
||||
CMP(group_[1]);
|
||||
CMP(group_[2]);
|
||||
return false;
|
||||
|
||||
#undef CMP
|
||||
}
|
||||
|
||||
#endif // STREAMINGSEARCHMODEL_H
|
||||
68
src/streaming/streamingsearchsortmodel.cpp
Normal file
68
src/streaming/streamingsearchsortmodel.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 <QAbstractItemModel>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QString>
|
||||
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "streamingsearchmodel.h"
|
||||
#include "streamingsearchsortmodel.h"
|
||||
#include "streamingsearchview.h"
|
||||
|
||||
StreamingSearchSortModel::StreamingSearchSortModel(QObject *parent) : QSortFilterProxyModel(parent) {}
|
||||
|
||||
bool StreamingSearchSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const {
|
||||
|
||||
// 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 StreamingSearchView::Result r1 = left.data(StreamingSearchModel::Role_Result).value<StreamingSearchView::Result>();
|
||||
const StreamingSearchView::Result r2 = right.data(StreamingSearchModel::Role_Result).value<StreamingSearchView::Result>();
|
||||
|
||||
if (r1.metadata_.disc() < r2.metadata_.disc()) return true;
|
||||
if (r1.metadata_.disc() > r2.metadata_.disc()) return false;
|
||||
|
||||
if (r1.metadata_.track() < r2.metadata_.track()) return true;
|
||||
if (r1.metadata_.track() > r2.metadata_.track()) return false;
|
||||
|
||||
int ret = QString::localeAwareCompare(r1.metadata_.title(), r2.metadata_.title());
|
||||
if (ret < 0) return true;
|
||||
if (ret > 0) return false;
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
39
src/streaming/streamingsearchsortmodel.h
Normal file
39
src/streaming/streamingsearchsortmodel.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 STREAMINGSEARCHSORTMODEL_H
|
||||
#define STREAMINGSEARCHSORTMODEL_H
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
class QObject;
|
||||
class QModelIndex;
|
||||
|
||||
class StreamingSearchSortModel : public QSortFilterProxyModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit StreamingSearchSortModel(QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
||||
};
|
||||
|
||||
#endif // STREAMINGSEARCHSORTMODEL_H
|
||||
881
src/streaming/streamingsearchview.cpp
Normal file
881
src/streaming/streamingsearchview.cpp
Normal file
@@ -0,0 +1,881 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2021, 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 <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QAbstractItemModel>
|
||||
#include <QStandardItemModel>
|
||||
#include <QItemSelectionModel>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QApplication>
|
||||
#include <QWidget>
|
||||
#include <QTimer>
|
||||
#include <QPair>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QRegularExpression>
|
||||
#include <QPixmap>
|
||||
#include <QPixmapCache>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
#include <QColor>
|
||||
#include <QFont>
|
||||
#include <QSize>
|
||||
#include <QStandardItem>
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
#include <QActionGroup>
|
||||
#include <QSettings>
|
||||
#include <QStackedWidget>
|
||||
#include <QLabel>
|
||||
#include <QProgressBar>
|
||||
#include <QRadioButton>
|
||||
#include <QScrollArea>
|
||||
#include <QToolButton>
|
||||
#include <QEvent>
|
||||
#include <QTimerEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QContextMenuEvent>
|
||||
#include <QShowEvent>
|
||||
#include <QHideEvent>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/mimedata.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/song.h"
|
||||
#include "core/settings.h"
|
||||
#include "collection/collectionfilterwidget.h"
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "collection/groupbydialog.h"
|
||||
#include "collection/savedgroupingmanager.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "covermanager/albumcoverloaderresult.h"
|
||||
#include "streamsongmimedata.h"
|
||||
#include "streamingservice.h"
|
||||
#include "streamingsearchitemdelegate.h"
|
||||
#include "streamingsearchmodel.h"
|
||||
#include "streamingsearchsortmodel.h"
|
||||
#include "streamingsearchview.h"
|
||||
#include "ui_streamingsearchview.h"
|
||||
#include "settings/appearancesettingspage.h"
|
||||
|
||||
using std::make_unique;
|
||||
|
||||
const int StreamingSearchView::kSwapModelsTimeoutMsec = 250;
|
||||
const int StreamingSearchView::kDelayedSearchTimeoutMs = 200;
|
||||
const int StreamingSearchView::kArtHeight = 32;
|
||||
|
||||
StreamingSearchView::StreamingSearchView(QWidget *parent)
|
||||
: QWidget(parent),
|
||||
app_(nullptr),
|
||||
service_(nullptr),
|
||||
ui_(new Ui_StreamingSearchView),
|
||||
context_menu_(nullptr),
|
||||
group_by_actions_(nullptr),
|
||||
front_model_(nullptr),
|
||||
back_model_(nullptr),
|
||||
current_model_(nullptr),
|
||||
front_proxy_(new StreamingSearchSortModel(this)),
|
||||
back_proxy_(new StreamingSearchSortModel(this)),
|
||||
current_proxy_(front_proxy_),
|
||||
swap_models_timer_(new QTimer(this)),
|
||||
use_pretty_covers_(true),
|
||||
search_type_(StreamingSearchView::SearchType::Artists),
|
||||
search_error_(false),
|
||||
last_search_id_(0),
|
||||
searches_next_id_(1) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
|
||||
ui_->search->installEventFilter(this);
|
||||
ui_->results_stack->installEventFilter(this);
|
||||
|
||||
ui_->settings->setIcon(IconLoader::Load(QStringLiteral("configure")));
|
||||
|
||||
// Set the appearance of the results list
|
||||
ui_->results->setItemDelegate(new StreamingSearchItemDelegate(this));
|
||||
ui_->results->setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
ui_->results->setStyleSheet(QStringLiteral("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);
|
||||
|
||||
// Hide progressbar
|
||||
ui_->progressbar->hide();
|
||||
ui_->progressbar->reset();
|
||||
|
||||
}
|
||||
|
||||
StreamingSearchView::~StreamingSearchView() { delete ui_; }
|
||||
|
||||
void StreamingSearchView::Init(Application *app, StreamingServicePtr service) {
|
||||
|
||||
app_ = app;
|
||||
service_ = service;
|
||||
|
||||
front_model_ = new StreamingSearchModel(service, this);
|
||||
back_model_ = new StreamingSearchModel(service, this);
|
||||
|
||||
front_proxy_ = new StreamingSearchSortModel(this);
|
||||
back_proxy_ = new StreamingSearchSortModel(this);
|
||||
|
||||
front_model_->set_proxy(front_proxy_);
|
||||
back_model_->set_proxy(back_proxy_);
|
||||
|
||||
current_model_ = front_model_;
|
||||
current_proxy_ = front_proxy_;
|
||||
|
||||
// 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);
|
||||
|
||||
// Add actions to the settings menu
|
||||
group_by_actions_ = CollectionFilterWidget::CreateGroupByActions(SavedGroupingManager::GetSavedGroupingsSettingsGroup(service_->settings_group()), this);
|
||||
QMenu *settings_menu = new QMenu(this);
|
||||
settings_menu->addActions(group_by_actions_->actions());
|
||||
settings_menu->addSeparator();
|
||||
settings_menu->addAction(IconLoader::Load(QStringLiteral("configure")), tr("Configure %1...").arg(Song::DescriptionForSource(service_->source())), this, &StreamingSearchView::OpenSettingsDialog);
|
||||
ui_->settings->setMenu(settings_menu);
|
||||
|
||||
swap_models_timer_->setSingleShot(true);
|
||||
swap_models_timer_->setInterval(kSwapModelsTimeoutMsec);
|
||||
QObject::connect(swap_models_timer_, &QTimer::timeout, this, &StreamingSearchView::SwapModels);
|
||||
|
||||
QObject::connect(ui_->radiobutton_search_artists, &QRadioButton::clicked, this, &StreamingSearchView::SearchArtistsClicked);
|
||||
QObject::connect(ui_->radiobutton_search_albums, &QRadioButton::clicked, this, &StreamingSearchView::SearchAlbumsClicked);
|
||||
QObject::connect(ui_->radiobutton_search_songs, &QRadioButton::clicked, this, &StreamingSearchView::SearchSongsClicked);
|
||||
QObject::connect(group_by_actions_, &QActionGroup::triggered, this, &StreamingSearchView::GroupByClicked);
|
||||
QObject::connect(group_by_actions_, &QActionGroup::triggered, this, &StreamingSearchView::GroupByClicked);
|
||||
|
||||
QObject::connect(ui_->search, &QSearchField::textChanged, this, &StreamingSearchView::TextEdited);
|
||||
QObject::connect(ui_->results, &AutoExpandingTreeView::AddToPlaylistSignal, this, &StreamingSearchView::AddToPlaylist);
|
||||
QObject::connect(ui_->results, &AutoExpandingTreeView::FocusOnFilterSignal, this, &StreamingSearchView::FocusOnFilter);
|
||||
|
||||
QObject::connect(&*service_, &StreamingService::SearchUpdateStatus, this, &StreamingSearchView::UpdateStatus);
|
||||
QObject::connect(&*service_, &StreamingService::SearchProgressSetMaximum, this, &StreamingSearchView::ProgressSetMaximum);
|
||||
QObject::connect(&*service_, &StreamingService::SearchUpdateProgress, this, &StreamingSearchView::UpdateProgress);
|
||||
QObject::connect(&*service_, &StreamingService::SearchResults, this, &StreamingSearchView::SearchDone);
|
||||
|
||||
QObject::connect(app_, &Application::SettingsChanged, this, &StreamingSearchView::ReloadSettings);
|
||||
QObject::connect(&*app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &StreamingSearchView::AlbumCoverLoaded);
|
||||
|
||||
QObject::connect(ui_->settings, &QToolButton::clicked, ui_->settings, &QToolButton::showMenu);
|
||||
|
||||
ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::ReloadSettings() {
|
||||
|
||||
Settings s;
|
||||
|
||||
// Collection settings
|
||||
|
||||
s.beginGroup(service_->settings_group());
|
||||
use_pretty_covers_ = s.value("pretty_covers", true).toBool();
|
||||
front_model_->set_use_pretty_covers(use_pretty_covers_);
|
||||
back_model_->set_use_pretty_covers(use_pretty_covers_);
|
||||
|
||||
// Streaming search settings
|
||||
|
||||
search_type_ = static_cast<StreamingSearchView::SearchType>(s.value("type", static_cast<int>(StreamingSearchView::SearchType::Artists)).toInt());
|
||||
switch (search_type_) {
|
||||
case StreamingSearchView::SearchType::Artists:
|
||||
ui_->radiobutton_search_artists->setChecked(true);
|
||||
break;
|
||||
case StreamingSearchView::SearchType::Albums:
|
||||
ui_->radiobutton_search_albums->setChecked(true);
|
||||
break;
|
||||
case StreamingSearchView::SearchType::Songs:
|
||||
ui_->radiobutton_search_songs->setChecked(true);
|
||||
break;
|
||||
}
|
||||
|
||||
int group_by_version = s.value("search_group_by_version", 0).toInt();
|
||||
if (group_by_version == 1 && s.contains("search_group_by1") && s.contains("search_group_by2") && s.contains("search_group_by3")) {
|
||||
SetGroupBy(CollectionModel::Grouping(
|
||||
static_cast<CollectionModel::GroupBy>(s.value("search_group_by1", static_cast<int>(CollectionModel::GroupBy::AlbumArtist)).toInt()),
|
||||
static_cast<CollectionModel::GroupBy>(s.value("search_group_by2", static_cast<int>(CollectionModel::GroupBy::AlbumDisc)).toInt()),
|
||||
static_cast<CollectionModel::GroupBy>(s.value("search_group_by3", static_cast<int>(CollectionModel::GroupBy::None)).toInt())));
|
||||
}
|
||||
else {
|
||||
SetGroupBy(CollectionModel::Grouping(CollectionModel::GroupBy::AlbumArtist, CollectionModel::GroupBy::AlbumDisc, CollectionModel::GroupBy::None));
|
||||
}
|
||||
s.endGroup();
|
||||
|
||||
s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
|
||||
int iconsize = s.value(AppearanceSettingsPage::kIconSizeConfigureButtons, 20).toInt();
|
||||
s.endGroup();
|
||||
|
||||
ui_->settings->setIconSize(QSize(iconsize, iconsize));
|
||||
ui_->search->setIconSize(iconsize);
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::showEvent(QShowEvent *e) {
|
||||
|
||||
QWidget::showEvent(e);
|
||||
|
||||
#ifndef Q_OS_MACOS
|
||||
FocusSearchField();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
bool StreamingSearchView::eventFilter(QObject *object, QEvent *e) {
|
||||
|
||||
if (object == ui_->search && e->type() == QEvent::KeyRelease) {
|
||||
if (SearchKeyEvent(static_cast<QKeyEvent*>(e))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (object == ui_->results_stack && e->type() == QEvent::ContextMenu) {
|
||||
if (ResultsContextMenuEvent(static_cast<QContextMenuEvent*>(e))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return QWidget::eventFilter(object, e);
|
||||
|
||||
}
|
||||
|
||||
bool StreamingSearchView::SearchKeyEvent(QKeyEvent *e) {
|
||||
|
||||
switch (e->key()) {
|
||||
case Qt::Key_Up:
|
||||
ui_->results->UpAndFocus();
|
||||
break;
|
||||
|
||||
case Qt::Key_Down:
|
||||
ui_->results->DownAndFocus();
|
||||
break;
|
||||
|
||||
case Qt::Key_Escape:
|
||||
ui_->search->clear();
|
||||
break;
|
||||
|
||||
case Qt::Key_Return:
|
||||
TextEdited(ui_->search->text());
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
e->accept();
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool StreamingSearchView::ResultsContextMenuEvent(QContextMenuEvent *e) {
|
||||
|
||||
if (!context_menu_) {
|
||||
context_menu_ = new QMenu(this);
|
||||
context_actions_ << context_menu_->addAction(IconLoader::Load(QStringLiteral("media-playback-start")), tr("Append to current playlist"), this, &StreamingSearchView::AddSelectedToPlaylist);
|
||||
context_actions_ << context_menu_->addAction(IconLoader::Load(QStringLiteral("media-playback-start")), tr("Replace current playlist"), this, &StreamingSearchView::LoadSelected);
|
||||
context_actions_ << context_menu_->addAction(IconLoader::Load(QStringLiteral("document-new")), tr("Open in new playlist"), this, &StreamingSearchView::OpenSelectedInNewPlaylist);
|
||||
|
||||
context_menu_->addSeparator();
|
||||
context_actions_ << context_menu_->addAction(IconLoader::Load(QStringLiteral("go-next")), tr("Queue track"), this, &StreamingSearchView::AddSelectedToPlaylistEnqueue);
|
||||
|
||||
context_menu_->addSeparator();
|
||||
|
||||
if (service_->artists_collection_model() || service_->albums_collection_model() || service_->songs_collection_model()) {
|
||||
if (service_->artists_collection_model()) {
|
||||
context_actions_ << context_menu_->addAction(IconLoader::Load(QStringLiteral("folder-new")), tr("Add to artists"), this, &StreamingSearchView::AddArtists);
|
||||
}
|
||||
if (service_->albums_collection_model()) {
|
||||
context_actions_ << context_menu_->addAction(IconLoader::Load(QStringLiteral("folder-new")), tr("Add to albums"), this, &StreamingSearchView::AddAlbums);
|
||||
}
|
||||
if (service_->songs_collection_model()) {
|
||||
context_actions_ << context_menu_->addAction(IconLoader::Load(QStringLiteral("folder-new")), tr("Add to songs"), this, &StreamingSearchView::AddSongs);
|
||||
}
|
||||
context_menu_->addSeparator();
|
||||
}
|
||||
|
||||
if (ui_->results->selectionModel() && ui_->results->selectionModel()->selectedRows().length() == 1) {
|
||||
context_actions_ << context_menu_->addAction(IconLoader::Load(QStringLiteral("search")), tr("Search for this"), this, &StreamingSearchView::SearchForThis);
|
||||
}
|
||||
|
||||
context_menu_->addSeparator();
|
||||
context_menu_->addMenu(tr("Group by"))->addActions(group_by_actions_->actions());
|
||||
|
||||
context_menu_->addAction(IconLoader::Load(QStringLiteral("configure")), tr("Configure %1...").arg(Song::TextForSource(service_->source())), this, &StreamingSearchView::OpenSettingsDialog);
|
||||
|
||||
}
|
||||
|
||||
const bool enable_context_actions = ui_->results->selectionModel() && ui_->results->selectionModel()->hasSelection();
|
||||
|
||||
for (QAction *action : std::as_const(context_actions_)) {
|
||||
action->setEnabled(enable_context_actions);
|
||||
}
|
||||
|
||||
context_menu_->popup(e->globalPos());
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::timerEvent(QTimerEvent *e) {
|
||||
|
||||
QMap<int, DelayedSearch>::iterator it = delayed_searches_.find(e->timerId());
|
||||
if (it != delayed_searches_.end()) {
|
||||
SearchAsync(it.value().id_, it.value().query_, it.value().type_);
|
||||
delayed_searches_.erase(it); // clazy:exclude=strict-iterators
|
||||
return;
|
||||
}
|
||||
|
||||
QObject::timerEvent(e);
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::StartSearch(const QString &query) {
|
||||
|
||||
ui_->search->setText(query);
|
||||
TextEdited(query);
|
||||
|
||||
// Swap models immediately
|
||||
swap_models_timer_->stop();
|
||||
SwapModels();
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::TextEdited(const QString &text) {
|
||||
|
||||
const QString trimmed(text.trimmed());
|
||||
|
||||
search_error_ = false;
|
||||
cover_loader_tasks_.clear();
|
||||
|
||||
// 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.
|
||||
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(tr("Enter search terms above to find music"));
|
||||
ui_->label_status->clear();
|
||||
ui_->progressbar->hide();
|
||||
ui_->progressbar->reset();
|
||||
}
|
||||
else {
|
||||
ui_->progressbar->reset();
|
||||
last_search_id_ = SearchAsync(trimmed, search_type_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::SwapModels() {
|
||||
|
||||
cover_loader_tasks_.clear();
|
||||
|
||||
std::swap(front_model_, back_model_);
|
||||
std::swap(front_proxy_, back_proxy_);
|
||||
|
||||
ui_->results->setModel(front_proxy_);
|
||||
|
||||
if (ui_->search->text().trimmed().isEmpty() || search_error_) {
|
||||
ui_->results_stack->setCurrentWidget(ui_->help_page);
|
||||
}
|
||||
else {
|
||||
ui_->results_stack->setCurrentWidget(ui_->results_page);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QStringList StreamingSearchView::TokenizeQuery(const QString &query) {
|
||||
|
||||
QStringList tokens(query.split(QRegularExpression(QStringLiteral("\\s+"))));
|
||||
|
||||
for (QStringList::iterator it = tokens.begin(); it != tokens.end(); ++it) {
|
||||
(*it).remove(QLatin1Char('('));
|
||||
(*it).remove(QLatin1Char(')'));
|
||||
(*it).remove(QLatin1Char('"'));
|
||||
|
||||
const qint64 colon = (*it).indexOf(QLatin1Char(':'));
|
||||
if (colon != -1) {
|
||||
(*it).remove(0, colon + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
|
||||
}
|
||||
|
||||
bool StreamingSearchView::Matches(const QStringList &tokens, const QString &string) {
|
||||
|
||||
for (const QString &token : tokens) {
|
||||
if (!string.contains(token, Qt::CaseInsensitive)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
int StreamingSearchView::SearchAsync(const QString &query, const SearchType type) {
|
||||
|
||||
const int id = searches_next_id_++;
|
||||
|
||||
int timer_id = startTimer(kDelayedSearchTimeoutMs);
|
||||
delayed_searches_[timer_id].id_ = id;
|
||||
delayed_searches_[timer_id].query_ = query;
|
||||
delayed_searches_[timer_id].type_ = type;
|
||||
|
||||
return id;
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::SearchAsync(const int id, const QString &query, const SearchType type) {
|
||||
|
||||
const int service_id = service_->Search(query, type);
|
||||
pending_searches_[service_id] = PendingState(id, TokenizeQuery(query));
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::SearchDone(const int service_id, const SongMap &songs, const QString &error) {
|
||||
|
||||
if (!pending_searches_.contains(service_id)) return;
|
||||
|
||||
// Map back to the original id.
|
||||
const PendingState state = pending_searches_.take(service_id);
|
||||
const int search_id = state.orig_id_;
|
||||
|
||||
if (songs.isEmpty()) {
|
||||
SearchError(search_id, error);
|
||||
return;
|
||||
}
|
||||
|
||||
ResultList results;
|
||||
results.reserve(songs.count());
|
||||
for (const Song &song : songs) {
|
||||
Result result;
|
||||
result.metadata_ = song;
|
||||
results << result;
|
||||
}
|
||||
|
||||
// Load cached pixmaps into the results
|
||||
for (StreamingSearchView::ResultList::iterator it = results.begin(); it != results.end(); ++it) {
|
||||
it->pixmap_cache_key_ = PixmapCacheKey(*it);
|
||||
}
|
||||
|
||||
AddResults(search_id, results);
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::CancelSearch(const int id) {
|
||||
|
||||
QMap<int, DelayedSearch>::iterator it;
|
||||
for (it = delayed_searches_.begin(); it != delayed_searches_.end(); ++it) {
|
||||
if (it.value().id_ == id) {
|
||||
killTimer(it.key());
|
||||
delayed_searches_.erase(it); // clazy:exclude=strict-iterators
|
||||
return;
|
||||
}
|
||||
}
|
||||
service_->CancelSearch();
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::AddResults(const int id, const StreamingSearchView::ResultList &results) {
|
||||
|
||||
if (id != last_search_id_ || results.isEmpty()) return;
|
||||
|
||||
ui_->label_status->clear();
|
||||
ui_->progressbar->reset();
|
||||
ui_->progressbar->hide();
|
||||
current_model_->AddResults(results);
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::SearchError(const int id, const QString &error) {
|
||||
|
||||
if (id != last_search_id_) return;
|
||||
|
||||
search_error_ = true;
|
||||
ui_->label_helptext->setText(error);
|
||||
ui_->label_status->clear();
|
||||
ui_->progressbar->reset();
|
||||
ui_->progressbar->hide();
|
||||
ui_->results_stack->setCurrentWidget(ui_->help_page);
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::UpdateStatus(const int service_id, const QString &text) {
|
||||
|
||||
if (!pending_searches_.contains(service_id)) return;
|
||||
const PendingState state = pending_searches_[service_id];
|
||||
const int search_id = state.orig_id_;
|
||||
if (search_id != last_search_id_) return;
|
||||
ui_->progressbar->show();
|
||||
ui_->label_status->setText(text);
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::ProgressSetMaximum(const int service_id, const int max) {
|
||||
|
||||
if (!pending_searches_.contains(service_id)) return;
|
||||
const PendingState state = pending_searches_[service_id];
|
||||
const int search_id = state.orig_id_;
|
||||
if (search_id != last_search_id_) return;
|
||||
ui_->progressbar->setMaximum(max);
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::UpdateProgress(const int service_id, const int progress) {
|
||||
|
||||
if (!pending_searches_.contains(service_id)) return;
|
||||
const PendingState state = pending_searches_[service_id];
|
||||
const int search_id = state.orig_id_;
|
||||
if (search_id != last_search_id_) return;
|
||||
ui_->progressbar->setValue(progress);
|
||||
|
||||
}
|
||||
|
||||
MimeData *StreamingSearchView::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 idx = front_proxy_->index(i, 0);
|
||||
if (!idx.data(CollectionModel::Role_IsDivider).toBool()) {
|
||||
indexes << idx; // clazy:exclude=reserve-candidates
|
||||
ui_->results->setCurrentIndex(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Still got nothing? Give up.
|
||||
if (indexes.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get items for these indexes
|
||||
QList<QStandardItem*> items;
|
||||
for (const QModelIndex &idx : std::as_const(indexes)) {
|
||||
items << (front_model_->itemFromIndex(front_proxy_->mapToSource(idx))); // clazy:exclude=reserve-candidates
|
||||
}
|
||||
|
||||
// Get a MimeData for these items
|
||||
return front_model_->LoadTracks(front_model_->GetChildResults(items));
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::AddSelectedToPlaylist() {
|
||||
emit AddToPlaylist(SelectedMimeData());
|
||||
}
|
||||
|
||||
void StreamingSearchView::LoadSelected() {
|
||||
|
||||
MimeData *mimedata = SelectedMimeData();
|
||||
if (!mimedata) return;
|
||||
|
||||
mimedata->clear_first_ = true;
|
||||
emit AddToPlaylist(mimedata);
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::AddSelectedToPlaylistEnqueue() {
|
||||
|
||||
MimeData *mimedata = SelectedMimeData();
|
||||
if (!mimedata) return;
|
||||
|
||||
mimedata->enqueue_now_ = true;
|
||||
emit AddToPlaylist(mimedata);
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::OpenSelectedInNewPlaylist() {
|
||||
|
||||
MimeData *mimedata = SelectedMimeData();
|
||||
if (!mimedata) return;
|
||||
|
||||
mimedata->open_in_new_playlist_ = true;
|
||||
emit AddToPlaylist(mimedata);
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::SearchForThis() {
|
||||
StartSearch(ui_->results->selectionModel()->selectedRows().first().data().toString());
|
||||
}
|
||||
|
||||
bool StreamingSearchView::SearchFieldHasFocus() const {
|
||||
|
||||
return ui_->search->hasFocus();
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::FocusSearchField() {
|
||||
|
||||
ui_->search->setFocus();
|
||||
ui_->search->selectAll();
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::FocusOnFilter(QKeyEvent *e) {
|
||||
|
||||
ui_->search->setFocus();
|
||||
QApplication::sendEvent(ui_->search, e);
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::OpenSettingsDialog() {
|
||||
app_->OpenSettingsDialogAtPage(service_->settings_page());
|
||||
}
|
||||
|
||||
void StreamingSearchView::GroupByClicked(QAction *action) {
|
||||
|
||||
if (action->property("group_by").isNull()) {
|
||||
if (!group_by_dialog_) {
|
||||
group_by_dialog_ = make_unique<GroupByDialog>();
|
||||
QObject::connect(&*group_by_dialog_, &GroupByDialog::Accepted, this, &StreamingSearchView::SetGroupBy);
|
||||
}
|
||||
|
||||
group_by_dialog_->show();
|
||||
return;
|
||||
}
|
||||
|
||||
SetGroupBy(action->property("group_by").value<CollectionModel::Grouping>());
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::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 StreamingSearchItemDelegate::paint will call LazyLoadAlbumCover)
|
||||
cover_loader_tasks_.clear();
|
||||
|
||||
// Update the models
|
||||
front_model_->SetGroupBy(g, true);
|
||||
back_model_->SetGroupBy(g, false);
|
||||
|
||||
// Save the setting
|
||||
Settings s;
|
||||
s.beginGroup(service_->settings_group());
|
||||
s.setValue("search_group_by_version", 1);
|
||||
s.setValue("search_group_by1", static_cast<int>(g.first));
|
||||
s.setValue("search_group_by2", static_cast<int>(g.second));
|
||||
s.setValue("search_group_by3", static_cast<int>(g.third));
|
||||
s.endGroup();
|
||||
|
||||
// Make sure the correct action is checked.
|
||||
const QList<QAction*> actions = group_by_actions_->actions();
|
||||
for (QAction *action : 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
|
||||
actions.last()->setChecked(true);
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::SearchArtistsClicked(const bool) {
|
||||
SetSearchType(StreamingSearchView::SearchType::Artists);
|
||||
}
|
||||
|
||||
void StreamingSearchView::SearchAlbumsClicked(const bool) {
|
||||
SetSearchType(StreamingSearchView::SearchType::Albums);
|
||||
}
|
||||
|
||||
void StreamingSearchView::SearchSongsClicked(const bool) {
|
||||
SetSearchType(StreamingSearchView::SearchType::Songs);
|
||||
}
|
||||
|
||||
void StreamingSearchView::SetSearchType(const StreamingSearchView::SearchType type) {
|
||||
|
||||
search_type_ = type;
|
||||
|
||||
Settings s;
|
||||
s.beginGroup(service_->settings_group());
|
||||
s.setValue("type", static_cast<int>(search_type_));
|
||||
s.endGroup();
|
||||
|
||||
TextEdited(ui_->search->text());
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::AddArtists() {
|
||||
|
||||
MimeData *mimedata = SelectedMimeData();
|
||||
if (!mimedata) return;
|
||||
if (const StreamSongMimeData *streaming_song_data = qobject_cast<const StreamSongMimeData*>(mimedata)) {
|
||||
emit AddArtistsSignal(streaming_song_data->songs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::AddAlbums() {
|
||||
|
||||
MimeData *mimedata = SelectedMimeData();
|
||||
if (!mimedata) return;
|
||||
if (const StreamSongMimeData *streaming_song_data = qobject_cast<const StreamSongMimeData*>(mimedata)) {
|
||||
emit AddAlbumsSignal(streaming_song_data->songs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::AddSongs() {
|
||||
|
||||
MimeData *mimedata = SelectedMimeData();
|
||||
if (!mimedata) return;
|
||||
if (const StreamSongMimeData *streaming_song_data = qobject_cast<const StreamSongMimeData*>(mimedata)) {
|
||||
emit AddSongsSignal(streaming_song_data->songs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString StreamingSearchView::PixmapCacheKey(const StreamingSearchView::Result &result) const {
|
||||
|
||||
if (result.metadata_.art_automatic_is_valid()) {
|
||||
return Song::TextForSource(service_->source()) + QLatin1Char('/') + result.metadata_.art_automatic().toString();
|
||||
}
|
||||
if (!result.metadata_.effective_albumartist().isEmpty() && !result.metadata_.album().isEmpty()) {
|
||||
return Song::TextForSource(service_->source()) + QLatin1Char('/') + result.metadata_.effective_albumartist() + QLatin1Char('/') + result.metadata_.album();
|
||||
}
|
||||
|
||||
return Song::TextForSource(service_->source()) + QLatin1Char('/') + result.metadata_.url().toString();
|
||||
|
||||
}
|
||||
|
||||
bool StreamingSearchView::FindCachedPixmap(const StreamingSearchView::Result &result, QPixmap *pixmap) const {
|
||||
return QPixmapCache::find(result.pixmap_cache_key_, pixmap);
|
||||
}
|
||||
|
||||
void StreamingSearchView::LazyLoadAlbumCover(const QModelIndex &proxy_index) {
|
||||
|
||||
if (!proxy_index.isValid() || proxy_index.model() != front_proxy_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QModelIndex source_index = front_proxy_->mapToSource(proxy_index);
|
||||
if (!source_index.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Already loading art for this item?
|
||||
if (source_index.data(StreamingSearchModel::Role_LazyLoadingArt).isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Should we even load art at all?
|
||||
if (!use_pretty_covers_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Is this an album?
|
||||
const CollectionModel::GroupBy container_type = static_cast<CollectionModel::GroupBy>(proxy_index.data(CollectionModel::Role_ContainerType).toInt());
|
||||
if (!CollectionModel::IsAlbumGroupBy(container_type)) return;
|
||||
|
||||
// Mark the item as loading art
|
||||
|
||||
QStandardItem *item_album = front_model_->itemFromIndex(source_index);
|
||||
if (!item_album) {
|
||||
return;
|
||||
}
|
||||
item_album->setData(true, StreamingSearchModel::Role_LazyLoadingArt);
|
||||
|
||||
// Walk down the item's children until we find a track
|
||||
QStandardItem *item_song = item_album;
|
||||
while (item_song->rowCount() > 0) {
|
||||
item_song = item_song->child(0);
|
||||
}
|
||||
|
||||
// Get the track's Result
|
||||
const StreamingSearchView::Result result = item_song->data(StreamingSearchModel::Role_Result).value<StreamingSearchView::Result>();
|
||||
|
||||
QPixmap cached_pixmap;
|
||||
if (QPixmapCache::find(result.pixmap_cache_key_, &cached_pixmap)) {
|
||||
item_album->setData(cached_pixmap, Qt::DecorationRole);
|
||||
}
|
||||
else {
|
||||
AlbumCoverLoaderOptions cover_loader_options(AlbumCoverLoaderOptions::Option::ScaledImage | AlbumCoverLoaderOptions::Option::PadScaledImage);
|
||||
cover_loader_options.desired_scaled_size = QSize(kArtHeight, kArtHeight);
|
||||
quint64 loader_id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options, result.metadata_);
|
||||
cover_loader_tasks_[loader_id] = qMakePair(source_index, result.pixmap_cache_key_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StreamingSearchView::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &albumcover_result) {
|
||||
|
||||
if (!cover_loader_tasks_.contains(id)) return;
|
||||
|
||||
QPair<QModelIndex, QString> cover_loader_task = cover_loader_tasks_.take(id);
|
||||
QModelIndex idx = cover_loader_task.first;
|
||||
QString key = cover_loader_task.second;
|
||||
|
||||
if (albumcover_result.success && !albumcover_result.image_scaled.isNull()) {
|
||||
QPixmap pixmap = QPixmap::fromImage(albumcover_result.image_scaled);
|
||||
if (!pixmap.isNull()) {
|
||||
QPixmapCache::insert(key, pixmap);
|
||||
}
|
||||
if (idx.isValid()) {
|
||||
QStandardItem *item = front_model_->itemFromIndex(idx);
|
||||
if (item) {
|
||||
item->setData(albumcover_result.image_scaled, Qt::DecorationRole);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
221
src/streaming/streamingsearchview.h
Normal file
221
src/streaming/streamingsearchview.h
Normal file
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine (GlobalSearch)
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2021, 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 STREAMINGSEARCHVIEW_H
|
||||
#define STREAMINGSEARCHVIEW_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QSet>
|
||||
#include <QPair>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QImage>
|
||||
#include <QPixmap>
|
||||
#include <QMetaType>
|
||||
|
||||
#include "core/scoped_ptr.h"
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "covermanager/albumcoverloaderresult.h"
|
||||
|
||||
class QSortFilterProxyModel;
|
||||
class QMimeData;
|
||||
class QTimer;
|
||||
class QMenu;
|
||||
class QAction;
|
||||
class QActionGroup;
|
||||
class QEvent;
|
||||
class QKeyEvent;
|
||||
class QShowEvent;
|
||||
class QContextMenuEvent;
|
||||
class QTimerEvent;
|
||||
|
||||
class Application;
|
||||
class MimeData;
|
||||
class GroupByDialog;
|
||||
class StreamingService;
|
||||
class StreamingSearchModel;
|
||||
class Ui_StreamingSearchView;
|
||||
|
||||
class StreamingSearchView : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit StreamingSearchView(QWidget *parent = nullptr);
|
||||
~StreamingSearchView() override;
|
||||
|
||||
enum class SearchType {
|
||||
Artists = 1,
|
||||
Albums = 2,
|
||||
Songs = 3
|
||||
};
|
||||
struct Result {
|
||||
Song metadata_;
|
||||
QString pixmap_cache_key_;
|
||||
};
|
||||
using ResultList = QList<Result>;
|
||||
|
||||
void Init(Application *app, SharedPtr<StreamingService> service);
|
||||
|
||||
bool SearchFieldHasFocus() const;
|
||||
void FocusSearchField();
|
||||
|
||||
void LazyLoadAlbumCover(const QModelIndex &proxy_index);
|
||||
|
||||
protected:
|
||||
struct PendingState {
|
||||
PendingState() : orig_id_(-1) {}
|
||||
PendingState(int orig_id, const QStringList &tokens) : orig_id_(orig_id), tokens_(tokens) {}
|
||||
int orig_id_;
|
||||
QStringList tokens_;
|
||||
|
||||
bool operator<(const PendingState &b) const {
|
||||
return orig_id_ < b.orig_id_;
|
||||
}
|
||||
|
||||
bool operator==(const PendingState &b) const {
|
||||
return orig_id_ == b.orig_id_;
|
||||
}
|
||||
};
|
||||
|
||||
void showEvent(QShowEvent *e) override;
|
||||
bool eventFilter(QObject *object, QEvent *e) override;
|
||||
void timerEvent(QTimerEvent *e) override;
|
||||
|
||||
// These functions treat queries in the same way as CollectionQuery.
|
||||
// They're useful for figuring out whether you got a result because it matched in the song title or the artist/album name.
|
||||
static QStringList TokenizeQuery(const QString &query);
|
||||
static bool Matches(const QStringList &tokens, const QString &string);
|
||||
|
||||
private:
|
||||
struct DelayedSearch {
|
||||
int id_;
|
||||
QString query_;
|
||||
SearchType type_;
|
||||
};
|
||||
|
||||
bool SearchKeyEvent(QKeyEvent *e);
|
||||
bool ResultsContextMenuEvent(QContextMenuEvent *e);
|
||||
|
||||
MimeData *SelectedMimeData();
|
||||
|
||||
void SetSearchType(const SearchType type);
|
||||
|
||||
int SearchAsync(const QString &query, SearchType type);
|
||||
void SearchAsync(const int id, const QString &query, const SearchType type);
|
||||
void SearchError(const int id, const QString &error);
|
||||
void CancelSearch(const int id);
|
||||
|
||||
QString PixmapCacheKey(const Result &result) const;
|
||||
bool FindCachedPixmap(const Result &result, QPixmap *pixmap) const;
|
||||
int LoadAlbumCoverAsync(const Result &result);
|
||||
|
||||
signals:
|
||||
void AddToPlaylist(QMimeData*);
|
||||
void AddArtistsSignal(const SongList &songs);
|
||||
void AddAlbumsSignal(const SongList &songs);
|
||||
void AddSongsSignal(const SongList &songs);
|
||||
|
||||
private slots:
|
||||
void SwapModels();
|
||||
void TextEdited(const QString &text);
|
||||
void StartSearch(const QString &query);
|
||||
void SearchDone(const int service_id, const SongMap &songs, const QString &error);
|
||||
|
||||
void UpdateStatus(const int service_id, const QString &text);
|
||||
void ProgressSetMaximum(const int service_id, const int max);
|
||||
void UpdateProgress(const int service_id, const int progress);
|
||||
void AddResults(const int service_id, const StreamingSearchView::ResultList &results);
|
||||
|
||||
void FocusOnFilter(QKeyEvent *e);
|
||||
|
||||
void AddSelectedToPlaylist();
|
||||
void LoadSelected();
|
||||
void OpenSelectedInNewPlaylist();
|
||||
void AddSelectedToPlaylistEnqueue();
|
||||
void AddArtists();
|
||||
void AddAlbums();
|
||||
void AddSongs();
|
||||
void SearchForThis();
|
||||
void OpenSettingsDialog();
|
||||
|
||||
void SearchArtistsClicked(const bool);
|
||||
void SearchAlbumsClicked(const bool);
|
||||
void SearchSongsClicked(const bool);
|
||||
void GroupByClicked(QAction *action);
|
||||
void SetGroupBy(const CollectionModel::Grouping g);
|
||||
|
||||
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &albumcover_result);
|
||||
|
||||
public slots:
|
||||
void ReloadSettings();
|
||||
|
||||
private:
|
||||
static const int kSwapModelsTimeoutMsec;
|
||||
static const int kDelayedSearchTimeoutMs;
|
||||
static const int kArtHeight;
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
SharedPtr<StreamingService> service_;
|
||||
Ui_StreamingSearchView *ui_;
|
||||
ScopedPtr<GroupByDialog> group_by_dialog_;
|
||||
|
||||
QMenu *context_menu_;
|
||||
QList<QAction*> context_actions_;
|
||||
QActionGroup *group_by_actions_;
|
||||
|
||||
// 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.
|
||||
StreamingSearchModel *front_model_;
|
||||
StreamingSearchModel *back_model_;
|
||||
StreamingSearchModel *current_model_;
|
||||
|
||||
QSortFilterProxyModel *front_proxy_;
|
||||
QSortFilterProxyModel *back_proxy_;
|
||||
QSortFilterProxyModel *current_proxy_;
|
||||
|
||||
QTimer *swap_models_timer_;
|
||||
|
||||
bool use_pretty_covers_;
|
||||
SearchType search_type_;
|
||||
bool search_error_;
|
||||
int last_search_id_;
|
||||
int searches_next_id_;
|
||||
|
||||
QMap<int, DelayedSearch> delayed_searches_;
|
||||
QMap<int, PendingState> pending_searches_;
|
||||
|
||||
QMap<quint64, QPair<QModelIndex, QString>> cover_loader_tasks_;
|
||||
};
|
||||
Q_DECLARE_METATYPE(StreamingSearchView::Result)
|
||||
Q_DECLARE_METATYPE(StreamingSearchView::ResultList)
|
||||
|
||||
#endif // STREAMINGSEARCHVIEW_H
|
||||
312
src/streaming/streamingsearchview.ui
Normal file
312
src/streaming/streamingsearchview.ui
Normal file
@@ -0,0 +1,312 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>StreamingSearchView</class>
|
||||
<widget class="QWidget" name="StreamingSearchView">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>660</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Streaming Search View</string>
|
||||
</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/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="settings">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>MenuPopupToolButton</string>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::MenuButtonPopup</enum>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_searchby" native="true">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radiobutton_search_artists">
|
||||
<property name="text">
|
||||
<string>artists</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radiobutton_search_albums">
|
||||
<property name="text">
|
||||
<string>albums</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radiobutton_search_songs">
|
||||
<property name="text">
|
||||
<string>songs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_searchby">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="layout_progress">
|
||||
<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>518</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>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</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>widgets/qsearchfield.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>AutoExpandingTreeView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>widgets/autoexpandingtreeview.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
36
src/streaming/streamingservice.cpp
Normal file
36
src/streaming/streamingservice.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018-2021, 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 <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include "streamingservice.h"
|
||||
#include "core/song.h"
|
||||
#include "settings/settingsdialog.h"
|
||||
|
||||
class Application;
|
||||
|
||||
StreamingService::StreamingService(const Song::Source source, const QString &name, const QString &url_scheme, const QString &settings_group, const SettingsDialog::Page settings_page, Application *app, QObject *parent)
|
||||
: QObject(parent),
|
||||
app_(app),
|
||||
source_(source),
|
||||
name_(name),
|
||||
url_scheme_(url_scheme),
|
||||
settings_group_(settings_group),
|
||||
settings_page_(settings_page) {}
|
||||
152
src/streaming/streamingservice.h
Normal file
152
src/streaming/streamingservice.h
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018-2021, 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 STREAMINGSERVICE_H
|
||||
#define STREAMINGSERVICE_H
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QMetaType>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QIcon>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
#include "settings/settingsdialog.h"
|
||||
#include "streamingsearchview.h"
|
||||
|
||||
class Application;
|
||||
class CollectionBackend;
|
||||
class CollectionModel;
|
||||
class CollectionFilter;
|
||||
|
||||
class StreamingService : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit StreamingService(const Song::Source source, const QString &name, const QString &url_scheme, const QString &settings_group, const SettingsDialog::Page settings_page, Application *app, QObject *parent = nullptr);
|
||||
|
||||
~StreamingService() override {}
|
||||
virtual void Exit() {}
|
||||
|
||||
virtual Song::Source source() const { return source_; }
|
||||
virtual QString name() const { return name_; }
|
||||
virtual QString url_scheme() const { return url_scheme_; }
|
||||
virtual QString settings_group() const { return settings_group_; }
|
||||
virtual SettingsDialog::Page settings_page() const { return settings_page_; }
|
||||
virtual bool has_initial_load_settings() const { return false; }
|
||||
virtual void InitialLoadSettings() {}
|
||||
virtual void ReloadSettings() {}
|
||||
virtual QIcon Icon() const { return Song::IconForSource(source_); }
|
||||
virtual bool oauth() const { return false; }
|
||||
virtual bool authenticated() const { return false; }
|
||||
virtual int Search(const QString &query, StreamingSearchView::SearchType type) { Q_UNUSED(query); Q_UNUSED(type); return 0; }
|
||||
virtual void CancelSearch() {}
|
||||
|
||||
virtual SharedPtr<CollectionBackend> artists_collection_backend() { return nullptr; }
|
||||
virtual SharedPtr<CollectionBackend> albums_collection_backend() { return nullptr; }
|
||||
virtual SharedPtr<CollectionBackend> songs_collection_backend() { return nullptr; }
|
||||
|
||||
virtual CollectionModel *artists_collection_model() { return nullptr; }
|
||||
virtual CollectionModel *albums_collection_model() { return nullptr; }
|
||||
virtual CollectionModel *songs_collection_model() { return nullptr; }
|
||||
|
||||
virtual CollectionFilter *artists_collection_filter_model() { return nullptr; }
|
||||
virtual CollectionFilter *albums_collection_filter_model() { return nullptr; }
|
||||
virtual CollectionFilter *songs_collection_filter_model() { return nullptr; }
|
||||
|
||||
public slots:
|
||||
virtual void ShowConfig() {}
|
||||
virtual void GetArtists() {}
|
||||
virtual void GetAlbums() {}
|
||||
virtual void GetSongs() {}
|
||||
virtual void ResetArtistsRequest() {}
|
||||
virtual void ResetAlbumsRequest() {}
|
||||
virtual void ResetSongsRequest() {}
|
||||
|
||||
signals:
|
||||
void ExitFinished();
|
||||
void RequestLogin();
|
||||
void RequestLogout();
|
||||
void LoginWithCredentials(const QString &api_token, const QString &username, const QString &password);
|
||||
void LoginSuccess();
|
||||
void LoginFailure(const QString &failure_reason);
|
||||
void LoginComplete(const bool success, const QString &error = QString());
|
||||
|
||||
void TestSuccess();
|
||||
void TestFailure(const QString &failure_reason);
|
||||
void TestComplete(const bool success, const QString &error = QString());
|
||||
|
||||
void Error(const QString &error);
|
||||
void Results(const SongMap &songs, const QString &error);
|
||||
void UpdateStatus(const QString &text);
|
||||
void ProgressSetMaximum(const int max);
|
||||
void UpdateProgress(const int max);
|
||||
|
||||
void ArtistsResults(const SongMap &songs, const QString &error);
|
||||
void ArtistsUpdateStatus(const QString &text);
|
||||
void ArtistsProgressSetMaximum(const int max);
|
||||
void ArtistsUpdateProgress(const int max);
|
||||
|
||||
void AlbumsResults(const SongMap &songs, const QString &error);
|
||||
void AlbumsUpdateStatus(const QString &text);
|
||||
void AlbumsProgressSetMaximum(const int max);
|
||||
void AlbumsUpdateProgress(const int max);
|
||||
|
||||
void SongsResults(const SongMap &songs, const QString &error);
|
||||
void SongsUpdateStatus(const QString &text);
|
||||
void SongsProgressSetMaximum(const int max);
|
||||
void SongsUpdateProgress(const int max);
|
||||
|
||||
void SearchResults(const int id, const SongMap &songs, const QString &error);
|
||||
void SearchUpdateStatus(const int id, const QString &text);
|
||||
void SearchProgressSetMaximum(const int id, const int max);
|
||||
void SearchUpdateProgress(const int id, const int max);
|
||||
|
||||
void AddArtists(const SongList &songs);
|
||||
void AddAlbums(const SongList &songs);
|
||||
void AddSongs(const SongList &songs);
|
||||
|
||||
void RemoveArtists(const SongList &songs);
|
||||
void RemoveAlbums(const SongList &songs);
|
||||
void RemoveSongsByList(const SongList &songs);
|
||||
void RemoveSongsByMap(const SongMap &songs);
|
||||
|
||||
void StreamURLFailure(const uint id, const QUrl &media_url, const QString &error);
|
||||
void StreamURLSuccess(const uint id, const QUrl &media_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration);
|
||||
|
||||
protected:
|
||||
Application *app_;
|
||||
|
||||
private:
|
||||
Song::Source source_;
|
||||
QString name_;
|
||||
QString url_scheme_;
|
||||
QString settings_group_;
|
||||
SettingsDialog::Page settings_page_;
|
||||
};
|
||||
|
||||
using StreamingServicePtr = SharedPtr<StreamingService>;
|
||||
|
||||
Q_DECLARE_METATYPE(StreamingService*)
|
||||
Q_DECLARE_METATYPE(StreamingServicePtr)
|
||||
|
||||
#endif // STREAMINGSERVICE_H
|
||||
97
src/streaming/streamingservices.cpp
Normal file
97
src/streaming/streamingservices.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2021, 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 <QObject>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "streamingservices.h"
|
||||
#include "streamingservice.h"
|
||||
|
||||
StreamingServices::StreamingServices(QObject *parent) : QObject(parent) {}
|
||||
|
||||
StreamingServices::~StreamingServices() {
|
||||
|
||||
while (!services_.isEmpty()) {
|
||||
StreamingServicePtr service = services_.first();
|
||||
RemoveService(service);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StreamingServices::AddService(StreamingServicePtr service) {
|
||||
|
||||
services_.insert(service->source(), service);
|
||||
if (service->has_initial_load_settings()) service->InitialLoadSettings();
|
||||
else service->ReloadSettings();
|
||||
|
||||
qLog(Debug) << "Added streaming service" << service->name();
|
||||
|
||||
}
|
||||
|
||||
void StreamingServices::RemoveService(StreamingServicePtr service) {
|
||||
|
||||
if (!services_.contains(service->source())) return;
|
||||
services_.remove(service->source());
|
||||
QObject::disconnect(&*service, nullptr, this, nullptr);
|
||||
|
||||
qLog(Debug) << "Removed streaming service" << service->name();
|
||||
|
||||
}
|
||||
|
||||
StreamingServicePtr StreamingServices::ServiceBySource(const Song::Source source) const {
|
||||
|
||||
if (services_.contains(source)) return services_.value(source);
|
||||
return nullptr;
|
||||
|
||||
}
|
||||
|
||||
void StreamingServices::ReloadSettings() {
|
||||
|
||||
const QList<StreamingServicePtr> services = services_.values();
|
||||
for (StreamingServicePtr service : services) {
|
||||
service->ReloadSettings();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StreamingServices::Exit() {
|
||||
|
||||
const QList<StreamingServicePtr> services = services_.values();
|
||||
for (StreamingServicePtr service : services) {
|
||||
wait_for_exit_ << &*service;
|
||||
QObject::connect(&*service, &StreamingService::ExitFinished, this, &StreamingServices::ExitReceived);
|
||||
service->Exit();
|
||||
}
|
||||
if (wait_for_exit_.isEmpty()) emit ExitFinished();
|
||||
|
||||
}
|
||||
|
||||
void StreamingServices::ExitReceived() {
|
||||
|
||||
StreamingService *service = qobject_cast<StreamingService*>(sender());
|
||||
wait_for_exit_.removeAll(service);
|
||||
if (wait_for_exit_.isEmpty()) emit ExitFinished();
|
||||
|
||||
}
|
||||
69
src/streaming/streamingservices.h
Normal file
69
src/streaming/streamingservices.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2021, 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 STREAMINGSERVICES_H
|
||||
#define STREAMINGSERVICES_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
#include <core/shared_ptr.h>
|
||||
#include "core/song.h"
|
||||
|
||||
class StreamingService;
|
||||
|
||||
class StreamingServices : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit StreamingServices(QObject *parent = nullptr);
|
||||
~StreamingServices() override;
|
||||
|
||||
SharedPtr<StreamingService> ServiceBySource(const Song::Source source) const;
|
||||
|
||||
template <typename T>
|
||||
SharedPtr<T> Service() {
|
||||
return std::static_pointer_cast<T>(ServiceBySource(T::kSource));
|
||||
}
|
||||
|
||||
void AddService(SharedPtr<StreamingService> service);
|
||||
void RemoveService(SharedPtr<StreamingService> service);
|
||||
void ReloadSettings();
|
||||
void Exit();
|
||||
|
||||
signals:
|
||||
void ExitFinished();
|
||||
|
||||
private slots:
|
||||
void ExitReceived();
|
||||
|
||||
private:
|
||||
QMap<Song::Source, SharedPtr<StreamingService>> services_;
|
||||
QList<StreamingService*> wait_for_exit_;
|
||||
};
|
||||
|
||||
#endif // STREAMINGSERVICES_H
|
||||
139
src/streaming/streamingsongsview.cpp
Normal file
139
src/streaming/streamingsongsview.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QWidget>
|
||||
#include <QString>
|
||||
#include <QStackedWidget>
|
||||
#include <QContextMenuEvent>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QLabel>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QAction>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "collection/collectionfilter.h"
|
||||
#include "streamingservice.h"
|
||||
#include "streamingsongsview.h"
|
||||
#include "streamingcollectionview.h"
|
||||
#include "ui_streamingcollectionviewcontainer.h"
|
||||
|
||||
StreamingSongsView::StreamingSongsView(Application *app, StreamingServicePtr service, const QString &settings_group, const SettingsDialog::Page settings_page, QWidget *parent)
|
||||
: QWidget(parent),
|
||||
app_(app),
|
||||
service_(service),
|
||||
settings_group_(settings_group),
|
||||
settings_page_(settings_page),
|
||||
ui_(new Ui_StreamingCollectionViewContainer) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
|
||||
ui_->stacked->setCurrentWidget(ui_->streamingcollection_page);
|
||||
ui_->view->Init(app_, service_->songs_collection_backend(), service_->songs_collection_model(), false);
|
||||
ui_->view->setModel(service_->songs_collection_filter_model());
|
||||
ui_->view->SetFilter(ui_->filter_widget);
|
||||
ui_->filter_widget->SetSettingsGroup(settings_group);
|
||||
ui_->filter_widget->Init(service_->songs_collection_model(), service_->songs_collection_filter_model());
|
||||
|
||||
QAction *action_configure = new QAction(IconLoader::Load(QStringLiteral("configure")), tr("Configure %1...").arg(Song::DescriptionForSource(service_->source())), this);
|
||||
QObject::connect(action_configure, &QAction::triggered, this, &StreamingSongsView::OpenSettingsDialog);
|
||||
ui_->filter_widget->AddMenuAction(action_configure);
|
||||
|
||||
QObject::connect(ui_->view, &StreamingCollectionView::GetSongs, this, &StreamingSongsView::GetSongs);
|
||||
QObject::connect(ui_->view, &StreamingCollectionView::RemoveSongs, &*service_, &StreamingService::RemoveSongsByList);
|
||||
|
||||
QObject::connect(ui_->refresh, &QPushButton::clicked, this, &StreamingSongsView::GetSongs);
|
||||
QObject::connect(ui_->close, &QPushButton::clicked, this, &StreamingSongsView::AbortGetSongs);
|
||||
QObject::connect(ui_->abort, &QPushButton::clicked, this, &StreamingSongsView::AbortGetSongs);
|
||||
QObject::connect(&*service_, &StreamingService::SongsResults, this, &StreamingSongsView::SongsFinished);
|
||||
QObject::connect(&*service_, &StreamingService::SongsUpdateStatus, ui_->status, &QLabel::setText);
|
||||
QObject::connect(&*service_, &StreamingService::SongsProgressSetMaximum, ui_->progressbar, &QProgressBar::setMaximum);
|
||||
QObject::connect(&*service_, &StreamingService::SongsUpdateProgress, ui_->progressbar, &QProgressBar::setValue);
|
||||
|
||||
QObject::connect(service_->songs_collection_model(), &CollectionModel::TotalArtistCountUpdated, ui_->view, &StreamingCollectionView::TotalArtistCountUpdated);
|
||||
QObject::connect(service_->songs_collection_model(), &CollectionModel::TotalAlbumCountUpdated, ui_->view, &StreamingCollectionView::TotalAlbumCountUpdated);
|
||||
QObject::connect(service_->songs_collection_model(), &CollectionModel::TotalSongCountUpdated, ui_->view, &StreamingCollectionView::TotalSongCountUpdated);
|
||||
QObject::connect(service_->songs_collection_model(), &CollectionModel::modelAboutToBeReset, ui_->view, &StreamingCollectionView::SaveFocus);
|
||||
QObject::connect(service_->songs_collection_model(), &CollectionModel::modelReset, ui_->view, &StreamingCollectionView::RestoreFocus);
|
||||
|
||||
ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
StreamingSongsView::~StreamingSongsView() { delete ui_; }
|
||||
|
||||
void StreamingSongsView::ReloadSettings() {
|
||||
|
||||
ui_->filter_widget->ReloadSettings();
|
||||
ui_->view->ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
void StreamingSongsView::OpenSettingsDialog() {
|
||||
app_->OpenSettingsDialogAtPage(service_->settings_page());
|
||||
}
|
||||
|
||||
|
||||
void StreamingSongsView::GetSongs() {
|
||||
|
||||
if (!service_->authenticated() && service_->oauth()) {
|
||||
service_->ShowConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
ui_->status->clear();
|
||||
ui_->progressbar->show();
|
||||
ui_->abort->show();
|
||||
ui_->close->hide();
|
||||
ui_->stacked->setCurrentWidget(ui_->help_page);
|
||||
service_->GetSongs();
|
||||
|
||||
}
|
||||
|
||||
void StreamingSongsView::AbortGetSongs() {
|
||||
|
||||
service_->ResetSongsRequest();
|
||||
ui_->progressbar->setValue(0);
|
||||
ui_->status->clear();
|
||||
ui_->stacked->setCurrentWidget(ui_->streamingcollection_page);
|
||||
|
||||
}
|
||||
|
||||
void StreamingSongsView::SongsFinished(const SongMap &songs, const QString &error) {
|
||||
|
||||
if (songs.isEmpty() && !error.isEmpty()) {
|
||||
ui_->status->setText(error);
|
||||
ui_->progressbar->setValue(0);
|
||||
ui_->progressbar->hide();
|
||||
ui_->abort->hide();
|
||||
ui_->close->show();
|
||||
}
|
||||
else {
|
||||
ui_->stacked->setCurrentWidget(ui_->streamingcollection_page);
|
||||
ui_->status->clear();
|
||||
service_->songs_collection_backend()->UpdateSongsBySongIDAsync(songs);
|
||||
}
|
||||
|
||||
}
|
||||
69
src/streaming/streamingsongsview.h
Normal file
69
src/streaming/streamingsongsview.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018-2021, 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 STREAMINGSONGSVIEW_H
|
||||
#define STREAMINGSONGSVIEW_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
#include "settings/settingsdialog.h"
|
||||
#include "ui_streamingcollectionviewcontainer.h"
|
||||
|
||||
class QContextMenuEvent;
|
||||
|
||||
class Application;
|
||||
class StreamingService;
|
||||
class StreamingCollectionView;
|
||||
|
||||
class StreamingSongsView : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit StreamingSongsView(Application *app, SharedPtr<StreamingService> service, const QString &settings_group, const SettingsDialog::Page settings_page, QWidget *parent = nullptr);
|
||||
~StreamingSongsView() override;
|
||||
|
||||
void ReloadSettings();
|
||||
|
||||
StreamingCollectionView *view() const { return ui_->view; }
|
||||
|
||||
bool SearchFieldHasFocus() const { return ui_->filter_widget->SearchFieldHasFocus(); }
|
||||
void FocusSearchField() { ui_->filter_widget->FocusSearchField(); }
|
||||
|
||||
private slots:
|
||||
void OpenSettingsDialog();
|
||||
void GetSongs();
|
||||
void AbortGetSongs();
|
||||
void SongsFinished(const SongMap &songs, const QString &error);
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
SharedPtr<StreamingService> service_;
|
||||
QString settings_group_;
|
||||
SettingsDialog::Page settings_page_;
|
||||
Ui_StreamingCollectionViewContainer *ui_;
|
||||
};
|
||||
|
||||
#endif // STREAMINGSONGSVIEW_H
|
||||
364
src/streaming/streamingtabsview.cpp
Normal file
364
src/streaming/streamingtabsview.cpp
Normal file
@@ -0,0 +1,364 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QWidget>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QLabel>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QTabWidget>
|
||||
#include <QStackedWidget>
|
||||
#include <QContextMenuEvent>
|
||||
#include <QAction>
|
||||
#include <QSettings>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/settings.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "collection/collectionfilter.h"
|
||||
#include "collection/collectionfilterwidget.h"
|
||||
#include "streamingservice.h"
|
||||
#include "streamingtabsview.h"
|
||||
#include "streamingcollectionview.h"
|
||||
#include "streamingcollectionviewcontainer.h"
|
||||
#include "ui_streamingtabsview.h"
|
||||
|
||||
StreamingTabsView::StreamingTabsView(Application *app, StreamingServicePtr service, const QString &settings_group, const SettingsDialog::Page settings_page, QWidget *parent)
|
||||
: QWidget(parent),
|
||||
app_(app),
|
||||
service_(service),
|
||||
settings_group_(settings_group),
|
||||
settings_page_(settings_page),
|
||||
ui_(new Ui_StreamingTabsView) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
|
||||
ui_->search_view->Init(app, service);
|
||||
QObject::connect(ui_->search_view, &StreamingSearchView::AddArtistsSignal, &*service_, &StreamingService::AddArtists);
|
||||
QObject::connect(ui_->search_view, &StreamingSearchView::AddAlbumsSignal, &*service_, &StreamingService::AddAlbums);
|
||||
QObject::connect(ui_->search_view, &StreamingSearchView::AddSongsSignal, &*service_, &StreamingService::AddSongs);
|
||||
|
||||
QAction *action_configure = new QAction(IconLoader::Load(QStringLiteral("configure")), tr("Configure %1...").arg(Song::TextForSource(service_->source())), this);
|
||||
QObject::connect(action_configure, &QAction::triggered, this, &StreamingTabsView::OpenSettingsDialog);
|
||||
|
||||
if (service_->artists_collection_model()) {
|
||||
ui_->artists_collection->stacked()->setCurrentWidget(ui_->artists_collection->streamingcollection_page());
|
||||
ui_->artists_collection->view()->Init(app_, service_->artists_collection_backend(), service_->artists_collection_model(), true);
|
||||
ui_->artists_collection->view()->setModel(service_->artists_collection_filter_model());
|
||||
ui_->artists_collection->view()->SetFilter(ui_->artists_collection->filter_widget());
|
||||
ui_->artists_collection->filter_widget()->SetSettingsGroup(settings_group);
|
||||
ui_->artists_collection->filter_widget()->SetSettingsPrefix(QStringLiteral("artists"));
|
||||
ui_->artists_collection->filter_widget()->Init(service_->artists_collection_model(), service_->artists_collection_filter_model());
|
||||
ui_->artists_collection->filter_widget()->AddMenuAction(action_configure);
|
||||
|
||||
QObject::connect(ui_->artists_collection->view(), &StreamingCollectionView::GetSongs, this, &StreamingTabsView::GetArtists);
|
||||
QObject::connect(ui_->artists_collection->view(), &StreamingCollectionView::RemoveSongs, &*service_, &StreamingService::RemoveArtists);
|
||||
|
||||
QObject::connect(ui_->artists_collection->button_refresh(), &QPushButton::clicked, this, &StreamingTabsView::GetArtists);
|
||||
QObject::connect(ui_->artists_collection->button_close(), &QPushButton::clicked, this, &StreamingTabsView::AbortGetArtists);
|
||||
QObject::connect(ui_->artists_collection->button_abort(), &QPushButton::clicked, this, &StreamingTabsView::AbortGetArtists);
|
||||
QObject::connect(&*service_, &StreamingService::ArtistsResults, this, &StreamingTabsView::ArtistsFinished);
|
||||
QObject::connect(&*service_, &StreamingService::ArtistsUpdateStatus, ui_->artists_collection->status(), &QLabel::setText);
|
||||
QObject::connect(&*service_, &StreamingService::ArtistsProgressSetMaximum, ui_->artists_collection->progressbar(), &QProgressBar::setMaximum);
|
||||
QObject::connect(&*service_, &StreamingService::ArtistsUpdateProgress, ui_->artists_collection->progressbar(), &QProgressBar::setValue);
|
||||
|
||||
QObject::connect(service_->artists_collection_model(), &CollectionModel::TotalArtistCountUpdated, ui_->artists_collection->view(), &StreamingCollectionView::TotalArtistCountUpdated);
|
||||
QObject::connect(service_->artists_collection_model(), &CollectionModel::TotalAlbumCountUpdated, ui_->artists_collection->view(), &StreamingCollectionView::TotalAlbumCountUpdated);
|
||||
QObject::connect(service_->artists_collection_model(), &CollectionModel::TotalSongCountUpdated, ui_->artists_collection->view(), &StreamingCollectionView::TotalSongCountUpdated);
|
||||
QObject::connect(service_->artists_collection_model(), &CollectionModel::modelAboutToBeReset, ui_->artists_collection->view(), &StreamingCollectionView::SaveFocus);
|
||||
QObject::connect(service_->artists_collection_model(), &CollectionModel::modelReset, ui_->artists_collection->view(), &StreamingCollectionView::RestoreFocus);
|
||||
|
||||
}
|
||||
else {
|
||||
ui_->tabs->removeTab(ui_->tabs->indexOf(ui_->artists));
|
||||
}
|
||||
|
||||
if (service_->albums_collection_model()) {
|
||||
ui_->albums_collection->stacked()->setCurrentWidget(ui_->albums_collection->streamingcollection_page());
|
||||
ui_->albums_collection->view()->Init(app_, service_->albums_collection_backend(), service_->albums_collection_model(), true);
|
||||
ui_->albums_collection->view()->setModel(service_->albums_collection_filter_model());
|
||||
ui_->albums_collection->view()->SetFilter(ui_->albums_collection->filter_widget());
|
||||
ui_->albums_collection->filter_widget()->SetSettingsGroup(settings_group);
|
||||
ui_->albums_collection->filter_widget()->SetSettingsPrefix(QStringLiteral("albums"));
|
||||
ui_->albums_collection->filter_widget()->Init(service_->albums_collection_model(), service_->albums_collection_filter_model());
|
||||
ui_->albums_collection->filter_widget()->AddMenuAction(action_configure);
|
||||
|
||||
QObject::connect(ui_->albums_collection->view(), &StreamingCollectionView::GetSongs, this, &StreamingTabsView::GetAlbums);
|
||||
QObject::connect(ui_->albums_collection->view(), &StreamingCollectionView::RemoveSongs, &*service_, &StreamingService::RemoveAlbums);
|
||||
|
||||
QObject::connect(ui_->albums_collection->button_refresh(), &QPushButton::clicked, this, &StreamingTabsView::GetAlbums);
|
||||
QObject::connect(ui_->albums_collection->button_close(), &QPushButton::clicked, this, &StreamingTabsView::AbortGetAlbums);
|
||||
QObject::connect(ui_->albums_collection->button_abort(), &QPushButton::clicked, this, &StreamingTabsView::AbortGetAlbums);
|
||||
QObject::connect(&*service_, &StreamingService::AlbumsResults, this, &StreamingTabsView::AlbumsFinished);
|
||||
QObject::connect(&*service_, &StreamingService::AlbumsUpdateStatus, ui_->albums_collection->status(), &QLabel::setText);
|
||||
QObject::connect(&*service_, &StreamingService::AlbumsProgressSetMaximum, ui_->albums_collection->progressbar(), &QProgressBar::setMaximum);
|
||||
QObject::connect(&*service_, &StreamingService::AlbumsUpdateProgress, ui_->albums_collection->progressbar(), &QProgressBar::setValue);
|
||||
|
||||
QObject::connect(service_->albums_collection_model(), &CollectionModel::TotalArtistCountUpdated, ui_->albums_collection->view(), &StreamingCollectionView::TotalArtistCountUpdated);
|
||||
QObject::connect(service_->albums_collection_model(), &CollectionModel::TotalAlbumCountUpdated, ui_->albums_collection->view(), &StreamingCollectionView::TotalAlbumCountUpdated);
|
||||
QObject::connect(service_->albums_collection_model(), &CollectionModel::TotalSongCountUpdated, ui_->albums_collection->view(), &StreamingCollectionView::TotalSongCountUpdated);
|
||||
QObject::connect(service_->albums_collection_model(), &CollectionModel::modelAboutToBeReset, ui_->albums_collection->view(), &StreamingCollectionView::SaveFocus);
|
||||
QObject::connect(service_->albums_collection_model(), &CollectionModel::modelReset, ui_->albums_collection->view(), &StreamingCollectionView::RestoreFocus);
|
||||
|
||||
}
|
||||
else {
|
||||
ui_->tabs->removeTab(ui_->tabs->indexOf(ui_->albums));
|
||||
}
|
||||
|
||||
if (service_->songs_collection_model()) {
|
||||
ui_->songs_collection->stacked()->setCurrentWidget(ui_->songs_collection->streamingcollection_page());
|
||||
ui_->songs_collection->view()->Init(app_, service_->songs_collection_backend(), service_->songs_collection_model(), true);
|
||||
ui_->songs_collection->view()->setModel(service_->songs_collection_filter_model());
|
||||
ui_->songs_collection->view()->SetFilter(ui_->songs_collection->filter_widget());
|
||||
ui_->songs_collection->filter_widget()->SetSettingsGroup(settings_group);
|
||||
ui_->songs_collection->filter_widget()->SetSettingsPrefix(QStringLiteral("songs"));
|
||||
ui_->songs_collection->filter_widget()->Init(service_->songs_collection_model(), service_->songs_collection_filter_model());
|
||||
ui_->songs_collection->filter_widget()->AddMenuAction(action_configure);
|
||||
|
||||
QObject::connect(ui_->songs_collection->view(), &StreamingCollectionView::GetSongs, this, &StreamingTabsView::GetSongs);
|
||||
QObject::connect(ui_->songs_collection->view(), &StreamingCollectionView::RemoveSongs, &*service_, &StreamingService::RemoveSongsByList);
|
||||
|
||||
QObject::connect(ui_->songs_collection->button_refresh(), &QPushButton::clicked, this, &StreamingTabsView::GetSongs);
|
||||
QObject::connect(ui_->songs_collection->button_close(), &QPushButton::clicked, this, &StreamingTabsView::AbortGetSongs);
|
||||
QObject::connect(ui_->songs_collection->button_abort(), &QPushButton::clicked, this, &StreamingTabsView::AbortGetSongs);
|
||||
QObject::connect(&*service_, &StreamingService::SongsResults, this, &StreamingTabsView::SongsFinished);
|
||||
QObject::connect(&*service_, &StreamingService::SongsUpdateStatus, ui_->songs_collection->status(), &QLabel::setText);
|
||||
QObject::connect(&*service_, &StreamingService::SongsProgressSetMaximum, ui_->songs_collection->progressbar(), &QProgressBar::setMaximum);
|
||||
QObject::connect(&*service_, &StreamingService::SongsUpdateProgress, ui_->songs_collection->progressbar(), &QProgressBar::setValue);
|
||||
|
||||
QObject::connect(service_->songs_collection_model(), &CollectionModel::TotalArtistCountUpdated, ui_->songs_collection->view(), &StreamingCollectionView::TotalArtistCountUpdated);
|
||||
QObject::connect(service_->songs_collection_model(), &CollectionModel::TotalAlbumCountUpdated, ui_->songs_collection->view(), &StreamingCollectionView::TotalAlbumCountUpdated);
|
||||
QObject::connect(service_->songs_collection_model(), &CollectionModel::TotalSongCountUpdated, ui_->songs_collection->view(), &StreamingCollectionView::TotalSongCountUpdated);
|
||||
QObject::connect(service_->songs_collection_model(), &CollectionModel::modelAboutToBeReset, ui_->songs_collection->view(), &StreamingCollectionView::SaveFocus);
|
||||
QObject::connect(service_->songs_collection_model(), &CollectionModel::modelReset, ui_->songs_collection->view(), &StreamingCollectionView::RestoreFocus);
|
||||
|
||||
}
|
||||
else {
|
||||
ui_->tabs->removeTab(ui_->tabs->indexOf(ui_->songs));
|
||||
}
|
||||
|
||||
Settings s;
|
||||
s.beginGroup(settings_group_);
|
||||
QString tab = s.value("tab", QStringLiteral("artists")).toString().toLower();
|
||||
s.endGroup();
|
||||
|
||||
if (tab == QLatin1String("artists")) {
|
||||
ui_->tabs->setCurrentWidget(ui_->artists);
|
||||
}
|
||||
else if (tab == QLatin1String("albums")) {
|
||||
ui_->tabs->setCurrentWidget(ui_->albums);
|
||||
}
|
||||
else if (tab == QLatin1String("songs")) {
|
||||
ui_->tabs->setCurrentWidget(ui_->songs);
|
||||
}
|
||||
else if (tab == QLatin1String("search")) {
|
||||
ui_->tabs->setCurrentWidget(ui_->search);
|
||||
}
|
||||
|
||||
ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
StreamingTabsView::~StreamingTabsView() {
|
||||
|
||||
Settings s;
|
||||
s.beginGroup(settings_group_);
|
||||
s.setValue("tab", ui_->tabs->currentWidget()->objectName().toLower());
|
||||
s.endGroup();
|
||||
|
||||
delete ui_;
|
||||
}
|
||||
|
||||
void StreamingTabsView::ReloadSettings() {
|
||||
|
||||
if (service_->artists_collection_model()) {
|
||||
ui_->artists_collection->view()->ReloadSettings();
|
||||
}
|
||||
if (service_->albums_collection_model()) {
|
||||
ui_->albums_collection->view()->ReloadSettings();
|
||||
}
|
||||
if (service_->songs_collection_model()) {
|
||||
ui_->songs_collection->view()->ReloadSettings();
|
||||
}
|
||||
ui_->search_view->ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
bool StreamingTabsView::SearchFieldHasFocus() const {
|
||||
|
||||
return ((ui_->tabs->currentWidget() == ui_->artists && ui_->artists_collection->SearchFieldHasFocus()) ||
|
||||
(ui_->tabs->currentWidget() == ui_->albums && ui_->albums_collection->SearchFieldHasFocus()) ||
|
||||
(ui_->tabs->currentWidget() == ui_->songs && ui_->songs_collection->SearchFieldHasFocus()) ||
|
||||
(ui_->tabs->currentWidget() == ui_->search && ui_->search_view->SearchFieldHasFocus()));
|
||||
|
||||
}
|
||||
|
||||
void StreamingTabsView::FocusSearchField() {
|
||||
|
||||
if (ui_->tabs->currentWidget() == ui_->artists) {
|
||||
ui_->artists_collection->FocusSearchField();
|
||||
}
|
||||
else if (ui_->tabs->currentWidget() == ui_->albums) {
|
||||
ui_->albums_collection->FocusSearchField();
|
||||
}
|
||||
else if (ui_->tabs->currentWidget() == ui_->songs) {
|
||||
ui_->songs_collection->FocusSearchField();
|
||||
}
|
||||
else if (ui_->tabs->currentWidget() == ui_->search) {
|
||||
ui_->search_view->FocusSearchField();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StreamingTabsView::GetArtists() {
|
||||
|
||||
if (!service_->authenticated() && service_->oauth()) {
|
||||
service_->ShowConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
ui_->artists_collection->status()->clear();
|
||||
ui_->artists_collection->progressbar()->show();
|
||||
ui_->artists_collection->button_abort()->show();
|
||||
ui_->artists_collection->button_close()->hide();
|
||||
ui_->artists_collection->stacked()->setCurrentWidget(ui_->artists_collection->help_page());
|
||||
service_->GetArtists();
|
||||
|
||||
}
|
||||
|
||||
void StreamingTabsView::AbortGetArtists() {
|
||||
|
||||
service_->ResetArtistsRequest();
|
||||
ui_->artists_collection->progressbar()->setValue(0);
|
||||
ui_->artists_collection->status()->clear();
|
||||
ui_->artists_collection->stacked()->setCurrentWidget(ui_->artists_collection->streamingcollection_page());
|
||||
|
||||
}
|
||||
|
||||
void StreamingTabsView::ArtistsFinished(const SongMap &songs, const QString &error) {
|
||||
|
||||
if (songs.isEmpty() && !error.isEmpty()) {
|
||||
ui_->artists_collection->status()->setText(error);
|
||||
ui_->artists_collection->progressbar()->setValue(0);
|
||||
ui_->artists_collection->progressbar()->hide();
|
||||
ui_->artists_collection->button_abort()->hide();
|
||||
ui_->artists_collection->button_close()->show();
|
||||
}
|
||||
else {
|
||||
ui_->artists_collection->stacked()->setCurrentWidget(ui_->artists_collection->streamingcollection_page());
|
||||
ui_->artists_collection->status()->clear();
|
||||
service_->artists_collection_backend()->UpdateSongsBySongIDAsync(songs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StreamingTabsView::GetAlbums() {
|
||||
|
||||
if (!service_->authenticated() && service_->oauth()) {
|
||||
service_->ShowConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
ui_->albums_collection->status()->clear();
|
||||
ui_->albums_collection->progressbar()->show();
|
||||
ui_->albums_collection->button_abort()->show();
|
||||
ui_->albums_collection->button_close()->hide();
|
||||
ui_->albums_collection->stacked()->setCurrentWidget(ui_->albums_collection->help_page());
|
||||
service_->GetAlbums();
|
||||
|
||||
}
|
||||
|
||||
void StreamingTabsView::AbortGetAlbums() {
|
||||
|
||||
service_->ResetAlbumsRequest();
|
||||
ui_->albums_collection->progressbar()->setValue(0);
|
||||
ui_->albums_collection->status()->clear();
|
||||
ui_->albums_collection->stacked()->setCurrentWidget(ui_->albums_collection->streamingcollection_page());
|
||||
|
||||
}
|
||||
|
||||
void StreamingTabsView::AlbumsFinished(const SongMap &songs, const QString &error) {
|
||||
|
||||
if (songs.isEmpty() && !error.isEmpty()) {
|
||||
ui_->albums_collection->status()->setText(error);
|
||||
ui_->albums_collection->progressbar()->setValue(0);
|
||||
ui_->albums_collection->progressbar()->hide();
|
||||
ui_->albums_collection->button_abort()->hide();
|
||||
ui_->albums_collection->button_close()->show();
|
||||
}
|
||||
else {
|
||||
ui_->albums_collection->stacked()->setCurrentWidget(ui_->albums_collection->streamingcollection_page());
|
||||
ui_->albums_collection->status()->clear();
|
||||
service_->albums_collection_backend()->UpdateSongsBySongIDAsync(songs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StreamingTabsView::GetSongs() {
|
||||
|
||||
if (!service_->authenticated() && service_->oauth()) {
|
||||
service_->ShowConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
ui_->songs_collection->status()->clear();
|
||||
ui_->songs_collection->progressbar()->show();
|
||||
ui_->songs_collection->button_abort()->show();
|
||||
ui_->songs_collection->button_close()->hide();
|
||||
ui_->songs_collection->stacked()->setCurrentWidget(ui_->songs_collection->help_page());
|
||||
service_->GetSongs();
|
||||
|
||||
}
|
||||
|
||||
void StreamingTabsView::AbortGetSongs() {
|
||||
|
||||
service_->ResetSongsRequest();
|
||||
ui_->songs_collection->progressbar()->setValue(0);
|
||||
ui_->songs_collection->status()->clear();
|
||||
ui_->songs_collection->stacked()->setCurrentWidget(ui_->songs_collection->streamingcollection_page());
|
||||
|
||||
}
|
||||
|
||||
void StreamingTabsView::SongsFinished(const SongMap &songs, const QString &error) {
|
||||
|
||||
if (songs.isEmpty() && !error.isEmpty()) {
|
||||
ui_->songs_collection->status()->setText(error);
|
||||
ui_->songs_collection->progressbar()->setValue(0);
|
||||
ui_->songs_collection->progressbar()->hide();
|
||||
ui_->songs_collection->button_abort()->hide();
|
||||
ui_->songs_collection->button_close()->show();
|
||||
}
|
||||
else {
|
||||
ui_->songs_collection->stacked()->setCurrentWidget(ui_->songs_collection->streamingcollection_page());
|
||||
ui_->songs_collection->status()->clear();
|
||||
service_->songs_collection_backend()->UpdateSongsBySongIDAsync(songs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StreamingTabsView::OpenSettingsDialog() {
|
||||
app_->OpenSettingsDialogAtPage(service_->settings_page());
|
||||
}
|
||||
80
src/streaming/streamingtabsview.h
Normal file
80
src/streaming/streamingtabsview.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018-2021, 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 STREAMINGTABSVIEW_H
|
||||
#define STREAMINGTABSVIEW_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "settings/settingsdialog.h"
|
||||
#include "streamingcollectionviewcontainer.h"
|
||||
#include "ui_streamingtabsview.h"
|
||||
#include "core/song.h"
|
||||
|
||||
class QContextMenuEvent;
|
||||
|
||||
class Application;
|
||||
class StreamingService;
|
||||
class StreamingCollectionView;
|
||||
class StreamingSearchView;
|
||||
|
||||
class StreamingTabsView : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit StreamingTabsView(Application *app, SharedPtr<StreamingService> service, const QString &settings_group, const SettingsDialog::Page settings_page, QWidget *parent = nullptr);
|
||||
~StreamingTabsView() override;
|
||||
|
||||
void ReloadSettings();
|
||||
|
||||
StreamingCollectionView *artists_collection_view() const { return ui_->artists_collection->view(); }
|
||||
StreamingCollectionView *albums_collection_view() const { return ui_->albums_collection->view(); }
|
||||
StreamingCollectionView *songs_collection_view() const { return ui_->songs_collection->view(); }
|
||||
StreamingSearchView *search_view() const { return ui_->search_view; }
|
||||
|
||||
bool SearchFieldHasFocus() const;
|
||||
void FocusSearchField();
|
||||
|
||||
private slots:
|
||||
void OpenSettingsDialog();
|
||||
void GetArtists();
|
||||
void GetAlbums();
|
||||
void GetSongs();
|
||||
void AbortGetArtists();
|
||||
void AbortGetAlbums();
|
||||
void AbortGetSongs();
|
||||
void ArtistsFinished(const SongMap &songs, const QString &error);
|
||||
void AlbumsFinished(const SongMap &songs, const QString &error);
|
||||
void SongsFinished(const SongMap &songs, const QString &error);
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
SharedPtr <StreamingService> service_;
|
||||
QString settings_group_;
|
||||
SettingsDialog::Page settings_page_;
|
||||
Ui_StreamingTabsView *ui_;
|
||||
};
|
||||
|
||||
#endif // STREAMINGTABSVIEW_H
|
||||
136
src/streaming/streamingtabsview.ui
Normal file
136
src/streaming/streamingtabsview.ui
Normal file
@@ -0,0 +1,136 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>StreamingTabsView</class>
|
||||
<widget class="QWidget" name="StreamingTabsView">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>660</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Streaming Tabs View</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="layout_streamingtabsview">
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabs">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="artists">
|
||||
<attribute name="title">
|
||||
<string>Artists</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<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="StreamingCollectionViewContainer" name="artists_collection" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="albums">
|
||||
<attribute name="title">
|
||||
<string>Albums</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
<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="StreamingCollectionViewContainer" name="albums_collection" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="songs">
|
||||
<attribute name="title">
|
||||
<string>Songs</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<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="StreamingCollectionViewContainer" name="songs_collection" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="search">
|
||||
<attribute name="title">
|
||||
<string>Search</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<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="StreamingSearchView" name="search_view" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>StreamingSearchView</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>streaming/streamingsearchview.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>StreamingCollectionViewContainer</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>streaming/streamingcollectionviewcontainer.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
84
src/streaming/streamplaylistitem.cpp
Normal file
84
src/streaming/streamplaylistitem.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2021, 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 <QApplication>
|
||||
#include <QVariant>
|
||||
|
||||
#include "streamplaylistitem.h"
|
||||
#include "streamingservice.h"
|
||||
#include "core/sqlrow.h"
|
||||
|
||||
StreamPlaylistItem::StreamPlaylistItem(const Song::Source source)
|
||||
: PlaylistItem(source),
|
||||
source_(source) {}
|
||||
|
||||
StreamPlaylistItem::StreamPlaylistItem(const Song &metadata)
|
||||
: PlaylistItem(metadata.source()),
|
||||
source_(metadata.source()),
|
||||
metadata_(metadata) {
|
||||
InitMetadata();
|
||||
}
|
||||
|
||||
StreamPlaylistItem::StreamPlaylistItem(StreamingServicePtr service, const Song &metadata)
|
||||
: PlaylistItem(metadata.source()),
|
||||
source_(service->source()),
|
||||
metadata_(metadata) {
|
||||
InitMetadata();
|
||||
}
|
||||
|
||||
bool StreamPlaylistItem::InitFromQuery(const SqlRow &query) {
|
||||
|
||||
metadata_.InitFromQuery(query, false, static_cast<int>(Song::kRowIdColumns.count()));
|
||||
InitMetadata();
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
QVariant StreamPlaylistItem::DatabaseValue(DatabaseColumn column) const {
|
||||
return PlaylistItem::DatabaseValue(column);
|
||||
}
|
||||
|
||||
void StreamPlaylistItem::InitMetadata() {
|
||||
|
||||
if (metadata_.title().isEmpty()) metadata_.set_title(metadata_.url().toString());
|
||||
if (metadata_.source() == Song::Source::Unknown) metadata_.set_source(Song::Source::Stream);
|
||||
if (metadata_.filetype() == Song::FileType::Unknown) metadata_.set_filetype(Song::FileType::Stream);
|
||||
metadata_.set_valid(true);
|
||||
|
||||
}
|
||||
|
||||
Song StreamPlaylistItem::Metadata() const {
|
||||
|
||||
if (HasTemporaryMetadata()) return temp_metadata_;
|
||||
return metadata_;
|
||||
|
||||
}
|
||||
|
||||
QUrl StreamPlaylistItem::Url() const { return metadata_.url(); }
|
||||
|
||||
void StreamPlaylistItem::SetArtManual(const QUrl &cover_url) {
|
||||
|
||||
metadata_.set_art_manual(cover_url);
|
||||
temp_metadata_.set_art_manual(cover_url);
|
||||
|
||||
}
|
||||
66
src/streaming/streamplaylistitem.h
Normal file
66
src/streaming/streamplaylistitem.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2021, 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 STREAMPLAYLISTITEM_H
|
||||
#define STREAMPLAYLISTITEM_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QVariant>
|
||||
#include <QUrl>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
#include "core/sqlrow.h"
|
||||
#include "playlist/playlistitem.h"
|
||||
|
||||
class StreamingService;
|
||||
|
||||
class StreamPlaylistItem : public PlaylistItem {
|
||||
|
||||
public:
|
||||
explicit StreamPlaylistItem(const Song::Source source);
|
||||
explicit StreamPlaylistItem(const Song &metadata);
|
||||
explicit StreamPlaylistItem(SharedPtr<StreamingService> service, const Song &metadata);
|
||||
|
||||
bool InitFromQuery(const SqlRow &query) override;
|
||||
Song Metadata() const override;
|
||||
Song OriginalMetadata() const override { return metadata_; }
|
||||
QUrl Url() const override;
|
||||
|
||||
void SetMetadata(const Song &metadata) override { metadata_ = metadata; }
|
||||
void SetArtManual(const QUrl &cover_url) override;
|
||||
|
||||
protected:
|
||||
QVariant DatabaseValue(DatabaseColumn) const override;
|
||||
Song DatabaseSongMetadata() const override { return metadata_; }
|
||||
|
||||
private:
|
||||
void InitMetadata();
|
||||
|
||||
private:
|
||||
Song::Source source_;
|
||||
Song metadata_;
|
||||
|
||||
Q_DISABLE_COPY(StreamPlaylistItem)
|
||||
};
|
||||
|
||||
#endif // STREAMPLAYLISTITEM_H
|
||||
40
src/streaming/streamsongmimedata.h
Normal file
40
src/streaming/streamsongmimedata.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2011, 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 STREAMSONGMIMEDATA_H
|
||||
#define STREAMSONGMIMEDATA_H
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/mimedata.h"
|
||||
#include "core/song.h"
|
||||
|
||||
class StreamingService;
|
||||
|
||||
class StreamSongMimeData : public MimeData {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit StreamSongMimeData(SharedPtr<StreamingService> _service, QObject* = nullptr) : service(_service) {}
|
||||
|
||||
SharedPtr<StreamingService> service;
|
||||
SongList songs;
|
||||
};
|
||||
|
||||
#endif // STREAMSONGMIMEDATA_H
|
||||
Reference in New Issue
Block a user