diff options
author | skuhne@chromium.org <skuhne@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-12-20 23:23:33 +0000 |
---|---|---|
committer | skuhne@chromium.org <skuhne@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-12-20 23:23:33 +0000 |
commit | 983ecb1a0f93e427187ae41baa6cd686954754d6 (patch) | |
tree | c9b5e40060e87acce6277fe793ddeec8b6cc372c | |
parent | a50ae535e1fbc14c1468376aad90908fccda9978 (diff) | |
download | chromium_src-983ecb1a0f93e427187ae41baa6cd686954754d6.zip chromium_src-983ecb1a0f93e427187ae41baa6cd686954754d6.tar.gz chromium_src-983ecb1a0f93e427187ae41baa6cd686954754d6.tar.bz2 |
Adding new Launcher behavior
This is only an intermediate CL - trying to keep the CL's in a handle-able size.
The new features of the Launcher behavior are:
- Group running programs (V1, V2, Browsers) under existing launcher application icons.
- When a V2 app gets started and it is not pinned to the launcher, it will be added for it's lifetime to the launcher (and disappear after the last incarnation goes away).
- Each shown launcher entry can ...
+ .. show a context menu (right click) which can e.g. close all applications of that type.
+ .. activate or create such a type upon first click.
+ .. produce a list of running "apps" upon click when one of the applications has already a focus. [more to come]
+ .. [maybe] produce a fancy hover menu. (at the moment it produces the bubble help)
- The list of apps (as described above) contains ...
+ .. The name of the app itself.
+ .. For each running instance the favicon & the title & (not yet implemented:) an icon for incognito.
- The new features are behind a flag.
The full specification can be seen here:
https://docs.google.com/a/google.com/document/d/1i39rO8uERWwTvV0e0TDtelgGjzU9Wj5L6CldMl9y6lE/edit
This is the second CL for these new features, however there are still several things missing which will be addressed in more CL's:
- More unit tests (activation state tracking, V2 apps)
- The "eye candy menus" (mocks are still missing) ..
+ .. including incognito marker
+ .. a definition how the menus get sorted
+ .. the open question about click or hover menus
+ .. touch integration
- The browser icon should be movable as well
- Rip out old classes and unused complexity - which can only be done after the flag gets removed: At the moment, the changes are trying to stay within the existing architecture to make it possible to switch with a flag.
BUG=164438, 145410
Review URL: https://chromiumcodereview.appspot.com/11552028
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@174270 0039d316-1c4b-4281-b951-d872f2087c98
45 files changed, 1724 insertions, 435 deletions
diff --git a/ash/launcher/launcher_delegate.h b/ash/launcher/launcher_delegate.h index e3eb140..1e3288f 100644 --- a/ash/launcher/launcher_delegate.h +++ b/ash/launcher/launcher_delegate.h @@ -47,6 +47,16 @@ class ASH_EXPORT LauncherDelegate { virtual ui::MenuModel* CreateContextMenu(const LauncherItem& item, aura::RootWindow* root_window) = 0; + // Returns the application menu model for the specified item. There are three + // possible return values: + // - A return of NULL indicates that no menu is wanted for this item. + // - A return of a menu with one item means that only the name of the + // application/item was added and there are no active applications. + // Note: This is useful for hover menus which also show context help. + // - A list containing the title and the active list of items. + // The caller takes ownership of the returned model. + virtual ui::MenuModel* CreateApplicationMenu(const LauncherItem& item) = 0; + // Returns the id of the item associated with the specified window, or 0 if // there isn't one. virtual LauncherID GetIDByWindow(aura::Window* window) = 0; diff --git a/ash/launcher/launcher_model.h b/ash/launcher/launcher_model.h index ce52126..3e2ba59 100644 --- a/ash/launcher/launcher_model.h +++ b/ash/launcher/launcher_model.h @@ -54,6 +54,9 @@ class ASH_EXPORT LauncherModel { // Returns the id assigned to the next item added. LauncherID next_id() const { return next_id_; } + // Returns a reserved id which will not be used by the |LauncherModel|. + LauncherID reserve_external_id() { return next_id_++; } + // Returns an iterator into items() for the item with the specified id, or // items().end() if there is no item with the specified id. LauncherItems::const_iterator ItemByID(LauncherID id) const; @@ -75,6 +78,7 @@ class ASH_EXPORT LauncherModel { // ID assigned to the next item. LauncherID next_id_; + LauncherItems items_; Status status_; ObserverList<LauncherModelObserver> observers_; diff --git a/ash/launcher/launcher_model_unittest.cc b/ash/launcher/launcher_model_unittest.cc index 21aa3f1..fb29267 100644 --- a/ash/launcher/launcher_model_unittest.cc +++ b/ash/launcher/launcher_model_unittest.cc @@ -211,4 +211,32 @@ TEST(LauncherModel, AddIndices) { EXPECT_EQ(TYPE_APP_LIST, model.items()[model.FirstPanelIndex() - 1].type); } +// Assertions around id generation and usage. +TEST(LauncherModel, LauncherIDTests) { + TestLauncherModelObserver observer; + LauncherModel model; + + EXPECT_EQ(2, model.item_count()); + + // Get the next to use ID counter. + LauncherID id = model.next_id(); + + // Calling this function multiple times does not change the returned ID. + EXPECT_EQ(model.next_id(), id); + + // Check that when we reserve a value it will be the previously retrieved ID, + // but it will not change the item count and retrieving the next ID should + // produce something new. + EXPECT_EQ(model.reserve_external_id(), id); + EXPECT_EQ(2, model.item_count()); + LauncherID id2 = model.next_id(); + EXPECT_NE(id2, id); + + // Adding another item to the list should also produce a new ID. + LauncherItem item; + item.type = TYPE_TABBED; + model.Add(item); + EXPECT_NE(model.next_id(), id2); +} + } // namespace ash diff --git a/ash/launcher/launcher_view.cc b/ash/launcher/launcher_view.cc index b4f05ef..4b6bac82 100644 --- a/ash/launcher/launcher_view.cc +++ b/ash/launcher/launcher_view.cc @@ -7,6 +7,7 @@ #include <algorithm> #include "ash/ash_constants.h" +#include "ash/ash_switches.h" #include "ash/launcher/app_list_button.h" #include "ash/launcher/launcher_button.h" #include "ash/launcher/launcher_delegate.h" @@ -19,6 +20,7 @@ #include "ash/shell_delegate.h" #include "ash/wm/shelf_layout_manager.h" #include "base/auto_reset.h" +#include "base/command_line.h" #include "base/memory/scoped_ptr.h" #include "grit/ash_strings.h" #include "grit/ash_resources.h" @@ -950,13 +952,21 @@ void LauncherView::LauncherItemChanged(int model_index, ReflectItemStatus(item, button); break; } - + case TYPE_BROWSER_SHORTCUT: + if (!CommandLine::ForCurrentProcess()->HasSwitch( + ash::switches::kAshEnablePerAppLauncher)) + break; + // Fallthrough for the new Launcher since it needs to show the activation + // change as well. case TYPE_APP_SHORTCUT: case TYPE_PLATFORM_APP: case TYPE_APP_PANEL: { LauncherButton* button = static_cast<LauncherButton*>(view); ReflectItemStatus(item, button); - button->SetImage(item.image); + // The browser shortcut is currently not a "real" item and as such the + // the image is bogous as well. We therefore keep the image as is for it. + if (item.type != TYPE_BROWSER_SHORTCUT) + button->SetImage(item.image); button->SchedulePaint(); break; } @@ -1100,38 +1110,89 @@ void LauncherView::ButtonPressed(views::Button* sender, if (view_index == -1) return; - if (event.IsShiftDown()) - ui::LayerAnimator::set_slow_animation_mode(true); tooltip_->Close(); - switch (model_->items()[view_index].type) { - case TYPE_TABBED: - case TYPE_APP_PANEL: - delegate_->ItemClicked(model_->items()[view_index], event.flags()); - break; + // Collect usage statistics before we decide what to do with the click. + switch (model_->items()[view_index].type) { case TYPE_APP_SHORTCUT: case TYPE_PLATFORM_APP: Shell::GetInstance()->delegate()->RecordUserMetricsAction( UMA_LAUNCHER_CLICK_ON_APP); - delegate_->ItemClicked(model_->items()[view_index], event.flags()); break; case TYPE_APP_LIST: Shell::GetInstance()->delegate()->RecordUserMetricsAction( UMA_LAUNCHER_CLICK_ON_APPLIST_BUTTON); - Shell::GetInstance()->ToggleAppList(GetWidget()->GetNativeView()); break; case TYPE_BROWSER_SHORTCUT: // Click on browser icon is counted in app clicks. Shell::GetInstance()->delegate()->RecordUserMetricsAction( UMA_LAUNCHER_CLICK_ON_APP); + break; - delegate_->OnBrowserShortcutClicked(event.flags()); + case TYPE_TABBED: + case TYPE_APP_PANEL: break; } - if (event.IsShiftDown()) - ui::LayerAnimator::set_slow_animation_mode(false); + + // If the item is already active we show a menu - otherwise we activate + // the item dependent on its type. + // Note that the old launcher has no menu and falls back automatically to + // the click action. + bool call_object_handler = model_->items()[view_index].type == TYPE_APP_LIST; + if (!call_object_handler) { + call_object_handler = + model_->items()[view_index].status != ash::STATUS_ACTIVE; + if (!call_object_handler) { + // ShowListMenuForView only returns true if the menu was shown. + if (ShowListMenuForView(model_->items()[view_index], + sender, + sender->GetBoundsInScreen().CenterPoint())) { + // When the menu was shown it is possible that this got deleted. + return; + } + call_object_handler = true; + } + } + + if (call_object_handler) { + if (event.IsShiftDown()) + ui::LayerAnimator::set_slow_animation_mode(true); + // The menu was not shown and the objects click handler should be called. + switch (model_->items()[view_index].type) { + case TYPE_TABBED: + case TYPE_APP_PANEL: + case TYPE_APP_SHORTCUT: + case TYPE_PLATFORM_APP: + delegate_->ItemClicked(model_->items()[view_index], event.flags()); + break; + case TYPE_APP_LIST: + Shell::GetInstance()->ToggleAppList(GetWidget()->GetNativeView()); + break; + case TYPE_BROWSER_SHORTCUT: + delegate_->OnBrowserShortcutClicked(event.flags()); + break; + } + if (event.IsShiftDown()) + ui::LayerAnimator::set_slow_animation_mode(false); + } + +} + +bool LauncherView::ShowListMenuForView(const LauncherItem& item, + views::View* source, + const gfx::Point& point) { + scoped_ptr<ui::MenuModel> menu_model; + menu_model.reset(delegate_->CreateApplicationMenu(item)); + + // Make sure we have a menu and it has at least one item in addition to the + // application title. + if (!menu_model.get() || menu_model->GetItemCount() <= 1) + return false; + + ShowMenu(menu_model.get(), source, point); + return true; } void LauncherView::ShowContextMenuForView(views::View* source, @@ -1141,7 +1202,7 @@ void LauncherView::ShowContextMenuForView(views::View* source, model_->items()[view_index].type == TYPE_APP_LIST) { view_index = -1; } -#if !defined(OS_MACOSX) + if (view_index == -1) { Shell::GetInstance()->ShowContextMenu(point); return; @@ -1154,7 +1215,14 @@ void LauncherView::ShowContextMenuForView(views::View* source, base::AutoReset<LauncherID> reseter( &context_menu_id_, view_index == -1 ? 0 : model_->items()[view_index].id); - views::MenuModelAdapter menu_model_adapter(menu_model.get()); + + ShowMenu(menu_model.get(), source, point); +} + +void LauncherView::ShowMenu(ui::MenuModel* menu_model, + views::View* source, + const gfx::Point& point) { + views::MenuModelAdapter menu_model_adapter(menu_model); launcher_menu_runner_.reset( new views::MenuRunner(menu_model_adapter.CreateMenu())); // NOTE: if you convert to HAS_MNEMONICS be sure and update menu building @@ -1166,7 +1234,6 @@ void LauncherView::ShowContextMenuForView(views::View* source, return; Shell::GetInstance()->UpdateShelfVisibility(); -#endif } void LauncherView::OnBoundsAnimatorProgressed(views::BoundsAnimator* animator) { diff --git a/ash/launcher/launcher_view.h b/ash/launcher/launcher_view.h index 48c14c7..4c91ade 100644 --- a/ash/launcher/launcher_view.h +++ b/ash/launcher/launcher_view.h @@ -25,6 +25,10 @@ class MenuRunner; class ViewModel; } +namespace ui { +class MenuModel; +} + namespace ash { namespace test { @@ -210,10 +214,25 @@ class ASH_EXPORT LauncherView : public views::View, virtual void ButtonPressed(views::Button* sender, const ui::Event& event) OVERRIDE; + // Show the list of all running items for this |item|. It will return true + // when the menu was shown and false if there were no possible items to + // choose from. |source| specifies the view which is responsible for showing + // the menu and |point| is the origin for the point. + // TODO(skuhne): Depending on the menu type we use in the end (hover vs. + // click), |point| might become obsolete. + bool ShowListMenuForView(const LauncherItem& item, + views::View* source, + const gfx::Point& point); + // Overridden from views::ContextMenuController: virtual void ShowContextMenuForView(views::View* source, const gfx::Point& point) OVERRIDE; + // Show either a context or normal click menu of given |menu_model|. + void ShowMenu(ui::MenuModel* menu_model, + views::View*source, + const gfx::Point& point); + // Overridden from views::BoundsAnimatorObserver: virtual void OnBoundsAnimatorProgressed( views::BoundsAnimator* animator) OVERRIDE; diff --git a/ash/launcher/launcher_view_unittest.cc b/ash/launcher/launcher_view_unittest.cc index 61c247d..2c5ade8 100644 --- a/ash/launcher/launcher_view_unittest.cc +++ b/ash/launcher/launcher_view_unittest.cc @@ -159,6 +159,10 @@ class MockLauncherDelegate : public ash::LauncherDelegate { aura::RootWindow* root_window) OVERRIDE { return NULL; } + virtual ui::MenuModel* CreateApplicationMenu( + const ash::LauncherItem&) OVERRIDE { + return NULL; + } virtual ash::LauncherID GetIDByWindow(aura::Window* window) OVERRIDE { NOTREACHED(); return -1; diff --git a/ash/shell/launcher_delegate_impl.cc b/ash/shell/launcher_delegate_impl.cc index 9cf4ca6..6caeb23 100644 --- a/ash/shell/launcher_delegate_impl.cc +++ b/ash/shell/launcher_delegate_impl.cc @@ -49,6 +49,11 @@ ui::MenuModel* LauncherDelegateImpl::CreateContextMenu( return NULL; } +ui::MenuModel* LauncherDelegateImpl::CreateApplicationMenu( + const ash::LauncherItem& item) { + return NULL; +} + ash::LauncherID LauncherDelegateImpl::GetIDByWindow(aura::Window* window) { return watcher_->GetIDByWindow(window); } diff --git a/ash/shell/launcher_delegate_impl.h b/ash/shell/launcher_delegate_impl.h index 978ddaf..6997360 100644 --- a/ash/shell/launcher_delegate_impl.h +++ b/ash/shell/launcher_delegate_impl.h @@ -33,6 +33,8 @@ class LauncherDelegateImpl : public ash::LauncherDelegate { virtual ui::MenuModel* CreateContextMenu( const ash::LauncherItem& item, aura::RootWindow* root) OVERRIDE; + virtual ui::MenuModel* CreateApplicationMenu( + const ash::LauncherItem&) OVERRIDE; virtual ash::LauncherID GetIDByWindow(aura::Window* window) OVERRIDE; virtual bool IsDraggable(const ash::LauncherItem& item) OVERRIDE; diff --git a/ash/test/test_launcher_delegate.cc b/ash/test/test_launcher_delegate.cc index e708e49..52c1ff0d 100644 --- a/ash/test/test_launcher_delegate.cc +++ b/ash/test/test_launcher_delegate.cc @@ -83,6 +83,11 @@ ui::MenuModel* TestLauncherDelegate::CreateContextMenu( return NULL; } +ui::MenuModel* TestLauncherDelegate::CreateApplicationMenu( + const ash::LauncherItem& item) { + return NULL; +} + ash::LauncherID TestLauncherDelegate::GetIDByWindow(aura::Window* window) { WindowToID::const_iterator found = window_to_id_.find(window); if (found == window_to_id_.end()) diff --git a/ash/test/test_launcher_delegate.h b/ash/test/test_launcher_delegate.h index 38140fa..417d3de 100644 --- a/ash/test/test_launcher_delegate.h +++ b/ash/test/test_launcher_delegate.h @@ -42,6 +42,8 @@ class TestLauncherDelegate : public LauncherDelegate, virtual string16 GetTitle(const LauncherItem& item) OVERRIDE; virtual ui::MenuModel* CreateContextMenu(const LauncherItem& item, aura::RootWindow* root) OVERRIDE; + virtual ui::MenuModel* CreateApplicationMenu( + const LauncherItem& item) OVERRIDE; virtual ash::LauncherID GetIDByWindow(aura::Window* window) OVERRIDE; virtual bool IsDraggable(const ash::LauncherItem& item) OVERRIDE; diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index de9c165..b02d4f1 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -3110,6 +3110,9 @@ Psst! Incognito mode <ph name="SHORTCUT_KEY">$1<ex>(Ctrl+Shift+N)</ex></ph> may Create Application Shortcuts </message> </if> + <message name="IDS_LAUNCHER_CHROME_BROWSER_NAME" desc="The name of the Chrome browser in the Launcher list."> + <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> browser + </message> <message name="IDS_CREATE_SHORTCUTS_LABEL" desc="Contents of the main label in the create application shortcuts dialog explaining the dialog."> Create application shortcuts in the following places: </message> diff --git a/chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.cc b/chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.cc new file mode 100644 index 0000000..2797dd8 --- /dev/null +++ b/chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.cc @@ -0,0 +1,171 @@ +// Copyright (c) 2012 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/ash/launcher/app_shortcut_launcher_item_controller.h" + +#include "ash/wm/window_util.h" +#include "chrome/browser/favicon/favicon_tab_helper.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h" +#include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_finder.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/extensions/native_app_window.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "content/public/browser/web_contents.h" + +using extensions::Extension; + +// Item controller for an app shortcut. Shortcuts track app and launcher ids, +// but do not have any associated windows (opening a shortcut will replace the +// item with the appropriate LauncherItemController type). +AppShortcutLauncherItemController::AppShortcutLauncherItemController( + const std::string& app_id, + ChromeLauncherControllerPerApp* controller) + : LauncherItemController(TYPE_SHORTCUT, app_id, controller), + app_controller_(controller) { + // To detect V1 applications we use their domain and match them against the + // used URL. This will also work with applications like Google Drive. + const Extension* extension = + launcher_controller()->GetExtensionForAppID(app_id); + refocus_url_ = GURL(extension->launch_web_url() + "*"); +} + +AppShortcutLauncherItemController::~AppShortcutLauncherItemController() { +} + +string16 AppShortcutLauncherItemController::GetTitle() { + return GetAppTitle(); +} + +bool AppShortcutLauncherItemController::HasWindow(aura::Window* window) const { + std::vector<content::WebContents*> content = + app_controller_->GetV1ApplicationsFromAppId(app_id()); + for (size_t i = 0; i < content.size(); i++) { + Browser* browser = chrome::FindBrowserWithWebContents(content[i]); + if (browser && browser->window()->GetNativeWindow() == window) + return true; + } + return false; +} + +bool AppShortcutLauncherItemController::IsOpen() const { + return !app_controller_->GetV1ApplicationsFromAppId(app_id()).empty(); +} + +void AppShortcutLauncherItemController::Launch(int event_flags) { + app_controller_->LaunchApp(app_id(), event_flags); +} + +void AppShortcutLauncherItemController::Activate() { + std::vector<content::WebContents*> content = + app_controller_->GetV1ApplicationsFromAppId(app_id()); + if (content.empty()) { + Launch(ui::EF_NONE); + return; + } + Browser* browser = chrome::FindBrowserWithWebContents(content[0]); + TabStripModel* tab_strip = browser->tab_strip_model(); + int index = tab_strip->GetIndexOfWebContents(content[0]); + DCHECK_NE(TabStripModel::kNoTab, index); + tab_strip->ActivateTabAt(index, false); + browser->window()->Show(); + ash::wm::ActivateWindow(browser->window()->GetNativeWindow()); +} + +void AppShortcutLauncherItemController::Close() { + // Close all running 'programs' of this type. + std::vector<content::WebContents*> content = + app_controller_->GetV1ApplicationsFromAppId(app_id()); + for (size_t i = 0; i < content.size(); i++) { + Browser* browser = chrome::FindBrowserWithWebContents(content[i]); + if (!browser) + continue; + TabStripModel* tab_strip = browser->tab_strip_model(); + int index = tab_strip->GetIndexOfWebContents(content[i]); + DCHECK(index != TabStripModel::kNoTab); + tab_strip->CloseWebContentsAt(index, TabStripModel::CLOSE_NONE); + } +} + +void AppShortcutLauncherItemController::Clicked() { + Activate(); +} + +void AppShortcutLauncherItemController::OnRemoved() { + // AppShortcutLauncherItemController is unowned; delete on removal. + delete this; +} + +void AppShortcutLauncherItemController::LauncherItemChanged( + int model_index, + const ash::LauncherItem& old_item) { +} + +ChromeLauncherAppMenuItems* +AppShortcutLauncherItemController::GetApplicationList() { + ChromeLauncherAppMenuItems* items = new ChromeLauncherAppMenuItems; + // Add the application name to the menu. + items->push_back(new ChromeLauncherAppMenuItem(GetTitle(), NULL)); + + std::vector<content::WebContents*> content_list = + GetRunningApplications(); + + for (size_t i = 0; i < content_list.size(); i++) { + content::WebContents* web_contents = content_list[i]; + // Get the icon. + FaviconTabHelper* favicon_tab_helper = + FaviconTabHelper::FromWebContents(web_contents); + gfx::Image app_icon = favicon_tab_helper->GetFavicon(); + items->push_back(new ChromeLauncherAppMenuItemTab( + web_contents->GetTitle(), + app_icon.IsEmpty() ? NULL : &app_icon, + web_contents)); + } + return items; +} + +std::vector<content::WebContents*> +AppShortcutLauncherItemController::GetRunningApplications() { + std::vector<content::WebContents*> items; + + URLPattern refocus_pattern(URLPattern::SCHEME_ALL); + refocus_pattern.SetMatchAllURLs(true); + + if (!refocus_url_.is_empty()) { + refocus_pattern.SetMatchAllURLs(false); + refocus_pattern.Parse(refocus_url_.spec()); + } + + for (BrowserList::const_reverse_iterator it = + BrowserList::begin_last_active(); + it != BrowserList::end_last_active(); ++it) { + Browser* browser = *it; + TabStripModel* tab_strip = browser->tab_strip_model(); + // We start to enumerate from the active index. + int active_index = tab_strip->active_index(); + for (int index = 0; index < tab_strip->count(); index++) { + content::WebContents* web_contents = tab_strip->GetWebContentsAt( + (index + active_index) % tab_strip->count()); + const GURL tab_url = web_contents->GetURL(); + // The following cases are a successful application identification: + // a.) There is a refocus pattern given and it matches. + // or + // b.) The tab was launched as an app of the searched type AND + // either there is no refocus pattern or the url matches it. + // This is needed since a V1 applications can change the URL over + // time, and might therefore loose it's "app" status. + if (refocus_pattern.MatchesURL(tab_url) && + (!refocus_pattern.match_all_urls() || + launcher_controller()->GetPerAppInterface()-> + IsWebContentHandledByApplication(web_contents, app_id()))) + items.push_back(web_contents); + } + } + return items; +} diff --git a/chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.h b/chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.h new file mode 100644 index 0000000..82bdaed --- /dev/null +++ b/chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.h @@ -0,0 +1,55 @@ +// Copyright (c) 2012 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. + +#ifndef CHROME_BROWSER_UI_ASH_LAUNCHER_APP_SHORTCUT_LAUNCHER_ITEM_CONTROLLER_H_ +#define CHROME_BROWSER_UI_ASH_LAUNCHER_APP_SHORTCUT_LAUNCHER_ITEM_CONTROLLER_H_ + +#include <string> + +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h" +#include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" + +namespace aura { +class Window; +} + +class ChromeLauncherController; + +// Item controller for an app shortcut. Shortcuts track app and launcher ids, +// but do not have any associated windows (opening a shortcut will replace the +// item with the appropriate LauncherItemController type). +class AppShortcutLauncherItemController : public LauncherItemController { + public: + AppShortcutLauncherItemController(const std::string& app_id, + ChromeLauncherControllerPerApp* controller); + + virtual ~AppShortcutLauncherItemController(); + + // LauncherItemController overrides: + virtual string16 GetTitle() OVERRIDE; + virtual bool HasWindow(aura::Window* window) const OVERRIDE; + virtual bool IsOpen() const OVERRIDE; + virtual void Launch(int event_flags) OVERRIDE; + virtual void Activate() OVERRIDE; + virtual void Close() OVERRIDE; + virtual void Clicked() OVERRIDE; + virtual void OnRemoved() OVERRIDE; + virtual void LauncherItemChanged( + int model_index, + const ash::LauncherItem& old_item) OVERRIDE; + virtual ChromeLauncherAppMenuItems* GetApplicationList() OVERRIDE; + std::vector<content::WebContents*> GetRunningApplications(); + + // Stores the optional refocus url pattern for this item. + const GURL& refocus_url() const { return refocus_url_; } + void set_refocus_url(const GURL& refocus_url) { refocus_url_ = refocus_url; } + + private: + GURL refocus_url_; + ChromeLauncherControllerPerApp* app_controller_; + + DISALLOW_COPY_AND_ASSIGN(AppShortcutLauncherItemController); +}; + +#endif // CHROME_BROWSER_UI_ASH_LAUNCHER_APP_SHORTCUT_LAUNCHER_ITEM_CONTROLLER_H_ diff --git a/chrome/browser/ui/ash/launcher/browser_launcher_item_controller.cc b/chrome/browser/ui/ash/launcher/browser_launcher_item_controller.cc index 8e99060..67a4107 100644 --- a/chrome/browser/ui/ash/launcher/browser_launcher_item_controller.cc +++ b/chrome/browser/ui/ash/launcher/browser_launcher_item_controller.cc @@ -14,6 +14,7 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" #include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/web_applications/web_app.h" @@ -160,12 +161,19 @@ void BrowserLauncherItemController::OnRemoved() { void BrowserLauncherItemController::LauncherItemChanged( int index, const ash::LauncherItem& old_item) { - if (launcher_model()->items()[index].status == ash::STATUS_ACTIVE && + if (!launcher_controller()->GetPerAppInterface() && + launcher_model()->items()[index].status == ash::STATUS_ACTIVE && old_item.status == ash::STATUS_RUNNING) { Activate(); } } +ChromeLauncherAppMenuItems* +BrowserLauncherItemController::GetApplicationList() { + // This will never be called and the entire class will go away. + return new ChromeLauncherAppMenuItems; +} + void BrowserLauncherItemController::ActiveTabChanged( content::WebContents* old_contents, content::WebContents* new_contents, @@ -230,6 +238,9 @@ void BrowserLauncherItemController::OnWindowPropertyChanged( } void BrowserLauncherItemController::UpdateItemStatus() { + if (launcher_controller()->GetPerAppInterface()) + return; + ash::LauncherItemStatus status; if (ash::wm::IsActiveWindow(window_)) { // Clear attention state if active. @@ -245,6 +256,9 @@ void BrowserLauncherItemController::UpdateItemStatus() { } void BrowserLauncherItemController::UpdateLauncher(content::WebContents* tab) { + if (launcher_controller()->GetPerAppInterface()) + return; + if (type() == TYPE_APP_PANEL) return; // Maintained entirely by ChromeLauncherController. @@ -292,7 +306,8 @@ void BrowserLauncherItemController::UpdateLauncher(content::WebContents* tab) { void BrowserLauncherItemController::UpdateAppState(content::WebContents* tab) { ChromeLauncherController::AppState app_state; - if (tab_model_->GetIndexOfWebContents(tab) == TabStripModel::kNoTab) { + if (!launcher_controller()->GetPerAppInterface() && + tab_model_->GetIndexOfWebContents(tab) == TabStripModel::kNoTab) { app_state = ChromeLauncherController::APP_STATE_REMOVED; } else if (tab_model_->GetActiveWebContents() == tab) { if (ash::wm::IsActiveWindow(window_)) diff --git a/chrome/browser/ui/ash/launcher/browser_launcher_item_controller.h b/chrome/browser/ui/ash/launcher/browser_launcher_item_controller.h index 6c71283..dde726c 100644 --- a/chrome/browser/ui/ash/launcher/browser_launcher_item_controller.h +++ b/chrome/browser/ui/ash/launcher/browser_launcher_item_controller.h @@ -80,6 +80,7 @@ class BrowserLauncherItemController : public LauncherItemController, virtual void OnRemoved() OVERRIDE; virtual void LauncherItemChanged(int index, const ash::LauncherItem& old_item) OVERRIDE; + virtual ChromeLauncherAppMenuItems* GetApplicationList() OVERRIDE; // TabStripModel overrides: virtual void ActiveTabChanged(content::WebContents* old_contents, diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.cc new file mode 100644 index 0000000..fed2925 --- /dev/null +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.cc @@ -0,0 +1,21 @@ +// Copyright (c) 2012 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/ash/launcher/chrome_launcher_app_menu_item.h" + +ChromeLauncherAppMenuItem::ChromeLauncherAppMenuItem(const string16 title, + const gfx::Image* icon) + : title_(title), + icon_(icon ? gfx::Image(*icon) : gfx::Image()) { +} + +ChromeLauncherAppMenuItem::~ChromeLauncherAppMenuItem() { +} + +bool ChromeLauncherAppMenuItem::IsEnabled() const { + return false; +} + +void ChromeLauncherAppMenuItem::Execute() { +} diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h new file mode 100644 index 0000000..a488ad8 --- /dev/null +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h @@ -0,0 +1,37 @@ +// Copyright (c) 2012 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. + +#ifndef CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_APP_MENU_ITEM_H_ +#define CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_APP_MENU_ITEM_H_ + +#include "base/string16.h" +#include "ui/gfx/image/image.h" + +// A description for a menu item. It contains the menu item description as well +// as the function which gets executed upon menu item click. +class ChromeLauncherAppMenuItem { + public: + ChromeLauncherAppMenuItem(const string16 title, const gfx::Image* icon); + + virtual ~ChromeLauncherAppMenuItem(); + + // Retrieves the title for this menu option. + const string16& title() const { return title_; } + + // Retrieves the icon for this menu option. + const gfx::Image& icon() const { return icon_; } + + // Returns true if item is enabled. + virtual bool IsEnabled() const; + + // Executes the option. + virtual void Execute(); + + private: + const string16 title_; + const gfx::Image icon_; + + DISALLOW_COPY_AND_ASSIGN(ChromeLauncherAppMenuItem); +}; +#endif // CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_APP_MENU_ITEM_H_ diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.cc new file mode 100644 index 0000000..e59b9de --- /dev/null +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2012 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/ash/launcher/chrome_launcher_app_menu_item_browser.h" + +#include "ash/wm/window_util.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/common/chrome_notification_types.h" +#include "content/public/browser/notification_service.h" + +ChromeLauncherAppMenuItemBrowser::ChromeLauncherAppMenuItemBrowser( + const string16 title, + const gfx::Image* icon, + Browser* browser) + : ChromeLauncherAppMenuItem(title, icon), + browser_(browser) { + registrar_.Add(this, + chrome::NOTIFICATION_BROWSER_CLOSING, + content::Source<Browser>(browser)); +} + +bool ChromeLauncherAppMenuItemBrowser::IsEnabled() const { + return true; +} + +void ChromeLauncherAppMenuItemBrowser::Execute() { + if (browser_) { + browser_->window()->Show(); + ash::wm::ActivateWindow(browser_->window()->GetNativeWindow()); + } +} + +void ChromeLauncherAppMenuItemBrowser::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + switch (type) { + case chrome::NOTIFICATION_BROWSER_CLOSING: + DCHECK_EQ(browser_, content::Source<Browser>(source).ptr()); + browser_ = NULL; + break; + + default: + NOTREACHED(); + } +} diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.h b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.h new file mode 100644 index 0000000..274d728 --- /dev/null +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.h @@ -0,0 +1,41 @@ +// Copyright (c) 2012 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. + +#ifndef CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_APP_MENU_ITEM_BROWSER_H_ +#define CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_APP_MENU_ITEM_BROWSER_H_ + +#include "base/values.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" + +class Browser; + +// A menu item controller for a running browser. It gets created when an +// application list gets created. It's main purpose is to add the activation +// method to the |ChromeLauncherAppMenuItem| class. +class ChromeLauncherAppMenuItemBrowser : public content::NotificationObserver, + public ChromeLauncherAppMenuItem { + public: + ChromeLauncherAppMenuItemBrowser(const string16 title, + const gfx::Image* icon, + Browser* browser); + virtual bool IsEnabled() const OVERRIDE; + virtual void Execute() OVERRIDE; + + private: + // content::NotificationObserver. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // The browser which is associated which this item. + Browser* browser_; + + content::NotificationRegistrar registrar_; + + DISALLOW_COPY_AND_ASSIGN(ChromeLauncherAppMenuItemBrowser); +}; + +#endif // CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_APP_MENU_ITEM_BROWSER_H_ diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.cc new file mode 100644 index 0000000..dcdcf7b --- /dev/null +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2012 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/ash/launcher/chrome_launcher_app_menu_item_tab.h" + +#include "ash/wm/window_util.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_finder.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" + +ChromeLauncherAppMenuItemTab::ChromeLauncherAppMenuItemTab( + const string16 title, + const gfx::Image* icon, + content::WebContents* content) + : ChromeLauncherAppMenuItem(title, icon), + content::WebContentsObserver(content) { +} + +bool ChromeLauncherAppMenuItemTab::IsEnabled() const { + return true; +} + +void ChromeLauncherAppMenuItemTab::Execute() { + if (!web_contents()) + return; + Browser* browser = chrome::FindBrowserWithWebContents(web_contents()); + if (!browser) + return; + TabStripModel* tab_strip = browser->tab_strip_model(); + int index = tab_strip->GetIndexOfWebContents(web_contents()); + DCHECK(index != TabStripModel::kNoTab); + tab_strip->ActivateTabAt(index, false); + browser->window()->Show(); + // Need this check to prevent unit tests from crashing. + if (browser->window()->GetNativeWindow()) + ash::wm::ActivateWindow(browser->window()->GetNativeWindow()); +} diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h new file mode 100644 index 0000000..403a78c --- /dev/null +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h @@ -0,0 +1,35 @@ +// Copyright (c) 2012 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. + +#ifndef CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_APP_MENU_ITEM_TAB_H_ +#define CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_APP_MENU_ITEM_TAB_H_ + +#include "base/string16.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h" +#include "content/public/browser/web_contents_observer.h" + +namespace content{ +class WebContents; +} + +class TabStripModel; + +// A menu item controller for a running browser tab. It gets created when an +// application/tab list gets created. It's main purpose is to add the +// activation method to the |ChromeLauncherAppMenuItem| class. +class ChromeLauncherAppMenuItemTab + : public ChromeLauncherAppMenuItem, + public content::WebContentsObserver { + public: + ChromeLauncherAppMenuItemTab(const string16 title, + const gfx::Image* icon, + content::WebContents* content); + virtual bool IsEnabled() const OVERRIDE; + virtual void Execute() OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(ChromeLauncherAppMenuItemTab); +}; + +#endif // CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_APP_MENU_ITEM_TAB_H_ diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.cc new file mode 100644 index 0000000..2cc5fc7 --- /dev/null +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.cc @@ -0,0 +1,29 @@ +// Copyright (c) 2012 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/ash/launcher/chrome_launcher_app_menu_item_v2app.h" + +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h" + +ChromeLauncherAppMenuItemV2App::ChromeLauncherAppMenuItemV2App( + const string16 title, + const gfx::Image* icon, + const std::string& app_id, + ChromeLauncherControllerPerApp* launcher_controller, + int app_index) + : ChromeLauncherAppMenuItem(title, icon), + launcher_controller_(launcher_controller), + app_id_(app_id), + app_index_(app_index) { +} + +bool ChromeLauncherAppMenuItemV2App::IsEnabled() const { + return true; +} + +void ChromeLauncherAppMenuItemV2App::Execute() { + // Note: If the application item did go away since the menu was created, + // The controller will take care of it. + launcher_controller_->ActivateShellApp(app_id_, app_index_); +} diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.h b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.h new file mode 100644 index 0000000..2c095a2 --- /dev/null +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.h @@ -0,0 +1,45 @@ +// Copyright (c) 2012 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. + +#ifndef CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_APP_MENU_ITEM_V2APP_H_ +#define CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_APP_MENU_ITEM_V2APP_H_ + +#include <string> + +#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h" + +namespace gfx { +class image; +} + +class ChromeLauncherControllerPerApp; + +// A menu item controller for a running V2 application. It gets created when an +// application list gets created. It's main purpose is to add the activation +// method to the |ChromeLauncherAppMenuItem| class. +class ChromeLauncherAppMenuItemV2App : public ChromeLauncherAppMenuItem { + public: + ChromeLauncherAppMenuItemV2App( + const string16 title, + const gfx::Image* icon, + const std::string& app_id, + ChromeLauncherControllerPerApp* launcher_controller, + int app_index); + virtual bool IsEnabled() const OVERRIDE; + virtual void Execute() OVERRIDE; + + private: + // The owning class which can be used to validate the controller. + ChromeLauncherControllerPerApp* launcher_controller_; + + // The application ID. + const std::string app_id_; + + // The index for the given application. + const int app_index_; + + DISALLOW_COPY_AND_ASSIGN(ChromeLauncherAppMenuItemV2App); +}; + +#endif // CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_APP_MENU_ITEM_V2APP_H_ diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc index da647f4..6a7ee2e 100644 --- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc @@ -21,9 +21,9 @@ ChromeLauncherController* ChromeLauncherController::CreateInstance( // can be re-created. if (CommandLine::ForCurrentProcess()->HasSwitch( ash::switches::kAshEnablePerAppLauncher)) - instance_ = new ChromeLauncherControllerPerBrowser(profile, model); - else instance_ = new ChromeLauncherControllerPerApp(profile, model); + else + instance_ = new ChromeLauncherControllerPerBrowser(profile, model); return instance_; } diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.h b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.h index 39f4317..d54faa3 100644 --- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.h +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.h @@ -10,11 +10,14 @@ #include "ash/launcher/launcher_delegate.h" #include "ash/launcher/launcher_types.h" #include "ash/shelf_types.h" +#include "base/memory/scoped_vector.h" #include "chrome/browser/extensions/extension_prefs.h" class BrowserLauncherItemControllerTest; class LauncherItemController; class Profile; +class ChromeLauncherAppMenuItem; +class ChromeLauncherControllerPerApp; namespace ash { class LauncherModel; @@ -29,6 +32,9 @@ namespace content { class WebContents; } +// A list of the elements which makes up a simple menu description. +typedef ScopedVector<ChromeLauncherAppMenuItem> ChromeLauncherAppMenuItems; + // ChromeLauncherController manages the launcher items needed for content // windows. Launcher items have a type, an optional app id, and a controller. // ChromeLauncherController will furthermore create the particular @@ -90,6 +96,11 @@ class ChromeLauncherController // Initializes this ChromeLauncherController. virtual void Init() = 0; + // Returns the new per application interface of the given launcher. If it is + // a per browser (old) controller, it will return NULL; + // TODO(skuhne): Remove when we rip out the old launcher. + virtual ChromeLauncherControllerPerApp* GetPerAppInterface() = 0; + // Creates an instance. static ChromeLauncherController* CreateInstance(Profile* profile, ash::LauncherModel* model); @@ -242,7 +253,8 @@ class ChromeLauncherController AppState app_state) = 0; // Limits application refocusing to urls that match |url| for |id|. - virtual void SetRefocusURLPattern(ash::LauncherID id, const GURL& url) = 0; + virtual void SetRefocusURLPatternForTest(ash::LauncherID id, + const GURL& url) = 0; // Returns the extension identified by |app_id|. virtual const extensions::Extension* GetExtensionForAppID( @@ -256,6 +268,8 @@ class ChromeLauncherController virtual string16 GetTitle(const ash::LauncherItem& item) OVERRIDE = 0; virtual ui::MenuModel* CreateContextMenu( const ash::LauncherItem& item, aura::RootWindow* root) OVERRIDE = 0; + virtual ui::MenuModel* CreateApplicationMenu( + const ash::LauncherItem& item) OVERRIDE = 0; virtual ash::LauncherID GetIDByWindow(aura::Window* window) OVERRIDE = 0; virtual bool IsDraggable(const ash::LauncherItem& item) OVERRIDE = 0; diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc index 6814fe4..6f6bf4d 100644 --- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc @@ -676,7 +676,7 @@ IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, RefocusFilter) { EXPECT_EQ(ash::STATUS_ACTIVE, model_->ItemByID(shortcut_id)->status); WebContents* first_tab = tab_strip->GetActiveWebContents(); - controller->SetRefocusURLPattern( + controller->SetRefocusURLPatternForTest( shortcut_id, GURL("http://www.example.com/path1/*")); // Create new tab owned by app. ui_test_utils::NavigateToURLWithDisposition( @@ -711,7 +711,7 @@ IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, RefocusFilterLaunch) { TabStripModel* tab_strip = browser()->tab_strip_model(); int tab_count = tab_strip->count(); ash::LauncherID shortcut_id = CreateShortcut("app1"); - controller->SetRefocusURLPattern( + controller->SetRefocusURLPatternForTest( shortcut_id, GURL("http://www.example.com/path1/*")); // Create new tab owned by app. diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.cc index 3bd4f49..bd2cf58 100644 --- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.cc +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.cc @@ -14,6 +14,7 @@ #include "base/values.h" #include "chrome/browser/defaults.h" #include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/favicon/favicon_tab_helper.h" #include "chrome/browser/prefs/incognito_mode_prefs.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/prefs/scoped_user_pref_update.h" @@ -22,14 +23,20 @@ #include "chrome/browser/ui/ash/app_sync_ui_state.h" #include "chrome/browser/ui/ash/chrome_launcher_prefs.h" #include "chrome/browser/ui/ash/extension_utils.h" +#include "chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.h" #include "chrome/browser/ui/ash/launcher/launcher_app_icon_loader.h" #include "chrome/browser/ui/ash/launcher/launcher_app_tab_helper.h" +#include "chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.h" #include "chrome/browser/ui/ash/launcher/launcher_context_menu.h" #include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" #include "chrome/browser/ui/ash/launcher/shell_window_launcher_controller.h" +#include "chrome/browser/ui/ash/launcher/shell_window_launcher_item_controller.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/browser_finder.h" +#include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_tabstrip.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" @@ -44,81 +51,20 @@ #include "content/public/browser/notification_service.h" #include "content/public/browser/web_contents.h" #include "extensions/common/url_pattern.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" #include "grit/theme_resources.h" #include "ui/aura/root_window.h" #include "ui/aura/window.h" +#include "ui/base/l10n/l10n_util.h" -using content::WebContents; -using extensions::Extension; - -namespace { - -// Item controller for an app shortcut. Shortcuts track app and launcher ids, -// but do not have any associated windows (opening a shortcut will replace the -// item with the appropriate LauncherItemController type). -class AppShortcutLauncherItemController : public LauncherItemController { - public: - AppShortcutLauncherItemController(const std::string& app_id, - ChromeLauncherControllerPerApp* controller) - : LauncherItemController(TYPE_SHORTCUT, app_id, controller) { - // Google Drive should just refocus to it's main app UI. - // TODO(davemoore): Generalize this for other applications. - if (app_id == "apdfllckaahabafndbhieahigkjlhalf") { - const Extension* extension = - launcher_controller()->GetExtensionForAppID(app_id); - refocus_url_ = GURL(extension->launch_web_url() + "*"); - } - } - - virtual ~AppShortcutLauncherItemController() {} - - // LauncherItemController overrides: - virtual string16 GetTitle() OVERRIDE { - return GetAppTitle(); - } - - virtual bool HasWindow(aura::Window* window) const OVERRIDE { - return false; - } - - virtual bool IsOpen() const OVERRIDE { - return false; - } - - virtual void Launch(int event_flags) OVERRIDE { - launcher_controller()->LaunchApp(app_id(), event_flags); - } - - virtual void Activate() OVERRIDE { - launcher_controller()->ActivateApp(app_id(), ui::EF_NONE); - } - - virtual void Close() OVERRIDE { - // TODO: maybe should treat as unpin? - } - virtual void Clicked() OVERRIDE { - Activate(); - } - - virtual void OnRemoved() OVERRIDE { - // AppShortcutLauncherItemController is unowned; delete on removal. - delete this; - } - - virtual void LauncherItemChanged( - int model_index, - const ash::LauncherItem& old_item) OVERRIDE { - } +using extensions::Extension; +using content::WebContents; - // Stores the optional refocus url pattern for this item. - const GURL& refocus_url() const { return refocus_url_; } - void set_refocus_url(const GURL& refocus_url) { refocus_url_ = refocus_url; } +// TODO(skuhne): Incognito markers need to be added with the advanced menu. - private: - GURL refocus_url_; - DISALLOW_COPY_AND_ASSIGN(AppShortcutLauncherItemController); -}; +namespace { std::string GetPrefKeyForRootWindow(aura::RootWindow* root_window) { gfx::Display display = gfx::Screen::GetScreenFor( @@ -198,8 +144,6 @@ void MaybePropagatePrefToLocal(PrefService* pref_service, } // namespace -// ChromeLauncherControllerPerApp --------------------------------------------- - ChromeLauncherControllerPerApp::ChromeLauncherControllerPerApp( Profile* profile, ash::LauncherModel* model) @@ -252,7 +196,14 @@ ChromeLauncherControllerPerApp::~ChromeLauncherControllerPerApp() { for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin(); i != id_to_item_controller_map_.end(); ++i) { i->second->OnRemoved(); - model_->RemoveItemAt(model_->ItemIndexByID(i->first)); + // TODO(skuhne): After getting rid of the old launcher, get also rid of the + // BrowserLauncherItemController (since it is only used for activation + // tracking at that point. + int index = model_->ItemIndexByID(i->first); + // A "browser proxy" is not known to the model and this removal does + // therefore not need to be propagated to the model. + if (index != -1) + model_->RemoveItemAt(index); } if (ash::Shell::HasInstance()) @@ -283,21 +234,27 @@ void ChromeLauncherControllerPerApp::Init() { } } +ChromeLauncherControllerPerApp* +ChromeLauncherControllerPerApp::GetPerAppInterface() { + return NULL; +} + ash::LauncherID ChromeLauncherControllerPerApp::CreateTabbedLauncherItem( LauncherItemController* controller, IncognitoState is_incognito, ash::LauncherItemStatus status) { - ash::LauncherID id = model_->next_id(); + // We are using the launcher id only for addressing purposes to make the + // old launcher model happy. The |model_| does neither know anything about + // the browser proxy nor ever use it. As such the controller will only be + // used for event tracking. + ash::LauncherID id = model_->reserve_external_id(); DCHECK(!HasItemController(id)); + // TODO(skuhne): We should get rid of this entire controller and make sure + // that we add only some general observers to make sure that we get the + // state changes. DCHECK(controller); id_to_item_controller_map_[id] = controller; controller->set_launcher_id(id); - - ash::LauncherItem item; - item.type = ash::TYPE_TABBED; - item.is_incognito = (is_incognito == STATE_INCOGNITO); - item.status = status; - model_->Add(item); return id; } @@ -318,6 +275,32 @@ void ChromeLauncherControllerPerApp::SetItemStatus( ash::LauncherItem item = model_->items()[index]; item.status = status; model_->Set(index, item); + + if (model_->items()[index].type == ash::TYPE_BROWSER_SHORTCUT) + return; + + // Determine the new browser's active state and change if necessary. + int browser_index = -1; + for (size_t index = 0; index < model_->items().size() && browser_index == -1; + index++) { + if (model_->items()[index].type == ash::TYPE_BROWSER_SHORTCUT) + browser_index = index; + } + DCHECK(browser_index >= 0); + ash::LauncherItem browser_item = model_->items()[browser_index]; + ash::LauncherItemStatus browser_status = browser_item.status; + // See if the active window is a browser. + if (chrome::FindBrowserWithWindow(ash::wm::GetActiveWindow())) { + browser_status = ash::STATUS_ACTIVE; + } else if (!BrowserList::empty()) { + browser_status = ash::STATUS_RUNNING; + } else { + browser_status = ash::STATUS_CLOSED; + } + if (browser_status != browser_item.status) { + browser_item.status = browser_status; + model_->Set(browser_index, browser_item); + } } void ChromeLauncherControllerPerApp::SetItemController( @@ -345,22 +328,6 @@ void ChromeLauncherControllerPerApp::CloseLauncherItem(ash::LauncherID id) { } } -void ChromeLauncherControllerPerApp::Unpin(ash::LauncherID id) { - DCHECK(HasItemController(id)); - - LauncherItemController* controller = id_to_item_controller_map_[id]; - if (controller->type() == LauncherItemController::TYPE_APP) { - int index = model_->ItemIndexByID(id); - ash::LauncherItem item = model_->items()[index]; - item.type = ash::TYPE_PLATFORM_APP; - model_->Set(index, item); - } else { - LauncherItemClosed(id); - } - if (CanPin()) - PersistPinnedState(); -} - void ChromeLauncherControllerPerApp::Pin(ash::LauncherID id) { DCHECK(HasItemController(id)); @@ -377,6 +344,22 @@ void ChromeLauncherControllerPerApp::Pin(ash::LauncherID id) { PersistPinnedState(); } +void ChromeLauncherControllerPerApp::Unpin(ash::LauncherID id) { + DCHECK(HasItemController(id)); + + LauncherItemController* controller = id_to_item_controller_map_[id]; + if (controller->type() == LauncherItemController::TYPE_APP) { + int index = model_->ItemIndexByID(id); + ash::LauncherItem item = model_->items()[index]; + item.type = ash::TYPE_PLATFORM_APP; + model_->Set(index, item); + } else { + LauncherItemClosed(id); + } + if (CanPin()) + PersistPinnedState(); +} + bool ChromeLauncherControllerPerApp::IsPinned(ash::LauncherID id) { int index = model_->ItemIndexByID(id); ash::LauncherItemType type = model_->items()[index].type; @@ -449,47 +432,13 @@ void ChromeLauncherControllerPerApp::ActivateApp(const std::string& app_id, // If there is an existing non-shortcut controller for this app, open it. ash::LauncherID id = GetLauncherIDForAppID(app_id); - URLPattern refocus_pattern(URLPattern::SCHEME_ALL); - refocus_pattern.SetMatchAllURLs(true); - - if (id > 0) { - LauncherItemController* controller = id_to_item_controller_map_[id]; - if (controller->type() != LauncherItemController::TYPE_SHORTCUT) { - controller->Activate(); - return; - } - - AppShortcutLauncherItemController* app_controller = - static_cast<AppShortcutLauncherItemController*>(controller); - const GURL refocus_url = app_controller->refocus_url(); - if (!refocus_url.is_empty()) - refocus_pattern.Parse(refocus_url.spec()); - } - - // Check if there are any open tabs for this app. - AppIDToWebContentsListMap::iterator app_i = - app_id_to_web_contents_list_.find(app_id); - if (app_i != app_id_to_web_contents_list_.end()) { - for (WebContentsList::iterator tab_i = app_i->second.begin(); - tab_i != app_i->second.end(); - ++tab_i) { - WebContents* tab = *tab_i; - const GURL tab_url = tab->GetURL(); - if (refocus_pattern.MatchesURL(tab_url)) { - Browser* browser = chrome::FindBrowserWithWebContents(tab); - TabStripModel* tab_strip = browser->tab_strip_model(); - int index = tab_strip->GetIndexOfWebContents(tab); - DCHECK_NE(TabStripModel::kNoTab, index); - tab_strip->ActivateTabAt(index, false); - browser->window()->Show(); - ash::wm::ActivateWindow(browser->window()->GetNativeWindow()); - return; - } - } - } + // Only pinned applications will be handled. + if (!id) + return; - LaunchApp(app_id, event_flags); + LauncherItemController* controller = id_to_item_controller_map_[id]; + controller->Activate(); } extensions::ExtensionPrefs::LaunchType @@ -585,7 +534,7 @@ void ChromeLauncherControllerPerApp::SetLaunchType( if (!HasItemController(id)) return; - return profile_->GetExtensionService()->extension_prefs()->SetLaunchType( + profile_->GetExtensionService()->extension_prefs()->SetLaunchType( id_to_item_controller_map_[id]->app_id(), launch_type); } @@ -616,6 +565,48 @@ bool ChromeLauncherControllerPerApp::CanPin() const { return pref && pref->IsUserModifiable(); } +void ChromeLauncherControllerPerApp::PersistPinnedState() { + // It is a coding error to call PersistPinnedState() if the pinned apps are + // not user-editable. The code should check earlier and not perform any + // modification actions that trigger persisting the state. + if (!CanPin()) { + NOTREACHED() << "Can't pin but pinned state being updated"; + return; + } + + // Mutating kPinnedLauncherApps is going to notify us and trigger us to + // process the change. We don't want that to happen so remove ourselves as a + // listener. + pref_change_registrar_.Remove(prefs::kPinnedLauncherApps); + { + ListPrefUpdate updater(profile_->GetPrefs(), prefs::kPinnedLauncherApps); + updater->Clear(); + for (size_t i = 0; i < model_->items().size(); ++i) { + if (model_->items()[i].type == ash::TYPE_APP_SHORTCUT) { + ash::LauncherID id = model_->items()[i].id; + if (HasItemController(id) && IsPinned(id)) { + base::DictionaryValue* app_value = ash::CreateAppDict( + id_to_item_controller_map_[id]->app_id()); + if (app_value) + updater->Append(app_value); + } + } + } + } + pref_change_registrar_.Add( + prefs::kPinnedLauncherApps, + base::Bind(&ChromeLauncherControllerPerApp::UpdateAppLaunchersFromPref, + base::Unretained(this))); +} + +ash::LauncherModel* ChromeLauncherControllerPerApp::model() { + return model_; +} + +Profile* ChromeLauncherControllerPerApp::profile() { + return profile_; +} + ash::ShelfAutoHideBehavior ChromeLauncherControllerPerApp::GetShelfAutoHideBehavior( aura::RootWindow* root_window) const { @@ -664,7 +655,7 @@ void ChromeLauncherControllerPerApp::RemoveTabFromRunningApp( app_id_to_web_contents_list_.erase(i_app_id); i_app_id = app_id_to_web_contents_list_.end(); ash::LauncherID id = GetLauncherIDForAppID(app_id); - if (id > 0) + if (id) SetItemStatus(id, ash::STATUS_CLOSED); } } @@ -709,7 +700,7 @@ void ChromeLauncherControllerPerApp::UpdateAppState( tab_list.push_front(contents); } ash::LauncherID id = GetLauncherIDForAppID(app_id); - if (id > 0) { + if (id) { // If the window is active, mark the app as active. SetItemStatus(id, app_state == APP_STATE_WINDOW_ACTIVE ? ash::STATUS_ACTIVE : ash::STATUS_RUNNING); @@ -717,7 +708,7 @@ void ChromeLauncherControllerPerApp::UpdateAppState( } } -void ChromeLauncherControllerPerApp::SetRefocusURLPattern( +void ChromeLauncherControllerPerApp::SetRefocusURLPatternForTest( ash::LauncherID id, const GURL& url) { DCHECK(HasItemController(id)); @@ -786,6 +777,11 @@ ui::MenuModel* ChromeLauncherControllerPerApp::CreateContextMenu( return new LauncherContextMenu(this, &item, root_window); } +ui::MenuModel* ChromeLauncherControllerPerApp::CreateApplicationMenu( + const ash::LauncherItem& item) { + return new LauncherApplicationMenuItemModel(GetApplicationList(item)); +} + ash::LauncherID ChromeLauncherControllerPerApp::GetIDByWindow( aura::Window* window) { for (IDToItemControllerMap::const_iterator i = @@ -822,6 +818,15 @@ void ChromeLauncherControllerPerApp::LauncherItemChanged( int index, const ash::LauncherItem& old_item) { ash::LauncherID id = model_->items()[index].id; + + // The browser item does not have a controller since it was created by the + // |LauncherModel|. + // TODO(skuhne): When removing the old launcher, this should get changed as + // well. + if (model_->items()[index].type == ash::TYPE_BROWSER_SHORTCUT) + return; + + DCHECK(HasItemController(id)); id_to_item_controller_map_[id]->LauncherItemChanged(index, old_item); } @@ -892,46 +897,82 @@ void ChromeLauncherControllerPerApp::OnAppSyncUIStatusChanged() { model_->SetStatus(ash::LauncherModel::STATUS_NORMAL); } -void ChromeLauncherControllerPerApp::PersistPinnedState() { - // It is a coding error to call PersistPinnedState() if the pinned apps are - // not user-editable. The code should check earlier and not perform any - // modification actions that trigger persisting the state. - if (!CanPin()) { - NOTREACHED() << "Can't pin but pinned state being updated"; - return; +ChromeLauncherAppMenuItems* ChromeLauncherControllerPerApp::GetApplicationList( + const ash::LauncherItem& item) { + if (item.type == ash::TYPE_BROWSER_SHORTCUT) + return GetBrowserApplicationList(); + + std::string app_id = id_to_item_controller_map_[item.id]->app_id(); + ash::LauncherID id = GetLauncherIDForAppID(app_id); + + // If there is no such an item pinned to the launcher, no menu gets created. + DCHECK(id); + DCHECK(id_to_item_controller_map_[id]); + + return id_to_item_controller_map_[id]->GetApplicationList(); +} + +std::vector<content::WebContents*> +ChromeLauncherControllerPerApp::GetV1ApplicationsFromAppId( + std::string app_id) { + ash::LauncherID id = GetLauncherIDForAppID(app_id); + + // If there is no such an item pinned to the launcher, no menu gets created. + if (id) { + LauncherItemController* controller = id_to_item_controller_map_[id]; + DCHECK(controller); + if (controller->type() == LauncherItemController::TYPE_SHORTCUT) + return GetV1ApplicationsFromController(controller); } + return std::vector<content::WebContents*>(); +} - // Mutating kPinnedLauncherApps is going to notify us and trigger us to - // process the change. We don't want that to happen so remove ourselves as a - // listener. - pref_change_registrar_.Remove(prefs::kPinnedLauncherApps); - { - ListPrefUpdate updater(profile_->GetPrefs(), prefs::kPinnedLauncherApps); - updater->Clear(); - for (size_t i = 0; i < model_->items().size(); ++i) { - if (model_->items()[i].type == ash::TYPE_APP_SHORTCUT) { - ash::LauncherID id = model_->items()[i].id; - if (HasItemController(id) && IsPinned(id)) { - base::DictionaryValue* app_value = ash::CreateAppDict( - id_to_item_controller_map_[id]->app_id()); - if (app_value) - updater->Append(app_value); - } - } +void ChromeLauncherControllerPerApp::ActivateShellApp( + const std::string& app_id, + int index) { + ash::LauncherID id = GetLauncherIDForAppID(app_id); + if (id) { + LauncherItemController* controller = id_to_item_controller_map_[id]; + if (controller->type() == LauncherItemController::TYPE_APP) { + ShellWindowLauncherItemController* shell_window_controller = + static_cast<ShellWindowLauncherItemController*>(controller); + shell_window_controller->ActivateIndexedApp(index); } } - pref_change_registrar_.Add( - prefs::kPinnedLauncherApps, - base::Bind(&ChromeLauncherControllerPerApp::UpdateAppLaunchersFromPref, - base::Unretained(this))); } -ash::LauncherModel* ChromeLauncherControllerPerApp::model() { - return model_; +bool ChromeLauncherControllerPerApp::IsWebContentHandledByApplication( + content::WebContents* web_contents, + const std::string& app_id) { + return ((web_contents_to_app_id_.find(web_contents) != + web_contents_to_app_id_.end()) && + (web_contents_to_app_id_[web_contents] == app_id)); } -Profile* ChromeLauncherControllerPerApp::profile() { - return profile_; +ash::LauncherID ChromeLauncherControllerPerApp::CreateAppShortcutLauncherItem( + const std::string& app_id, + int index) { + AppShortcutLauncherItemController* controller = + new AppShortcutLauncherItemController(app_id, this); + ash::LauncherID launcher_id = InsertAppLauncherItem( + controller, app_id, ash::STATUS_CLOSED, index); + return launcher_id; +} + +void ChromeLauncherControllerPerApp::SetAppTabHelperForTest( + AppTabHelper* helper) { + app_tab_helper_.reset(helper); +} + +void ChromeLauncherControllerPerApp::SetAppIconLoaderForTest( + AppIconLoader* loader) { + app_icon_loader_.reset(loader); +} + +const std::string& +ChromeLauncherControllerPerApp::GetAppIdFromLauncherIdForTest( + ash::LauncherID id) { + return id_to_item_controller_map_[id]->app_id(); } Profile* ChromeLauncherControllerPerApp::GetProfileForNewWindows() { @@ -944,7 +985,11 @@ void ChromeLauncherControllerPerApp::LauncherItemClosed(ash::LauncherID id) { app_icon_loader_->ClearImage(iter->second->app_id()); iter->second->OnRemoved(); id_to_item_controller_map_.erase(iter); - model_->RemoveItemAt(model_->ItemIndexByID(id)); + int index = model_->ItemIndexByID(id); + // A "browser proxy" is not known to the model and this removal does + // therefore not need to be propagated to the model. + if (index != -1) + model_->RemoveItemAt(index); } void ChromeLauncherControllerPerApp::DoPinAppWithID( @@ -1160,28 +1205,36 @@ bool ChromeLauncherControllerPerApp::HasItemController( id_to_item_controller_map_.end(); } -ash::LauncherID ChromeLauncherControllerPerApp::CreateAppShortcutLauncherItem( - const std::string& app_id, - int index) { - AppShortcutLauncherItemController* controller = - new AppShortcutLauncherItemController(app_id, this); - ash::LauncherID launcher_id = InsertAppLauncherItem( - controller, app_id, ash::STATUS_CLOSED, index); - return launcher_id; -} - -void ChromeLauncherControllerPerApp::SetAppTabHelperForTest( - AppTabHelper* helper) { - app_tab_helper_.reset(helper); -} - -void ChromeLauncherControllerPerApp::SetAppIconLoaderForTest( - AppIconLoader* loader) { - app_icon_loader_.reset(loader); -} - -const std::string& -ChromeLauncherControllerPerApp::GetAppIdFromLauncherIdForTest( - ash::LauncherID id) { - return id_to_item_controller_map_[id]->app_id(); +std::vector<content::WebContents*> +ChromeLauncherControllerPerApp::GetV1ApplicationsFromController( + LauncherItemController* controller) { + DCHECK(controller->type() == LauncherItemController::TYPE_SHORTCUT); + AppShortcutLauncherItemController* app_controller = + static_cast<AppShortcutLauncherItemController*>(controller); + return app_controller->GetRunningApplications(); +} + +ChromeLauncherAppMenuItems* +ChromeLauncherControllerPerApp::GetBrowserApplicationList() { + ChromeLauncherAppMenuItems* items = new ChromeLauncherAppMenuItems; + // Add the application name to the menu. + items->push_back(new ChromeLauncherAppMenuItem( + l10n_util::GetStringFUTF16(IDS_LAUNCHER_CHROME_BROWSER_NAME, + l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)), NULL)); + int index = 1; + for (BrowserList::const_reverse_iterator it = + BrowserList::begin_last_active(); + it != BrowserList::end_last_active(); ++it, ++index) { + Browser *browser = *it; + WebContents* web_contents = browser->tab_strip_model()->GetWebContentsAt( + browser->active_index()); + FaviconTabHelper* favicon_tab_helper = + FaviconTabHelper::FromWebContents(web_contents); + gfx::Image app_icon = favicon_tab_helper->GetFavicon(); + items->push_back(new ChromeLauncherAppMenuItemBrowser( + web_contents->GetTitle(), + app_icon.IsEmpty() ? NULL : &app_icon, + browser)); + } + return items; } diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h index 7288355..158d5a3 100644 --- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h @@ -8,6 +8,7 @@ #include <list> #include <map> #include <string> +#include <vector> #include "ash/launcher/launcher_model_observer.h" #include "ash/launcher/launcher_types.h" @@ -16,11 +17,14 @@ #include "base/basictypes.h" #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" #include "base/prefs/public/pref_change_registrar.h" #include "chrome/browser/extensions/extension_prefs.h" #include "chrome/browser/prefs/pref_service_observer.h" #include "chrome/browser/ui/ash/app_sync_ui_state_observer.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h" #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" +#include "chrome/browser/ui/ash/launcher/shell_window_launcher_controller.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" #include "ui/aura/window_observer.h" @@ -30,7 +34,7 @@ class Browser; class BrowserLauncherItemControllerTest; class LauncherItemController; class Profile; -class ShellWindowLauncherController; +class TabContents; namespace ash { class LauncherModel; @@ -69,6 +73,11 @@ class ChromeLauncherControllerPerApp // Initializes this ChromeLauncherControllerPerApp. virtual void Init() OVERRIDE; + // Returns the new per application interface of the given launcher. If it is + // a per browser (old) controller, it will return NULL; + // TODO(skuhne): Remove when we rip out the old launcher. + virtual ChromeLauncherControllerPerApp* GetPerAppInterface() OVERRIDE; + // Creates a new tabbed item on the launcher for |controller|. virtual ash::LauncherID CreateTabbedLauncherItem( LauncherItemController* controller, @@ -81,7 +90,8 @@ class ChromeLauncherControllerPerApp const std::string& app_id, ash::LauncherItemStatus status) OVERRIDE; - // Updates the running status of an item. + // Updates the running status of an item. It will also update the status of + // browsers launcher item if needed. virtual void SetItemStatus(ash::LauncherID id, ash::LauncherItemStatus status) OVERRIDE; @@ -139,6 +149,7 @@ class ChromeLauncherControllerPerApp // Returns the id of the app for the specified tab. virtual std::string GetAppID(content::WebContents* tab) OVERRIDE; + // Returns the |LauncherModel|'s ID or 0 if the AppId was not found. virtual ash::LauncherID GetLauncherIDForAppID( const std::string& app_id) OVERRIDE; virtual std::string GetAppIDForLauncherID(ash::LauncherID id) OVERRIDE; @@ -216,8 +227,8 @@ class ChromeLauncherControllerPerApp AppState app_state) OVERRIDE; // Limits application refocusing to urls that match |url| for |id|. - virtual void SetRefocusURLPattern(ash::LauncherID id, - const GURL& url) OVERRIDE; + virtual void SetRefocusURLPatternForTest(ash::LauncherID id, + const GURL& url) OVERRIDE; // Returns the extension identified by |app_id|. virtual const extensions::Extension* GetExtensionForAppID( @@ -231,6 +242,8 @@ class ChromeLauncherControllerPerApp virtual string16 GetTitle(const ash::LauncherItem& item) OVERRIDE; virtual ui::MenuModel* CreateContextMenu( const ash::LauncherItem& item, aura::RootWindow* root) OVERRIDE; + virtual ui::MenuModel* CreateApplicationMenu( + const ash::LauncherItem& item) OVERRIDE; virtual ash::LauncherID GetIDByWindow(aura::Window* window) OVERRIDE; virtual bool IsDraggable(const ash::LauncherItem& item) OVERRIDE; @@ -256,6 +269,22 @@ class ChromeLauncherControllerPerApp // Overridden from AppSyncUIStateObserver virtual void OnAppSyncUIStatusChanged() OVERRIDE; + // Get the list of all running incarnations of this item. + ChromeLauncherAppMenuItems* GetApplicationList( + const ash::LauncherItem& item); + + // Get the list of all tabs which belong to a certain application type. + std::vector<content::WebContents*> GetV1ApplicationsFromAppId( + std::string app_id); + + // Activates a specified shell application. + void ActivateShellApp(const std::string& app_id, int index); + + // Checks if a given |web_contents| is known to be associated with an + // application of type |app_id|. + bool IsWebContentHandledByApplication(content::WebContents* web_contents, + const std::string& app_id); + protected: // ChromeLauncherController overrides: @@ -318,6 +347,15 @@ class ChromeLauncherControllerPerApp bool HasItemController(ash::LauncherID id) const; + // Enumerate all Web contents which match a given shortcut |controller|. + std::vector<content::WebContents*> GetV1ApplicationsFromController( + LauncherItemController* controller); + + // Returns the list of all browsers runing. + // TODO(skuhne): Move to wherever the BrowserLauncherItemController + // functionality moves to. + ChromeLauncherAppMenuItems* GetBrowserApplicationList(); + ash::LauncherModel* model_; // Profile used for prefs and loading extensions. This is NOT necessarily the diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app_unittest.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app_unittest.cc index dc6b2e7..91f660f 100644 --- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app_unittest.cc +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app_unittest.cc @@ -14,31 +14,40 @@ #include "base/file_path.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop.h" +#include "base/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/test_extension_system.h" #include "chrome/browser/ui/ash/chrome_launcher_prefs.h" #include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" +#include "chrome/browser/ui/browser_commands.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/pref_names.h" +#include "chrome/test/base/browser_with_test_window_test.h" #include "chrome/test/base/testing_pref_service.h" #include "chrome/test/base/testing_profile.h" #include "content/public/test/test_browser_thread.h" #include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/models/menu_model.h" using extensions::Extension; namespace { const int kExpectedAppIndex = 1; +const char* gmail_app_id = "pjkljhegncpnkpknbcohdijeoejaedia"; +const char* gmail_url = "https://mail.google.com/mail/u"; } -class ChromeLauncherControllerPerAppTest : public testing::Test { +class ChromeLauncherControllerPerAppTest : public BrowserWithTestWindowTest { protected: - ChromeLauncherControllerPerAppTest() - : ui_thread_(content::BrowserThread::UI, &loop_), - file_thread_(content::BrowserThread::FILE, &loop_), - profile_(new TestingProfile()), - extension_service_(NULL) { + ChromeLauncherControllerPerAppTest() : extension_service_(NULL) { + } + + virtual void SetUp() OVERRIDE { + BrowserWithTestWindowTest::SetUp(); + DictionaryValue manifest; manifest.SetString("name", "launcher controller test extension"); manifest.SetString("version", "1"); @@ -46,7 +55,7 @@ class ChromeLauncherControllerPerAppTest : public testing::Test { extensions::TestExtensionSystem* extension_system( static_cast<extensions::TestExtensionSystem*>( - extensions::ExtensionSystem::Get(profile_.get()))); + extensions::ExtensionSystem::Get(profile()))); extension_service_ = extension_system->CreateExtensionService( CommandLine::ForCurrentProcess(), FilePath(), false); @@ -62,8 +71,9 @@ class ChromeLauncherControllerPerAppTest : public testing::Test { // Fake gmail extension. extension3_ = Extension::Create(FilePath(), Extension::LOAD, manifest, Extension::NO_FLAGS, - "pjkljhegncpnkpknbcohdijeoejaedia", + gmail_app_id, &error); + // Fake search extension. extension4_ = Extension::Create(FilePath(), Extension::LOAD, manifest, Extension::NO_FLAGS, @@ -71,12 +81,6 @@ class ChromeLauncherControllerPerAppTest : public testing::Test { &error); } - virtual void TearDown() OVERRIDE { - profile_.reset(); - // Execute any pending deletion tasks. - loop_.RunUntilIdle(); - } - void InsertPrefValue(base::ListValue* pref_value, int index, const std::string& extension_id) { @@ -101,15 +105,10 @@ class ChromeLauncherControllerPerAppTest : public testing::Test { } // Needed for extension service & friends to work. - MessageLoop loop_; - content::TestBrowserThread ui_thread_; - content::TestBrowserThread file_thread_; - scoped_refptr<Extension> extension1_; scoped_refptr<Extension> extension2_; scoped_refptr<Extension> extension3_; scoped_refptr<Extension> extension4_; - scoped_ptr<TestingProfile> profile_; ash::LauncherModel model_; ExtensionService* extension_service_; @@ -118,7 +117,7 @@ class ChromeLauncherControllerPerAppTest : public testing::Test { }; TEST_F(ChromeLauncherControllerPerAppTest, DefaultApps) { - ChromeLauncherControllerPerApp launcher_controller(profile_.get(), &model_); + ChromeLauncherControllerPerApp launcher_controller(profile(), &model_); launcher_controller.Init(); // Model should only contain the browser shortcut and app list items. @@ -143,13 +142,13 @@ TEST_F(ChromeLauncherControllerPerAppTest, Policy) { base::ListValue policy_value; InsertPrefValue(&policy_value, 0, extension1_->id()); InsertPrefValue(&policy_value, 1, extension2_->id()); - profile_->GetTestingPrefService()->SetManagedPref(prefs::kPinnedLauncherApps, - policy_value.DeepCopy()); + profile()->GetTestingPrefService()->SetManagedPref(prefs::kPinnedLauncherApps, + policy_value.DeepCopy()); // Only |extension1_| should get pinned. |extension2_| is specified but not // installed, and |extension3_| is part of the default set, but that shouldn't // take effect when the policy override is in place. - ChromeLauncherControllerPerApp launcher_controller(profile_.get(), &model_); + ChromeLauncherControllerPerApp launcher_controller(profile(), &model_); launcher_controller.Init(); EXPECT_EQ(3, model_.item_count()); EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_.items()[kExpectedAppIndex].type); @@ -168,8 +167,8 @@ TEST_F(ChromeLauncherControllerPerAppTest, Policy) { // Removing |extension1_| from the policy should be reflected in the launcher. policy_value.Remove(0, NULL); - profile_->GetTestingPrefService()->SetManagedPref(prefs::kPinnedLauncherApps, - policy_value.DeepCopy()); + profile()->GetTestingPrefService()->SetManagedPref(prefs::kPinnedLauncherApps, + policy_value.DeepCopy()); EXPECT_EQ(3, model_.item_count()); EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_.items()[kExpectedAppIndex].type); EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); @@ -181,7 +180,7 @@ TEST_F(ChromeLauncherControllerPerAppTest, UnpinWithUninstall) { extension_service_->AddExtension(extension3_.get()); extension_service_->AddExtension(extension4_.get()); - ChromeLauncherControllerPerApp launcher_controller(profile_.get(), &model_); + ChromeLauncherControllerPerApp launcher_controller(profile(), &model_); launcher_controller.Init(); EXPECT_TRUE(launcher_controller.IsAppPinned(extension3_->id())); @@ -198,12 +197,12 @@ TEST_F(ChromeLauncherControllerPerAppTest, PrefUpdates) { extension_service_->AddExtension(extension2_.get()); extension_service_->AddExtension(extension3_.get()); extension_service_->AddExtension(extension4_.get()); - ChromeLauncherControllerPerApp controller(profile_.get(), &model_); + ChromeLauncherControllerPerApp controller(profile(), &model_); std::vector<std::string> expected_launchers; std::vector<std::string> actual_launchers; base::ListValue pref_value; - profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, + profile()->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, pref_value.DeepCopy()); GetAppLaunchers(&controller, &actual_launchers); EXPECT_EQ(expected_launchers, actual_launchers); @@ -212,7 +211,7 @@ TEST_F(ChromeLauncherControllerPerAppTest, PrefUpdates) { InsertPrefValue(&pref_value, 0, extension1_->id()); InsertPrefValue(&pref_value, 1, extension2_->id()); InsertPrefValue(&pref_value, 2, extension4_->id()); - profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, + profile()->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, pref_value.DeepCopy()); expected_launchers.push_back(extension2_->id()); expected_launchers.push_back(extension4_->id()); @@ -223,7 +222,7 @@ TEST_F(ChromeLauncherControllerPerAppTest, PrefUpdates) { InsertPrefValue(&pref_value, 2, extension3_->id()); InsertPrefValue(&pref_value, 2, extension3_->id()); InsertPrefValue(&pref_value, 5, extension3_->id()); - profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, + profile()->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, pref_value.DeepCopy()); expected_launchers.insert(expected_launchers.begin() + 1, extension3_->id()); GetAppLaunchers(&controller, &actual_launchers); @@ -234,7 +233,7 @@ TEST_F(ChromeLauncherControllerPerAppTest, PrefUpdates) { InsertPrefValue(&pref_value, 0, extension4_->id()); InsertPrefValue(&pref_value, 1, extension3_->id()); InsertPrefValue(&pref_value, 2, extension2_->id()); - profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, + profile()->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, pref_value.DeepCopy()); std::reverse(expected_launchers.begin(), expected_launchers.end()); GetAppLaunchers(&controller, &actual_launchers); @@ -242,7 +241,7 @@ TEST_F(ChromeLauncherControllerPerAppTest, PrefUpdates) { // Clearing works. pref_value.Clear(); - profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, + profile()->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, pref_value.DeepCopy()); expected_launchers.clear(); GetAppLaunchers(&controller, &actual_launchers); @@ -252,13 +251,13 @@ TEST_F(ChromeLauncherControllerPerAppTest, PrefUpdates) { TEST_F(ChromeLauncherControllerPerAppTest, PendingInsertionOrder) { extension_service_->AddExtension(extension1_.get()); extension_service_->AddExtension(extension3_.get()); - ChromeLauncherControllerPerApp controller(profile_.get(), &model_); + ChromeLauncherControllerPerApp controller(profile(), &model_); base::ListValue pref_value; InsertPrefValue(&pref_value, 0, extension1_->id()); InsertPrefValue(&pref_value, 1, extension2_->id()); InsertPrefValue(&pref_value, 2, extension3_->id()); - profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, + profile()->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, pref_value.DeepCopy()); std::vector<std::string> expected_launchers; @@ -275,3 +274,189 @@ TEST_F(ChromeLauncherControllerPerAppTest, PendingInsertionOrder) { GetAppLaunchers(&controller, &actual_launchers); EXPECT_EQ(expected_launchers, actual_launchers); } + +// Checks the created menus and menu lists for correctness. It uses the given +// |controller| to create the objects for the given |item| and checks the +// found item count against the |expected_items|. The |title| list contains the +// menu titles in the order of their appearance in the menu (not including the +// application name). +void CheckMenuCreation(ChromeLauncherControllerPerApp* controller, + const ash::LauncherItem& item, + size_t expected_items, + string16 title[]) { + ChromeLauncherAppMenuItems items(controller->GetApplicationList(item)); + // There should be one item in there: The title. + EXPECT_EQ(expected_items + 1, items.size()); + EXPECT_EQ(false, items[0]->IsEnabled()); + for (size_t i = 0; i < expected_items; i++) { + EXPECT_EQ(title[i], items[1 + i]->title()); + } + + scoped_ptr<ui::MenuModel> menu(controller->CreateApplicationMenu(item)); + // There should be one item in there. + int expected_menu_items = expected_items ? (expected_items + 2) : 1; + EXPECT_EQ(expected_menu_items, menu->GetItemCount()); + EXPECT_EQ(false, menu->IsEnabledAt(0)); + if (expected_items) { + EXPECT_EQ(ui::MenuModel::TYPE_SEPARATOR , menu->GetTypeAt(1)); + } +} + +// Check that browsers get reflected correctly in the launcher menu. +TEST_F(ChromeLauncherControllerPerAppTest, BrowserMenuGeneration) { + EXPECT_EQ(1U, BrowserList::size()); + chrome::NewTab(browser()); + + ChromeLauncherControllerPerApp launcher_controller(profile(), &model_); + launcher_controller.Init(); + + // Check that the browser list is empty at this time. + ash::LauncherItem item_browser; + item_browser.type = ash::TYPE_BROWSER_SHORTCUT; + CheckMenuCreation(&launcher_controller, item_browser, 0, NULL); + + // Now make the created browser() visible by adding it to the active browser + // list. + BrowserList::SetLastActive(browser()); + string16 title1 = ASCIIToUTF16("Test1"); + NavigateAndCommitActiveTabWithTitle(browser(), GURL("http://test1"), title1); + string16 one_menu_item[] = {title1}; + CheckMenuCreation(&launcher_controller, item_browser, 1, one_menu_item); + + // Create one more browser/window and check that one more was added. + scoped_ptr<Browser> browser2( + chrome::CreateBrowserWithTestWindowForProfile(profile())); + chrome::NewTab(browser2.get()); + BrowserList::SetLastActive(browser2.get()); + string16 title2 = ASCIIToUTF16("Test2"); + NavigateAndCommitActiveTabWithTitle(browser2.get(), GURL("http://test2"), + title2); + + // Check that the list contains now two entries - make furthermore sure that + // the active item is the first entry. + string16 two_menu_items[] = {title2, title1}; + CheckMenuCreation(&launcher_controller, item_browser, 2, two_menu_items); + + // Apparently we have to close all tabs we have. + chrome::CloseTab(browser2.get()); +} + +// Check that V1 apps are correctly reflected in the launcher menu. +TEST_F(ChromeLauncherControllerPerAppTest, V1AppMenuGeneration) { + EXPECT_EQ(1U, BrowserList::size()); + EXPECT_EQ(0, browser()->tab_strip_model()->count()); + chrome::NewTab(browser()); + BrowserList::SetLastActive(browser()); + + ChromeLauncherControllerPerApp launcher_controller(profile(), &model_); + launcher_controller.Init(); + + // Model should only contain the browser shortcut and app list items. + EXPECT_EQ(2, model_.item_count()); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); + + // Installing |extension3_| adds it to the launcher. + ash::LauncherID gmail_id = model_.next_id(); + extension_service_->AddExtension(extension3_.get()); + EXPECT_EQ(3, model_.item_count()); + int gmail_index = model_.ItemIndexByID(gmail_id); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_.items()[gmail_index].type); + EXPECT_TRUE(launcher_controller.IsAppPinned(extension3_->id())); + launcher_controller.SetRefocusURLPatternForTest(gmail_id, GURL(gmail_url)); + + // Check the menu content. + ash::LauncherItem item_browser; + item_browser.type = ash::TYPE_BROWSER_SHORTCUT; + + ash::LauncherItem item_gmail; + item_gmail.type = ash::TYPE_APP_SHORTCUT; + item_gmail.id = gmail_id; + CheckMenuCreation(&launcher_controller, item_gmail, 0, NULL); + + // Set the gmail URL to a new tab. + string16 title1 = ASCIIToUTF16("Test1"); + NavigateAndCommitActiveTabWithTitle(browser(), GURL(gmail_url), title1); + + string16 one_menu_item[] = {title1}; + CheckMenuCreation(&launcher_controller, item_gmail, 1, one_menu_item); + + // Create one empty tab. + chrome::NewTab(browser()); + string16 title2 = ASCIIToUTF16("Test2"); + NavigateAndCommitActiveTabWithTitle( + browser(), + GURL("https://bla"), + title2); + + // and another one with another gmail instance. + chrome::NewTab(browser()); + string16 title3 = ASCIIToUTF16("Test3"); + NavigateAndCommitActiveTabWithTitle(browser(), GURL(gmail_url), title3); + string16 two_menu_items[] = {title3, title1}; + CheckMenuCreation(&launcher_controller, item_gmail, 2, two_menu_items); + + // Even though the item is in the V1 app list, it should also be in the + // browser list. + string16 browser_menu_item[] = {title3}; + CheckMenuCreation(&launcher_controller, item_browser, 1, browser_menu_item); + + // Test that closing of (all) the item(s) does work (and all menus get + // updated properly). + launcher_controller.Close(item_gmail.id); + + CheckMenuCreation(&launcher_controller, item_gmail, 0, NULL); + string16 browser_menu_item2[] = {title2}; + CheckMenuCreation(&launcher_controller, item_browser, 1, browser_menu_item2); +} + +// Checks that the generated menu list properly activates items. +TEST_F(ChromeLauncherControllerPerAppTest, V1AppMenuExecution) { + chrome::NewTab(browser()); + BrowserList::SetLastActive(browser()); + + ChromeLauncherControllerPerApp launcher_controller(profile(), &model_); + launcher_controller.Init(); + + // Add |extension3_| to the launcher and add two items. + GURL gmail = GURL("https://mail.google.com/mail/u"); + ash::LauncherID gmail_id = model_.next_id(); + extension_service_->AddExtension(extension3_.get()); + launcher_controller.SetRefocusURLPatternForTest(gmail_id, GURL(gmail_url)); + string16 title1 = ASCIIToUTF16("Test1"); + NavigateAndCommitActiveTabWithTitle(browser(), GURL(gmail_url), title1); + chrome::NewTab(browser()); + string16 title2 = ASCIIToUTF16("Test2"); + NavigateAndCommitActiveTabWithTitle(browser(), GURL(gmail_url), title2); + + // Check that the menu is properly set. + ash::LauncherItem item_gmail; + item_gmail.type = ash::TYPE_APP_SHORTCUT; + item_gmail.id = gmail_id; + string16 two_menu_items[] = {title2, title1}; + CheckMenuCreation(&launcher_controller, item_gmail, 2, two_menu_items); + EXPECT_EQ(1, browser()->tab_strip_model()->active_index()); + // Execute the first item in the list (which shouldn't do anything since that + // item is per definition already the active tab). + { + scoped_ptr<ui::MenuModel> menu( + launcher_controller.CreateApplicationMenu(item_gmail)); + menu->ActivatedAt(2); + } + EXPECT_EQ(1, browser()->tab_strip_model()->active_index()); + + // Execute the second item. + { + scoped_ptr<ui::MenuModel> menu( + launcher_controller.CreateApplicationMenu(item_gmail)); + menu->ActivatedAt(3); + } + // Now the active tab should be the second item. + EXPECT_EQ(0, browser()->tab_strip_model()->active_index()); +} + +// TODO(skuhne) Add tests for: +// - V2 apps: create through item in launcher or directly +// - Tracking correct activation state (seems not to work from unit_test) +// - Check that browser is always running or active when browser is active +// - Check that v1 app active shows browser active and app. +// .. diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.cc index e900163..bc6da1c 100644 --- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.cc +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.cc @@ -22,6 +22,7 @@ #include "chrome/browser/ui/ash/app_sync_ui_state.h" #include "chrome/browser/ui/ash/chrome_launcher_prefs.h" #include "chrome/browser/ui/ash/extension_utils.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h" #include "chrome/browser/ui/ash/launcher/launcher_app_icon_loader.h" #include "chrome/browser/ui/ash/launcher/launcher_app_tab_helper.h" #include "chrome/browser/ui/ash/launcher/launcher_context_menu.h" @@ -112,6 +113,10 @@ class AppShortcutLauncherItemController : public LauncherItemController { const ash::LauncherItem& old_item) OVERRIDE { } + virtual ChromeLauncherAppMenuItems* GetApplicationList() OVERRIDE { + return new ChromeLauncherAppMenuItems; + } + // Stores the optional refocus url pattern for this item. const GURL& refocus_url() const { return refocus_url_; } void set_refocus_url(const GURL& refocus_url) { refocus_url_ = refocus_url; } @@ -287,6 +292,11 @@ void ChromeLauncherControllerPerBrowser::Init() { } } +ChromeLauncherControllerPerApp* +ChromeLauncherControllerPerBrowser::GetPerAppInterface() { + return NULL; +} + ash::LauncherID ChromeLauncherControllerPerBrowser::CreateTabbedLauncherItem( LauncherItemController* controller, IncognitoState is_incognito, @@ -724,7 +734,7 @@ void ChromeLauncherControllerPerBrowser::UpdateAppState( } } -void ChromeLauncherControllerPerBrowser::SetRefocusURLPattern( +void ChromeLauncherControllerPerBrowser::SetRefocusURLPatternForTest( ash::LauncherID id, const GURL& url) { DCHECK(HasItemController(id)); @@ -794,6 +804,12 @@ ui::MenuModel* ChromeLauncherControllerPerBrowser::CreateContextMenu( return new LauncherContextMenu(this, &item, root_window); } +ui::MenuModel* ChromeLauncherControllerPerBrowser::CreateApplicationMenu( + const ash::LauncherItem& item) { + // Not used by this launcher type. + return NULL; +} + ash::LauncherID ChromeLauncherControllerPerBrowser::GetIDByWindow( aura::Window* window) { for (IDToItemControllerMap::const_iterator i = diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.h b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.h index e961afc..6c39f0b 100644 --- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.h +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.h @@ -71,6 +71,11 @@ class ChromeLauncherControllerPerBrowser // Initializes this ChromeLauncherControllerPerBrowser. virtual void Init() OVERRIDE; + // Returns the new per application interface of the given launcher. If it is + // a per browser (old) controller, it will return NULL; + // TODO(skuhne): Remove when we rip out the old launcher. + virtual ChromeLauncherControllerPerApp* GetPerAppInterface() OVERRIDE; + // Creates a new tabbed item on the launcher for |controller|. virtual ash::LauncherID CreateTabbedLauncherItem( LauncherItemController* controller, @@ -218,8 +223,8 @@ class ChromeLauncherControllerPerBrowser AppState app_state) OVERRIDE; // Limits application refocusing to urls that match |url| for |id|. - virtual void SetRefocusURLPattern(ash::LauncherID id, - const GURL& url) OVERRIDE; + virtual void SetRefocusURLPatternForTest(ash::LauncherID id, + const GURL& url) OVERRIDE; // Returns the extension identified by |app_id|. virtual const extensions::Extension* GetExtensionForAppID( @@ -233,6 +238,8 @@ class ChromeLauncherControllerPerBrowser virtual string16 GetTitle(const ash::LauncherItem& item) OVERRIDE; virtual ui::MenuModel* CreateContextMenu( const ash::LauncherItem& item, aura::RootWindow* root) OVERRIDE; + virtual ui::MenuModel* CreateApplicationMenu( + const ash::LauncherItem& item) OVERRIDE; virtual ash::LauncherID GetIDByWindow(aura::Window* window) OVERRIDE; virtual bool IsDraggable(const ash::LauncherItem& item) OVERRIDE; diff --git a/chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.cc b/chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.cc new file mode 100644 index 0000000..9661be3 --- /dev/null +++ b/chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2012 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/ash/launcher/launcher_application_menu_item_model.h" + +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h" + +LauncherApplicationMenuItemModel::LauncherApplicationMenuItemModel( + ChromeLauncherAppMenuItems* item_list) + : ALLOW_THIS_IN_INITIALIZER_LIST(ui::SimpleMenuModel(this)), + launcher_items_(item_list) { + Build(); +} + +LauncherApplicationMenuItemModel::~LauncherApplicationMenuItemModel() { +} + +bool LauncherApplicationMenuItemModel::IsCommandIdChecked( + int command_id) const { + return false; +} + +bool LauncherApplicationMenuItemModel::IsCommandIdEnabled( + int command_id) const { + DCHECK(command_id < static_cast<int>(launcher_items_.size())); + return launcher_items_[command_id]->IsEnabled(); +} + +bool LauncherApplicationMenuItemModel::GetAcceleratorForCommandId( + int command_id, + ui::Accelerator* accelerator) { + return false; +} + +void LauncherApplicationMenuItemModel::ExecuteCommand(int command_id) { + DCHECK(command_id < static_cast<int>(launcher_items_.size())); + launcher_items_[command_id]->Execute(); +} + +void LauncherApplicationMenuItemModel::Build() { + for (size_t i = 0; i < launcher_items_.size(); i++) { + ChromeLauncherAppMenuItem* item = launcher_items_[i]; + // The first item is the context menu, the others are the running apps. + AddItem(i, item->title()); + + if (!item->icon().IsEmpty()) + SetIcon(GetIndexOfCommandId(i), item->icon()); + // The first item is most likely the application name which should get + // separated from the rest. + if (!i && launcher_items_.size() > 1) + AddSeparator(ui::NORMAL_SEPARATOR); + } +} + diff --git a/chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.h b/chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.h new file mode 100644 index 0000000..5053b23 --- /dev/null +++ b/chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.h @@ -0,0 +1,38 @@ +// Copyright (c) 2012 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. + +#ifndef CHROME_BROWSER_UI_ASH_LAUNCHER_LAUNCHER_APPLICATION_MENU_ITEM_MODEL_H_ +#define CHROME_BROWSER_UI_ASH_LAUNCHER_LAUNCHER_APPLICATION_MENU_ITEM_MODEL_H_ + +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h" +#include "ui/base/models/simple_menu_model.h" + +// A menu model that builds the contents of a menu for a launcher item +// containing a list of running applications. +class LauncherApplicationMenuItemModel : public ui::SimpleMenuModel, + public ui::SimpleMenuModel::Delegate { + public: + explicit LauncherApplicationMenuItemModel( + ChromeLauncherAppMenuItems* item_list); + virtual ~LauncherApplicationMenuItemModel(); + + // Overridden from ui::SimpleMenuModel::Delegate: + virtual bool IsCommandIdChecked(int command_id) const OVERRIDE; + virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE; + virtual bool GetAcceleratorForCommandId( + int command_id, + ui::Accelerator* accelerator) OVERRIDE; + virtual void ExecuteCommand(int command_id) OVERRIDE; + + private: + void Build(); + + // The list of menu items as returned from the launcher controller. + ChromeLauncherAppMenuItems launcher_items_; + + DISALLOW_COPY_AND_ASSIGN(LauncherApplicationMenuItemModel); +}; + +#endif // CHROME_BROWSER_UI_ASH_LAUNCHER_LAUNCHER_APPLICATION_MENU_ITEM_MODEL_H_ diff --git a/chrome/browser/ui/ash/launcher/launcher_context_menu.cc b/chrome/browser/ui/ash/launcher/launcher_context_menu.cc index d505fde..4b7801f 100644 --- a/chrome/browser/ui/ash/launcher/launcher_context_menu.cc +++ b/chrome/browser/ui/ash/launcher/launcher_context_menu.cc @@ -66,8 +66,13 @@ void LauncherContextMenu::Init() { if (is_valid_item()) { if (item_.type == ash::TYPE_APP_SHORTCUT) { DCHECK(controller_->IsPinned(item_.id)); - AddItem(MENU_OPEN_NEW, string16()); - AddSeparator(ui::NORMAL_SEPARATOR); + // Everything can be started as many times as needed through the menu, + // except for V2 apps - there should be only one instance of it. + if (!controller_->IsPlatformApp(item_.id) || + !controller_->IsOpen(item_.id)) { + AddItem(MENU_OPEN_NEW, string16()); + AddSeparator(ui::NORMAL_SEPARATOR); + } AddItem( MENU_PIN, l10n_util::GetStringUTF16(IDS_LAUNCHER_CONTEXT_MENU_UNPIN)); diff --git a/chrome/browser/ui/ash/launcher/launcher_item_controller.h b/chrome/browser/ui/ash/launcher/launcher_item_controller.h index af1cd5f..049e99d 100644 --- a/chrome/browser/ui/ash/launcher/launcher_item_controller.h +++ b/chrome/browser/ui/ash/launcher/launcher_item_controller.h @@ -8,9 +8,13 @@ #include "ash/launcher/launcher_types.h" #include "base/basictypes.h" #include "base/compiler_specific.h" +#include "base/memory/scoped_vector.h" #include "base/string16.h" class ChromeLauncherController; +class ChromeLauncherAppMenuItem; + +typedef ScopedVector<ChromeLauncherAppMenuItem> ChromeLauncherAppMenuItems; namespace aura { class Window; @@ -77,6 +81,9 @@ class LauncherItemController { // Called when the controlled item is removed from the launcher. virtual void OnRemoved() = 0; + // Called to retrieve the list of running applications. + virtual ChromeLauncherAppMenuItems* GetApplicationList() = 0; + // Helper function to get the ash::LauncherItemType for the item type. ash::LauncherItemType GetLauncherItemType() const; diff --git a/chrome/browser/ui/ash/launcher/shell_window_launcher_controller.cc b/chrome/browser/ui/ash/launcher/shell_window_launcher_controller.cc index 55e688c..9c33dad 100644 --- a/chrome/browser/ui/ash/launcher/shell_window_launcher_controller.cc +++ b/chrome/browser/ui/ash/launcher/shell_window_launcher_controller.cc @@ -4,20 +4,13 @@ #include "chrome/browser/ui/ash/launcher/shell_window_launcher_controller.h" -#include <algorithm> - #include "ash/shell.h" #include "ash/wm/window_util.h" #include "base/stl_util.h" -#include "base/string_number_conversions.h" #include "base/stringprintf.h" -#include "base/utf_string_conversions.h" -#include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" -#include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" -#include "chrome/browser/ui/extensions/native_app_window.h" +#include "chrome/browser/ui/ash/launcher/shell_window_launcher_item_controller.h" #include "chrome/browser/ui/extensions/shell_window.h" -#include "chrome/common/extensions/extension.h" #include "ui/aura/client/activation_client.h" namespace { @@ -28,160 +21,8 @@ std::string GetAppLauncherId(ShellWindow* shell_window) { return shell_window->extension()->id(); } -// Functor for std::find_if used in AppLauncherItemController. -class ShellWindowHasWindow { - public: - explicit ShellWindowHasWindow(aura::Window* window) : window_(window) { } - - bool operator()(ShellWindow* shell_window) const { - return shell_window->GetNativeWindow() == window_; - } - - private: - const aura::Window* window_; -}; - } // namespace -// AppLauncherItemController --------------------------------------------------- - -// This is a LauncherItemController for shell windows. There is one instance -// per app, per launcher id. [[[Currently, apps can not define multiple launcher -// ids (see GatAppLauncherId() above) so there is one instance per app]]]. -// For apps with multiple windows, each item controller keeps track of all -// windows associated with the app and their activation order. -// Instances are owned by ShellWindowLauncherController. -class ShellWindowLauncherController::AppLauncherItemController - : public LauncherItemController { - public: - AppLauncherItemController(const std::string& app_launcher_id, - const std::string& app_id, - ChromeLauncherController* controller) : - LauncherItemController(TYPE_APP, app_id, controller), - app_launcher_id_(app_launcher_id) { - } - virtual ~AppLauncherItemController() {} - - void AddShellWindow(ShellWindow* shell_window, - ash::LauncherItemStatus status) { - if (status == ash::STATUS_ACTIVE) - shell_windows_.push_front(shell_window); - else - shell_windows_.push_back(shell_window); - } - - void RemoveShellWindowForWindow(aura::Window* window) { - ShellWindowList::iterator iter = - std::find_if(shell_windows_.begin(), shell_windows_.end(), - ShellWindowHasWindow(window)); - if (iter != shell_windows_.end()) - shell_windows_.erase(iter); - } - - void SetActiveWindow(aura::Window* window) { - ShellWindowList::iterator iter = - std::find_if(shell_windows_.begin(), shell_windows_.end(), - ShellWindowHasWindow(window)); - if (iter != shell_windows_.end()) { - ShellWindow* shell_window = *iter; - shell_windows_.erase(iter); - shell_windows_.push_front(shell_window); - } - } - - const std::string& app_launcher_id() const { return app_launcher_id_; } - - virtual string16 GetTitle() OVERRIDE { - return GetAppTitle(); - } - - virtual bool HasWindow(aura::Window* window) const OVERRIDE { - ShellWindowList::const_iterator iter = - std::find_if(shell_windows_.begin(), shell_windows_.end(), - ShellWindowHasWindow(window)); - return iter != shell_windows_.end(); - } - - virtual bool IsOpen() const OVERRIDE { - return !shell_windows_.empty(); - } - - virtual void Launch(int event_flags) OVERRIDE { - launcher_controller()->LaunchApp(app_id(), ui::EF_NONE); - } - - virtual void Activate() OVERRIDE { - DCHECK(!shell_windows_.empty()); - shell_windows_.front()->GetBaseWindow()->Activate(); - } - - virtual void Close() OVERRIDE { - // Note: Closing windows may affect the contents of shell_windows_. - ShellWindowList windows_to_close = shell_windows_; - for (ShellWindowList::iterator iter = windows_to_close.begin(); - iter != windows_to_close.end(); ++iter) { - (*iter)->GetBaseWindow()->Close(); - } - } - - // Behavior for app windows: - // * One window: Toggle minimization when clicked. - // * Multiple windows: - // ** If the first window is not active, activate it. - // ** Otherwise activate the next window. - virtual void Clicked() OVERRIDE { - if (shell_windows_.empty()) - return; - ShellWindow* first_window = shell_windows_.front(); - if (shell_windows_.size() == 1) { - if (first_window->GetBaseWindow()->IsActive()) - first_window->GetBaseWindow()->Minimize(); - else - RestoreOrShow(first_window); - } else { - if (!first_window->GetBaseWindow()->IsActive()) { - RestoreOrShow(first_window); - } else { - shell_windows_.pop_front(); - shell_windows_.push_back(first_window); - RestoreOrShow(shell_windows_.front()); - } - } - } - - virtual void OnRemoved() OVERRIDE { - } - - virtual void LauncherItemChanged(int model_index, - const ash::LauncherItem& old_item) OVERRIDE { - } - - size_t shell_window_count() const { return shell_windows_.size(); } - - private: - typedef std::list<ShellWindow*> ShellWindowList; - - void RestoreOrShow(ShellWindow* shell_window) { - if (shell_window->GetBaseWindow()->IsMinimized()) - shell_window->GetBaseWindow()->Restore(); - else - shell_window->GetBaseWindow()->Show(); - // Always activate windows when shown from the launcher. - shell_window->GetBaseWindow()->Activate(); - } - - // List of associated shell windows in activation order. - ShellWindowList shell_windows_; - - // The launcher id associated with this set of windows. There is one - // AppLauncherItemController for each |app_launcher_id_|. - const std::string app_launcher_id_; - - DISALLOW_COPY_AND_ASSIGN(AppLauncherItemController); -}; - -// ShellWindowLauncherController ----------------------------------------------- - ShellWindowLauncherController::ShellWindowLauncherController( ChromeLauncherController* owner) : owner_(owner), @@ -229,13 +70,13 @@ void ShellWindowLauncherController::OnShellWindowAdded( AppControllerMap::iterator iter = app_controller_map_.find(app_launcher_id); ash::LauncherID launcher_id = 0; if (iter != app_controller_map_.end()) { - AppLauncherItemController* controller = iter->second; + ShellWindowLauncherItemController* controller = iter->second; DCHECK(controller->app_id() == app_id); launcher_id = controller->launcher_id(); controller->AddShellWindow(shell_window, status); } else { - AppLauncherItemController* controller = - new AppLauncherItemController(app_launcher_id, app_id, owner_); + ShellWindowLauncherItemController* controller = + new ShellWindowLauncherItemController(app_launcher_id, app_id, owner_); controller->AddShellWindow(shell_window, status); // If the app launcher id is not unique, and there is already a launcher // item for this app id (e.g. pinned), use that launcher item. @@ -257,7 +98,7 @@ void ShellWindowLauncherController::OnShellWindowIconChanged( AppControllerMap::iterator iter = app_controller_map_.find(app_launcher_id); if (iter == app_controller_map_.end()) return; - AppLauncherItemController* controller = iter->second; + ShellWindowLauncherItemController* controller = iter->second; owner_->SetLauncherItemImage(controller->launcher_id(), shell_window->app_icon().AsImageSkia()); } @@ -281,7 +122,7 @@ void ShellWindowLauncherController::OnWindowDestroying(aura::Window* window) { AppControllerMap::iterator iter2 = app_controller_map_.find(app_launcher_id); DCHECK(iter2 != app_controller_map_.end()); - AppLauncherItemController* controller = iter2->second; + ShellWindowLauncherItemController* controller = iter2->second; controller->RemoveShellWindowForWindow(window); if (controller->shell_window_count() == 0) { // If this is the last window associated with the app launcher id, close the @@ -296,22 +137,24 @@ void ShellWindowLauncherController::OnWindowDestroying(aura::Window* window) { void ShellWindowLauncherController::OnWindowActivated( aura::Window* new_active, aura::Window* old_active) { - // Make the newly active window the active (first) entry in the controlelr. - AppLauncherItemController* new_controller = ControllerForWindow(new_active); + // Make the newly active window the active (first) entry in the controller. + ShellWindowLauncherItemController* new_controller = + ControllerForWindow(new_active); if (new_controller) { new_controller->SetActiveWindow(new_active); owner_->SetItemStatus(new_controller->launcher_id(), ash::STATUS_ACTIVE); } // Mark the old active window's launcher item as running (if different). - AppLauncherItemController* old_controller = ControllerForWindow(old_active); + ShellWindowLauncherItemController* old_controller = + ControllerForWindow(old_active); if (old_controller && old_controller != new_controller) owner_->SetItemStatus(old_controller->launcher_id(), ash::STATUS_RUNNING); } // Private Methods -ShellWindowLauncherController::AppLauncherItemController* +ShellWindowLauncherItemController* ShellWindowLauncherController::ControllerForWindow( aura::Window* window) { WindowToAppLauncherIdMap::iterator iter1 = diff --git a/chrome/browser/ui/ash/launcher/shell_window_launcher_controller.h b/chrome/browser/ui/ash/launcher/shell_window_launcher_controller.h index 9dba82a..9c279f6 100644 --- a/chrome/browser/ui/ash/launcher/shell_window_launcher_controller.h +++ b/chrome/browser/ui/ash/launcher/shell_window_launcher_controller.h @@ -24,8 +24,8 @@ class ActivationClient; } class ChromeLauncherController; -class LauncherItemController; class ShellWindow; +class ShellWindowLauncherItemController; // ShellWindowLauncherController observes the Shell Window registry and the // aura window manager. It handles adding and removing launcher items from @@ -50,13 +50,18 @@ class ShellWindowLauncherController virtual void OnWindowActivated(aura::Window* gained_active, aura::Window* lost_active) OVERRIDE; - private: - class AppLauncherItemController; + // Returns the number of running applications. + int NumberOfAppsRunning(); + + // Activate the application with the given index. + void ActivateAppAt(int index); - typedef std::map<std::string, AppLauncherItemController*> AppControllerMap; + private: + typedef std::map<std::string, ShellWindowLauncherItemController*> + AppControllerMap; typedef std::map<aura::Window*, std::string> WindowToAppLauncherIdMap; - AppLauncherItemController* ControllerForWindow(aura::Window* window); + ShellWindowLauncherItemController* ControllerForWindow(aura::Window* window); ChromeLauncherController* owner_; extensions::ShellWindowRegistry* registry_; // Unowned convenience pointer diff --git a/chrome/browser/ui/ash/launcher/shell_window_launcher_item_controller.cc b/chrome/browser/ui/ash/launcher/shell_window_launcher_item_controller.cc new file mode 100644 index 0000000..597e817 --- /dev/null +++ b/chrome/browser/ui/ash/launcher/shell_window_launcher_item_controller.cc @@ -0,0 +1,189 @@ +// Copyright (c) 2012 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/ash/launcher/shell_window_launcher_item_controller.h" + +#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h" +#include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" +#include "chrome/browser/ui/extensions/native_app_window.h" +#include "chrome/browser/ui/extensions/shell_window.h" + +namespace { + +// Functor for std::find_if used in AppLauncherItemController. +class ShellWindowHasWindow { + public: + explicit ShellWindowHasWindow(aura::Window* window) : window_(window) { } + + bool operator()(ShellWindow* shell_window) const { + return shell_window->GetNativeWindow() == window_; + } + + private: + const aura::Window* window_; +}; + +} // namespace + +ShellWindowLauncherItemController::ShellWindowLauncherItemController( + const std::string& app_launcher_id, + const std::string& app_id, + ChromeLauncherController* controller) + : LauncherItemController(TYPE_APP, app_id, controller), + app_launcher_id_(app_launcher_id) { +} + +ShellWindowLauncherItemController::~ShellWindowLauncherItemController() { +} + +void ShellWindowLauncherItemController::AddShellWindow( + ShellWindow* shell_window, + ash::LauncherItemStatus status) { + if (status == ash::STATUS_ACTIVE) + shell_windows_.push_front(shell_window); + else + shell_windows_.push_back(shell_window); +} + +void ShellWindowLauncherItemController::RemoveShellWindowForWindow( + aura::Window* window) { + ShellWindowList::iterator iter = + std::find_if(shell_windows_.begin(), shell_windows_.end(), + ShellWindowHasWindow(window)); + if (iter != shell_windows_.end()) + shell_windows_.erase(iter); +} + +void ShellWindowLauncherItemController::SetActiveWindow( + aura::Window* window) { + ShellWindowList::iterator iter = + std::find_if(shell_windows_.begin(), shell_windows_.end(), + ShellWindowHasWindow(window)); + if (iter != shell_windows_.end()) { + ShellWindow* shell_window = *iter; + shell_windows_.erase(iter); + shell_windows_.push_front(shell_window); + } +} + +string16 ShellWindowLauncherItemController::GetTitle() { + return GetAppTitle(); +} + +bool ShellWindowLauncherItemController::HasWindow( + aura::Window* window) const { + ShellWindowList::const_iterator iter = + std::find_if(shell_windows_.begin(), shell_windows_.end(), + ShellWindowHasWindow(window)); + return iter != shell_windows_.end(); +} + +bool ShellWindowLauncherItemController::IsOpen() const { + return !shell_windows_.empty(); +} + +void ShellWindowLauncherItemController::Launch( + int event_flags) { + launcher_controller()->LaunchApp(app_id(), ui::EF_NONE); +} + +void ShellWindowLauncherItemController::Activate() { + DCHECK(!shell_windows_.empty()); + shell_windows_.front()->GetBaseWindow()->Activate(); +} + +void ShellWindowLauncherItemController::Close() { + // Note: Closing windows may affect the contents of shell_windows_. + ShellWindowList windows_to_close = shell_windows_; + for (ShellWindowList::iterator iter = windows_to_close.begin(); + iter != windows_to_close.end(); ++iter) { + (*iter)->GetBaseWindow()->Close(); + } +} + +// Behavior for app windows: +// * One window: Toggle minimization when clicked. +// * Multiple windows: +// ** If the first window is not active, activate it. +// ** Otherwise activate the next window. +void ShellWindowLauncherItemController::Clicked() { + if (shell_windows_.empty()) + return; + ShellWindow* first_window = shell_windows_.front(); + if (shell_windows_.size() == 1) { + if (first_window->GetBaseWindow()->IsActive()) + first_window->GetBaseWindow()->Minimize(); + else + RestoreOrShow(first_window); + } else { + if (!first_window->GetBaseWindow()->IsActive()) { + RestoreOrShow(first_window); + } else { + shell_windows_.pop_front(); + shell_windows_.push_back(first_window); + RestoreOrShow(shell_windows_.front()); + } + } +} + +void ShellWindowLauncherItemController::ActivateIndexedApp( + size_t index) { + if (index < shell_windows_.size()) { + ShellWindowList::iterator it = shell_windows_.begin(); + std::advance(it, index); + RestoreOrShow(*it); + } +} + +string16 ShellWindowLauncherItemController::GetTitleOfIndexedApp( + size_t index) { + if (index < shell_windows_.size()) { + ShellWindowList::iterator it = shell_windows_.begin(); + std::advance(it, index); + return (*it)->GetTitle(); + } + return string16(); +} + +gfx::Image* +ShellWindowLauncherItemController::GetIconOfIndexedApp(size_t index) { + if (index < shell_windows_.size()) { + ShellWindowList::iterator it = shell_windows_.begin(); + std::advance(it, index); + return (*it)->GetAppListIcon(); + } + return new gfx::Image(); +} + +ChromeLauncherAppMenuItems* +ShellWindowLauncherItemController::GetApplicationList() { + ChromeLauncherAppMenuItems* items = new ChromeLauncherAppMenuItems; + if (!launcher_controller()->GetPerAppInterface()) { + items->push_back(new ChromeLauncherAppMenuItem(GetTitle(), NULL)); + for (size_t i = 0; i < shell_window_count(); i++) { + gfx::Image* image = GetIconOfIndexedApp(i); + items->push_back(new ChromeLauncherAppMenuItemV2App( + GetTitleOfIndexedApp(i), + image, + app_id(), + launcher_controller()->GetPerAppInterface(), + i)); + delete image; + } + } + return items; +} + +void ShellWindowLauncherItemController::RestoreOrShow( + ShellWindow* shell_window) { + if (shell_window->GetBaseWindow()->IsMinimized()) + shell_window->GetBaseWindow()->Restore(); + else + shell_window->GetBaseWindow()->Show(); + // Always activate windows when shown from the launcher. + shell_window->GetBaseWindow()->Activate(); +} diff --git a/chrome/browser/ui/ash/launcher/shell_window_launcher_item_controller.h b/chrome/browser/ui/ash/launcher/shell_window_launcher_item_controller.h new file mode 100644 index 0000000..a80ebb6 --- /dev/null +++ b/chrome/browser/ui/ash/launcher/shell_window_launcher_item_controller.h @@ -0,0 +1,88 @@ +// Copyright (c) 2012 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. + +#ifndef CHROME_BROWSER_UI_ASH_LAUNCHER_SHELL_WINDOW_LAUNCHER_ITEM_CONTROLLER_H_ +#define CHROME_BROWSER_UI_ASH_LAUNCHER_SHELL_WINDOW_LAUNCHER_ITEM_CONTROLLER_H_ + +#include <list> +#include <string> + +#include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" + +namespace aura { +class Window; +} + +namespace gfx { +class Image; +} + +class ChromeLauncherController; +class ShellWindow; + +// This is a ShellWindowItemLauncherController for shell windows. There is one +// instance per app, per launcher id. +// For apps with multiple windows, each item controller keeps track of all +// windows associated with the app and their activation order. +// Instances are owned by ShellWindowLauncherController. +class ShellWindowLauncherItemController : public LauncherItemController { + public: + ShellWindowLauncherItemController(const std::string& app_launcher_id, + const std::string& app_id, + ChromeLauncherController* controller); + + virtual ~ShellWindowLauncherItemController(); + + void AddShellWindow(ShellWindow* shell_window, + ash::LauncherItemStatus status); + + void RemoveShellWindowForWindow(aura::Window* window); + + void SetActiveWindow(aura::Window* window); + + const std::string& app_launcher_id() const { return app_launcher_id_; } + + // LauncherItemController overrides. + virtual string16 GetTitle() OVERRIDE; + virtual bool HasWindow(aura::Window* window) const OVERRIDE; + virtual bool IsOpen() const OVERRIDE; + virtual void Launch(int event_flags) OVERRIDE; + virtual void Activate() OVERRIDE; + virtual void Close() OVERRIDE; + virtual void Clicked() OVERRIDE; + virtual void OnRemoved() OVERRIDE {} + virtual void LauncherItemChanged( + int model_index, + const ash::LauncherItem& old_item) OVERRIDE {} + virtual ChromeLauncherAppMenuItems* GetApplicationList() OVERRIDE; + + // Get the number of running applications/incarnations of this. + size_t shell_window_count() const { return shell_windows_.size(); } + + // Activates the window at position |index|. + void ActivateIndexedApp(size_t index); + + private: + typedef std::list<ShellWindow*> ShellWindowList; + + // Get the title of the running application with this |index|. + string16 GetTitleOfIndexedApp(size_t index); + + // Get the title of the running application with this |index|. + // Note that the returned image needs to be released by the caller. + gfx::Image* GetIconOfIndexedApp(size_t index); + + void RestoreOrShow(ShellWindow* shell_window); + + // List of associated shell windows in activation order. + ShellWindowList shell_windows_; + + // The launcher id associated with this set of windows. There is one + // AppLauncherItemController for each |app_launcher_id_|. + const std::string app_launcher_id_; + + DISALLOW_COPY_AND_ASSIGN(ShellWindowLauncherItemController); +}; + +#endif // CHROME_BROWSER_UI_ASH_LAUNCHER_SHELL_WINDOW_LAUNCHER_ITEM_CONTROLLER_H_ diff --git a/chrome/browser/ui/extensions/shell_window.cc b/chrome/browser/ui/extensions/shell_window.cc index c01b5d5..93752c0 100644 --- a/chrome/browser/ui/extensions/shell_window.cc +++ b/chrome/browser/ui/extensions/shell_window.cc @@ -48,6 +48,7 @@ #include "content/public/browser/web_intents_dispatcher.h" #include "content/public/common/media_stream_request.h" #include "content/public/common/renderer_preferences.h" +#include "skia/ext/image_operations.h" #include "third_party/skia/include/core/SkRegion.h" #if defined(USE_ASH) @@ -376,6 +377,21 @@ void ShellWindow::OnNativeWindowChanged() { false)); } +gfx::Image* ShellWindow::GetAppListIcon() { + // TODO(skuhne): We might want to use LoadImages in UpdateExtensionAppIcon + // instead to let the extension give us pre-defined icons in the launcher + // and the launcher list sizes. Since there is no mock yet, doing this now + // seems a bit premature and we scale for the time being. + if (app_icon_.IsEmpty()) + return new gfx::Image(); + + SkBitmap bmp = skia::ImageOperations::Resize( + *app_icon_.ToSkBitmap(), skia::ImageOperations::RESIZE_BEST, + extension_misc::EXTENSION_ICON_SMALLISH, + extension_misc::EXTENSION_ICON_SMALLISH); + return new gfx::Image(bmp); +} + NativeAppWindow* ShellWindow::GetBaseWindow() { return native_app_window_.get(); } diff --git a/chrome/browser/ui/extensions/shell_window.h b/chrome/browser/ui/extensions/shell_window.h index 929d485..692c824 100644 --- a/chrome/browser/ui/extensions/shell_window.h +++ b/chrome/browser/ui/extensions/shell_window.h @@ -100,6 +100,11 @@ class ShellWindow : public content::NotificationObserver, NativeAppWindow* GetBaseWindow(); gfx::NativeWindow GetNativeWindow(); + // This will return a slightly smaller icon then the app_icon to be used in + // application lists. It is the responsibility of the caller to delete the + // returned image after use. + gfx::Image* GetAppListIcon(); + // NativeAppWindows should call this to determine what the window's title // is on startup and from within UpdateWindowTitle(). virtual string16 GetTitle() const; diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h index 0590ca0..0b430f4 100644 --- a/chrome/browser/ui/views/frame/browser_view.h +++ b/chrome/browser/ui/views/frame/browser_view.h @@ -235,6 +235,10 @@ class BrowserView : public BrowserWindow, #if defined(USE_ASH) // Test support. + // Note: This is only needed to be BrowserLauncherItemController instead of + // LauncherItemController because of the "favicon_loader" member - to be more + // exact that member function is the only one being called. + // TODO(skuhne): Remove once per-app is default. BrowserLauncherItemController* launcher_item_controller() const { return launcher_item_controller_.get(); } @@ -701,6 +705,9 @@ class BrowserView : public BrowserWindow, #endif #if defined(USE_ASH) + // Needs to be BrowserLauncerItemController for + // "BrowserActivationStateChanged" and "favicon_loader". + // TODO(skuhne): Remove once per-app is default. scoped_ptr<BrowserLauncherItemController> launcher_item_controller_; #endif diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi index 6c8e2d3..285da44 100644 --- a/chrome/chrome_browser_ui.gypi +++ b/chrome/chrome_browser_ui.gypi @@ -141,8 +141,18 @@ 'browser/ui/ash/extension_utils.h', 'browser/ui/ash/ime_controller_chromeos.cc', 'browser/ui/ash/ime_controller_chromeos.h', + 'browser/ui/ash/launcher/app_shortcut_launcher_item_controller.cc', + 'browser/ui/ash/launcher/app_shortcut_launcher_item_controller.h', 'browser/ui/ash/launcher/browser_launcher_item_controller.cc', 'browser/ui/ash/launcher/browser_launcher_item_controller.h', + 'browser/ui/ash/launcher/chrome_launcher_app_menu_item.cc', + 'browser/ui/ash/launcher/chrome_launcher_app_menu_item.h', + 'browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.cc', + 'browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.h', + 'browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.cc', + 'browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h', + 'browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.cc', + 'browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.h', 'browser/ui/ash/launcher/chrome_launcher_controller.cc', 'browser/ui/ash/launcher/chrome_launcher_controller.h', 'browser/ui/ash/launcher/chrome_launcher_controller_per_app.cc', @@ -159,6 +169,10 @@ 'browser/ui/ash/launcher/launcher_favicon_loader.h', 'browser/ui/ash/launcher/launcher_item_controller.cc', 'browser/ui/ash/launcher/launcher_item_controller.h', + 'browser/ui/ash/launcher/launcher_application_menu_item_model.cc', + 'browser/ui/ash/launcher/launcher_application_menu_item_model.h', + 'browser/ui/ash/launcher/shell_window_launcher_item_controller.cc', + 'browser/ui/ash/launcher/shell_window_launcher_item_controller.h', 'browser/ui/ash/launcher/shell_window_launcher_controller.cc', 'browser/ui/ash/launcher/shell_window_launcher_controller.h', 'browser/ui/ash/screenshot_taker.cc', diff --git a/chrome/test/base/browser_with_test_window_test.cc b/chrome/test/base/browser_with_test_window_test.cc index 44dec85..0bac364 100644 --- a/chrome/test/base/browser_with_test_window_test.cc +++ b/chrome/test/base/browser_with_test_window_test.cc @@ -146,6 +146,16 @@ void BrowserWithTestWindowTest::NavigateAndCommitActiveTab(const GURL& url) { url); } +void BrowserWithTestWindowTest::NavigateAndCommitActiveTabWithTitle( + Browser* navigating_browser, + const GURL& url, + const string16& title) { + NavigationController* controller = + &chrome::GetActiveWebContents(navigating_browser)->GetController(); + NavigateAndCommit(controller, url); + controller->GetActiveEntry()->SetTitle(title); +} + void BrowserWithTestWindowTest::DestroyBrowserAndProfile() { if (browser_.get()) { // Make sure we close all tabs, otherwise Browser isn't happy in its diff --git a/chrome/test/base/browser_with_test_window_test.h b/chrome/test/base/browser_with_test_window_test.h index e2191f9a..76d7b4c 100644 --- a/chrome/test/base/browser_with_test_window_test.h +++ b/chrome/test/base/browser_with_test_window_test.h @@ -97,6 +97,12 @@ class BrowserWithTestWindowTest : public testing::Test { // Navigates the current tab. This is a wrapper around NavigateAndCommit. void NavigateAndCommitActiveTab(const GURL& url); + // Set the |title| of the current tab. + void NavigateAndCommitActiveTabWithTitle( + Browser* browser, + const GURL& url, + const string16& title); + protected: // Destroys the browser, window, and profile created by this class. This is // invoked from the destructor. |