Combine similar search results
This commit is contained in:
parent
4065037aba
commit
02f2d5dc88
@ -47,7 +47,9 @@ void GlobalSearchItemDelegate::paint(QPainter* p,
|
|||||||
const QStyleOptionViewItem& option,
|
const QStyleOptionViewItem& option,
|
||||||
const QModelIndex& index) const {
|
const QModelIndex& index) const {
|
||||||
const SearchProvider::Result result =
|
const SearchProvider::Result result =
|
||||||
index.data(GlobalSearchWidget::Role_Result).value<SearchProvider::Result>();
|
index.data(GlobalSearchWidget::Role_PrimaryResult).value<SearchProvider::Result>();
|
||||||
|
const SearchProvider::ResultList all_results =
|
||||||
|
index.data(GlobalSearchWidget::Role_AllResults).value<SearchProvider::ResultList>();
|
||||||
const Song& m = result.metadata_;
|
const Song& m = result.metadata_;
|
||||||
const bool selected = option.state & QStyle::State_Selected;
|
const bool selected = option.state & QStyle::State_Selected;
|
||||||
|
|
||||||
@ -101,15 +103,21 @@ void GlobalSearchItemDelegate::paint(QPainter* p,
|
|||||||
p->setFont(big_font);
|
p->setFont(big_font);
|
||||||
p->drawText(count_rect, Qt::TextSingleLine | Qt::AlignCenter, count);
|
p->drawText(count_rect, Qt::TextSingleLine | Qt::AlignCenter, count);
|
||||||
|
|
||||||
// Draw a provider icon on the right.
|
// Draw provider icons on the right.
|
||||||
QRect icon_rect(rect.right() - kArtMargin - kProviderIconSize,
|
const int icons_width = (kProviderIconSize + kArtMargin) * all_results.count();
|
||||||
|
QRect icons_rect(rect.right() - icons_width,
|
||||||
rect.top() + (rect.height() - kProviderIconSize) / 2,
|
rect.top() + (rect.height() - kProviderIconSize) / 2,
|
||||||
kProviderIconSize, kProviderIconSize);
|
icons_width, kProviderIconSize);
|
||||||
p->drawPixmap(icon_rect, result.provider_->icon().pixmap(kProviderIconSize));
|
|
||||||
|
QRect icon_rect(icons_rect.topLeft(), QSize(kProviderIconSize, kProviderIconSize));
|
||||||
|
foreach (const SearchProvider::Result& result, all_results) {
|
||||||
|
p->drawPixmap(icon_rect, result.provider_->icon().pixmap(kProviderIconSize));
|
||||||
|
icon_rect.translate(kProviderIconSize + kArtMargin, 0);
|
||||||
|
}
|
||||||
|
|
||||||
// Position text
|
// Position text
|
||||||
QRect text_rect(count_rect.right() + kArtMargin, count_rect.top(),
|
QRect text_rect(count_rect.right() + kArtMargin, count_rect.top(),
|
||||||
icon_rect.left() - count_rect.right() - kArtMargin*2, kHeight);
|
icons_rect.left() - count_rect.right() - kArtMargin*2, kHeight);
|
||||||
QRect text_rect_1(text_rect.adjusted(0, 0, 0, -kHeight/2));
|
QRect text_rect_1(text_rect.adjusted(0, 0, 0, -kHeight/2));
|
||||||
QRect text_rect_2(text_rect.adjusted(0, kHeight/2, 0, 0));
|
QRect text_rect_2(text_rect.adjusted(0, kHeight/2, 0, 0));
|
||||||
|
|
||||||
|
@ -26,9 +26,9 @@ GlobalSearchSortModel::GlobalSearchSortModel(QObject* parent)
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool GlobalSearchSortModel::lessThan(const QModelIndex& left, const QModelIndex& right) const {
|
bool GlobalSearchSortModel::lessThan(const QModelIndex& left, const QModelIndex& right) const {
|
||||||
const SearchProvider::Result r1 = left.data(GlobalSearchWidget::Role_Result)
|
const SearchProvider::Result r1 = left.data(GlobalSearchWidget::Role_PrimaryResult)
|
||||||
.value<SearchProvider::Result>();
|
.value<SearchProvider::Result>();
|
||||||
const SearchProvider::Result r2 = right.data(GlobalSearchWidget::Role_Result)
|
const SearchProvider::Result r2 = right.data(GlobalSearchWidget::Role_PrimaryResult)
|
||||||
.value<SearchProvider::Result>();
|
.value<SearchProvider::Result>();
|
||||||
|
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
@ -35,12 +35,14 @@
|
|||||||
#include <QDesktopWidget>
|
#include <QDesktopWidget>
|
||||||
#include <QListView>
|
#include <QListView>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
#include <QSettings>
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
#include <QStandardItemModel>
|
#include <QStandardItemModel>
|
||||||
|
|
||||||
|
|
||||||
const int GlobalSearchWidget::kMinVisibleItems = 3;
|
const int GlobalSearchWidget::kMinVisibleItems = 3;
|
||||||
const int GlobalSearchWidget::kMaxVisibleItems = 25;
|
const int GlobalSearchWidget::kMaxVisibleItems = 25;
|
||||||
|
const char* GlobalSearchWidget::kSettingsGroup = "GlobalSearch";
|
||||||
|
|
||||||
|
|
||||||
GlobalSearchWidget::GlobalSearchWidget(QWidget* parent)
|
GlobalSearchWidget::GlobalSearchWidget(QWidget* parent)
|
||||||
@ -54,9 +56,11 @@ GlobalSearchWidget::GlobalSearchWidget(QWidget* parent)
|
|||||||
view_(new QListView),
|
view_(new QListView),
|
||||||
eat_focus_out_(false),
|
eat_focus_out_(false),
|
||||||
background_(":allthethings.png"),
|
background_(":allthethings.png"),
|
||||||
desktop_(qApp->desktop())
|
desktop_(qApp->desktop()),
|
||||||
|
combine_identical_results_(true)
|
||||||
{
|
{
|
||||||
ui_->setupUi(this);
|
ui_->setupUi(this);
|
||||||
|
ReloadSettings();
|
||||||
|
|
||||||
// Set up the sorting proxy model
|
// Set up the sorting proxy model
|
||||||
proxy_->setSourceModel(model_);
|
proxy_->setSourceModel(model_);
|
||||||
@ -97,7 +101,8 @@ GlobalSearchWidget::~GlobalSearchWidget() {
|
|||||||
void GlobalSearchWidget::Init(LibraryBackendInterface* library) {
|
void GlobalSearchWidget::Init(LibraryBackendInterface* library) {
|
||||||
// Add providers
|
// Add providers
|
||||||
engine_->AddProvider(new LibrarySearchProvider(
|
engine_->AddProvider(new LibrarySearchProvider(
|
||||||
library, tr("Library"), IconLoader::Load("folder-sound"), engine_));
|
library, tr("Library"), "library",
|
||||||
|
IconLoader::Load("folder-sound"), engine_));
|
||||||
|
|
||||||
#ifdef HAVE_SPOTIFY
|
#ifdef HAVE_SPOTIFY
|
||||||
engine_->AddProvider(new SpotifySearchProvider(engine_));
|
engine_->AddProvider(new SpotifySearchProvider(engine_));
|
||||||
@ -189,7 +194,8 @@ void GlobalSearchWidget::AddResults(int id, const SearchProvider::ResultList& re
|
|||||||
|
|
||||||
foreach (const SearchProvider::Result& result, results) {
|
foreach (const SearchProvider::Result& result, results) {
|
||||||
QStandardItem* item = new QStandardItem;
|
QStandardItem* item = new QStandardItem;
|
||||||
item->setData(QVariant::fromValue(result), Role_Result);
|
item->setData(QVariant::fromValue(result), Role_PrimaryResult);
|
||||||
|
item->setData(QVariant::fromValue(SearchProvider::ResultList() << result), Role_AllResults);
|
||||||
|
|
||||||
QPixmap pixmap;
|
QPixmap pixmap;
|
||||||
if (engine_->FindCachedPixmap(result, &pixmap)) {
|
if (engine_->FindCachedPixmap(result, &pixmap)) {
|
||||||
@ -197,6 +203,39 @@ void GlobalSearchWidget::AddResults(int id, const SearchProvider::ResultList& re
|
|||||||
}
|
}
|
||||||
|
|
||||||
model_->appendRow(item);
|
model_->appendRow(item);
|
||||||
|
|
||||||
|
if (combine_identical_results_) {
|
||||||
|
// Maybe we can combine this result with an identical result from another
|
||||||
|
// provider. Only look at the results above and below this one in the
|
||||||
|
// sorted model.
|
||||||
|
QModelIndex my_proxy_index = proxy_->mapFromSource(item->index());
|
||||||
|
QModelIndexList candidates;
|
||||||
|
candidates << my_proxy_index.sibling(my_proxy_index.row() - 1, 0)
|
||||||
|
<< my_proxy_index.sibling(my_proxy_index.row() + 1, 0);
|
||||||
|
|
||||||
|
foreach (const QModelIndex& index, candidates) {
|
||||||
|
if (!index.isValid())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
CombineAction action = CanCombineResults(my_proxy_index, index);
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case CannotCombine:
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case LeftPreferred:
|
||||||
|
CombineResults(my_proxy_index, index);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RightPreferred:
|
||||||
|
CombineResults(index, my_proxy_index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We've just invalidated the indexes so we have to stop.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RepositionPopup();
|
RepositionPopup();
|
||||||
@ -371,7 +410,7 @@ void GlobalSearchWidget::LazyLoadArt(const QModelIndex& proxy_index) {
|
|||||||
model_->itemFromIndex(source_index)->setData(true, Role_LazyLoadingArt);
|
model_->itemFromIndex(source_index)->setData(true, Role_LazyLoadingArt);
|
||||||
|
|
||||||
const SearchProvider::Result result =
|
const SearchProvider::Result result =
|
||||||
source_index.data(Role_Result).value<SearchProvider::Result>();
|
source_index.data(Role_PrimaryResult).value<SearchProvider::Result>();
|
||||||
|
|
||||||
int id = engine_->LoadArtAsync(result);
|
int id = engine_->LoadArtAsync(result);
|
||||||
art_requests_[id] = source_index;
|
art_requests_[id] = source_index;
|
||||||
@ -393,7 +432,7 @@ void GlobalSearchWidget::AddCurrent() {
|
|||||||
if (!index.isValid())
|
if (!index.isValid())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
engine_->LoadTracksAsync(index.data(Role_Result).value<SearchProvider::Result>());
|
engine_->LoadTracksAsync(index.data(Role_PrimaryResult).value<SearchProvider::Result>());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GlobalSearchWidget::TracksLoaded(int id, MimeData* mime_data) {
|
void GlobalSearchWidget::TracksLoaded(int id, MimeData* mime_data) {
|
||||||
@ -406,3 +445,59 @@ void GlobalSearchWidget::TracksLoaded(int id, MimeData* mime_data) {
|
|||||||
emit AddToPlaylist(mime_data);
|
emit AddToPlaylist(mime_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GlobalSearchWidget::ReloadSettings() {
|
||||||
|
QSettings s;
|
||||||
|
s.beginGroup(kSettingsGroup);
|
||||||
|
|
||||||
|
combine_identical_results_ = s.value("combine_identical_results", true).toBool();
|
||||||
|
provider_order_ = s.value("provider_order", QStringList() << "library").toStringList();
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalSearchWidget::CombineAction GlobalSearchWidget::CanCombineResults(
|
||||||
|
const QModelIndex& left, const QModelIndex& right) const {
|
||||||
|
const SearchProvider::Result r1 = left.data(Role_PrimaryResult)
|
||||||
|
.value<SearchProvider::Result>();
|
||||||
|
const SearchProvider::Result r2 = right.data(Role_PrimaryResult)
|
||||||
|
.value<SearchProvider::Result>();
|
||||||
|
|
||||||
|
if (r1.match_quality_ != r2.match_quality_ || r1.type_ != r2.type_)
|
||||||
|
return CannotCombine;
|
||||||
|
|
||||||
|
#define StringsDiffer(field) \
|
||||||
|
(QString::compare(r1.metadata_.field(), r2.metadata_.field(), Qt::CaseInsensitive) != 0)
|
||||||
|
|
||||||
|
switch (r1.type_) {
|
||||||
|
case SearchProvider::Result::Type_Track:
|
||||||
|
if (StringsDiffer(title))
|
||||||
|
return CannotCombine;
|
||||||
|
// fallthrough
|
||||||
|
case SearchProvider::Result::Type_Album:
|
||||||
|
if (StringsDiffer(album) || StringsDiffer(artist))
|
||||||
|
return CannotCombine;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef StringsDiffer
|
||||||
|
|
||||||
|
// They look the same - decide which provider we like best.
|
||||||
|
const int p1 = provider_order_.indexOf(r1.provider_->id());
|
||||||
|
const int p2 = provider_order_.indexOf(r2.provider_->id());
|
||||||
|
|
||||||
|
return p2 > p1 ? RightPreferred : LeftPreferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GlobalSearchWidget::CombineResults(const QModelIndex& superior, const QModelIndex& inferior) {
|
||||||
|
QStandardItem* superior_item = model_->itemFromIndex(proxy_->mapToSource(superior));
|
||||||
|
QStandardItem* inferior_item = model_->itemFromIndex(proxy_->mapToSource(inferior));
|
||||||
|
|
||||||
|
SearchProvider::ResultList superior_results =
|
||||||
|
superior_item->data(Role_AllResults).value<SearchProvider::ResultList>();
|
||||||
|
SearchProvider::ResultList inferior_results =
|
||||||
|
inferior_item->data(Role_AllResults).value<SearchProvider::ResultList>();
|
||||||
|
|
||||||
|
superior_results.append(inferior_results);
|
||||||
|
superior_item->setData(QVariant::fromValue(superior_results), Role_AllResults);
|
||||||
|
|
||||||
|
model_->invisibleRootItem()->removeRow(inferior_item->row());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -43,9 +43,11 @@ public:
|
|||||||
|
|
||||||
static const int kMinVisibleItems;
|
static const int kMinVisibleItems;
|
||||||
static const int kMaxVisibleItems;
|
static const int kMaxVisibleItems;
|
||||||
|
static const char* kSettingsGroup;
|
||||||
|
|
||||||
enum Role {
|
enum Role {
|
||||||
Role_Result = Qt::UserRole + 1,
|
Role_PrimaryResult = Qt::UserRole + 1,
|
||||||
|
Role_AllResults,
|
||||||
Role_LazyLoadingArt
|
Role_LazyLoadingArt
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -58,6 +60,9 @@ public:
|
|||||||
bool eventFilter(QObject* o, QEvent* e);
|
bool eventFilter(QObject* o, QEvent* e);
|
||||||
void setFocus(Qt::FocusReason reason);
|
void setFocus(Qt::FocusReason reason);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void ReloadSettings();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void AddToPlaylist(QMimeData* data);
|
void AddToPlaylist(QMimeData* data);
|
||||||
|
|
||||||
@ -77,8 +82,17 @@ private slots:
|
|||||||
void AddCurrent();
|
void AddCurrent();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Return values from CanCombineResults
|
||||||
|
enum CombineAction {
|
||||||
|
CannotCombine, // The two results are different and can't be combined
|
||||||
|
LeftPreferred, // The two results can be combined - the left one is better
|
||||||
|
RightPreferred // The two results can be combined - the right one is better
|
||||||
|
};
|
||||||
|
|
||||||
void Reset();
|
void Reset();
|
||||||
void RepositionPopup();
|
void RepositionPopup();
|
||||||
|
CombineAction CanCombineResults(const QModelIndex& left, const QModelIndex& right) const;
|
||||||
|
void CombineResults(const QModelIndex& superior, const QModelIndex& inferior);
|
||||||
|
|
||||||
bool EventFilterSearchWidget(QObject* o, QEvent* e);
|
bool EventFilterSearchWidget(QObject* o, QEvent* e);
|
||||||
bool EventFilterPopup(QObject* o, QEvent* e);
|
bool EventFilterPopup(QObject* o, QEvent* e);
|
||||||
@ -101,6 +115,9 @@ private:
|
|||||||
QPixmap background_scaled_;
|
QPixmap background_scaled_;
|
||||||
|
|
||||||
QDesktopWidget* desktop_;
|
QDesktopWidget* desktop_;
|
||||||
|
|
||||||
|
bool combine_identical_results_;
|
||||||
|
QStringList provider_order_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // GLOBALSEARCHWIDGET_H
|
#endif // GLOBALSEARCHWIDGET_H
|
||||||
|
@ -26,13 +26,14 @@
|
|||||||
|
|
||||||
LibrarySearchProvider::LibrarySearchProvider(LibraryBackendInterface* backend,
|
LibrarySearchProvider::LibrarySearchProvider(LibraryBackendInterface* backend,
|
||||||
const QString& name,
|
const QString& name,
|
||||||
|
const QString& id,
|
||||||
const QIcon& icon,
|
const QIcon& icon,
|
||||||
QObject* parent)
|
QObject* parent)
|
||||||
: BlockingSearchProvider(parent),
|
: BlockingSearchProvider(parent),
|
||||||
backend_(backend),
|
backend_(backend),
|
||||||
cover_loader_(new BackgroundThreadImplementation<AlbumCoverLoader, AlbumCoverLoader>(this))
|
cover_loader_(new BackgroundThreadImplementation<AlbumCoverLoader, AlbumCoverLoader>(this))
|
||||||
{
|
{
|
||||||
Init(name, icon, false, true);
|
Init(name, id, icon, false, true);
|
||||||
|
|
||||||
cover_loader_->Start(true);
|
cover_loader_->Start(true);
|
||||||
cover_loader_->Worker()->SetDesiredHeight(kArtHeight);
|
cover_loader_->Worker()->SetDesiredHeight(kArtHeight);
|
||||||
|
@ -30,7 +30,7 @@ class LibrarySearchProvider : public BlockingSearchProvider {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
LibrarySearchProvider(LibraryBackendInterface* backend, const QString& name,
|
LibrarySearchProvider(LibraryBackendInterface* backend, const QString& name,
|
||||||
const QIcon& icon, QObject* parent = 0);
|
const QString& id, const QIcon& icon, QObject* parent = 0);
|
||||||
|
|
||||||
void LoadArtAsync(int id, const Result& result);
|
void LoadArtAsync(int id, const Result& result);
|
||||||
void LoadTracksAsync(int id, const Result& result);
|
void LoadTracksAsync(int id, const Result& result);
|
||||||
|
@ -29,9 +29,10 @@ SearchProvider::SearchProvider(QObject* parent)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void SearchProvider::Init(const QString& name, const QIcon& icon,
|
void SearchProvider::Init(const QString& name, const QString& id, const QIcon& icon,
|
||||||
bool delay_searches, bool serialised_art) {
|
bool delay_searches, bool serialised_art) {
|
||||||
name_ = name;
|
name_ = name;
|
||||||
|
id_ = id;
|
||||||
icon_ = icon;
|
icon_ = icon;
|
||||||
delay_searches_ = delay_searches;
|
delay_searches_ = delay_searches;
|
||||||
serialised_art_ = serialised_art;
|
serialised_art_ = serialised_art;
|
||||||
|
@ -72,6 +72,7 @@ public:
|
|||||||
typedef QList<Result> ResultList;
|
typedef QList<Result> ResultList;
|
||||||
|
|
||||||
const QString& name() const { return name_; }
|
const QString& name() const { return name_; }
|
||||||
|
const QString& id() const { return id_; }
|
||||||
const QIcon& icon() const { return icon_; }
|
const QIcon& icon() const { return icon_; }
|
||||||
const bool wants_delayed_queries() const { return delay_searches_; }
|
const bool wants_delayed_queries() const { return delay_searches_; }
|
||||||
const bool wants_serialised_art() const { return serialised_art_; }
|
const bool wants_serialised_art() const { return serialised_art_; }
|
||||||
@ -106,17 +107,19 @@ protected:
|
|||||||
static Result::MatchQuality MatchQuality(const QStringList& tokens, const QString& string);
|
static Result::MatchQuality MatchQuality(const QStringList& tokens, const QString& string);
|
||||||
|
|
||||||
// Subclasses must call this from their constructor
|
// Subclasses must call this from their constructor
|
||||||
void Init(const QString& name, const QIcon& icon,
|
void Init(const QString& name, const QString& id, const QIcon& icon,
|
||||||
bool delay_searches, bool serialised_art);
|
bool delay_searches, bool serialised_art);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString name_;
|
QString name_;
|
||||||
|
QString id_;
|
||||||
QIcon icon_;
|
QIcon icon_;
|
||||||
bool delay_searches_;
|
bool delay_searches_;
|
||||||
bool serialised_art_;
|
bool serialised_art_;
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(SearchProvider::Result)
|
Q_DECLARE_METATYPE(SearchProvider::Result)
|
||||||
|
Q_DECLARE_METATYPE(SearchProvider::ResultList)
|
||||||
|
|
||||||
|
|
||||||
class BlockingSearchProvider : public SearchProvider {
|
class BlockingSearchProvider : public SearchProvider {
|
||||||
|
@ -28,7 +28,7 @@ SpotifySearchProvider::SpotifySearchProvider(QObject* parent)
|
|||||||
server_(NULL),
|
server_(NULL),
|
||||||
service_(NULL)
|
service_(NULL)
|
||||||
{
|
{
|
||||||
Init("Spotify", QIcon(":icons/svg/spotify.svg"), true, true);
|
Init("Spotify", "spotify", QIcon(":icons/svg/spotify.svg"), true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
SpotifyServer* SpotifySearchProvider::server() {
|
SpotifyServer* SpotifySearchProvider::server() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user