Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(257)

Side by Side Diff: chrome/browser/ui/app_list/apps_model_builder.cc

Issue 10957048: app_list: Use NTP ordering in ExtensionSorting to sort apps. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/ui/app_list/apps_model_builder.h" 5 #include "chrome/browser/ui/app_list/apps_model_builder.h"
6 6
7 #include "base/i18n/case_conversion.h" 7 #include <algorithm>
8 #include "base/utf_string_conversions.h" 8 #include <vector>
9 #include "chrome/browser/browser_process.h" 9
10 #include "chrome/browser/extensions/extension_prefs.h"
10 #include "chrome/browser/extensions/extension_service.h" 11 #include "chrome/browser/extensions/extension_service.h"
12 #include "chrome/browser/prefs/pref_service.h"
11 #include "chrome/browser/profiles/profile.h" 13 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/ui/app_list/extension_app_item.h" 14 #include "chrome/browser/ui/app_list/extension_app_item.h"
13 #include "chrome/common/chrome_notification_types.h" 15 #include "chrome/common/chrome_notification_types.h"
14 #include "chrome/common/extensions/extension.h" 16 #include "chrome/common/extensions/extension.h"
15 #include "content/public/browser/notification_service.h" 17 #include "content/public/browser/notification_service.h"
16 #include "ui/base/l10n/l10n_util_collator.h"
17 18
18 using extensions::Extension; 19 using extensions::Extension;
19 20
20 namespace { 21 namespace {
21 22
22 // TODO(benwells): Get the list of special apps from the controller. 23 typedef std::vector<ExtensionAppItem*> Apps;
23 const char* kSpecialApps[] = {
24 extension_misc::kChromeAppId,
25 extension_misc::kWebStoreAppId,
26 };
27 24
28 // ModelItemSortData provides a string key to sort with 25 bool AppPrecedes(const ExtensionAppItem* app1, const ExtensionAppItem* app2) {
sky 2012/09/21 23:10:53 Is there some way to share this with what the NTP
xiyuan 2012/09/24 20:39:40 NTP sorts its app list in js.
29 // l10n_util::StringComparator. 26 const syncer::StringOrdinal& page1 = app1->GetPageOrdinal();
30 struct ModelItemSortData { 27 const syncer::StringOrdinal& page2 = app2->GetPageOrdinal();
31 explicit ModelItemSortData(app_list::AppListItemModel* app) 28 if (page1.LessThan(page2))
32 : app(app), 29 return true;
33 key(base::i18n::ToLower(UTF8ToUTF16(app->title()))) {
34 }
35 30
36 // Needed by StringComparator<Element> in SortVectorWithStringKey, which uses 31 if (page1.Equals(page2))
37 // this method to get a key to sort. 32 return app1->GetAppLaunchOrdinal().LessThan(app2->GetAppLaunchOrdinal());
38 const string16& GetStringKey() const {
39 return key;
40 }
41
42 app_list::AppListItemModel* app;
43 string16 key;
44 };
45
46 // Returns true if given |extension_id| is listed in kSpecialApps.
47 bool IsSpecialApp(const std::string& extension_id) {
48 for (size_t i = 0; i < arraysize(kSpecialApps); ++i) {
49 if (extension_id == kSpecialApps[i])
50 return true;
51 }
52 33
53 return false; 34 return false;
54 } 35 }
55 36
56 } // namespace 37 } // namespace
57 38
58 AppsModelBuilder::AppsModelBuilder(Profile* profile, 39 AppsModelBuilder::AppsModelBuilder(Profile* profile,
59 app_list::AppListModel::Apps* model, 40 app_list::AppListModel::Apps* model,
60 AppListController* controller) 41 AppListController* controller)
61 : profile_(profile), 42 : profile_(profile),
62 controller_(controller), 43 controller_(controller),
63 model_(model), 44 model_(model) {
64 special_apps_count_(0) { 45 extensions::ExtensionPrefs* extension_prefs =
46 profile_->GetExtensionService()->extension_prefs();
47
65 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, 48 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
66 content::Source<Profile>(profile_)); 49 content::Source<Profile>(profile_));
67 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, 50 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
68 content::Source<Profile>(profile_)); 51 content::Source<Profile>(profile_));
52 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LAUNCHER_REORDERED,
53 content::Source<ExtensionSorting>(extension_prefs->extension_sorting()));
69 registrar_.Add(this, chrome::NOTIFICATION_APP_INSTALLED_TO_APPLIST, 54 registrar_.Add(this, chrome::NOTIFICATION_APP_INSTALLED_TO_APPLIST,
70 content::Source<Profile>(profile_)); 55 content::Source<Profile>(profile_));
56
57 pref_change_registrar_.Init(extension_prefs->pref_service());
58 pref_change_registrar_.Add(extensions::ExtensionPrefs::kExtensionsPref, this);
71 } 59 }
72 60
73 AppsModelBuilder::~AppsModelBuilder() { 61 AppsModelBuilder::~AppsModelBuilder() {
74 } 62 }
75 63
76 void AppsModelBuilder::Build() { 64 void AppsModelBuilder::Build() {
77 DCHECK(model_ && model_->item_count() == 0); 65 DCHECK(model_ && model_->item_count() == 0);
78 CreateSpecialApps();
79 66
80 Apps apps; 67 PopulateApps();
81 GetExtensionApps(&apps);
82
83 SortAndPopulateModel(apps);
84 HighlightApp(); 68 HighlightApp();
85 } 69 }
86 70
87 void AppsModelBuilder::SortAndPopulateModel(const Apps& apps) { 71 void AppsModelBuilder::PopulateApps() {
88 // Just return if there is nothing to populate.
89 if (apps.empty())
90 return;
91
92 // Sort apps case insensitive alphabetically.
93 std::vector<ModelItemSortData> sorted;
94 for (Apps::const_iterator it = apps.begin(); it != apps.end(); ++it)
95 sorted.push_back(ModelItemSortData(*it));
96
97 l10n_util::SortVectorWithStringKey(g_browser_process->GetApplicationLocale(),
98 &sorted,
99 false /* needs_stable_sort */);
100 for (std::vector<ModelItemSortData>::const_iterator it = sorted.begin();
101 it != sorted.end();
102 ++it) {
103 model_->Add(it->app);
104 }
105 }
106
107 void AppsModelBuilder::InsertItemByTitle(app_list::AppListItemModel* app) {
108 DCHECK(model_);
109
110 icu::Locale locale(g_browser_process->GetApplicationLocale().c_str());
111 UErrorCode error = U_ZERO_ERROR;
112 scoped_ptr<icu::Collator> collator(
113 icu::Collator::createInstance(locale, error));
114 if (U_FAILURE(error))
115 collator.reset();
116
117 l10n_util::StringComparator<string16> c(collator.get());
118 ModelItemSortData data(app);
119 for (size_t i = special_apps_count_; i < model_->item_count(); ++i) {
120 ModelItemSortData current(model_->GetItemAt(i));
121 if (!c(current.key, data.key)) {
122 model_->AddAt(i, app);
123 return;
124 }
125 }
126 model_->Add(app);
127 }
128
129 void AppsModelBuilder::GetExtensionApps(Apps* apps) {
130 DCHECK(profile_);
131 ExtensionService* service = profile_->GetExtensionService(); 72 ExtensionService* service = profile_->GetExtensionService();
132 if (!service) 73 if (!service)
133 return; 74 return;
134 75
135 // Get extension apps. 76 Apps apps;
136 const ExtensionSet* extensions = service->extensions(); 77 const ExtensionSet* extensions = service->extensions();
137 for (ExtensionSet::const_iterator app = extensions->begin(); 78 for (ExtensionSet::const_iterator app = extensions->begin();
138 app != extensions->end(); ++app) { 79 app != extensions->end(); ++app) {
139 if ((*app)->ShouldDisplayInLauncher() && 80 if ((*app)->ShouldDisplayInLauncher())
140 !IsSpecialApp((*app)->id())) { 81 apps.push_back(new ExtensionAppItem(profile_, *app, controller_));
141 apps->push_back(new ExtensionAppItem(profile_, *app, controller_));
142 }
143 } 82 }
144 83
145 #if defined(OS_CHROMEOS) 84 #if defined(OS_CHROMEOS)
146 // Explicitly add Talk extension if it's installed and enabled. 85 // Explicitly add Talk extension if it's installed and enabled.
147 // Add it here instead of in CreateSpecialApps() so it sorts naturally. 86 // Add it here instead of in CreateSpecialApps() so it sorts naturally.
148 // Prefer debug > alpha > beta > production version. 87 // Prefer debug > alpha > beta > production version.
149 const char* kTalkIds[] = { 88 const char* kTalkIds[] = {
150 extension_misc::kTalkDebugExtensionId, 89 extension_misc::kTalkDebugExtensionId,
151 extension_misc::kTalkAlphaExtensionId, 90 extension_misc::kTalkAlphaExtensionId,
152 extension_misc::kTalkBetaExtensionId, 91 extension_misc::kTalkBetaExtensionId,
153 extension_misc::kTalkExtensionId, 92 extension_misc::kTalkExtensionId,
154 }; 93 };
155 for (size_t i = 0; i < arraysize(kTalkIds); ++i) { 94 for (size_t i = 0; i < arraysize(kTalkIds); ++i) {
156 const Extension* talk = service->GetExtensionById( 95 const Extension* talk = service->GetExtensionById(
157 kTalkIds[i], false /*include_disabled*/); 96 kTalkIds[i], false /*include_disabled*/);
158 if (talk) { 97 if (talk) {
159 apps->push_back(new ExtensionAppItem(profile_, talk, controller_)); 98 apps.push_back(new ExtensionAppItem(profile_, talk, controller_));
160 break; 99 break;
161 } 100 }
162 } 101 }
163 #endif // OS_CHROMEOS 102 #endif // OS_CHROMEOS
103
104 if (apps.empty())
105 return;
106
107 std::sort(apps.begin(), apps.end(), &AppPrecedes);
108
109 for (Apps::const_iterator it = apps.begin();
110 it != apps.end();
111 ++it) {
112 model_->Add(*it);
113 }
164 } 114 }
165 115
166 void AppsModelBuilder::CreateSpecialApps() { 116 void AppsModelBuilder::GetAppsRange(size_t* start, size_t* end) {
167 DCHECK(model_ && model_->item_count() == 0); 117 // For now, all items are apps.
118 if (start)
119 *start = 0;
120 if (end)
121 *end = model_->item_count() - 1;
sky 2012/09/21 23:10:53 Is it possible item_count is 0, so that this overf
xiyuan 2012/09/24 20:39:40 There should have at least two bundled apps. So it
122 }
168 123
169 bool is_guest_session = Profile::IsGuestSession(); 124 void AppsModelBuilder::ResortApps() {
170 ExtensionService* service = profile_->GetExtensionService(); 125 size_t start;
171 DCHECK(service); 126 size_t end;
172 for (size_t i = 0; i < arraysize(kSpecialApps); ++i) { 127 GetAppsRange(&start, &end);
173 const std::string extension_id(kSpecialApps[i]);
174 if (is_guest_session && extension_id == extension_misc::kWebStoreAppId)
175 continue;
176 128
177 const Extension* extension = service->GetInstalledExtension(extension_id); 129 Apps apps;
178 if (!extension) 130 for (size_t i = start; i <= end; ++i)
179 continue; 131 apps.push_back(GetAppAt(i));
180 132
181 model_->Add(new ExtensionAppItem(profile_, extension, controller_)); 133 if (apps.empty())
134 return;
135
136 std::sort(apps.begin(), apps.end(), &AppPrecedes);
137
138 for (size_t i = 0; i < apps.size(); ++i) {
sky 2012/09/21 23:10:53 Add a comment as to what this is doing.
xiyuan 2012/09/24 20:39:40 Done.
139 ExtensionAppItem* app_item = apps[i];
140
141 const size_t insert_index = start + i;
142
143 bool found = false;
144 size_t index = 0;
145
146 for (size_t j = insert_index; j <= end; ++j) {
sky 2012/09/21 23:10:53 Can this use FindApp?
xiyuan 2012/09/24 20:39:40 FindApp is based on extension id. Here we are usin
147 if (GetAppAt(j) == app_item) {
148 found = true;
149 index = j;
150 break;
151 }
152 }
153
154 if (found) {
155 if (index != insert_index) {
156 model_->RemoveAt(index);
157 model_->AddAt(insert_index, app_item);
158 }
159 } else {
160 NOTREACHED();
161 }
182 } 162 }
163 }
183 164
184 special_apps_count_ = model_->item_count(); 165 void AppsModelBuilder::InsertApp(ExtensionAppItem* app) {
166 DCHECK(model_);
167
168 size_t start;
169 size_t end;
170 GetAppsRange(&start, &end);
171
172 syncer::StringOrdinal page_ordinal = app->GetPageOrdinal();
sky 2012/09/21 23:10:53 const syncer::StringOrdinal& ?
xiyuan 2012/09/24 20:39:40 Removed. It's left-over from a CL that passes |pag
173 for (size_t i = start; i <= end; ++i) {
174 if (!AppPrecedes(GetAppAt(i), app)) {
sky 2012/09/21 23:10:53 It would be nice to use a binary search here.
xiyuan 2012/09/24 20:39:40 Done.
175 // TODO(xiyuan): Set up page id properly.
176 model_->AddAt(i, app);
177 return;
178 }
179 }
180 model_->Add(app);
185 } 181 }
186 182
187 int AppsModelBuilder::FindApp(const std::string& app_id) { 183 int AppsModelBuilder::FindApp(const std::string& app_id) {
188 DCHECK(model_); 184 DCHECK(model_);
189 for (size_t i = special_apps_count_; i < model_->item_count(); ++i) {
190 ChromeAppListItem* app =
191 static_cast<ChromeAppListItem*>(model_->GetItemAt(i));
192 if (app->type() != ChromeAppListItem::TYPE_APP)
193 continue;
194 185
195 ExtensionAppItem* extension_item = static_cast<ExtensionAppItem*>(app); 186 size_t start;
196 if (extension_item->extension_id() == app_id) 187 size_t end;
188 GetAppsRange(&start, &end);
189
190 for (size_t i = 0; i < model_->item_count(); ++i) {
191 if (GetAppAt(i)->extension_id() == app_id)
197 return i; 192 return i;
198 } 193 }
199 194
200 return -1; 195 return -1;
201 } 196 }
202 197
203 void AppsModelBuilder::HighlightApp() { 198 void AppsModelBuilder::HighlightApp() {
204 DCHECK(model_); 199 DCHECK(model_);
205 if (highlight_app_id_.empty()) 200 if (highlight_app_id_.empty())
206 return; 201 return;
207 202
208 int index = FindApp(highlight_app_id_); 203 int index = FindApp(highlight_app_id_);
209 if (index == -1) 204 if (index == -1)
210 return; 205 return;
211 206
212 model_->GetItemAt(index)->SetHighlighted(true); 207 model_->GetItemAt(index)->SetHighlighted(true);
213 highlight_app_id_.clear(); 208 highlight_app_id_.clear();
214 } 209 }
215 210
211 ExtensionAppItem* AppsModelBuilder::GetAppAt(size_t index) {
212 DCHECK_LT(index, model_->item_count());
213 ChromeAppListItem* item =
214 static_cast<ChromeAppListItem*>(model_->GetItemAt(index));
215 DCHECK(item->type() == ChromeAppListItem::TYPE_APP);
sky 2012/09/21 23:10:53 DCHECK_EQ
xiyuan 2012/09/24 20:39:40 Done.
216
217 return static_cast<ExtensionAppItem*>(item);
218 }
219
216 void AppsModelBuilder::Observe(int type, 220 void AppsModelBuilder::Observe(int type,
217 const content::NotificationSource& source, 221 const content::NotificationSource& source,
218 const content::NotificationDetails& details) { 222 const content::NotificationDetails& details) {
219 switch (type) { 223 switch (type) {
220 case chrome::NOTIFICATION_EXTENSION_LOADED: { 224 case chrome::NOTIFICATION_EXTENSION_LOADED: {
221 const Extension* extension = 225 const Extension* extension =
222 content::Details<const Extension>(details).ptr(); 226 content::Details<const Extension>(details).ptr();
223 if (!extension->ShouldDisplayInLauncher()) 227 if (!extension->ShouldDisplayInLauncher())
224 return; 228 return;
225 229
226 if (FindApp(extension->id()) != -1) 230 if (FindApp(extension->id()) != -1)
227 return; 231 return;
228 232
229 InsertItemByTitle(new ExtensionAppItem(profile_, extension, controller_)); 233 InsertApp(new ExtensionAppItem(profile_, extension, controller_));
230 HighlightApp(); 234 HighlightApp();
231 break; 235 break;
232 } 236 }
233 case chrome::NOTIFICATION_EXTENSION_UNLOADED: { 237 case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
234 const Extension* extension = 238 const Extension* extension =
235 content::Details<extensions::UnloadedExtensionInfo>( 239 content::Details<extensions::UnloadedExtensionInfo>(
236 details)->extension; 240 details)->extension;
237 int index = FindApp(extension->id()); 241 int index = FindApp(extension->id());
238 if (index >= 0) 242 if (index >= 0)
239 model_->DeleteAt(index); 243 model_->DeleteAt(index);
240 break; 244 break;
241 } 245 }
246 case chrome::NOTIFICATION_EXTENSION_LAUNCHER_REORDERED: {
247 ResortApps();
248 break;
249 }
242 case chrome::NOTIFICATION_APP_INSTALLED_TO_APPLIST: { 250 case chrome::NOTIFICATION_APP_INSTALLED_TO_APPLIST: {
243 highlight_app_id_ = *content::Details<const std::string>(details).ptr(); 251 highlight_app_id_ = *content::Details<const std::string>(details).ptr();
244 HighlightApp(); 252 HighlightApp();
245 break; 253 break;
246 } 254 }
255 case chrome::NOTIFICATION_PREF_CHANGED: {
256 ResortApps();
257 break;
258 }
247 default: 259 default:
248 NOTREACHED(); 260 NOTREACHED();
249 } 261 }
250 } 262 }
OLDNEW
« no previous file with comments | « chrome/browser/ui/app_list/apps_model_builder.h ('k') | chrome/browser/ui/app_list/apps_model_builder_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698