/* This file is part of Clementine. Copyright 2012, David Sansome 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. 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. You should have received a copy of the GNU General Public License along with Clementine. If not, see . */ #include "globalsearchmodel.h" #include #include "core/mimedata.h" #include "globalsearch.h" #include "ui/iconloader.h" GlobalSearchModel::GlobalSearchModel(GlobalSearch* engine, QObject* parent) : QStandardItemModel(parent), engine_(engine), proxy_(nullptr), use_pretty_covers_(true), artist_icon_(IconLoader::Load("x-clementine-artist", IconLoader::Base)), album_icon_(IconLoader::Load("x-clementine-album", IconLoader::Base)) { group_by_[0] = LibraryModel::GroupBy_AlbumArtist; group_by_[1] = LibraryModel::GroupBy_Album; group_by_[2] = LibraryModel::GroupBy_None; QIcon nocover = IconLoader::Load("nocover", IconLoader::Other); no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()) .scaled(LibraryModel::kPrettyCoverSize, LibraryModel::kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); } 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)) { // 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 { sort_index = next_provider_sort_index_++; } QStandardItem* divider = new QStandardItem(provider->icon(), provider->name()); 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]; } for (const SearchProvider::Result& result : results) { 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 QStandardItem* item = new QStandardItem; item->setText(result.metadata_.TitleWithCompilationArtist()); item->setData(QVariant::fromValue(result), Role_Result); item->setData(sort_index, Role_ProviderIndex); parent->appendRow(item); } } QStandardItem* GlobalSearchModel::BuildContainers(const Song& s, QStandardItem* parent, ContainerKey* key, int level) { if (level >= 3) { return parent; } bool has_artist_icon = false; bool has_album_icon = false; QString display_text; QString sort_text; int unique_tag = -1; int year = 0; switch (group_by_[level]) { case 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()); sort_text = LibraryModel::SortTextForNumber(year) + s.album(); unique_tag = s.album_id(); has_album_icon = true; break; 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; case LibraryModel::GroupBy_Year: year = qMax(0, s.year()); display_text = QString::number(year); sort_text = LibraryModel::SortTextForNumber(year) + " "; break; case LibraryModel::GroupBy_OriginalYear: year = qMax(0, s.effective_originalyear()); display_text = QString::number(year); sort_text = LibraryModel::SortTextForNumber(year) + " "; break; case LibraryModel::GroupBy_Composer: display_text = s.composer(); // fallthrough case LibraryModel::GroupBy_Performer: display_text = s.performer(); // fallthrough case LibraryModel::GroupBy_Disc: display_text = s.disc(); // fallthrough case LibraryModel::GroupBy_Grouping: display_text = s.grouping(); // fallthrough case LibraryModel::GroupBy_Genre: if (display_text.isNull()) display_text = s.genre(); // fallthrough case LibraryModel::GroupBy_Album: unique_tag = s.album_id(); if (display_text.isNull()) { display_text = s.album(); } // fallthrough 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; } // Find a container for this level key->group_[level] = display_text + QString::number(unique_tag); QStandardItem* container = containers_[*key]; if (!container) { container = new QStandardItem(display_text); container->setData(key->provider_index_, Role_ProviderIndex); container->setData(sort_text, 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(); } SearchProvider::ResultList GlobalSearchModel::GetChildResults( const QModelIndexList& indexes) const { QList items; for (const QModelIndex& index : indexes) { items << itemFromIndex(index); } return GetChildResults(items); } SearchProvider::ResultList GlobalSearchModel::GetChildResults( const QList& items) const { SearchProvider::ResultList results; QSet visited; for (QStandardItem* item : items) { GetChildResults(item, &results, &visited); } return results; } void GlobalSearchModel::GetChildResults( const QStandardItem* item, SearchProvider::ResultList* results, QSet* visited) const { if (visited->contains(item)) { return; } visited->insert(item); // Does this item have children? if (item->rowCount()) { const QModelIndex parent_proxy_index = proxy_->mapFromSource(item->index()); // Yes - visit all the children, but do so through the proxy so we get them // in the right order. for (int i = 0; i < item->rowCount(); ++i) { const QModelIndex proxy_index = parent_proxy_index.model()->index(i, 0, parent_proxy_index); const QModelIndex index = proxy_->mapToSource(proxy_index); GetChildResults(itemFromIndex(index), results, visited); } } else { // No - maybe it's a song, add its result if valid QVariant result = item->data(Role_Result); if (result.isValid()) { results->append(result.value()); } else { // Maybe it's a provider then? bool is_provider; const int sort_index = item->data(Role_ProviderIndex).toInt(&is_provider); if (is_provider) { // Go through all the items (through the proxy to keep them ordered) and // add the ones belonging to this provider to our list for (int i = 0; i < proxy_->rowCount(invisibleRootItem()->index()); ++i) { QModelIndex child_index = proxy_->index(i, 0, invisibleRootItem()->index()); const QStandardItem* child_item = itemFromIndex(proxy_->mapToSource(child_index)); if (child_item->data(Role_ProviderIndex).toInt() == sort_index) { GetChildResults(child_item, results, visited); } } } } } } QMimeData* GlobalSearchModel::mimeData(const QModelIndexList& indexes) const { return engine_->LoadTracks(GetChildResults(indexes)); } namespace { void GatherResults(const QStandardItem* parent, QMap* results) { QVariant result_variant = parent->data(GlobalSearchModel::Role_Result); if (result_variant.isValid()) { SearchProvider::Result result = result_variant.value(); (*results)[result.provider_].append(result); } for (int i = 0; i < parent->rowCount(); ++i) { GatherResults(parent->child(i), results); } } } // namespace 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 results; GatherResults(invisibleRootItem(), &results); // Reset the model and re-add all the results using the new grouping. Clear(); for (const SearchProvider::ResultList& result_list : results) { AddResults(result_list); } } }