2012-06-10 22:11:55 +02:00
|
|
|
/* This file is part of Clementine.
|
|
|
|
Copyright 2012, David Sansome <me@davidsansome.com>
|
2014-02-07 16:34:20 +01:00
|
|
|
|
2012-06-10 22:11:55 +02:00
|
|
|
Clementine 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.
|
2014-02-07 16:34:20 +01:00
|
|
|
|
2012-06-10 22:11:55 +02:00
|
|
|
Clementine 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.
|
2014-02-07 16:34:20 +01:00
|
|
|
|
2012-06-10 22:11:55 +02:00
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "globalsearchmodel.h"
|
|
|
|
|
2012-06-24 22:08:33 +02:00
|
|
|
#include <QSortFilterProxyModel>
|
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
#include "core/mimedata.h"
|
|
|
|
#include "globalsearch.h"
|
|
|
|
#include "ui/iconloader.h"
|
|
|
|
|
2012-06-10 22:27:00 +02:00
|
|
|
GlobalSearchModel::GlobalSearchModel(GlobalSearch* engine, QObject* parent)
|
2014-02-07 16:34:20 +01:00
|
|
|
: QStandardItemModel(parent),
|
|
|
|
engine_(engine),
|
|
|
|
proxy_(nullptr),
|
|
|
|
use_pretty_covers_(true),
|
2015-10-14 03:01:08 +02:00
|
|
|
artist_icon_(IconLoader::Load("x-clementine-artist", IconLoader::Base)),
|
|
|
|
album_icon_(IconLoader::Load("x-clementine-album", IconLoader::Base)) {
|
2019-11-13 20:20:44 +01:00
|
|
|
group_by_[0] = LibraryModel::GroupBy_AlbumArtist;
|
2012-06-10 22:11:55 +02:00
|
|
|
group_by_[1] = LibraryModel::GroupBy_Album;
|
|
|
|
group_by_[2] = LibraryModel::GroupBy_None;
|
|
|
|
|
2016-02-11 11:41:37 +01:00
|
|
|
QIcon nocover = IconLoader::Load("nocover", IconLoader::Other);
|
2020-09-18 16:15:19 +02:00
|
|
|
no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last())
|
|
|
|
.scaled(LibraryModel::kPrettyCoverSize,
|
|
|
|
LibraryModel::kPrettyCoverSize,
|
|
|
|
Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
2012-06-10 22:11:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void GlobalSearchModel::AddResults(const SearchProvider::ResultList& results) {
|
|
|
|
int sort_index = 0;
|
|
|
|
|
|
|
|
// Create a divider for this provider if we haven't seen it before.
|
|
|
|
SearchProvider* provider = results.first().provider_;
|
|
|
|
|
|
|
|
if (!provider_sort_indices_.contains(provider)) {
|
2012-06-11 17:26:33 +02:00
|
|
|
// Use the user's preferred order if one was set
|
|
|
|
int configured_index = provider_order_.indexOf(provider->id());
|
|
|
|
if (configured_index != -1) {
|
|
|
|
sort_index = configured_index;
|
|
|
|
} else {
|
2014-02-07 16:34:20 +01:00
|
|
|
sort_index = next_provider_sort_index_++;
|
2012-06-11 17:26:33 +02:00
|
|
|
}
|
2012-06-10 22:11:55 +02:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
QStandardItem* divider =
|
|
|
|
new QStandardItem(provider->icon(), provider->name());
|
2012-06-10 22:11:55 +02:00
|
|
|
divider->setData(true, LibraryModel::Role_IsDivider);
|
|
|
|
divider->setData(sort_index, Role_ProviderIndex);
|
|
|
|
divider->setFlags(Qt::ItemIsEnabled);
|
|
|
|
appendRow(divider);
|
|
|
|
|
|
|
|
provider_sort_indices_[provider] = sort_index;
|
|
|
|
} else {
|
|
|
|
sort_index = provider_sort_indices_[provider];
|
|
|
|
}
|
|
|
|
|
2014-02-10 14:29:07 +01:00
|
|
|
for (const SearchProvider::Result& result : results) {
|
2012-06-10 22:11:55 +02:00
|
|
|
QStandardItem* parent = invisibleRootItem();
|
|
|
|
|
|
|
|
// Find (or create) the container nodes for this result if we can.
|
|
|
|
if (result.group_automatically_) {
|
|
|
|
ContainerKey key;
|
|
|
|
key.provider_index_ = sort_index;
|
|
|
|
|
|
|
|
parent = BuildContainers(result.metadata_, parent, &key);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the item
|
2012-06-11 22:06:26 +02:00
|
|
|
QStandardItem* item = new QStandardItem;
|
|
|
|
item->setText(result.metadata_.TitleWithCompilationArtist());
|
2012-06-10 22:11:55 +02:00
|
|
|
item->setData(QVariant::fromValue(result), Role_Result);
|
|
|
|
item->setData(sort_index, Role_ProviderIndex);
|
|
|
|
|
|
|
|
parent->appendRow(item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
QStandardItem* GlobalSearchModel::BuildContainers(const Song& s,
|
|
|
|
QStandardItem* parent,
|
|
|
|
ContainerKey* key,
|
|
|
|
int level) {
|
2012-06-10 22:11:55 +02:00
|
|
|
if (level >= 3) {
|
|
|
|
return parent;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool has_artist_icon = false;
|
|
|
|
bool has_album_icon = false;
|
|
|
|
QString display_text;
|
|
|
|
QString sort_text;
|
2013-02-02 10:22:08 +01:00
|
|
|
int unique_tag = -1;
|
2012-06-10 22:11:55 +02:00
|
|
|
int year = 0;
|
|
|
|
|
|
|
|
switch (group_by_[level]) {
|
2014-02-07 16:34:20 +01:00
|
|
|
case LibraryModel::GroupBy_Artist:
|
|
|
|
if (s.is_compilation()) {
|
|
|
|
display_text = tr("Various artists");
|
|
|
|
sort_text = "aaaaaa";
|
|
|
|
} else {
|
|
|
|
display_text = LibraryModel::TextOrUnknown(s.artist());
|
|
|
|
sort_text = LibraryModel::SortTextForArtist(s.artist());
|
|
|
|
}
|
|
|
|
has_artist_icon = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LibraryModel::GroupBy_YearAlbum:
|
|
|
|
year = qMax(0, s.year());
|
|
|
|
display_text = LibraryModel::PrettyYearAlbum(year, s.album());
|
2015-05-23 09:02:07 +02:00
|
|
|
sort_text = LibraryModel::SortTextForNumber(year) + s.album();
|
2014-02-07 16:34:20 +01:00
|
|
|
unique_tag = s.album_id();
|
|
|
|
has_album_icon = true;
|
|
|
|
break;
|
|
|
|
|
2015-06-30 18:34:34 +02:00
|
|
|
case LibraryModel::GroupBy_OriginalYearAlbum:
|
|
|
|
year = qMax(0, s.effective_originalyear());
|
|
|
|
display_text = LibraryModel::PrettyYearAlbum(year, s.album());
|
|
|
|
sort_text = LibraryModel::SortTextForNumber(year) + s.album();
|
|
|
|
unique_tag = s.album_id();
|
|
|
|
has_album_icon = true;
|
|
|
|
break;
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
case LibraryModel::GroupBy_Year:
|
|
|
|
year = qMax(0, s.year());
|
|
|
|
display_text = QString::number(year);
|
2015-05-23 09:02:07 +02:00
|
|
|
sort_text = LibraryModel::SortTextForNumber(year) + " ";
|
2014-02-07 16:34:20 +01:00
|
|
|
break;
|
|
|
|
|
2015-06-30 18:34:34 +02:00
|
|
|
case LibraryModel::GroupBy_OriginalYear:
|
|
|
|
year = qMax(0, s.effective_originalyear());
|
|
|
|
display_text = QString::number(year);
|
|
|
|
sort_text = LibraryModel::SortTextForNumber(year) + " ";
|
|
|
|
break;
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
case LibraryModel::GroupBy_Composer:
|
|
|
|
display_text = s.composer();
|
2020-01-04 23:29:19 +01:00
|
|
|
// fallthrough
|
2014-02-07 16:34:20 +01:00
|
|
|
case LibraryModel::GroupBy_Performer:
|
|
|
|
display_text = s.performer();
|
2020-01-04 23:29:19 +01:00
|
|
|
// fallthrough
|
2014-10-27 13:31:38 +01:00
|
|
|
case LibraryModel::GroupBy_Disc:
|
|
|
|
display_text = s.disc();
|
2020-01-04 23:29:19 +01:00
|
|
|
// fallthrough
|
2014-02-07 16:34:20 +01:00
|
|
|
case LibraryModel::GroupBy_Grouping:
|
|
|
|
display_text = s.grouping();
|
2020-01-04 23:29:19 +01:00
|
|
|
// fallthrough
|
2014-02-07 16:34:20 +01:00
|
|
|
case LibraryModel::GroupBy_Genre:
|
|
|
|
if (display_text.isNull()) display_text = s.genre();
|
2020-01-04 23:29:19 +01:00
|
|
|
// fallthrough
|
2014-02-07 16:34:20 +01:00
|
|
|
case LibraryModel::GroupBy_Album:
|
|
|
|
unique_tag = s.album_id();
|
|
|
|
if (display_text.isNull()) {
|
2014-10-05 15:06:56 +02:00
|
|
|
display_text = s.album();
|
2014-02-07 16:34:20 +01:00
|
|
|
}
|
2020-01-04 23:29:19 +01:00
|
|
|
// fallthrough
|
2014-02-07 16:34:20 +01:00
|
|
|
case LibraryModel::GroupBy_AlbumArtist:
|
|
|
|
if (display_text.isNull()) display_text = s.effective_albumartist();
|
|
|
|
display_text = LibraryModel::TextOrUnknown(display_text);
|
|
|
|
sort_text = LibraryModel::SortTextForArtist(display_text);
|
|
|
|
has_album_icon = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LibraryModel::GroupBy_FileType:
|
|
|
|
display_text = s.TextForFiletype();
|
|
|
|
sort_text = display_text;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LibraryModel::GroupBy_Bitrate:
|
|
|
|
display_text = QString(s.bitrate(), 1);
|
|
|
|
sort_text = display_text;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LibraryModel::GroupBy_None:
|
|
|
|
return parent;
|
2012-06-10 22:11:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Find a container for this level
|
2013-02-02 10:22:08 +01:00
|
|
|
key->group_[level] = display_text + QString::number(unique_tag);
|
2012-06-10 22:11:55 +02:00
|
|
|
QStandardItem* container = containers_[*key];
|
|
|
|
if (!container) {
|
|
|
|
container = new QStandardItem(display_text);
|
|
|
|
container->setData(key->provider_index_, Role_ProviderIndex);
|
|
|
|
container->setData(sort_text, LibraryModel::Role_SortText);
|
|
|
|
container->setData(group_by_[level], LibraryModel::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 GlobalSearchModel::Clear() {
|
|
|
|
provider_sort_indices_.clear();
|
|
|
|
containers_.clear();
|
|
|
|
next_provider_sort_index_ = 1000;
|
|
|
|
clear();
|
|
|
|
}
|
2012-06-10 22:27:00 +02:00
|
|
|
|
|
|
|
SearchProvider::ResultList GlobalSearchModel::GetChildResults(
|
|
|
|
const QModelIndexList& indexes) const {
|
|
|
|
QList<QStandardItem*> items;
|
2014-02-10 14:29:07 +01:00
|
|
|
for (const QModelIndex& index : indexes) {
|
|
|
|
items << itemFromIndex(index);
|
|
|
|
}
|
2012-06-10 22:27:00 +02:00
|
|
|
return GetChildResults(items);
|
|
|
|
}
|
|
|
|
|
|
|
|
SearchProvider::ResultList GlobalSearchModel::GetChildResults(
|
|
|
|
const QList<QStandardItem*>& items) const {
|
|
|
|
SearchProvider::ResultList results;
|
|
|
|
QSet<const QStandardItem*> visited;
|
|
|
|
|
2014-02-10 14:29:07 +01:00
|
|
|
for (QStandardItem* item : items) {
|
2012-06-10 22:27:00 +02:00
|
|
|
GetChildResults(item, &results, &visited);
|
|
|
|
}
|
|
|
|
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
void GlobalSearchModel::GetChildResults(
|
|
|
|
const QStandardItem* item, SearchProvider::ResultList* results,
|
|
|
|
QSet<const QStandardItem*>* visited) const {
|
2012-06-10 22:27:00 +02:00
|
|
|
if (visited->contains(item)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
visited->insert(item);
|
|
|
|
|
|
|
|
// Does this item have children?
|
|
|
|
if (item->rowCount()) {
|
2012-06-24 22:08:33 +02:00
|
|
|
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.
|
2014-02-07 16:34:20 +01:00
|
|
|
for (int i = 0; i < item->rowCount(); ++i) {
|
2020-01-05 19:09:03 +01:00
|
|
|
const QModelIndex proxy_index =
|
|
|
|
parent_proxy_index.model()->index(i, 0, parent_proxy_index);
|
2012-06-24 22:08:33 +02:00
|
|
|
const QModelIndex index = proxy_->mapToSource(proxy_index);
|
|
|
|
GetChildResults(itemFromIndex(index), results, visited);
|
2012-06-10 22:27:00 +02:00
|
|
|
}
|
|
|
|
} else {
|
2014-09-13 21:31:33 +02:00
|
|
|
// No - maybe it's a song, add its result if valid
|
|
|
|
QVariant result = item->data(Role_Result);
|
|
|
|
if (result.isValid()) {
|
|
|
|
results->append(result.value<SearchProvider::Result>());
|
2014-09-13 22:57:58 +02:00
|
|
|
} else {
|
|
|
|
// Maybe it's a provider then?
|
|
|
|
bool is_provider;
|
|
|
|
const int sort_index = item->data(Role_ProviderIndex).toInt(&is_provider);
|
|
|
|
if (is_provider) {
|
|
|
|
// Go through all the items (through the proxy to keep them ordered) and
|
|
|
|
// add the ones belonging to this provider to our list
|
2015-05-23 09:03:36 +02:00
|
|
|
for (int i = 0; i < proxy_->rowCount(invisibleRootItem()->index());
|
|
|
|
++i) {
|
2014-09-13 22:57:58 +02:00
|
|
|
QModelIndex child_index =
|
|
|
|
proxy_->index(i, 0, invisibleRootItem()->index());
|
|
|
|
const QStandardItem* child_item =
|
|
|
|
itemFromIndex(proxy_->mapToSource(child_index));
|
|
|
|
if (child_item->data(Role_ProviderIndex).toInt() == sort_index) {
|
|
|
|
GetChildResults(child_item, results, visited);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-09-13 21:31:33 +02:00
|
|
|
}
|
2012-06-10 22:27:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QMimeData* GlobalSearchModel::mimeData(const QModelIndexList& indexes) const {
|
|
|
|
return engine_->LoadTracks(GetChildResults(indexes));
|
|
|
|
}
|
2012-06-17 17:20:40 +02:00
|
|
|
|
|
|
|
namespace {
|
|
|
|
void GatherResults(const QStandardItem* parent,
|
|
|
|
QMap<SearchProvider*, SearchProvider::ResultList>* results) {
|
|
|
|
QVariant result_variant = parent->data(GlobalSearchModel::Role_Result);
|
|
|
|
if (result_variant.isValid()) {
|
2014-02-07 16:34:20 +01:00
|
|
|
SearchProvider::Result result =
|
|
|
|
result_variant.value<SearchProvider::Result>();
|
2012-06-17 17:20:40 +02:00
|
|
|
(*results)[result.provider_].append(result);
|
|
|
|
}
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
for (int i = 0; i < parent->rowCount(); ++i) {
|
2012-06-17 17:20:40 +02:00
|
|
|
GatherResults(parent->child(i), results);
|
|
|
|
}
|
|
|
|
}
|
2020-09-18 16:15:19 +02:00
|
|
|
} // namespace
|
2012-06-17 17:20:40 +02:00
|
|
|
|
|
|
|
void GlobalSearchModel::SetGroupBy(const LibraryModel::Grouping& grouping,
|
|
|
|
bool regroup_now) {
|
|
|
|
const LibraryModel::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
|
|
|
|
QMap<SearchProvider*, SearchProvider::ResultList> results;
|
|
|
|
GatherResults(invisibleRootItem(), &results);
|
|
|
|
|
|
|
|
// Reset the model and re-add all the results using the new grouping.
|
|
|
|
Clear();
|
|
|
|
|
2014-02-10 14:29:07 +01:00
|
|
|
for (const SearchProvider::ResultList& result_list : results) {
|
2012-06-17 17:20:40 +02:00
|
|
|
AddResults(result_list);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|