OLD | NEW |
| (Empty) |
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 | |
3 // found in the LICENSE file. | |
4 | |
5 #include "chrome/browser/ui/views/aura/launcher/chrome_launcher_delegate.h" | |
6 | |
7 #include "ash/launcher/launcher_model.h" | |
8 #include "ash/launcher/launcher_types.h" | |
9 #include "ash/wm/window_util.h" | |
10 #include "base/command_line.h" | |
11 #include "base/utf_string_conversions.h" | |
12 #include "base/values.h" | |
13 #include "chrome/browser/defaults.h" | |
14 #include "chrome/browser/extensions/extension_service.h" | |
15 #include "chrome/browser/prefs/incognito_mode_prefs.h" | |
16 #include "chrome/browser/prefs/pref_service.h" | |
17 #include "chrome/browser/prefs/scoped_user_pref_update.h" | |
18 #include "chrome/browser/profiles/profile.h" | |
19 #include "chrome/browser/profiles/profile_manager.h" | |
20 #include "chrome/browser/tabs/tab_strip_model.h" | |
21 #include "chrome/browser/ui/browser.h" | |
22 #include "chrome/browser/ui/browser_window.h" | |
23 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" | |
24 #include "chrome/browser/ui/views/aura/launcher/launcher_context_menu.h" | |
25 #include "chrome/browser/ui/views/aura/launcher/launcher_icon_loader.h" | |
26 #include "chrome/browser/ui/views/aura/launcher/launcher_updater.h" | |
27 #include "chrome/browser/web_applications/web_app.h" | |
28 #include "chrome/common/chrome_notification_types.h" | |
29 #include "chrome/common/extensions/extension.h" | |
30 #include "chrome/common/extensions/extension_resource.h" | |
31 #include "chrome/common/pref_names.h" | |
32 #include "content/public/browser/notification_service.h" | |
33 #include "content/public/browser/web_contents.h" | |
34 #include "grit/theme_resources.h" | |
35 #include "ui/aura/window.h" | |
36 #include "ui/views/widget/widget.h" | |
37 | |
38 namespace { | |
39 | |
40 // See description in PersistPinnedState(). | |
41 const char kAppIDPath[] = "id"; | |
42 const char kAppTypePath[] = "type"; | |
43 const char kAppTypeTab[] = "tab"; | |
44 const char kAppTypeWindow[] = "window"; | |
45 | |
46 } // namespace | |
47 | |
48 // ChromeLauncherDelegate::Item ------------------------------------------------ | |
49 | |
50 ChromeLauncherDelegate::Item::Item() | |
51 : item_type(TYPE_TABBED_BROWSER), | |
52 app_type(APP_TYPE_WINDOW), | |
53 updater(NULL), | |
54 pinned(false) { | |
55 } | |
56 | |
57 ChromeLauncherDelegate::Item::~Item() { | |
58 } | |
59 | |
60 // ChromeLauncherDelegate ------------------------------------------------------ | |
61 | |
62 // static | |
63 ChromeLauncherDelegate* ChromeLauncherDelegate::instance_ = NULL; | |
64 | |
65 ChromeLauncherDelegate::ChromeLauncherDelegate(Profile* profile, | |
66 ash::LauncherModel* model) | |
67 : model_(model), | |
68 profile_(profile) { | |
69 if (!profile_) { | |
70 // Use the original profile as on chromeos we may get a temporary off the | |
71 // record profile. | |
72 profile_ = ProfileManager::GetDefaultProfile()->GetOriginalProfile(); | |
73 } | |
74 instance_ = this; | |
75 model_->AddObserver(this); | |
76 app_icon_loader_.reset(new LauncherIconLoader(profile_, this)); | |
77 registrar_.Add(this, | |
78 chrome::NOTIFICATION_EXTENSION_UNLOADED, | |
79 content::Source<Profile>(profile_)); | |
80 } | |
81 | |
82 ChromeLauncherDelegate::~ChromeLauncherDelegate() { | |
83 model_->RemoveObserver(this); | |
84 for (IDToItemMap::iterator i = id_to_item_map_.begin(); | |
85 i != id_to_item_map_.end(); ++i) { | |
86 model_->RemoveItemAt(model_->ItemIndexByID(i->first)); | |
87 } | |
88 if (instance_ == this) | |
89 instance_ = NULL; | |
90 } | |
91 | |
92 void ChromeLauncherDelegate::Init() { | |
93 const base::ListValue* pinned_apps = | |
94 profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps); | |
95 for (size_t i = 0; i < pinned_apps->GetSize(); ++i) { | |
96 DictionaryValue* app = NULL; | |
97 if (pinned_apps->GetDictionary(i, &app)) { | |
98 std::string app_id, type_string; | |
99 if (app->GetString(kAppIDPath, &app_id) && | |
100 app->GetString(kAppTypePath, &type_string) && | |
101 app_icon_loader_->IsValidID(app_id)) { | |
102 AppType app_type = (type_string == kAppTypeWindow) ? | |
103 APP_TYPE_WINDOW : APP_TYPE_TAB; | |
104 CreateAppLauncherItem(NULL, app_id, app_type); | |
105 } | |
106 } | |
107 } | |
108 } | |
109 | |
110 // static | |
111 void ChromeLauncherDelegate::RegisterUserPrefs(PrefService* user_prefs) { | |
112 // TODO: If we want to support multiple profiles this will likely need to be | |
113 // pushed to local state and we'll need to track profile per item. | |
114 user_prefs->RegisterListPref(prefs::kPinnedLauncherApps, | |
115 PrefService::SYNCABLE_PREF); | |
116 } | |
117 | |
118 ash::LauncherID ChromeLauncherDelegate::CreateTabbedLauncherItem( | |
119 LauncherUpdater* updater) { | |
120 // Tabbed items always get a new item. Put the tabbed item before the app | |
121 // tabs. If there are no app tabs put it at the end. | |
122 int index = static_cast<int>(model_->items().size()); | |
123 for (IDToItemMap::const_iterator i = id_to_item_map_.begin(); | |
124 i != id_to_item_map_.end(); ++i) { | |
125 if (i->second.updater == updater) { | |
126 DCHECK_EQ(TYPE_APP, i->second.item_type); | |
127 index = std::min(index, model_->ItemIndexByID(i->first)); | |
128 } | |
129 } | |
130 ash::LauncherID id = model_->next_id(); | |
131 ash::LauncherItem item(ash::TYPE_TABBED); | |
132 model_->Add(index, item); | |
133 DCHECK(id_to_item_map_.find(id) == id_to_item_map_.end()); | |
134 id_to_item_map_[id].item_type = TYPE_TABBED_BROWSER; | |
135 id_to_item_map_[id].updater = updater; | |
136 return id; | |
137 } | |
138 | |
139 ash::LauncherID ChromeLauncherDelegate::CreateAppLauncherItem( | |
140 LauncherUpdater* updater, | |
141 const std::string& app_id, | |
142 AppType app_type) { | |
143 // See if we have a closed item that matches the app. | |
144 if (updater) { | |
145 for (IDToItemMap::iterator i = id_to_item_map_.begin(); | |
146 i != id_to_item_map_.end(); ++i) { | |
147 if (i->second.updater == NULL && i->second.app_id == app_id && | |
148 i->second.app_type == app_type) { | |
149 i->second.updater = updater; | |
150 return i->first; | |
151 } | |
152 } | |
153 } | |
154 | |
155 // Newly created apps go after all existing apps. If there are no apps put it | |
156 // at after the tabbed item, and if there is no tabbed item put it at the end. | |
157 int item_count = static_cast<int>(model_->items().size()); | |
158 int min_app_index = item_count; | |
159 int min_tab_index = min_app_index; | |
160 if (updater) { | |
161 for (IDToItemMap::const_iterator i = id_to_item_map_.begin(); | |
162 i != id_to_item_map_.end(); ++i) { | |
163 if (i->second.updater == updater) { | |
164 if (i->second.item_type == TYPE_APP) { | |
165 min_app_index = | |
166 std::min(min_app_index, model_->ItemIndexByID(i->first)); | |
167 } else { | |
168 min_tab_index = | |
169 std::min(min_app_index, model_->ItemIndexByID(i->first)); | |
170 } | |
171 } | |
172 } | |
173 } | |
174 int insert_index = min_app_index != item_count ? | |
175 min_app_index : std::min(item_count, min_tab_index + 1); | |
176 ash::LauncherID id = model_->next_id(); | |
177 ash::LauncherItem item(ash::TYPE_APP); | |
178 model_->Add(insert_index, item); | |
179 DCHECK(id_to_item_map_.find(id) == id_to_item_map_.end()); | |
180 id_to_item_map_[id].item_type = TYPE_APP; | |
181 id_to_item_map_[id].app_type = app_type; | |
182 id_to_item_map_[id].app_id = app_id; | |
183 id_to_item_map_[id].updater = updater; | |
184 id_to_item_map_[id].pinned = updater == NULL; | |
185 | |
186 app_icon_loader_->FetchImage(app_id); | |
187 return id; | |
188 } | |
189 | |
190 void ChromeLauncherDelegate::ConvertAppToTabbed(ash::LauncherID id) { | |
191 DCHECK(id_to_item_map_.find(id) != id_to_item_map_.end()); | |
192 DCHECK_EQ(TYPE_APP, id_to_item_map_[id].item_type); | |
193 DCHECK(!id_to_item_map_[id].pinned); | |
194 id_to_item_map_[id].item_type = TYPE_TABBED_BROWSER; | |
195 id_to_item_map_[id].app_id.clear(); | |
196 } | |
197 | |
198 void ChromeLauncherDelegate::ConvertTabbedToApp(ash::LauncherID id, | |
199 const std::string& app_id, | |
200 AppType app_type) { | |
201 DCHECK(id_to_item_map_.find(id) != id_to_item_map_.end()); | |
202 DCHECK_EQ(TYPE_TABBED_BROWSER, id_to_item_map_[id].item_type); | |
203 DCHECK(!id_to_item_map_[id].pinned); | |
204 id_to_item_map_[id].item_type = TYPE_APP; | |
205 id_to_item_map_[id].app_type = app_type; | |
206 id_to_item_map_[id].app_id = app_id; | |
207 | |
208 ash::LauncherItem item(ash::TYPE_APP); | |
209 item.id = id; | |
210 model_->Set(model_->ItemIndexByID(id), item); | |
211 | |
212 app_icon_loader_->FetchImage(app_id); | |
213 } | |
214 | |
215 void ChromeLauncherDelegate::LauncherItemClosed(ash::LauncherID id) { | |
216 DCHECK(id_to_item_map_.find(id) != id_to_item_map_.end()); | |
217 if (id_to_item_map_[id].pinned) { | |
218 // The item is pinned, leave it in the launcher. | |
219 id_to_item_map_[id].updater = NULL; | |
220 } else { | |
221 id_to_item_map_.erase(id); | |
222 model_->RemoveItemAt(model_->ItemIndexByID(id)); | |
223 } | |
224 } | |
225 | |
226 void ChromeLauncherDelegate::AppIDChanged(ash::LauncherID id, | |
227 const std::string& app_id) { | |
228 DCHECK(id_to_item_map_.find(id) != id_to_item_map_.end()); | |
229 id_to_item_map_[id].app_id = app_id; | |
230 PersistPinnedState(); | |
231 | |
232 app_icon_loader_->FetchImage(app_id); | |
233 } | |
234 | |
235 bool ChromeLauncherDelegate::HasClosedAppItem(const std::string& app_id, | |
236 AppType app_type) { | |
237 for (IDToItemMap::const_iterator i = id_to_item_map_.begin(); | |
238 i != id_to_item_map_.end(); ++i) { | |
239 if (!i->second.updater && i->second.item_type == TYPE_APP && | |
240 i->second.app_type == app_type && i->second.app_id == app_id) | |
241 return true; | |
242 } | |
243 return false; | |
244 } | |
245 | |
246 void ChromeLauncherDelegate::Pin(ash::LauncherID id) { | |
247 DCHECK(id_to_item_map_.find(id) != id_to_item_map_.end()); | |
248 id_to_item_map_[id].pinned = true; | |
249 PersistPinnedState(); | |
250 } | |
251 | |
252 void ChromeLauncherDelegate::Unpin(ash::LauncherID id) { | |
253 DCHECK(id_to_item_map_.find(id) != id_to_item_map_.end()); | |
254 id_to_item_map_[id].pinned = false; | |
255 if (!id_to_item_map_[id].updater) | |
256 LauncherItemClosed(id); | |
257 PersistPinnedState(); | |
258 } | |
259 | |
260 bool ChromeLauncherDelegate::IsPinned(ash::LauncherID id) { | |
261 DCHECK(id_to_item_map_.find(id) != id_to_item_map_.end()); | |
262 return id_to_item_map_[id].pinned; | |
263 } | |
264 | |
265 void ChromeLauncherDelegate::TogglePinned(ash::LauncherID id) { | |
266 if (id_to_item_map_.find(id) == id_to_item_map_.end()) | |
267 return; // May happen if item closed with menu open. | |
268 | |
269 if (IsPinned(id)) | |
270 Unpin(id); | |
271 else | |
272 Pin(id); | |
273 } | |
274 | |
275 bool ChromeLauncherDelegate::IsPinnable(ash::LauncherID id) { | |
276 return id_to_item_map_.find(id) != id_to_item_map_.end() && | |
277 id_to_item_map_[id].item_type == TYPE_APP; | |
278 } | |
279 | |
280 void ChromeLauncherDelegate::Open(ash::LauncherID id) { | |
281 if (id_to_item_map_.find(id) == id_to_item_map_.end()) | |
282 return; // In case invoked from menu and item closed while menu up. | |
283 | |
284 LauncherUpdater* updater = id_to_item_map_[id].updater; | |
285 if (updater) { | |
286 updater->window()->Show(); | |
287 ash::wm::ActivateWindow(updater->window()); | |
288 TabContentsWrapper* tab = updater->GetTab(id); | |
289 if (tab) { | |
290 updater->tab_model()->ActivateTabAt( | |
291 updater->tab_model()->GetIndexOfTabContents(tab), true); | |
292 } | |
293 } else { | |
294 DCHECK_EQ(TYPE_APP, id_to_item_map_[id].item_type); | |
295 if (id_to_item_map_[id].app_type == APP_TYPE_TAB) { | |
296 const Extension* extension = | |
297 profile_->GetExtensionService()->GetInstalledExtension( | |
298 id_to_item_map_[id].app_id); | |
299 DCHECK(extension); | |
300 Browser::OpenApplicationTab(GetProfileForNewWindows(), extension, GURL(), | |
301 NEW_FOREGROUND_TAB); | |
302 if (id_to_item_map_[id].updater) | |
303 id_to_item_map_[id].updater->window()->Show(); | |
304 } else { | |
305 std::string app_name = web_app::GenerateApplicationNameFromExtensionId( | |
306 id_to_item_map_[id].app_id); | |
307 Browser* browser = Browser::CreateForApp( | |
308 Browser::TYPE_POPUP, app_name, gfx::Rect(), | |
309 GetProfileForNewWindows()); | |
310 browser->window()->Show(); | |
311 } | |
312 } | |
313 } | |
314 | |
315 void ChromeLauncherDelegate::Close(ash::LauncherID id) { | |
316 if (id_to_item_map_.find(id) == id_to_item_map_.end()) | |
317 return; // May happen if menu closed. | |
318 | |
319 if (!id_to_item_map_[id].updater) | |
320 return; // TODO: maybe should treat as unpin? | |
321 | |
322 TabContentsWrapper* tab = id_to_item_map_[id].updater->GetTab(id); | |
323 if (tab) { | |
324 content::WebContentsDelegate* delegate = | |
325 tab->web_contents()->GetDelegate(); | |
326 if (delegate) | |
327 delegate->CloseContents(tab->web_contents()); | |
328 else | |
329 delete tab; | |
330 } else { | |
331 views::Widget* widget = views::Widget::GetWidgetForNativeView( | |
332 id_to_item_map_[id].updater->window()); | |
333 if (widget) | |
334 widget->Close(); | |
335 } | |
336 } | |
337 | |
338 bool ChromeLauncherDelegate::IsOpen(ash::LauncherID id) { | |
339 return id_to_item_map_.find(id) != id_to_item_map_.end() && | |
340 id_to_item_map_[id].updater != NULL; | |
341 } | |
342 | |
343 ChromeLauncherDelegate::AppType ChromeLauncherDelegate::GetAppType( | |
344 ash::LauncherID id) { | |
345 DCHECK(id_to_item_map_.find(id) != id_to_item_map_.end()); | |
346 return id_to_item_map_[id].app_type; | |
347 } | |
348 | |
349 std::string ChromeLauncherDelegate::GetAppID(TabContentsWrapper* tab) { | |
350 return app_icon_loader_->GetAppID(tab); | |
351 } | |
352 | |
353 void ChromeLauncherDelegate::SetAppImage(const std::string& id, | |
354 SkBitmap* image) { | |
355 for (IDToItemMap::const_iterator i = id_to_item_map_.begin(); | |
356 i != id_to_item_map_.end(); ++i) { | |
357 if (i->second.app_id == id) { | |
358 int index = model_->ItemIndexByID(i->first); | |
359 ash::LauncherItem item = model_->items()[index]; | |
360 item.image = image ? *image : Extension::GetDefaultIcon(true); | |
361 model_->Set(index, item); | |
362 // It's possible we're waiting on more than one item, so don't break. | |
363 } | |
364 } | |
365 } | |
366 | |
367 void ChromeLauncherDelegate::CreateNewWindow() { | |
368 Browser::OpenEmptyWindow(GetProfileForNewWindows()); | |
369 } | |
370 | |
371 void ChromeLauncherDelegate::ItemClicked(const ash::LauncherItem& item) { | |
372 DCHECK(id_to_item_map_.find(item.id) != id_to_item_map_.end()); | |
373 Open(item.id); | |
374 } | |
375 | |
376 int ChromeLauncherDelegate::GetBrowserShortcutResourceId() { | |
377 return IDR_PRODUCT_LOGO_32; | |
378 } | |
379 | |
380 string16 ChromeLauncherDelegate::GetTitle(const ash::LauncherItem& item) { | |
381 DCHECK(id_to_item_map_.find(item.id) != id_to_item_map_.end()); | |
382 LauncherUpdater* updater = id_to_item_map_[item.id].updater; | |
383 if (updater) { | |
384 if (id_to_item_map_[item.id].item_type == TYPE_TABBED_BROWSER) { | |
385 return updater->tab_model()->GetActiveTabContents() ? | |
386 updater->tab_model()->GetActiveTabContents()->web_contents()-> | |
387 GetTitle() : string16(); | |
388 } | |
389 // Fall through to get title from extension. | |
390 } | |
391 const Extension* extension = profile_->GetExtensionService()-> | |
392 GetInstalledExtension(id_to_item_map_[item.id].app_id); | |
393 return extension ? UTF8ToUTF16(extension->name()) : string16(); | |
394 } | |
395 | |
396 ui::MenuModel* ChromeLauncherDelegate::CreateContextMenu( | |
397 const ash::LauncherItem& item) { | |
398 return new LauncherContextMenu(this, item.id); | |
399 } | |
400 | |
401 void ChromeLauncherDelegate::LauncherItemAdded(int index) { | |
402 } | |
403 | |
404 void ChromeLauncherDelegate::LauncherItemRemoved(int index, | |
405 ash::LauncherID id) { | |
406 } | |
407 | |
408 void ChromeLauncherDelegate::LauncherItemMoved( | |
409 int start_index, | |
410 int target_index) { | |
411 ash::LauncherID id = model_->items()[target_index].id; | |
412 if (id_to_item_map_.find(id) != id_to_item_map_.end() && | |
413 id_to_item_map_[id].pinned) { | |
414 PersistPinnedState(); | |
415 } | |
416 } | |
417 | |
418 void ChromeLauncherDelegate::LauncherItemChanged( | |
419 int index, | |
420 const ash::LauncherItem& old_item) { | |
421 } | |
422 | |
423 void ChromeLauncherDelegate::LauncherItemWillChange(int index) { | |
424 } | |
425 | |
426 void ChromeLauncherDelegate::Observe( | |
427 int type, | |
428 const content::NotificationSource& source, | |
429 const content::NotificationDetails& details) { | |
430 DCHECK_EQ(type, chrome::NOTIFICATION_EXTENSION_UNLOADED); | |
431 const Extension* extension = | |
432 content::Details<UnloadedExtensionInfo>(details)->extension; | |
433 UnpinAppsWithID(extension->id()); | |
434 } | |
435 | |
436 void ChromeLauncherDelegate::PersistPinnedState() { | |
437 ListPrefUpdate updater(profile_->GetPrefs(), prefs::kPinnedLauncherApps); | |
438 updater.Get()->Clear(); | |
439 for (size_t i = 0; i < model_->items().size(); ++i) { | |
440 if (model_->items()[i].type == ash::TYPE_APP) { | |
441 ash::LauncherID id = model_->items()[i].id; | |
442 if (id_to_item_map_.find(id) != id_to_item_map_.end() && | |
443 id_to_item_map_[id].pinned) { | |
444 base::DictionaryValue* app_value = new base::DictionaryValue; | |
445 app_value->SetString(kAppIDPath, id_to_item_map_[id].app_id); | |
446 const char* app_type_string = | |
447 id_to_item_map_[id].app_type == APP_TYPE_WINDOW ? | |
448 kAppTypeWindow : kAppTypeTab; | |
449 app_value->SetString(kAppTypePath, app_type_string); | |
450 updater.Get()->Append(app_value); | |
451 } | |
452 } | |
453 } | |
454 } | |
455 | |
456 void ChromeLauncherDelegate::UnpinAppsWithID(const std::string& app_id) { | |
457 for (IDToItemMap::iterator i = id_to_item_map_.begin(); | |
458 i != id_to_item_map_.end(); ) { | |
459 IDToItemMap::iterator current(i); | |
460 ++i; | |
461 if (current->second.app_id == app_id && current->second.pinned) | |
462 Unpin(current->first); | |
463 } | |
464 } | |
465 | |
466 void ChromeLauncherDelegate::SetAppIconLoaderForTest(AppIconLoader* loader) { | |
467 app_icon_loader_.reset(loader); | |
468 } | |
469 | |
470 Profile* ChromeLauncherDelegate::GetProfileForNewWindows() { | |
471 Profile* profile = ProfileManager::GetDefaultProfile(); | |
472 if (browser_defaults::kAlwaysOpenIncognitoWindow && | |
473 IncognitoModePrefs::ShouldLaunchIncognito( | |
474 *CommandLine::ForCurrentProcess(), | |
475 profile->GetPrefs())) { | |
476 profile = profile->GetOffTheRecordProfile(); | |
477 } | |
478 return profile; | |
479 } | |
OLD | NEW |