diff options
-rw-r--r-- | ash/ash.gyp | 3 | ||||
-rw-r--r-- | ash/ash_strings.grd | 12 | ||||
-rw-r--r-- | ash/shell/window_type_launcher.cc | 16 | ||||
-rw-r--r-- | ash/shell/window_type_launcher.h | 1 | ||||
-rw-r--r-- | ash/system/status_area_widget.cc | 49 | ||||
-rw-r--r-- | ash/system/status_area_widget.h | 29 | ||||
-rw-r--r-- | ash/system/tray/system_tray.cc | 15 | ||||
-rw-r--r-- | ash/system/tray/system_tray.h | 10 | ||||
-rw-r--r-- | ash/system/tray/system_tray_bubble.cc | 14 | ||||
-rw-r--r-- | ash/system/tray/system_tray_bubble.h | 2 | ||||
-rw-r--r-- | ash/system/web_notification/web_notification_tray.cc | 827 | ||||
-rw-r--r-- | ash/system/web_notification/web_notification_tray.h | 165 | ||||
-rw-r--r-- | ash/system/web_notification/web_notification_tray_unittest.cc | 106 | ||||
-rw-r--r-- | ash/wm/shelf_layout_manager.cc | 15 |
14 files changed, 1251 insertions, 13 deletions
diff --git a/ash/ash.gyp b/ash/ash.gyp index 67907da..15bf18d 100644 --- a/ash/ash.gyp +++ b/ash/ash.gyp @@ -193,6 +193,8 @@ 'system/user/tray_user.h', 'system/user/update_observer.h', 'system/user/user_observer.h', + 'system/web_notification/web_notification_tray.cc', + 'system/web_notification/web_notification_tray.h', 'tooltips/tooltip_controller.cc', 'tooltips/tooltip_controller.h', 'touch/touch_observer_hud.cc', @@ -383,6 +385,7 @@ 'screensaver/screensaver_view_unittest.cc', 'shell_unittest.cc', 'system/tray/system_tray_unittest.cc', + 'system/web_notification/web_notification_tray_unittest.cc', 'test/ash_test_base.cc', 'test/ash_test_base.h', 'test/ash_unittests.cc', diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd index e28fbbf..702edbf 100644 --- a/ash/ash_strings.grd +++ b/ash/ash_strings.grd @@ -393,6 +393,18 @@ Press Search key to cancel. <message name="IDS_ASH_STATUS_TRAY_PREVIOUS_MENU" desc="The accessible text for header entries for detailed versions of status tray items."> Previous menu </message> + <message name="IDS_ASH_WEB_NOTFICATION_TRAY_EXTENSIONS_DISABLE" desc="The menu entry for disabling extensions from a notification."> + Disable extension + </message> + <message name="IDS_ASH_WEB_NOTFICATION_TRAY_SITE_DISABLE" desc="The menu entry for disabling notification from a site."> + Disable notifications from <ph name="site">$1<ex>mail.google.com</ex></ph> + </message> + <message name="IDS_ASH_WEB_NOTFICATION_TRAY_SETTINGS" desc="The menu entry or button for visiting the appropriate settings page."> + Settings... + </message> + <message name="IDS_ASH_WEB_NOTFICATION_TRAY_CLOSE_ALL" desc="The button for closing all notifications."> + Close All + </message> </messages> </release> </grit> diff --git a/ash/shell/window_type_launcher.cc b/ash/shell/window_type_launcher.cc index 243273a..b783518 100644 --- a/ash/shell/window_type_launcher.cc +++ b/ash/shell/window_type_launcher.cc @@ -11,6 +11,8 @@ #include "ash/shell/toplevel_window.h" #include "ash/shell_delegate.h" #include "ash/shell_window_ids.h" +#include "ash/system/status_area_widget.h" +#include "ash/system/web_notification/web_notification_tray.h" #include "ash/wm/shadow_types.h" #include "base/bind.h" #include "base/time.h" @@ -223,7 +225,10 @@ WindowTypeLauncher::WindowTypeLauncher() this, ASCIIToUTF16("Show/Hide a Window")))), ALLOW_THIS_IN_INITIALIZER_LIST(show_screensaver_( new views::NativeTextButton( - this, ASCIIToUTF16("Show the Screensaver [for 5 seconds]")))) { + this, ASCIIToUTF16("Show the Screensaver [for 5 seconds]")))), + ALLOW_THIS_IN_INITIALIZER_LIST(show_web_notification_( + new views::NativeTextButton( + this, ASCIIToUTF16("Show a web/app notification")))) { views::GridLayout* layout = new views::GridLayout(this); layout->SetInsets(5, 5, 5, 5); SetLayoutManager(layout); @@ -247,6 +252,7 @@ WindowTypeLauncher::WindowTypeLauncher() AddViewToLayout(layout, examples_button_); AddViewToLayout(layout, show_hide_window_button_); AddViewToLayout(layout, show_screensaver_); + AddViewToLayout(layout, show_web_notification_); #if !defined(OS_MACOSX) set_context_menu_controller(this); #endif @@ -320,6 +326,14 @@ void WindowTypeLauncher::ButtonPressed(views::Button* sender, base::Bind(&ash::CloseScreensaver), base::TimeDelta::FromSeconds(5)); + } else if (sender == show_web_notification_) { + ash::Shell::GetInstance()->status_area_widget()-> + web_notification_tray()->AddNotification( + "id0", + ASCIIToUTF16("Test Shell Web Notification"), + ASCIIToUTF16("Notification message body."), + ASCIIToUTF16("www.testshell.org"), + "" /* extension id */); } #if !defined(OS_MACOSX) else if (sender == examples_button_) { diff --git a/ash/shell/window_type_launcher.h b/ash/shell/window_type_launcher.h index 4dcc5ab..69e7a93 100644 --- a/ash/shell/window_type_launcher.h +++ b/ash/shell/window_type_launcher.h @@ -78,6 +78,7 @@ class WindowTypeLauncher : public views::WidgetDelegateView, views::NativeTextButton* examples_button_; views::NativeTextButton* show_hide_window_button_; views::NativeTextButton* show_screensaver_; + views::NativeTextButton* show_web_notification_; #if !defined(OS_MACOSX) scoped_ptr<views::MenuRunner> menu_runner_; #endif diff --git a/ash/system/status_area_widget.cc b/ash/system/status_area_widget.cc index 9c0a312..b3eb72f 100644 --- a/ash/system/status_area_widget.cc +++ b/ash/system/status_area_widget.cc @@ -13,6 +13,7 @@ #include "ash/system/status_area_widget_delegate.h" #include "ash/system/tray/system_tray.h" #include "ash/system/tray/system_tray_delegate.h" +#include "ash/system/web_notification/web_notification_tray.h" #include "base/i18n/time_formatting.h" #include "base/utf_string_conversions.h" #include "ui/aura/window.h" @@ -282,7 +283,8 @@ namespace internal { StatusAreaWidget::StatusAreaWidget() : widget_delegate_(new internal::StatusAreaWidgetDelegate), - system_tray_(NULL) { + system_tray_(NULL), + web_notification_tray_(NULL) { views::Widget::InitParams params( views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); params.delegate = widget_delegate_; @@ -300,6 +302,7 @@ StatusAreaWidget::~StatusAreaWidget() { } void StatusAreaWidget::CreateTrayViews(ShellDelegate* shell_delegate) { + AddWebNotificationTray(new WebNotificationTray(this)); AddSystemTray(new SystemTray(), shell_delegate); } @@ -309,6 +312,8 @@ void StatusAreaWidget::Shutdown() { // in the destructor if Shutdown() is not called (e.g. in tests). delete system_tray_; system_tray_ = NULL; + delete web_notification_tray_; + web_notification_tray_ = NULL; } void StatusAreaWidget::AddSystemTray(SystemTray* system_tray, @@ -327,9 +332,51 @@ void StatusAreaWidget::AddSystemTray(SystemTray* system_tray, system_tray->CreateItems(); // Called after delegate is created. } +void StatusAreaWidget::AddWebNotificationTray( + WebNotificationTray* web_notification_tray) { + web_notification_tray_ = web_notification_tray; + widget_delegate_->AddTray(web_notification_tray); +} + void StatusAreaWidget::SetShelfAlignment(ShelfAlignment alignment) { widget_delegate_->set_alignment(alignment); widget_delegate_->UpdateLayout(); + if (system_tray_) + system_tray_->SetShelfAlignment(alignment); + if (web_notification_tray_) + web_notification_tray_->SetShelfAlignment(alignment); +} + +void StatusAreaWidget::SetPaintsBackground( + bool value, + internal::BackgroundAnimator::ChangeType change_type) { + if (system_tray_) + system_tray_->SetPaintsBackground(value, change_type); + if (web_notification_tray_) + web_notification_tray_->SetPaintsBackground(value, change_type); +} + +void StatusAreaWidget::ShowWebNotificationBubble(UserAction user_action) { + if (system_tray_ && system_tray_->IsBubbleVisible()) { + // User actions should always hide the system tray bubble first. + DCHECK(user_action != USER_ACTION); + // Don't immediately show the web notification bubble if the system tray + // bubble is visible. + return; + } + DCHECK(web_notification_tray_); + web_notification_tray_->ShowBubble(); + // Disable showing system notifications while viewing web notifications. + if (system_tray_) + system_tray_->SetHideNotifications(true); +} + +void StatusAreaWidget::HideWebNotificationBubble() { + DCHECK(web_notification_tray_); + web_notification_tray_->HideBubble(); + // Show any hidden or suppressed system notifications. + if (system_tray_) + system_tray_->SetHideNotifications(false); } } // namespace internal diff --git a/ash/system/status_area_widget.h b/ash/system/status_area_widget.h index 8e56688..5b66862 100644 --- a/ash/system/status_area_widget.h +++ b/ash/system/status_area_widget.h @@ -7,6 +7,7 @@ #pragma once #include "ash/ash_export.h" +#include "ash/launcher/background_animator.h" #include "ash/wm/shelf_auto_hide_behavior.h" #include "ui/views/widget/widget.h" @@ -15,6 +16,7 @@ namespace ash { class ShellDelegate; class SystemTray; class SystemTrayDelegate; +class WebNotificationTray; namespace internal { @@ -22,30 +24,51 @@ class StatusAreaWidgetDelegate; class ASH_EXPORT StatusAreaWidget : public views::Widget { public: + enum UserAction { + NON_USER_ACTION, + USER_ACTION + }; + StatusAreaWidget(); virtual ~StatusAreaWidget(); - // Creates the SystemTray. + // Creates the SystemTray and the WebNotificationTray. void CreateTrayViews(ShellDelegate* shell_delegate); - // Destroys the system tray. Called before tearing down the windows to avoid - // shutdown ordering issues. + // Destroys the system tray and web notification tray. Called before + // tearing down the windows to avoid shutdown ordering issues. void Shutdown(); + // Update the alignment of the widget and tray views. void SetShelfAlignment(ShelfAlignment alignment); + // Update whether to paint a background for each tray view. + void SetPaintsBackground( + bool value, + internal::BackgroundAnimator::ChangeType change_type); + + // Always used to show/hide the web notification tray. These handle any logic + // with hiding/supressing notifications from the system tray. + void ShowWebNotificationBubble(UserAction user_action); + void HideWebNotificationBubble(); + SystemTray* system_tray() { return system_tray_; } SystemTrayDelegate* system_tray_delegate() { return system_tray_delegate_.get(); } + WebNotificationTray* web_notification_tray() { + return web_notification_tray_; + } private: void AddSystemTray(SystemTray* system_tray, ShellDelegate* shell_delegate); + void AddWebNotificationTray(WebNotificationTray* web_notification_tray); scoped_ptr<SystemTrayDelegate> system_tray_delegate_; // Weak pointers to View classes that are parented to StatusAreaWidget: internal::StatusAreaWidgetDelegate* widget_delegate_; SystemTray* system_tray_; + WebNotificationTray* web_notification_tray_; DISALLOW_COPY_AND_ASSIGN(StatusAreaWidget); }; diff --git a/ash/system/tray/system_tray.cc b/ash/system/tray/system_tray.cc index 707c962..0b6e2df 100644 --- a/ash/system/tray/system_tray.cc +++ b/ash/system/tray/system_tray.cc @@ -138,7 +138,8 @@ SystemTray::SystemTray() update_observer_(NULL), user_observer_(NULL), should_show_launcher_(false), - default_bubble_height_(0) { + default_bubble_height_(0), + hide_notifications_(false) { tray_container_ = new internal::SystemTrayContainer; tray_container_->SetLayoutManager(new views::BoxLayout( views::BoxLayout::kHorizontal, 0, 0, 0)); @@ -298,6 +299,16 @@ void SystemTray::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) { } } +void SystemTray::SetHideNotifications(bool hide_notifications) { + if (notification_bubble_.get()) + notification_bubble_->SetVisible(!hide_notifications); + hide_notifications_ = hide_notifications; +} + +bool SystemTray::IsBubbleVisible() const { + return bubble_.get() && bubble_->IsVisible(); +} + bool SystemTray::CloseBubbleForTest() const { if (!bubble_.get()) return false; @@ -462,6 +473,8 @@ void SystemTray::UpdateNotificationBubble() { if (arrow_offset >= 0) init_params.arrow_offset = arrow_offset; notification_bubble_->InitView(init_params); + if (hide_notifications_) + notification_bubble_->SetVisible(false); } void SystemTray::UpdateNotificationAnchor() { diff --git a/ash/system/tray/system_tray.h b/ash/system/tray/system_tray.h index f6cf1ca..d79fc93 100644 --- a/ash/system/tray/system_tray.h +++ b/ash/system/tray/system_tray.h @@ -96,6 +96,12 @@ class ASH_EXPORT SystemTray : public internal::TrayBackgroundView { // Updates the items when the shelf alignment changes. void UpdateAfterShelfAlignmentChange(ShelfAlignment alignment); + // Temporarily hides/unhides the notification bubble. + void SetHideNotifications(bool hidden); + + // Returns true if the primary bubble is visible. + bool IsBubbleVisible() const; + // Returns true if the launcher should show. bool should_show_launcher() const { return bubble_.get() && should_show_launcher_; @@ -241,6 +247,10 @@ class ASH_EXPORT SystemTray : public internal::TrayBackgroundView { // views directly (e.g. from a notification) we know what height to use. int default_bubble_height_; + // Set to true when system notifications should be hidden (e.g. web + // notification bubble is visible). + bool hide_notifications_; + DISALLOW_COPY_AND_ASSIGN(SystemTray); }; diff --git a/ash/system/tray/system_tray_bubble.cc b/ash/system/tray/system_tray_bubble.cc index df7c2d5..6888cc4 100644 --- a/ash/system/tray/system_tray_bubble.cc +++ b/ash/system/tray/system_tray_bubble.cc @@ -336,6 +336,7 @@ void SystemTrayBubble::InitView(const InitParams& init_params) { void SystemTrayBubble::BubbleViewDestroyed() { DestroyItemViews(); + bubble_view_ = NULL; } gfx::Rect SystemTrayBubble::GetAnchorRect() const { @@ -422,6 +423,19 @@ void SystemTrayBubble::Close() { bubble_widget_->Close(); } +void SystemTrayBubble::SetVisible(bool is_visible) { + if (!bubble_widget_) + return; + if (is_visible) + bubble_widget_->Show(); + else + bubble_widget_->Hide(); +} + +bool SystemTrayBubble::IsVisible() { + return bubble_widget_ && bubble_widget_->IsVisible(); +} + void SystemTrayBubble::CreateItemViews(user::LoginStatus login_status) { for (std::vector<ash::SystemTrayItem*>::iterator it = items_.begin(); it != items_.end(); diff --git a/ash/system/tray/system_tray_bubble.h b/ash/system/tray/system_tray_bubble.h index bdc138e..5373601 100644 --- a/ash/system/tray/system_tray_bubble.h +++ b/ash/system/tray/system_tray_bubble.h @@ -80,6 +80,8 @@ class SystemTrayBubble : public aura::EventFilter, void StopAutoCloseTimer(); void RestartAutoCloseTimer(); void Close(); + void SetVisible(bool is_visible); + bool IsVisible(); private: void CreateItemViews(user::LoginStatus login_status); diff --git a/ash/system/web_notification/web_notification_tray.cc b/ash/system/web_notification/web_notification_tray.cc new file mode 100644 index 0000000..ba81fa3 --- /dev/null +++ b/ash/system/web_notification/web_notification_tray.cc @@ -0,0 +1,827 @@ +// 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 "ash/system/web_notification/web_notification_tray.h" + +#include "ash/shell.h" +#include "ash/system/status_area_widget.h" +#include "ash/system/tray/tray_bubble_view.h" +#include "ash/system/tray/tray_constants.h" +#include "ash/system/tray/tray_views.h" +#include "grit/ash_strings.h" +#include "grit/ui_resources.h" +#include "grit/ui_resources_standard.h" +#include "ui/aura/event.h" +#include "ui/aura/window.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/models/simple_menu_model.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/views/controls/button/button.h" +#include "ui/views/controls/button/menu_button.h" +#include "ui/views/controls/button/menu_button_listener.h" +#include "ui/views/controls/label.h" +#include "ui/views/controls/menu/menu_model_adapter.h" +#include "ui/views/controls/menu/menu_runner.h" +#include "ui/views/layout/box_layout.h" +#include "ui/views/layout/fill_layout.h" +#include "ui/views/layout/grid_layout.h" +#include "ui/views/painter.h" + +namespace { + +const int kTrayBorder = 4; +const int kNotificationIconWidth = 40; +const int kNotificationIconHeight = 25; +const int kWebNotificationBubbleMinHeight = 80; +const int kWebNotificationBubbleMaxHeight = 400; +const int kWebNotificationWidth = 400; +const int kWebNotificationButtonWidth = 32; + +const int kTogglePermissionCommand = 0; +const int kToggleExtensionCommand = 1; +const int kShowSettingsCommand = 2; + +// The image has three icons: 1 notifiaction, 2 notifications, and 3+. +SkBitmap GetNotificationImage(int notification_count) { + SkBitmap image; + gfx::Image all = ui::ResourceBundle::GetSharedInstance().GetImageNamed( + IDR_AURA_UBER_TRAY_WEB_NOTIFICATON); + int image_index = notification_count - 1; + image_index = std::max(0, std::min(image_index, 2)); + SkIRect region = SkIRect::MakeXYWH( + 0, image_index * kNotificationIconHeight, + kNotificationIconWidth, kNotificationIconHeight); + all.ToSkBitmap()->extractSubset(&image, region); + return image; +} + +} // namespace + +namespace ash { + +namespace internal { + +struct WebNotification { + WebNotification(const std::string& i, + const string16& t, + const string16& m, + const string16& s, + const std::string& e) + : id(i), + title(t), + message(m), + display_source(s), + extension_id(e) { + } + + std::string id; + string16 title; + string16 message; + string16 display_source; + std::string extension_id; + gfx::ImageSkia image; +}; + +// A helper class to manage the list of notifications. +class WebNotificationList { + public: + typedef std::list<WebNotification> Notifications; + + WebNotificationList() { + } + + void AddNotification(const std::string& id, + const string16& title, + const string16& message, + const string16& display_source, + const std::string& extension_id) { + Notifications::iterator iter = GetNotification(id); + if (iter != notifications_.end()) { + // Update existing notification. + iter->title = title; + iter->message = message; + iter->display_source = display_source; + iter->extension_id = extension_id; + } else { + notifications_.push_back( + WebNotification(id, title, message, display_source, extension_id)); + } + } + + void UpdateNotificationMessage(const std::string& id, + const string16& title, + const string16& message) { + Notifications::iterator iter = GetNotification(id); + if (iter == notifications_.end()) + return; + iter->title = title; + iter->message = message; + } + + bool RemoveNotification(const std::string& id) { + Notifications::iterator iter = GetNotification(id); + if (iter == notifications_.end()) + return false; + notifications_.erase(iter); + return true; + } + + void RemoveAllNotifications() { + notifications_.clear(); + } + + void RemoveNotificationsBySource(const std::string& id) { + Notifications::iterator source_iter = GetNotification(id); + if (source_iter == notifications_.end()) + return; + string16 display_source = source_iter->display_source; + for (Notifications::iterator loopiter = notifications_.begin(); + loopiter != notifications_.end(); ) { + Notifications::iterator curiter = loopiter++; + if (curiter->display_source == display_source) + notifications_.erase(curiter); + } + } + + void RemoveNotificationsByExtension(const std::string& id) { + Notifications::iterator source_iter = GetNotification(id); + if (source_iter == notifications_.end()) + return; + std::string extension_id = source_iter->extension_id; + for (Notifications::iterator loopiter = notifications_.begin(); + loopiter != notifications_.end(); ) { + Notifications::iterator curiter = loopiter++; + if (curiter->extension_id == extension_id) + notifications_.erase(curiter); + } + } + + bool SetNotificationImage(const std::string& id, + const gfx::ImageSkia& image) { + Notifications::iterator iter = GetNotification(id); + if (iter == notifications_.end()) + return false; + iter->image = image; + return true; + } + + const Notifications& notifications() const { return notifications_; } + + private: + Notifications::iterator GetNotification(const std::string& id) { + for (Notifications::iterator iter = notifications_.begin(); + iter != notifications_.end(); ++iter) { + if (iter->id == id) + return iter; + } + return notifications_.end(); + } + + Notifications notifications_; + + DISALLOW_COPY_AND_ASSIGN(WebNotificationList); +}; + +// A simple view for the text (title and message) of a notification. +class WebNotificationMessageView : public views::View { + public: + explicit WebNotificationMessageView(const WebNotification& notification) { + views::Label* title = new views::Label(notification.title); + title->SetHorizontalAlignment(views::Label::ALIGN_LEFT); + title->SetFont(title->font().DeriveFont(0, gfx::Font::BOLD)); + views::Label* message = new views::Label(notification.message); + message->SetHorizontalAlignment(views::Label::ALIGN_LEFT); + message->SetMultiLine(true); + + SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1)); + AddChildView(title); + AddChildView(message); + } + + virtual ~WebNotificationMessageView() { + } + + private: + DISALLOW_COPY_AND_ASSIGN(WebNotificationMessageView); +}; + +// A dropdown menu for notifications. +class WebNotificationMenuModel : public ui::SimpleMenuModel, + public ui::SimpleMenuModel::Delegate { + public: + explicit WebNotificationMenuModel(WebNotificationTray* tray, + const WebNotification& notification) + : ALLOW_THIS_IN_INITIALIZER_LIST(ui::SimpleMenuModel(this)), + tray_(tray), + notification_(notification) { + // Add 'disable notifications' menu item. + if (!notification.extension_id.empty()) { + AddItem(kToggleExtensionCommand, + GetLabelForCommandId(kToggleExtensionCommand)); + } else if (!notification.display_source.empty()) { + AddItem(kTogglePermissionCommand, + GetLabelForCommandId(kTogglePermissionCommand)); + } + // Add settings menu item. + if (!notification.display_source.empty()) { + AddItem(kShowSettingsCommand, + GetLabelForCommandId(kShowSettingsCommand)); + } + } + + virtual ~WebNotificationMenuModel() { + } + + // Overridden from ui::SimpleMenuModel: + virtual string16 GetLabelForCommandId(int command_id) const OVERRIDE { + switch (command_id) { + case kToggleExtensionCommand: + return l10n_util::GetStringUTF16( + IDS_ASH_WEB_NOTFICATION_TRAY_EXTENSIONS_DISABLE); + case kTogglePermissionCommand: + return l10n_util::GetStringFUTF16( + IDS_ASH_WEB_NOTFICATION_TRAY_SITE_DISABLE, + notification_.display_source); + case kShowSettingsCommand: + return l10n_util::GetStringUTF16( + IDS_ASH_WEB_NOTFICATION_TRAY_SETTINGS); + default: + NOTREACHED(); + } + return string16(); + } + + // Overridden from ui::SimpleMenuModel::Delegate: + virtual bool IsCommandIdChecked(int command_id) const OVERRIDE { + return false; + } + + virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE { + return true; + } + + virtual bool GetAcceleratorForCommandId( + int command_id, + ui::Accelerator* accelerator) OVERRIDE { + return false; + } + + virtual void ExecuteCommand(int command_id) OVERRIDE { + switch (command_id) { + case kToggleExtensionCommand: + tray_->DisableByExtension(notification_.id); + break; + case kTogglePermissionCommand: + tray_->DisableByUrl(notification_.id); + break; + case kShowSettingsCommand: + tray_->ShowSettings(notification_.id); + break; + default: + NOTREACHED(); + } + } + + private: + WebNotificationTray* tray_; + WebNotification notification_; + + DISALLOW_COPY_AND_ASSIGN(WebNotificationMenuModel); +}; + +// The view for a notification entry (icon + message + buttons). +class WebNotificationView : public views::View, + public views::ButtonListener, + public views::MenuButtonListener { + public: + WebNotificationView(WebNotificationTray* tray, + const WebNotification& notification) + : tray_(tray), + notification_(notification), + icon_(NULL), + menu_button_(NULL), + close_button_(NULL) { + InitView(tray, notification); + } + + virtual ~WebNotificationView() { + } + + void InitView(WebNotificationTray* tray, + const WebNotification& notification) { + set_border(views::Border::CreateSolidSidedBorder( + 1, 0, 0, 0, kBorderLightColor)); + set_background(views::Background::CreateSolidBackground(kBackgroundColor)); + + icon_ = new views::ImageView; + icon_->SetImage(notification.image); + + WebNotificationMessageView* message_view + = new WebNotificationMessageView(notification); + + close_button_ = new views::ImageButton(this); + close_button_->SetImage( + views::CustomButton::BS_NORMAL, + ResourceBundle::GetSharedInstance().GetImageSkiaNamed( + IDR_AURA_WINDOW_CLOSE)); + + if (!notification.extension_id.empty() || + !notification.display_source.empty()) { + menu_button_ = new views::MenuButton(NULL, string16(), this, true); + menu_button_->set_border(NULL); + } + + views::GridLayout* layout = new views::GridLayout(this); + SetLayoutManager(layout); + + views::ColumnSet* columns = layout->AddColumnSet(0); + + columns->AddPaddingColumn(0, kTrayPopupPaddingHorizontal/2); + + // Notification Icon. + columns->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER, + 0, /* resize percent */ + views::GridLayout::FIXED, + kNotificationIconWidth, kNotificationIconWidth); + + columns->AddPaddingColumn(0, kTrayPopupPaddingHorizontal/2); + + // Notification message text. + columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, + 100, /* resize percent */ + views::GridLayout::USE_PREF, 0, 0); + + columns->AddPaddingColumn(0, kTrayPopupPaddingHorizontal/2); + + // Close and menu buttons. + columns->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER, + 0, /* resize percent */ + views::GridLayout::FIXED, + kWebNotificationButtonWidth, + kWebNotificationButtonWidth); + + columns->AddPaddingColumn(0, kTrayPopupPaddingHorizontal/2); + + // Layout rows + layout->AddPaddingRow(0, kTrayPopupPaddingBetweenItems); + + layout->StartRow(0, 0); + layout->AddView(icon_, 1, 2); + layout->AddView(message_view, 1, 2); + layout->AddView(close_button_); + + layout->StartRow(0, 0); + if (menu_button_) { + layout->SkipColumns(4); + layout->AddView(menu_button_); + } + layout->AddPaddingRow(0, kTrayPopupPaddingBetweenItems); + } + + // view::Views overrodes. + virtual bool OnMousePressed(const views::MouseEvent& event) OVERRIDE { + tray_->OnClicked(notification_.id); + return true; + } + + // Overridden from ButtonListener. + virtual void ButtonPressed(views::Button* sender, + const views::Event& event) OVERRIDE { + if (sender == close_button_) + tray_->RemoveNotification(notification_.id); + } + + // Overridden from MenuButtonListener. + virtual void OnMenuButtonClicked( + View* source, const gfx::Point& point) OVERRIDE { + if (source != menu_button_) + return; + WebNotificationMenuModel menu_model(tray_, notification_); + views::MenuModelAdapter menu_model_adapter(&menu_model); + views::MenuRunner menu_runner(menu_model_adapter.CreateMenu()); + + gfx::Point screen_location; + views::View::ConvertPointToScreen(menu_button_, &screen_location); + ignore_result(menu_runner.RunMenuAt( + source->GetWidget()->GetTopLevelWidget(), + menu_button_, + gfx::Rect(screen_location, menu_button_->size()), + views::MenuItemView::TOPRIGHT, + views::MenuRunner::HAS_MNEMONICS)); + } + + private: + WebNotificationTray* tray_; + WebNotification notification_; + views::ImageView* icon_; + views::MenuButton* menu_button_; + views::ImageButton* close_button_; + + DISALLOW_COPY_AND_ASSIGN(WebNotificationView); +}; + +// The view for the buttons at the bottom of the web notification tray. +class WebNotificationButtonView : public TrayPopupTextButtonContainer, + public views::ButtonListener { + public: + explicit WebNotificationButtonView(WebNotificationTray* tray) + : tray_(tray), + settings_button_(NULL), + close_all_button_(NULL) { + set_background(views::Background::CreateBackgroundPainter( + true, + views::Painter::CreateVerticalGradient( + kHeaderBackgroundColorLight, + kHeaderBackgroundColorDark))); + set_border(views::Border::CreateSolidSidedBorder( + 2, 0, 0, 0, ash::kBorderDarkColor)); + + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + settings_button_ = new TrayPopupTextButton( + this, rb.GetLocalizedString(IDS_ASH_WEB_NOTFICATION_TRAY_SETTINGS)); + AddTextButton(settings_button_); + + close_all_button_ = new TrayPopupTextButton( + this, rb.GetLocalizedString(IDS_ASH_WEB_NOTFICATION_TRAY_CLOSE_ALL)); + AddTextButton(close_all_button_); + } + + virtual ~WebNotificationButtonView() { + } + + // Overridden from ButtonListener. + virtual void ButtonPressed(views::Button* sender, + const views::Event& event) OVERRIDE { + if (sender == settings_button_) + tray_->ShowSettings(""); + else if (sender == close_all_button_) + tray_->RemoveAllNotifications(); + } + + private: + WebNotificationTray* tray_; + TrayPopupTextButton* settings_button_; + TrayPopupTextButton* close_all_button_; + + DISALLOW_COPY_AND_ASSIGN(WebNotificationButtonView); +}; + +} // namespace internal + +using internal::WebNotificationList; +using internal::WebNotificationView; + +class WebNotificationTray::BubbleContentsView : public views::View { + public: + explicit BubbleContentsView(WebNotificationTray* tray) + : tray_(tray) { + SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1)); + set_background(views::Background::CreateSolidBackground(kBackgroundColor)); + + scroll_content_ = new views::View; + scroll_content_->SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1)); + scroller_ = new internal::FixedSizedScrollView; + scroller_->SetContentsView(scroll_content_); + AddChildView(scroller_); + + button_view_ = new internal::WebNotificationButtonView(tray); + AddChildView(button_view_); +} + + void Update(const WebNotificationList::Notifications& notifications) { + scroll_content_->RemoveAllChildViews(true); + for (WebNotificationList::Notifications::const_iterator iter = + notifications.begin(); iter != notifications.end(); ++iter) { + WebNotificationView* view = new WebNotificationView(tray_, *iter); + scroll_content_->AddChildView(view); + } + SizeScrollContent(); + scroller_->Layout(); + Layout(); + PreferredSizeChanged(); + SchedulePaint(); + } + + private: + void SizeScrollContent() { + gfx::Size scroll_size = scroll_content_->GetPreferredSize(); + int button_height = button_view_->GetPreferredSize().height(); + int scroll_height = std::min( + std::max(scroll_size.height(), + kWebNotificationBubbleMinHeight - button_height), + kWebNotificationBubbleMaxHeight - button_height); + scroll_size.set_height(scroll_height); + scroller_->set_fixed_size(scroll_size); + } + + WebNotificationTray* tray_; + internal::FixedSizedScrollView* scroller_; + views::View* scroll_content_; + internal::WebNotificationButtonView* button_view_; + + DISALLOW_COPY_AND_ASSIGN(BubbleContentsView); +}; + +class WebNotificationTray::Bubble : public internal::TrayBubbleView::Host, + public views::Widget::Observer { + public: + explicit Bubble(WebNotificationTray* tray) + : tray_(tray), + bubble_view_(NULL), + bubble_widget_(NULL), + contents_view_(NULL) { + views::View* anchor = tray->tray_container(); + views::BubbleBorder::ArrowLocation arrow_location; + int arrow_offset = 0; + if (tray_->shelf_alignment() == SHELF_ALIGNMENT_BOTTOM) { + arrow_location = views::BubbleBorder::BOTTOM_RIGHT; + arrow_offset = anchor->GetContentsBounds().width() / 2; + } else if (tray_->shelf_alignment() == SHELF_ALIGNMENT_LEFT) { + arrow_location = views::BubbleBorder::LEFT_BOTTOM; + } else { + arrow_location = views::BubbleBorder::RIGHT_BOTTOM; + } + bubble_view_ = new internal::TrayBubbleView( + anchor, arrow_location, this, false, kWebNotificationWidth); + bubble_view_->SetMaxHeight(kWebNotificationBubbleMaxHeight); + + bubble_widget_ = views::BubbleDelegateView::CreateBubble(bubble_view_); + + bubble_view_->SetAlignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE); + bubble_widget_->non_client_view()->frame_view()->set_background(NULL); + bubble_view_->SetBubbleBorder(arrow_offset); + + bubble_widget_->AddObserver(this); + + contents_view_ = new BubbleContentsView(tray); + bubble_view_->AddChildView(contents_view_); + + Update(); + bubble_view_->Show(); + } + + virtual ~Bubble() { + if (bubble_view_) + bubble_view_->reset_host(); + if (bubble_widget_) { + bubble_widget_->RemoveObserver(this); + bubble_widget_->Close(); + } + } + + void Update() { + contents_view_->Update(tray_->notification_list()->notifications()); + bubble_view_->Layout(); + bubble_view_->SchedulePaint(); + } + + views::Widget* bubble_widget() const { return bubble_widget_; } + + // Overridden from TrayBubbleView::Host. + virtual void BubbleViewDestroyed() OVERRIDE { + bubble_view_ = NULL; + contents_view_ = NULL; + } + + virtual gfx::Rect GetAnchorRect() const OVERRIDE { + gfx::Rect anchor_rect = tray_->tray_container()->GetScreenBounds(); + return anchor_rect; + } + + virtual void OnMouseEnteredView() OVERRIDE { + } + + virtual void OnMouseExitedView() OVERRIDE { + } + + // Overridden from views::Widget::Observer. + virtual void OnWidgetClosing(views::Widget* widget) OVERRIDE { + CHECK_EQ(bubble_widget_, widget); + bubble_widget_ = NULL; + tray_->HideBubble(); // Will destroy |this|. + } + + private: + WebNotificationTray* tray_; + internal::TrayBubbleView* bubble_view_; + views::Widget* bubble_widget_; + BubbleContentsView* contents_view_; + + DISALLOW_COPY_AND_ASSIGN(Bubble); +}; + +WebNotificationTray::WebNotificationTray( + internal::StatusAreaWidget* status_area_widget) + : status_area_widget_(status_area_widget), + notification_list_(new WebNotificationList()), + tray_container_(NULL), + icon_(NULL), + delegate_(NULL) { + tray_container_ = new views::View; + tray_container_->set_border(views::Border::CreateEmptyBorder( + kTrayBorder, kTrayBorder, kTrayBorder, kTrayBorder)); + SetShelfAlignment(shelf_alignment()); + + icon_ = new views::ImageView; + tray_container_->AddChildView(icon_); + UpdateIcon(); // Hides the tray initially. + + SetContents(tray_container_); + + Shell::GetInstance()->AddEnvEventFilter(this); +} + +WebNotificationTray::~WebNotificationTray() { + Shell::GetInstance()->RemoveEnvEventFilter(this); +} + +void WebNotificationTray::SetDelegate(Delegate* delegate) { + DCHECK(!delegate_); + delegate_ = delegate; +} + +void WebNotificationTray::AddNotification(const std::string& id, + const string16& title, + const string16& message, + const string16& display_source, + const std::string& extension_id) { + notification_list_->AddNotification( + id, title, message, display_source, extension_id); + UpdateIcon(); + if (bubble()) { + bubble_->Update(); + } else { + status_area_widget_->ShowWebNotificationBubble( + internal::StatusAreaWidget::NON_USER_ACTION); + } +} + +void WebNotificationTray::UpdateNotification(const std::string& id, + const string16& title, + const string16& message) { + notification_list_->UpdateNotificationMessage(id, title, message); + if (bubble()) + bubble_->Update(); +} + +void WebNotificationTray::RemoveNotification(const std::string& id) { + if (!notification_list_->RemoveNotification(id)) + return; + if (delegate_) + delegate_->NotificationRemoved(id); + UpdateBubbleAndIcon(); +} + +void WebNotificationTray::RemoveAllNotifications() { + const WebNotificationList::Notifications& notifications = + notification_list_->notifications(); + if (delegate_) { + for (WebNotificationList::Notifications::const_iterator loopiter = + notifications.begin(); + loopiter != notifications.end(); ) { + WebNotificationList::Notifications::const_iterator curiter = loopiter++; + std::string notification_id = curiter->id; + // May call RemoveNotification and erase curiter. + delegate_->NotificationRemoved(notification_id); + } + } + notification_list_->RemoveAllNotifications(); + UpdateBubbleAndIcon(); +} + +void WebNotificationTray::SetNotificationImage(const std::string& id, + const gfx::ImageSkia& image) { + if (!notification_list_->SetNotificationImage(id, image)) + return; + if (bubble()) + bubble_->Update(); +} + +void WebNotificationTray::DisableByExtension(const std::string& id) { + // When we disable notifications, we remove any existing matching + // notifications to avoid adding complicated UI to re-enable the source. + notification_list_->RemoveNotificationsByExtension(id); + UpdateBubbleAndIcon(); + if (delegate_) + delegate_->DisableExtension(id); +} + +void WebNotificationTray::DisableByUrl(const std::string& id) { + // See comment for DisableByExtension. + notification_list_->RemoveNotificationsBySource(id); + UpdateBubbleAndIcon(); + if (delegate_) + delegate_->DisableNotificationsFromSource(id); +} + +void WebNotificationTray::ShowBubble() { + if (bubble()) + return; + bubble_.reset(new Bubble(this)); +} + +void WebNotificationTray::HideBubble() { + bubble_.reset(); +} + +void WebNotificationTray::ShowSettings(const std::string& id) { + if (delegate_) + delegate_->ShowSettings(id); +} + +void WebNotificationTray::OnClicked(const std::string& id) { + if (delegate_) + delegate_->OnClicked(id); +} + +bool WebNotificationTray::PreHandleKeyEvent(aura::Window* target, + aura::KeyEvent* event) { + return false; +} + +bool WebNotificationTray::PreHandleMouseEvent(aura::Window* target, + aura::MouseEvent* event) { + if (event->type() == ui::ET_MOUSE_PRESSED) + return ProcessLocatedEvent(*event); + return false; +} + +ui::TouchStatus WebNotificationTray::PreHandleTouchEvent( + aura::Window* target, + aura::TouchEvent* event) { + if (event->type() != ui::ET_TOUCH_PRESSED) + return ui::TOUCH_STATUS_UNKNOWN; + if (ProcessLocatedEvent(*event)) + return ui::TOUCH_STATUS_END; + return ui::TOUCH_STATUS_UNKNOWN; +} + +ui::GestureStatus WebNotificationTray::PreHandleGestureEvent( + aura::Window* target, + aura::GestureEvent* event) { + return ui::GESTURE_STATUS_UNKNOWN; +} + +void WebNotificationTray::SetShelfAlignment(ShelfAlignment alignment) { + internal::TrayBackgroundView::SetShelfAlignment(alignment); + tray_container_->SetLayoutManager(new views::BoxLayout( + alignment == SHELF_ALIGNMENT_BOTTOM ? + views::BoxLayout::kHorizontal : views::BoxLayout::kVertical, + 0, 0, 0)); +} + +bool WebNotificationTray::PerformAction(const views::Event& event) { + if (bubble()) { + status_area_widget_->HideWebNotificationBubble(); + } else { + status_area_widget_->ShowWebNotificationBubble( + internal::StatusAreaWidget::USER_ACTION); + } + return true; +} + +int WebNotificationTray::GetNotificationCount() const { + return notification_list()->notifications().size(); +} + +void WebNotificationTray::UpdateIcon() { + int count = GetNotificationCount(); + if (count == 0) { + SetVisible(false); + } else { + icon_->SetImage(gfx::ImageSkia(GetNotificationImage(count))); + SetVisible(true); + } + PreferredSizeChanged(); +} + +void WebNotificationTray::UpdateBubbleAndIcon() { + UpdateIcon(); + if (!bubble()) + return; + if (GetNotificationCount() == 0) + status_area_widget_->HideWebNotificationBubble(); + else + bubble_->Update(); +} + +bool WebNotificationTray::ProcessLocatedEvent(const aura::LocatedEvent& event) { + if (!bubble()) + return false; + gfx::Rect bounds = + bubble_->bubble_widget()->GetNativeWindow()->GetBoundsInRootWindow(); + if (bounds.Contains(event.root_location())) + return false; + status_area_widget_->HideWebNotificationBubble(); + // If the event occurred in the tray widget, don't process the click. + bounds = GetWidget()->GetNativeWindow()->GetBoundsInRootWindow(); + if (bounds.Contains(event.root_location())) + return true; + return false; +} + +} // namespace ash diff --git a/ash/system/web_notification/web_notification_tray.h b/ash/system/web_notification/web_notification_tray.h new file mode 100644 index 0000000..18219da --- /dev/null +++ b/ash/system/web_notification/web_notification_tray.h @@ -0,0 +1,165 @@ +// 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 ASH_SYSTEM_NOTIFICATION_WEB_NOTIFICATION_TRAY_H_ +#define ASH_SYSTEM_NOTIFICATION_WEB_NOTIFICATION_TRAY_H_ +#pragma once + +#include "ash/ash_export.h" +#include "ash/system/tray/tray_background_view.h" +#include "base/gtest_prod_util.h" +#include "ui/aura/event_filter.h" + +namespace aura { +class LocatedEvent; +} + +namespace gfx { +class ImageSkia; +} + +namespace views { +class ImageView; +} + +namespace ash { + +namespace internal { +class StatusAreaWidget; +class WebNotificationList; +} + +// Status area tray for showing browser and app notifications. The client +// (e.g. Chrome) calls [Add|Remove|Update]Notification to create and update +// notifications in the tray. It can also implement Delegate to receive +// callbacks when a notification is removed (closed), clicked on, or a menu +// item is triggered. +// +// Note: These are not related to system notifications (i.e NotificationView +// generated by SystemTrayItem). Visibility of one notification type or other +// is controlled by StatusAreaWidget. + +class ASH_EXPORT WebNotificationTray : public aura::EventFilter, + public internal::TrayBackgroundView { + public: + class Delegate { + public: + virtual ~Delegate() {} + + // Called when the notification associated with |notification_id| is + // removed (i.e. closed by the user). + virtual void NotificationRemoved(const std::string& notifcation_id) = 0; + + // Request to disable the extension associated with |notification_id|. + virtual void DisableExtension(const std::string& notifcation_id) = 0; + + // Request to disable notifications from the source of |notification_id|. + virtual void DisableNotificationsFromSource( + const std::string& notifcation_id) = 0; + + // Request to show the notification settings (|notification_id| is used + // to identify the requesting browser context). + virtual void ShowSettings(const std::string& notifcation_id) = 0; + + // Called when the notification body is clicked on. + virtual void OnClicked(const std::string& notifcation_id) = 0; + }; + + explicit WebNotificationTray(internal::StatusAreaWidget* status_area_widget); + virtual ~WebNotificationTray(); + + // Called once to set the delegate. + void SetDelegate(Delegate* delegate); + + // Add a new notification. |id| is a unique identifier, used to update or + // remove notifications. |title| and |meesage| describe the notification text. + // Use SetNotificationImage to set the icon image. If |extension_id| is + // provided then 'Disable extension' will appear in a dropdown menu and the + // id will be used to disable notifications from the extension. Otherwise if + // |display_source| is provided, a menu item showing the source and allowing + // notifications from that source to be disabled will be shown. All actual + // disabling is handled by the Delegate. + void AddNotification(const std::string& id, + const string16& title, + const string16& message, + const string16& display_source, + const std::string& extension_id); + + // Update an existing notification. + void UpdateNotification(const std::string& id, + const string16& title, + const string16& message); + + // Remove an existing notification and notify the delegate. + void RemoveNotification(const std::string& id); + + // Remove all notifications and notify the delegate. + void RemoveAllNotifications(); + + // Set the notification image. + void SetNotificationImage(const std::string& id, + const gfx::ImageSkia& image); + + // Disable all notifications matching notification |id|. + void DisableByExtension(const std::string& id); + void DisableByUrl(const std::string& id); + + // Show the notification bubble. Should only be called by StatusAreaWidget. + void ShowBubble(); + + // Hide the notification bubble. Should only be called by StatusAreaWidget. + void HideBubble(); + + // Request the Delegate to the settings dialog. + void ShowSettings(const std::string& id); + + // Called when a notification is clicked on. Event is passed to the Delegate. + void OnClicked(const std::string& id); + + // Overridden from aura::EventFilter. + virtual bool PreHandleKeyEvent(aura::Window* target, + aura::KeyEvent* event) OVERRIDE; + virtual bool PreHandleMouseEvent(aura::Window* target, + aura::MouseEvent* event) OVERRIDE; + virtual ui::TouchStatus PreHandleTouchEvent(aura::Window* target, + aura::TouchEvent* event) OVERRIDE; + virtual ui::GestureStatus PreHandleGestureEvent( + aura::Window* target, + aura::GestureEvent* event) OVERRIDE; + + // Overridden from TrayBackgroundView. + virtual void SetShelfAlignment(ShelfAlignment alignment) OVERRIDE; + + // Overridden from internal::ActionableView. + virtual bool PerformAction(const views::Event& event) OVERRIDE; + + private: + class Bubble; + class BubbleContentsView; + FRIEND_TEST_ALL_PREFIXES(WebNotificationTrayTest, WebNotifications); + + int GetNotificationCount() const; + void UpdateIcon(); + void UpdateBubbleAndIcon(); + bool ProcessLocatedEvent(const aura::LocatedEvent& event); + + const internal::WebNotificationList* notification_list() const { + return notification_list_.get(); + } + views::View* tray_container() const { return tray_container_; } + Bubble* bubble() const { return bubble_.get(); } + + internal::StatusAreaWidget* status_area_widget_; // Unowned parent. + scoped_ptr<internal::WebNotificationList> notification_list_; + scoped_ptr<Bubble> bubble_; + views::View* tray_container_; + views::ImageView* icon_; + Delegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(WebNotificationTray); +}; + +} // namespace ash + +#endif // ASH_SYSTEM_NOTIFICATION_WEB_NOTIFICATION_TRAY_H_ diff --git a/ash/system/web_notification/web_notification_tray_unittest.cc b/ash/system/web_notification/web_notification_tray_unittest.cc new file mode 100644 index 0000000..a153371 --- /dev/null +++ b/ash/system/web_notification/web_notification_tray_unittest.cc @@ -0,0 +1,106 @@ +// 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 "ash/system/web_notification/web_notification_tray.h" + +#include <vector> + +#include "ash/system/status_area_widget.h" +#include "ash/system/tray/system_tray_item.h" +#include "ash/test/ash_test_base.h" +#include "base/utf_string_conversions.h" +#include "ui/views/controls/label.h" +#include "ui/views/layout/fill_layout.h" +#include "ui/views/view.h" +#include "ui/views/widget/widget.h" + +namespace ash { + +namespace { + +WebNotificationTray* CreateWebNotificationTray() { + internal::StatusAreaWidget* widget = new internal::StatusAreaWidget; + widget->CreateTrayViews(NULL); + widget->Show(); + return widget->web_notification_tray(); +} + +class TestDelegate : public WebNotificationTray::Delegate { + public: + TestDelegate() {} + virtual ~TestDelegate() {} + + // WebNotificationTray::Delegate overrides. + virtual void NotificationRemoved(const std::string& notifcation_id) { + notification_ids_.erase(notifcation_id); + } + + virtual void DisableExtension(const std::string& notifcation_id) { + } + + virtual void DisableNotificationsFromSource( + const std::string& notifcation_id) { + } + + virtual void ShowSettings(const std::string& notifcation_id) { + } + + virtual void OnClicked(const std::string& notifcation_id) { + } + + void AddNotification(WebNotificationTray* tray, const std::string& id) { + notification_ids_.insert(id); + tray->AddNotification(id, + ASCIIToUTF16("Test Web Notification"), + ASCIIToUTF16("Notification message body."), + ASCIIToUTF16("www.test.org"), + "" /* extension id */); + } + + void RemoveNotification(WebNotificationTray* tray, const std::string& id) { + tray->RemoveNotification(id); + } + + bool HasNotificationId(const std::string& id) { + return notification_ids_.find(id) != notification_ids_.end(); + } + + private: + std::set<std::string> notification_ids_; + + DISALLOW_COPY_AND_ASSIGN(TestDelegate); +}; + +} // namespace + +typedef test::AshTestBase WebNotificationTrayTest; + +TEST_F(WebNotificationTrayTest, WebNotifications) { + scoped_ptr<WebNotificationTray> tray(CreateWebNotificationTray()); + scoped_ptr<TestDelegate> delegate(new TestDelegate); + tray->SetDelegate(delegate.get()); + + ASSERT_TRUE(tray->GetWidget()); + + // Adding a notification should show the bubble. + delegate->AddNotification(tray.get(), "test_id1"); + EXPECT_TRUE(tray->bubble() != NULL); + EXPECT_EQ(1, tray->GetNotificationCount()); + delegate->AddNotification(tray.get(), "test_id2"); + delegate->AddNotification(tray.get(), "test_id2"); + EXPECT_EQ(2, tray->GetNotificationCount()); + // Ensure that removing a notification removes it from the tray, and signals + // the delegate. + EXPECT_TRUE(delegate->HasNotificationId("test_id2")); + delegate->RemoveNotification(tray.get(), "test_id2"); + EXPECT_FALSE(delegate->HasNotificationId("test_id2")); + EXPECT_EQ(1, tray->GetNotificationCount()); + + // Removing the last notification should hide the bubble. + delegate->RemoveNotification(tray.get(), "test_id1"); + EXPECT_EQ(0, tray->GetNotificationCount()); + EXPECT_TRUE(tray->bubble() == NULL); +} + +} // namespace ash diff --git a/ash/wm/shelf_layout_manager.cc b/ash/wm/shelf_layout_manager.cc index 3f2e857..e6c674a 100644 --- a/ash/wm/shelf_layout_manager.cc +++ b/ash/wm/shelf_layout_manager.cc @@ -13,6 +13,7 @@ #include "ash/shell_window_ids.h" #include "ash/system/status_area_widget.h" #include "ash/system/tray/system_tray.h" +#include "ash/system/web_notification/web_notification_tray.h" #include "ash/wm/workspace/workspace_manager.h" #include "base/auto_reset.h" #include "base/i18n/rtl.h" @@ -196,10 +197,10 @@ bool ShelfLayoutManager::SetAlignment(ShelfAlignment alignment) { alignment_ = alignment; if (launcher_) launcher_->SetAlignment(alignment); - if (Shell::GetInstance()->status_area_widget()) + StatusAreaWidget* status_area_widget = + Shell::GetInstance()->status_area_widget(); + if (status_area_widget) Shell::GetInstance()->status_area_widget()->SetShelfAlignment(alignment); - if (Shell::GetInstance()->system_tray()) - Shell::GetInstance()->system_tray()->SetShelfAlignment(alignment); LayoutShelf(); return true; } @@ -493,10 +494,10 @@ void ShelfLayoutManager::UpdateShelfBackground( launcher_->SetPaintsBackground(launcher_paints, type); // SystemTray normally draws a background, but we don't want it to draw a // background when the launcher does. - if (Shell::GetInstance()->system_tray()) { - Shell::GetInstance()->system_tray()->SetPaintsBackground( - !launcher_paints, type); - } + StatusAreaWidget* status_area_widget = + Shell::GetInstance()->status_area_widget(); + if (status_area_widget) + status_area_widget->SetPaintsBackground(!launcher_paints, type); } bool ShelfLayoutManager::GetLauncherPaintsBackground() const { |