From 97b4298002c556217646c6e7ae7625bfa63bc215 Mon Sep 17 00:00:00 2001 From: David Sansome Date: Sun, 10 Jun 2012 21:11:55 +0100 Subject: [PATCH] Split some bits of GlobalSearchView into a GlobalSearchModel --- src/CMakeLists.txt | 2 + src/globalsearch/globalsearchmodel.cpp | 158 +++++++++++++++++++++ src/globalsearch/globalsearchmodel.h | 91 ++++++++++++ src/globalsearch/globalsearchsortmodel.cpp | 10 +- src/globalsearch/globalsearchview.cpp | 153 +++----------------- src/globalsearch/globalsearchview.h | 58 +------- 6 files changed, 277 insertions(+), 195 deletions(-) create mode 100644 src/globalsearch/globalsearchmodel.cpp create mode 100644 src/globalsearch/globalsearchmodel.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 28fb16564..5b0558a5d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -139,6 +139,7 @@ set(SOURCES globalsearch/digitallyimportedsearchprovider.cpp globalsearch/globalsearch.cpp globalsearch/globalsearchitemdelegate.cpp + globalsearch/globalsearchmodel.cpp globalsearch/globalsearchsettingspage.cpp globalsearch/globalsearchsortmodel.cpp globalsearch/globalsearchview.cpp @@ -414,6 +415,7 @@ set(HEADERS engines/gstelementdeleter.h globalsearch/globalsearch.h + globalsearch/globalsearchmodel.h globalsearch/globalsearchsettingspage.h globalsearch/globalsearchview.h globalsearch/groovesharksearchprovider.h diff --git a/src/globalsearch/globalsearchmodel.cpp b/src/globalsearch/globalsearchmodel.cpp new file mode 100644 index 000000000..0b435d000 --- /dev/null +++ b/src/globalsearch/globalsearchmodel.cpp @@ -0,0 +1,158 @@ +/* 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" + +GlobalSearchModel::GlobalSearchModel(QObject* parent) + : QStandardItemModel(parent), + use_pretty_covers_(true), + artist_icon_(":/icons/22x22/x-clementine-artist.png"), + album_icon_(":/icons/22x22/x-clementine-album.png") +{ + group_by_[0] = LibraryModel::GroupBy_Artist; + group_by_[1] = LibraryModel::GroupBy_Album; + group_by_[2] = LibraryModel::GroupBy_None; + + no_cover_icon_ = QPixmap(":nocover.png").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)) { + // TODO: Check if the user has configured a sort order for this provider. + 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]; + } + + foreach (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(result.metadata_.title()); + 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 year = 0; + + switch (group_by_[level]) { + case LibraryModel::GroupBy_Artist: + 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::SortTextForYear(year) + s.album(); + has_album_icon = true; + break; + + case LibraryModel::GroupBy_Year: + year = qMax(0, s.year()); + display_text = QString::number(year); + sort_text = LibraryModel::SortTextForYear(year) + " "; + break; + + case LibraryModel::GroupBy_Composer: display_text = s.composer(); + case LibraryModel::GroupBy_Genre: if (display_text.isNull()) display_text = s.genre(); + case LibraryModel::GroupBy_Album: if (display_text.isNull()) display_text = s.album(); + 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_None: + return parent; + } + + // Find a container for this level + key->group_[level] = display_text; + 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(); +} diff --git a/src/globalsearch/globalsearchmodel.h b/src/globalsearch/globalsearchmodel.h new file mode 100644 index 000000000..3fab84f93 --- /dev/null +++ b/src/globalsearch/globalsearchmodel.h @@ -0,0 +1,91 @@ +/* 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 . +*/ + +#ifndef GLOBALSEARCHMODEL_H +#define GLOBALSEARCHMODEL_H + +#include "searchprovider.h" +#include "library/librarymodel.h" + +#include + +class GlobalSearchModel : public QStandardItemModel { + Q_OBJECT + +public: + GlobalSearchModel(QObject* parent = 0); + + enum Role { + Role_Result = LibraryModel::LastRole, + Role_LazyLoadingArt, + Role_ProviderIndex, + + LastRole + }; + + struct ContainerKey { + int provider_index_; + QString group_[3]; + }; + + void set_use_pretty_covers(bool pretty) { use_pretty_covers_ = pretty; } + + void Clear(); + +public slots: + void AddResults(const SearchProvider::ResultList& results); + +private: + QStandardItem* BuildContainers(const Song& metadata, QStandardItem* parent, + ContainerKey* key, int level = 0); + +private: + LibraryModel::Grouping group_by_; + + QMap provider_sort_indices_; + int next_provider_sort_index_; + QMap containers_; + + bool use_pretty_covers_; + QIcon artist_icon_; + QIcon album_icon_; + QPixmap no_cover_icon_; +}; + +inline uint qHash(const GlobalSearchModel::ContainerKey& key) { + return qHash(key.provider_index_) + ^ qHash(key.group_[0]) + ^ qHash(key.group_[1]) + ^ qHash(key.group_[2]); +} + +inline bool operator <(const GlobalSearchModel::ContainerKey& left, + const GlobalSearchModel::ContainerKey& right) { + #define CMP(field) \ + if (left.field < right.field) return true; \ + if (left.field > right.field) return false + + CMP(provider_index_); + CMP(group_[0]); + CMP(group_[1]); + CMP(group_[2]); + return false; + + #undef CMP +} + +#endif // GLOBALSEARCHMODEL_H diff --git a/src/globalsearch/globalsearchsortmodel.cpp b/src/globalsearch/globalsearchsortmodel.cpp index 14fc9d5ac..9e2758cdc 100644 --- a/src/globalsearch/globalsearchsortmodel.cpp +++ b/src/globalsearch/globalsearchsortmodel.cpp @@ -15,8 +15,8 @@ along with Clementine. If not, see . */ +#include "globalsearchmodel.h" #include "globalsearchsortmodel.h" -#include "globalsearchview.h" #include "searchprovider.h" #include "core/logging.h" @@ -27,8 +27,8 @@ GlobalSearchSortModel::GlobalSearchSortModel(QObject* parent) bool GlobalSearchSortModel::lessThan(const QModelIndex& left, const QModelIndex& right) const { // Compare the provider sort index first. - const int index_left = left.data(GlobalSearchView::Role_ProviderIndex).toInt(); - const int index_right = right.data(GlobalSearchView::Role_ProviderIndex).toInt(); + const int index_left = left.data(GlobalSearchModel::Role_ProviderIndex).toInt(); + const int index_right = right.data(GlobalSearchModel::Role_ProviderIndex).toInt(); if (index_left < index_right) return true; if (index_left > index_right) return false; @@ -50,9 +50,9 @@ bool GlobalSearchSortModel::lessThan(const QModelIndex& left, const QModelIndex& } // Otherwise we're comparing songs. Sort by disc, track, then title. - const SearchProvider::Result r1 = left.data(GlobalSearchView::Role_Result) + const SearchProvider::Result r1 = left.data(GlobalSearchModel::Role_Result) .value(); - const SearchProvider::Result r2 = right.data(GlobalSearchView::Role_Result) + const SearchProvider::Result r2 = right.data(GlobalSearchModel::Role_Result) .value(); #define CompareInt(field) \ diff --git a/src/globalsearch/globalsearchview.cpp b/src/globalsearch/globalsearchview.cpp index c27af46cf..7670cd49a 100644 --- a/src/globalsearch/globalsearchview.cpp +++ b/src/globalsearch/globalsearchview.cpp @@ -17,6 +17,7 @@ #include "globalsearch.h" #include "globalsearchitemdelegate.h" +#include "globalsearchmodel.h" #include "globalsearchsortmodel.h" #include "globalsearchview.h" #include "searchprovider.h" @@ -39,15 +40,13 @@ GlobalSearchView::GlobalSearchView(Application* app, QWidget* parent) engine_(app_->global_search()), ui_(new Ui_GlobalSearchView), last_search_id_(0), - front_model_(new QStandardItemModel(this)), - back_model_(new QStandardItemModel(this)), + front_model_(new GlobalSearchModel(this)), + back_model_(new GlobalSearchModel(this)), current_model_(front_model_), front_proxy_(new GlobalSearchSortModel(this)), back_proxy_(new GlobalSearchSortModel(this)), current_proxy_(front_proxy_), - swap_models_timer_(new QTimer(this)), - artist_icon_(":/icons/22x22/x-clementine-artist.png"), - album_icon_(":/icons/22x22/x-clementine-album.png") + swap_models_timer_(new QTimer(this)) { ui_->setupUi(this); @@ -81,14 +80,6 @@ GlobalSearchView::GlobalSearchView(Application* app, QWidget* parent) help_font.setBold(true); ui_->help_text->setFont(help_font); - group_by_[0] = LibraryModel::GroupBy_Artist; - group_by_[1] = LibraryModel::GroupBy_Album; - group_by_[2] = LibraryModel::GroupBy_None; - - no_cover_icon_ = QPixmap(":nocover.png").scaled( - LibraryModel::kPrettyCoverSize, LibraryModel::kPrettyCoverSize, - Qt::KeepAspectRatio, Qt::SmoothTransformation); - // Set up the sorting proxy model front_proxy_->setSourceModel(front_model_); front_proxy_->setDynamicSortFilter(true); @@ -149,6 +140,11 @@ void GlobalSearchView::ReloadSettings() { } ui_->disabled_label->setVisible(any_disabled); + + // Update models to use pretty covers. + const bool pretty = app_->library_model()->use_pretty_covers(); + front_model_->set_use_pretty_covers(pretty); + back_model_->set_use_pretty_covers(pretty); } void GlobalSearchView::StartSearch(const QString& query) { @@ -164,10 +160,7 @@ void GlobalSearchView::TextEdited(const QString& text) { const QString trimmed(text.trimmed()); // Add results to the back model, switch models after some delay. - provider_sort_indices_.clear(); - containers_.clear(); - next_provider_sort_index_ = 1000; - back_model_->clear(); + back_model_->Clear(); current_model_ = back_model_; current_proxy_ = back_proxy_; swap_models_timer_->start(); @@ -186,121 +179,7 @@ void GlobalSearchView::AddResults(int id, const SearchProvider::ResultList& resu if (id != last_search_id_ || results.isEmpty()) return; - 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)) { - // TODO: Check if the user has configured a sort order for this provider. - 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); - current_model_->appendRow(divider); - - provider_sort_indices_[provider] = sort_index; - } else { - sort_index = provider_sort_indices_[provider]; - } - - foreach (const SearchProvider::Result& result, results) { - QStandardItem* parent = current_model_->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(result.metadata_.title()); - item->setData(QVariant::fromValue(result), Role_Result); - item->setData(sort_index, Role_ProviderIndex); - - parent->appendRow(item); - } -} - -QStandardItem* GlobalSearchView::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 year = 0; - - switch (group_by_[level]) { - case LibraryModel::GroupBy_Artist: - 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::SortTextForYear(year) + s.album(); - has_album_icon = true; - break; - - case LibraryModel::GroupBy_Year: - year = qMax(0, s.year()); - display_text = QString::number(year); - sort_text = LibraryModel::SortTextForYear(year) + " "; - break; - - case LibraryModel::GroupBy_Composer: display_text = s.composer(); - case LibraryModel::GroupBy_Genre: if (display_text.isNull()) display_text = s.genre(); - case LibraryModel::GroupBy_Album: if (display_text.isNull()) display_text = s.album(); - 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_None: - return parent; - } - - // Find a container for this level - key->group_[level] = display_text; - 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 (app_->library_model()->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); + current_model_->AddResults(results); } void GlobalSearchView::SwapModels() { @@ -324,7 +203,7 @@ void GlobalSearchView::LazyLoadArt(const QModelIndex& proxy_index) { } // Already loading art for this item? - if (proxy_index.data(Role_LazyLoadingArt).isValid()) { + if (proxy_index.data(GlobalSearchModel::Role_LazyLoadingArt).isValid()) { return; } @@ -345,7 +224,7 @@ void GlobalSearchView::LazyLoadArt(const QModelIndex& proxy_index) { // Mark the item as loading art const QModelIndex source_index = front_proxy_->mapToSource(proxy_index); QStandardItem* item = front_model_->itemFromIndex(source_index); - item->setData(true, Role_LazyLoadingArt); + item->setData(true, GlobalSearchModel::Role_LazyLoadingArt); // Walk down the item's children until we find a track while (item->rowCount()) { @@ -354,7 +233,7 @@ void GlobalSearchView::LazyLoadArt(const QModelIndex& proxy_index) { // Get the track's Result const SearchProvider::Result result = - item->data(Role_Result).value(); + item->data(GlobalSearchModel::Role_Result).value(); // Load the art. int id = engine_->LoadArtAsync(result); @@ -387,7 +266,7 @@ void GlobalSearchView::GetChildResults(const QStandardItem* item, } } else { // No - it's a song, add its result - results->append(item->data(Role_Result).value()); + results->append(item->data(GlobalSearchModel::Role_Result).value()); } } @@ -408,7 +287,7 @@ MimeData* GlobalSearchView::LoadSelectedTracks() { // Still got nothing? Give up. if (indexes.isEmpty()) { - return; + return NULL; } // Get all the results in these indexes diff --git a/src/globalsearch/globalsearchview.h b/src/globalsearch/globalsearchview.h index ec8807f7a..bc90b0063 100644 --- a/src/globalsearch/globalsearchview.h +++ b/src/globalsearch/globalsearchview.h @@ -25,6 +25,7 @@ #include class Application; +class GlobalSearchModel; class SearchProviderStatusWidget; class Ui_GlobalSearchView; @@ -42,19 +43,6 @@ public: static const int kSwapModelsTimeoutMsec; - enum Role { - Role_Result = LibraryModel::LastRole, - Role_LazyLoadingArt, - Role_ProviderIndex, - - LastRole - }; - - struct ContainerKey { - int provider_index_; - QString group_[3]; - }; - // Called by the delegate void LazyLoadArt(const QModelIndex& index); @@ -71,13 +59,10 @@ private slots: void SwapModels(); void TextEdited(const QString& text); void AddResults(int id, const SearchProvider::ResultList& results); - void ArtLoaded(int id, const QPixmap& pixmap); private: MimeData* LoadSelectedTracks(); - QStandardItem* BuildContainers(const Song& metadata, QStandardItem* parent, - ContainerKey* key, int level = 0); void GetChildResults(const QStandardItem* item, SearchProvider::ResultList* results, @@ -94,52 +79,19 @@ private: // 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. - QStandardItemModel* front_model_; - QStandardItemModel* back_model_; - QStandardItemModel* current_model_; + GlobalSearchModel* front_model_; + GlobalSearchModel* back_model_; + GlobalSearchModel* current_model_; QSortFilterProxyModel* front_proxy_; QSortFilterProxyModel* back_proxy_; QSortFilterProxyModel* current_proxy_; - QTimer* swap_models_timer_; - - LibraryModel::Grouping group_by_; - - QMap provider_sort_indices_; - int next_provider_sort_index_; - QMap containers_; - - QMap track_requests_; QMap art_requests_; - QIcon artist_icon_; - QIcon album_icon_; - QPixmap no_cover_icon_; + QTimer* swap_models_timer_; QList provider_status_widgets_; }; -inline uint qHash(const GlobalSearchView::ContainerKey& key) { - return qHash(key.provider_index_) - ^ qHash(key.group_[0]) - ^ qHash(key.group_[1]) - ^ qHash(key.group_[2]); -} - -inline bool operator <(const GlobalSearchView::ContainerKey& left, - const GlobalSearchView::ContainerKey& right) { - #define CMP(field) \ - if (left.field < right.field) return true; \ - if (left.field > right.field) return false - - CMP(provider_index_); - CMP(group_[0]); - CMP(group_[1]); - CMP(group_[2]); - return false; - - #undef CMP -} - #endif // GLOBALSEARCHVIEW_H