mixer.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/ui/app_list/search/mixer.h" 6 7#include <algorithm> 8#include <set> 9#include <string> 10#include <vector> 11 12#include "chrome/browser/ui/app_list/search/chrome_search_result.h" 13#include "ui/app_list/search_provider.h" 14 15namespace app_list { 16 17namespace { 18 19// Maximum number of results to show. 20const size_t kMaxResults = 6; 21const size_t kMaxMainGroupResults = 4; 22const size_t kMaxWebstoreResults = 2; 23const size_t kMaxPeopleResults = 2; 24 25// A value to indicate no max number of results limit. 26const size_t kNoMaxResultsLimit = 0; 27 28// Used for sorting and mixing results. 29struct SortData { 30 SortData() 31 : result(NULL), 32 score(0.0) { 33 } 34 SortData(ChromeSearchResult* result, double score) 35 : result(result), 36 score(score) { 37 } 38 39 bool operator<(const SortData& other) const { 40 // This data precedes (less than) |other| if it has higher score. 41 return score > other.score; 42 } 43 44 ChromeSearchResult* result; // Not owned. 45 double score; 46}; 47typedef std::vector<SortData> SortedResults; 48 49// Removes duplicates from |results|. 50void RemoveDuplicates(SortedResults* results) { 51 SortedResults final; 52 final.reserve(results->size()); 53 54 std::set<std::string> id_set; 55 for (SortedResults::iterator it = results->begin(); 56 it != results->end(); 57 ++it) { 58 const std::string& id = it->result->id(); 59 if (id_set.find(id) != id_set.end()) 60 continue; 61 62 id_set.insert(id); 63 final.push_back(*it); 64 } 65 66 results->swap(final); 67} 68 69// Publishes the given |results| to |ui_results|. Reuse existing ones to avoid 70// flickering. 71void Publish(const SortedResults& results, 72 AppListModel::SearchResults* ui_results) { 73 for (size_t i = 0; i < results.size(); ++i) { 74 ChromeSearchResult* result = results[i].result; 75 76 ChromeSearchResult* ui_result = i < ui_results->item_count() ? 77 static_cast<ChromeSearchResult*>(ui_results->GetItemAt(i)) : NULL; 78 if (ui_result && ui_result->id() == result->id()) { 79 ui_result->set_title(result->title()); 80 ui_result->set_title_tags(result->title_tags()); 81 ui_result->set_details(result->details()); 82 ui_result->set_details_tags(result->details_tags()); 83 ui_results->NotifyItemsChanged(i, 1); 84 } else { 85 if (ui_result) 86 ui_results->DeleteAt(i); 87 ui_results->AddAt(i, result->Duplicate().release()); 88 } 89 } 90 91 while (ui_results->item_count() > results.size()) 92 ui_results->DeleteAt(ui_results->item_count() - 1); 93} 94 95} // namespace 96 97// Used to group relevant providers together fox mixing their results. 98class Mixer::Group { 99 public: 100 Group(size_t max_results, double boost) 101 : max_results_(max_results), 102 boost_(boost) { 103 } 104 ~Group() {} 105 106 void AddProvider(SearchProvider* provider) { 107 providers_.push_back(provider); 108 } 109 110 void FetchResults(const KnownResults& known_results) { 111 results_.clear(); 112 113 for (Providers::const_iterator provider_it = providers_.begin(); 114 provider_it != providers_.end(); 115 ++provider_it) { 116 for (SearchProvider::Results::const_iterator 117 result_it = (*provider_it)->results().begin(); 118 result_it != (*provider_it)->results().end(); 119 ++result_it) { 120 DCHECK_GE((*result_it)->relevance(), 0.0); 121 DCHECK_LE((*result_it)->relevance(), 1.0); 122 DCHECK(!(*result_it)->id().empty()); 123 124 double boost = boost_; 125 KnownResults::const_iterator known_it = 126 known_results.find((*result_it)->id()); 127 if (known_it != known_results.end()) { 128 switch (known_it->second) { 129 case PERFECT_PRIMARY: 130 boost = 4.0; 131 break; 132 case PREFIX_PRIMARY: 133 boost = 3.75; 134 break; 135 case PERFECT_SECONDARY: 136 boost = 3.25; 137 break; 138 case PREFIX_SECONDARY: 139 boost = 3.0; 140 break; 141 case UNKNOWN_RESULT: 142 NOTREACHED() << "Unknown result in KnownResults?"; 143 break; 144 } 145 } 146 147 results_.push_back( 148 SortData(static_cast<ChromeSearchResult*>(*result_it), 149 (*result_it)->relevance() + boost)); 150 } 151 } 152 153 std::sort(results_.begin(), results_.end()); 154 if (max_results_ != kNoMaxResultsLimit && results_.size() > max_results_) 155 results_.resize(max_results_); 156 } 157 158 const SortedResults& results() const { return results_; } 159 160 private: 161 typedef std::vector<SearchProvider*> Providers; 162 const size_t max_results_; 163 const double boost_; 164 165 Providers providers_; // Not owned. 166 SortedResults results_; 167 168 DISALLOW_COPY_AND_ASSIGN(Group); 169}; 170 171Mixer::Mixer(AppListModel::SearchResults* ui_results) 172 : ui_results_(ui_results) {} 173Mixer::~Mixer() {} 174 175void Mixer::Init() { 176 groups_.push_back(new Group(kMaxMainGroupResults, 3.0)); 177 groups_.push_back(new Group(kNoMaxResultsLimit, 2.0)); 178 groups_.push_back(new Group(kMaxWebstoreResults, 1.0)); 179 groups_.push_back(new Group(kMaxPeopleResults, 0.0)); 180} 181 182void Mixer::AddProviderToGroup(GroupId group, SearchProvider* provider) { 183 size_t group_index = static_cast<size_t>(group); 184 groups_[group_index]->AddProvider(provider); 185} 186 187void Mixer::MixAndPublish(const KnownResults& known_results) { 188 FetchResults(known_results); 189 190 SortedResults results; 191 results.reserve(kMaxResults); 192 193 // Adds main group and web store results first. 194 results.insert(results.end(), 195 groups_[MAIN_GROUP]->results().begin(), 196 groups_[MAIN_GROUP]->results().end()); 197 results.insert(results.end(), 198 groups_[WEBSTORE_GROUP]->results().begin(), 199 groups_[WEBSTORE_GROUP]->results().end()); 200 results.insert(results.end(), 201 groups_[PEOPLE_GROUP]->results().begin(), 202 groups_[PEOPLE_GROUP]->results().end()); 203 204 // Collapse duplicate apps from local and web store. 205 RemoveDuplicates(&results); 206 207 DCHECK_GE(kMaxResults, results.size()); 208 size_t remaining_slots = kMaxResults - results.size(); 209 210 // Reserves at least one slot for the omnibox result. If there is no available 211 // slot for omnibox results, removes the last one from web store. 212 const size_t omnibox_results = groups_[OMNIBOX_GROUP]->results().size(); 213 if (!remaining_slots && omnibox_results) 214 results.pop_back(); 215 216 remaining_slots = std::min(kMaxResults - results.size(), omnibox_results); 217 results.insert(results.end(), 218 groups_[OMNIBOX_GROUP]->results().begin(), 219 groups_[OMNIBOX_GROUP]->results().begin() + remaining_slots); 220 221 std::sort(results.begin(), results.end()); 222 RemoveDuplicates(&results); 223 if (results.size() > kMaxResults) 224 results.resize(kMaxResults); 225 226 Publish(results, ui_results_); 227} 228 229void Mixer::FetchResults(const KnownResults& known_results) { 230 for (Groups::iterator group_it = groups_.begin(); 231 group_it != groups_.end(); 232 ++group_it) { 233 (*group_it)->FetchResults(known_results); 234 } 235} 236 237} // namespace app_list 238