Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 } |
| OLD | NEW |