// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/app_list/search/mixer.h" #include #include #include #include #include "chrome/browser/ui/app_list/search/chrome_search_result.h" #include "chrome/browser/ui/app_list/search/search_provider.h" namespace app_list { namespace { // Maximum number of results to show. const size_t kMaxResults = 6; const size_t kMaxMainGroupResults = 4; const size_t kMaxWebstoreResults = 2; const size_t kMaxPeopleResults = 2; // A value to indicate no max number of results limit. const size_t kNoMaxResultsLimit = 0; // Used for sorting and mixing results. struct SortData { SortData() : result(NULL), score(0.0) { } SortData(ChromeSearchResult* result, double score) : result(result), score(score) { } bool operator<(const SortData& other) const { // This data precedes (less than) |other| if it has higher score. return score > other.score; } ChromeSearchResult* result; // Not owned. double score; }; typedef std::vector SortedResults; // Removes duplicates from |results|. void RemoveDuplicates(SortedResults* results) { SortedResults final; final.reserve(results->size()); std::set id_set; for (SortedResults::iterator it = results->begin(); it != results->end(); ++it) { const std::string& id = it->result->id(); if (id_set.find(id) != id_set.end()) continue; id_set.insert(id); final.push_back(*it); } results->swap(final); } // Publishes the given |results| to |ui_results|. Reuse existing ones to avoid // flickering. void Publish(const SortedResults& results, AppListModel::SearchResults* ui_results) { for (size_t i = 0; i < results.size(); ++i) { ChromeSearchResult* result = results[i].result; ChromeSearchResult* ui_result = i < ui_results->item_count() ? static_cast(ui_results->GetItemAt(i)) : NULL; if (ui_result && ui_result->id() == result->id()) { ui_result->set_title(result->title()); ui_result->set_title_tags(result->title_tags()); ui_result->set_details(result->details()); ui_result->set_details_tags(result->details_tags()); ui_results->NotifyItemsChanged(i, 1); } else { if (ui_result) ui_results->DeleteAt(i); ui_results->AddAt(i, result->Duplicate().release()); } } while (ui_results->item_count() > results.size()) ui_results->DeleteAt(ui_results->item_count() - 1); } } // namespace // Used to group relevant providers together fox mixing their results. class Mixer::Group { public: Group(size_t max_results, double boost) : max_results_(max_results), boost_(boost) { } ~Group() {} void AddProvider(SearchProvider* provider) { providers_.push_back(provider); } void FetchResults(const KnownResults& known_results) { results_.clear(); for (Providers::const_iterator provider_it = providers_.begin(); provider_it != providers_.end(); ++provider_it) { for (SearchProvider::Results::const_iterator result_it = (*provider_it)->results().begin(); result_it != (*provider_it)->results().end(); ++result_it) { DCHECK_GE((*result_it)->relevance(), 0.0); DCHECK_LE((*result_it)->relevance(), 1.0); DCHECK(!(*result_it)->id().empty()); double boost = boost_; KnownResults::const_iterator known_it = known_results.find((*result_it)->id()); if (known_it != known_results.end()) { switch (known_it->second) { case PERFECT_PRIMARY: boost = 4.0; break; case PREFIX_PRIMARY: boost = 3.75; break; case PERFECT_SECONDARY: boost = 3.25; break; case PREFIX_SECONDARY: boost = 3.0; break; case UNKNOWN_RESULT: NOTREACHED() << "Unknown result in KnownResults?"; break; } } results_.push_back( SortData(*result_it, (*result_it)->relevance() + boost)); } } std::sort(results_.begin(), results_.end()); if (max_results_ != kNoMaxResultsLimit && results_.size() > max_results_) results_.resize(max_results_); } const SortedResults& results() const { return results_; } private: typedef std::vector Providers; const size_t max_results_; const double boost_; Providers providers_; // Not owned. SortedResults results_; DISALLOW_COPY_AND_ASSIGN(Group); }; Mixer::Mixer(AppListModel::SearchResults* ui_results) : ui_results_(ui_results) {} Mixer::~Mixer() {} void Mixer::Init() { groups_.push_back(new Group(kMaxMainGroupResults, 3.0)); groups_.push_back(new Group(kNoMaxResultsLimit, 2.0)); groups_.push_back(new Group(kMaxWebstoreResults, 1.0)); groups_.push_back(new Group(kMaxPeopleResults, 0.0)); } void Mixer::AddProviderToGroup(GroupId group, SearchProvider* provider) { size_t group_index = static_cast(group); groups_[group_index]->AddProvider(provider); } void Mixer::MixAndPublish(const KnownResults& known_results) { FetchResults(known_results); SortedResults results; results.reserve(kMaxResults); // Adds main group and web store results first. results.insert(results.end(), groups_[MAIN_GROUP]->results().begin(), groups_[MAIN_GROUP]->results().end()); results.insert(results.end(), groups_[WEBSTORE_GROUP]->results().begin(), groups_[WEBSTORE_GROUP]->results().end()); results.insert(results.end(), groups_[PEOPLE_GROUP]->results().begin(), groups_[PEOPLE_GROUP]->results().end()); // Collapse duplicate apps from local and web store. RemoveDuplicates(&results); DCHECK_GE(kMaxResults, results.size()); size_t remaining_slots = kMaxResults - results.size(); // Reserves at least one slot for the omnibox result. If there is no available // slot for omnibox results, removes the last one from web store. const size_t omnibox_results = groups_[OMNIBOX_GROUP]->results().size(); if (!remaining_slots && omnibox_results) results.pop_back(); remaining_slots = std::min(kMaxResults - results.size(), omnibox_results); results.insert(results.end(), groups_[OMNIBOX_GROUP]->results().begin(), groups_[OMNIBOX_GROUP]->results().begin() + remaining_slots); std::sort(results.begin(), results.end()); RemoveDuplicates(&results); if (results.size() > kMaxResults) results.resize(kMaxResults); Publish(results, ui_results_); } void Mixer::FetchResults(const KnownResults& known_results) { for (Groups::iterator group_it = groups_.begin(); group_it != groups_.end(); ++group_it) { (*group_it)->FetchResults(known_results); } } } // namespace app_list