1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ash/system/web_notification/web_notification_tray.h"
7 #include "ash/ash_switches.h"
8 #include "ash/root_window_controller.h"
9 #include "ash/shelf/shelf_layout_manager.h"
10 #include "ash/shelf/shelf_layout_manager_observer.h"
11 #include "ash/shelf/shelf_widget.h"
12 #include "ash/shell.h"
13 #include "ash/shell_window_ids.h"
14 #include "ash/system/status_area_widget.h"
15 #include "ash/system/tray/system_tray.h"
16 #include "ash/system/tray/tray_background_view.h"
17 #include "ash/system/tray/tray_bubble_wrapper.h"
18 #include "ash/system/tray/tray_constants.h"
19 #include "ash/system/tray/tray_utils.h"
20 #include "ash/system/web_notification/ash_popup_alignment_delegate.h"
21 #include "base/auto_reset.h"
22 #include "base/i18n/number_formatting.h"
23 #include "base/i18n/rtl.h"
24 #include "base/strings/utf_string_conversions.h"
25 #include "grit/ash_strings.h"
26 #include "ui/aura/window.h"
27 #include "ui/aura/window_event_dispatcher.h"
28 #include "ui/base/l10n/l10n_util.h"
29 #include "ui/gfx/screen.h"
30 #include "ui/message_center/message_center_style.h"
31 #include "ui/message_center/message_center_tray_delegate.h"
32 #include "ui/message_center/views/message_bubble_base.h"
33 #include "ui/message_center/views/message_center_bubble.h"
34 #include "ui/message_center/views/message_popup_collection.h"
35 #include "ui/strings/grit/ui_strings.h"
36 #include "ui/views/bubble/tray_bubble_view.h"
37 #include "ui/views/controls/button/custom_button.h"
38 #include "ui/views/controls/image_view.h"
39 #include "ui/views/controls/label.h"
40 #include "ui/views/controls/menu/menu_runner.h"
41 #include "ui/views/layout/fill_layout.h"
43 #if defined(OS_CHROMEOS)
45 namespace message_center
{
47 MessageCenterTrayDelegate
* CreateMessageCenterTray() {
48 // On Windows+Ash the Tray will not be hosted in ash::Shell.
53 } // namespace message_center
55 #endif // defined(OS_CHROMEOS)
61 const int kToggleQuietMode
= 0;
62 const int kEnableQuietModeDay
= 2;
68 const SkColor kWebNotificationColorNoUnread
=
69 SkColorSetARGB(128, 255, 255, 255);
70 const SkColor kWebNotificationColorWithUnread
= SK_ColorWHITE
;
74 // Class to initialize and manage the WebNotificationBubble and
75 // TrayBubbleWrapper instances for a bubble.
76 class WebNotificationBubbleWrapper
{
78 // Takes ownership of |bubble| and creates |bubble_wrapper_|.
79 WebNotificationBubbleWrapper(WebNotificationTray
* tray
,
80 message_center::MessageBubbleBase
* bubble
) {
81 bubble_
.reset(bubble
);
82 views::TrayBubbleView::AnchorAlignment anchor_alignment
=
83 tray
->GetAnchorAlignment();
84 views::TrayBubbleView::InitParams init_params
=
85 bubble
->GetInitParams(anchor_alignment
);
86 views::View
* anchor
= tray
->tray_container();
87 if (anchor_alignment
== views::TrayBubbleView::ANCHOR_ALIGNMENT_BOTTOM
) {
88 gfx::Point
bounds(anchor
->width() / 2, 0);
89 views::View::ConvertPointToWidget(anchor
, &bounds
);
90 init_params
.arrow_offset
= bounds
.x();
92 views::TrayBubbleView
* bubble_view
= views::TrayBubbleView::Create(
93 tray
->GetBubbleWindowContainer(), anchor
, tray
, &init_params
);
94 bubble_wrapper_
.reset(new TrayBubbleWrapper(tray
, bubble_view
));
95 bubble_view
->SetArrowPaintType(views::BubbleBorder::PAINT_NONE
);
96 bubble
->InitializeContents(bubble_view
);
99 message_center::MessageBubbleBase
* bubble() const { return bubble_
.get(); }
101 // Convenience accessors.
102 views::TrayBubbleView
* bubble_view() const { return bubble_
->bubble_view(); }
105 scoped_ptr
<message_center::MessageBubbleBase
> bubble_
;
106 scoped_ptr
<TrayBubbleWrapper
> bubble_wrapper_
;
108 DISALLOW_COPY_AND_ASSIGN(WebNotificationBubbleWrapper
);
111 class WebNotificationButton
: public views::CustomButton
{
113 WebNotificationButton(views::ButtonListener
* listener
)
114 : views::CustomButton(listener
),
115 is_bubble_visible_(false),
117 SetLayoutManager(new views::FillLayout
);
118 unread_label_
= new views::Label();
119 SetupLabelForTray(unread_label_
);
120 AddChildView(unread_label_
);
123 void SetBubbleVisible(bool visible
) {
124 if (visible
== is_bubble_visible_
)
127 is_bubble_visible_
= visible
;
128 UpdateIconVisibility();
131 void SetUnreadCount(int unread_count
) {
132 // base::FormatNumber doesn't convert to arabic numeric characters.
133 // TODO(mukai): use ICU to support conversion for such locales.
134 unread_count_
= unread_count
;
135 // TODO(mukai): move NINE_PLUS message to ui_strings, it doesn't need to be
137 unread_label_
->SetText((unread_count
> 9) ?
138 l10n_util::GetStringUTF16(IDS_ASH_NOTIFICATION_UNREAD_COUNT_NINE_PLUS
) :
139 base::FormatNumber(unread_count
));
140 UpdateIconVisibility();
144 // Overridden from views::ImageButton:
145 gfx::Size
GetPreferredSize() const override
{
146 return gfx::Size(kShelfItemHeight
, kShelfItemHeight
);
149 int GetHeightForWidth(int width
) const override
{
150 return GetPreferredSize().height();
154 void UpdateIconVisibility() {
155 unread_label_
->SetEnabledColor(
156 (!is_bubble_visible_
&& unread_count_
> 0) ?
157 kWebNotificationColorWithUnread
: kWebNotificationColorNoUnread
);
161 bool is_bubble_visible_
;
164 views::Label
* unread_label_
;
166 DISALLOW_COPY_AND_ASSIGN(WebNotificationButton
);
169 WebNotificationTray::WebNotificationTray(StatusAreaWidget
* status_area_widget
)
170 : TrayBackgroundView(status_area_widget
),
172 show_message_center_on_unlock_(false),
173 should_update_tray_content_(false),
174 should_block_shelf_auto_hide_(false) {
175 button_
= new WebNotificationButton(this);
176 button_
->set_triggerable_event_flags(
177 ui::EF_LEFT_MOUSE_BUTTON
| ui::EF_RIGHT_MOUSE_BUTTON
);
178 tray_container()->AddChildView(button_
);
179 SetContentsBackground();
180 tray_container()->SetBorder(views::Border::NullBorder());
181 message_center_tray_
.reset(new message_center::MessageCenterTray(
183 message_center::MessageCenter::Get()));
184 popup_alignment_delegate_
.reset(new AshPopupAlignmentDelegate());
185 popup_collection_
.reset(new message_center::MessagePopupCollection(
186 ash::Shell::GetContainer(
187 status_area_widget
->GetNativeView()->GetRootWindow(),
188 kShellWindowId_StatusContainer
),
190 message_center_tray_
.get(),
191 popup_alignment_delegate_
.get()));
192 const gfx::Display
& display
= Shell::GetScreen()->GetDisplayNearestWindow(
193 status_area_widget
->GetNativeView());
194 popup_alignment_delegate_
->StartObserving(Shell::GetScreen(), display
);
195 OnMessageCenterTrayChanged();
198 WebNotificationTray::~WebNotificationTray() {
199 // Release any child views that might have back pointers before ~View().
200 message_center_bubble_
.reset();
201 popup_alignment_delegate_
.reset();
202 popup_collection_
.reset();
207 bool WebNotificationTray::ShowMessageCenterInternal(bool show_settings
) {
208 if (!ShouldShowMessageCenter())
211 should_block_shelf_auto_hide_
= true;
212 message_center::MessageCenterBubble
* message_center_bubble
=
213 new message_center::MessageCenterBubble(
215 message_center_tray_
.get(),
219 aura::Window
* status_area_window
= status_area_widget()->GetNativeView();
220 switch (GetShelfLayoutManager()->GetAlignment()) {
221 case SHELF_ALIGNMENT_BOTTOM
: {
222 gfx::Rect shelf_bounds
= GetShelfLayoutManager()->GetIdealBounds();
223 max_height
= shelf_bounds
.y();
226 case SHELF_ALIGNMENT_TOP
: {
227 aura::Window
* root
= status_area_window
->GetRootWindow();
229 root
->bounds().height() - status_area_window
->bounds().height();
232 case SHELF_ALIGNMENT_LEFT
:
233 case SHELF_ALIGNMENT_RIGHT
: {
234 // Assume that the bottom line of the status area widget and the bubble
236 max_height
= status_area_window
->GetBoundsInRootWindow().bottom();
243 message_center_bubble
->SetMaxHeight(std::max(0,
244 max_height
- kTraySpacing
));
246 message_center_bubble
->SetSettingsVisible();
247 message_center_bubble_
.reset(
248 new WebNotificationBubbleWrapper(this, message_center_bubble
));
250 status_area_widget()->SetHideSystemNotifications(true);
251 GetShelfLayoutManager()->UpdateAutoHideState();
252 button_
->SetBubbleVisible(true);
253 SetDrawBackgroundAsActive(true);
257 bool WebNotificationTray::ShowMessageCenter() {
258 return ShowMessageCenterInternal(false /* show_settings */);
261 void WebNotificationTray::HideMessageCenter() {
262 if (!message_center_bubble())
264 SetDrawBackgroundAsActive(false);
265 message_center_bubble_
.reset();
266 should_block_shelf_auto_hide_
= false;
267 show_message_center_on_unlock_
= false;
268 status_area_widget()->SetHideSystemNotifications(false);
269 GetShelfLayoutManager()->UpdateAutoHideState();
270 button_
->SetBubbleVisible(false);
273 void WebNotificationTray::SetSystemTrayHeight(int height
) {
274 popup_alignment_delegate_
->SetSystemTrayHeight(height
);
277 bool WebNotificationTray::ShowPopups() {
278 if (message_center_bubble())
281 popup_collection_
->DoUpdateIfPossible();
285 void WebNotificationTray::HidePopups() {
286 DCHECK(popup_collection_
.get());
287 popup_collection_
->MarkAllPopupsShown();
292 bool WebNotificationTray::ShouldShowMessageCenter() {
293 return status_area_widget()->login_status() != user::LOGGED_IN_LOCKED
&&
294 !(status_area_widget()->system_tray() &&
295 status_area_widget()->system_tray()->HasNotificationBubble());
298 bool WebNotificationTray::ShouldBlockShelfAutoHide() const {
299 return should_block_shelf_auto_hide_
;
302 bool WebNotificationTray::IsMessageCenterBubbleVisible() const {
303 return (message_center_bubble() &&
304 message_center_bubble()->bubble()->IsVisible());
307 bool WebNotificationTray::IsMouseInNotificationBubble() const {
311 void WebNotificationTray::ShowMessageCenterBubble() {
312 if (!IsMessageCenterBubbleVisible())
313 message_center_tray_
->ShowMessageCenterBubble();
316 void WebNotificationTray::UpdateAfterLoginStatusChange(
317 user::LoginStatus login_status
) {
318 OnMessageCenterTrayChanged();
321 void WebNotificationTray::SetShelfAlignment(ShelfAlignment alignment
) {
322 if (alignment
== shelf_alignment())
324 TrayBackgroundView::SetShelfAlignment(alignment
);
325 tray_container()->SetBorder(views::Border::NullBorder());
326 // Destroy any existing bubble so that it will be rebuilt correctly.
327 message_center_tray_
->HideMessageCenterBubble();
328 message_center_tray_
->HidePopupBubble();
331 void WebNotificationTray::AnchorUpdated() {
332 if (message_center_bubble()) {
333 message_center_bubble()->bubble_view()->UpdateBubble();
334 UpdateBubbleViewArrow(message_center_bubble()->bubble_view());
338 base::string16
WebNotificationTray::GetAccessibleNameForTray() {
339 return l10n_util::GetStringUTF16(
340 IDS_MESSAGE_CENTER_ACCESSIBLE_NAME
);
343 void WebNotificationTray::HideBubbleWithView(
344 const views::TrayBubbleView
* bubble_view
) {
345 if (message_center_bubble() &&
346 bubble_view
== message_center_bubble()->bubble_view()) {
347 message_center_tray_
->HideMessageCenterBubble();
348 } else if (popup_collection_
.get()) {
349 message_center_tray_
->HidePopupBubble();
353 bool WebNotificationTray::PerformAction(const ui::Event
& event
) {
354 if (message_center_bubble())
355 message_center_tray_
->HideMessageCenterBubble();
357 message_center_tray_
->ShowMessageCenterBubble();
361 void WebNotificationTray::BubbleViewDestroyed() {
362 if (message_center_bubble())
363 message_center_bubble()->bubble()->BubbleViewDestroyed();
366 void WebNotificationTray::OnMouseEnteredView() {}
368 void WebNotificationTray::OnMouseExitedView() {}
370 base::string16
WebNotificationTray::GetAccessibleNameForBubble() {
371 return GetAccessibleNameForTray();
374 gfx::Rect
WebNotificationTray::GetAnchorRect(
375 views::Widget
* anchor_widget
,
376 views::TrayBubbleView::AnchorType anchor_type
,
377 views::TrayBubbleView::AnchorAlignment anchor_alignment
) const {
378 return GetBubbleAnchorRect(anchor_widget
, anchor_type
, anchor_alignment
);
381 void WebNotificationTray::HideBubble(const views::TrayBubbleView
* bubble_view
) {
382 HideBubbleWithView(bubble_view
);
385 bool WebNotificationTray::ShowNotifierSettings() {
386 if (message_center_bubble()) {
387 static_cast<message_center::MessageCenterBubble
*>(
388 message_center_bubble()->bubble())->SetSettingsVisible();
391 return ShowMessageCenterInternal(true /* show_settings */);
394 bool WebNotificationTray::IsContextMenuEnabled() const {
395 user::LoginStatus login_status
= status_area_widget()->login_status();
396 bool userAddingRunning
= ash::Shell::GetInstance()
397 ->session_state_delegate()
398 ->IsInSecondaryLoginScreen();
400 return login_status
!= user::LOGGED_IN_NONE
401 && login_status
!= user::LOGGED_IN_LOCKED
&& !userAddingRunning
;
404 message_center::MessageCenterTray
* WebNotificationTray::GetMessageCenterTray() {
405 return message_center_tray_
.get();
408 bool WebNotificationTray::IsCommandIdChecked(int command_id
) const {
409 if (command_id
!= kToggleQuietMode
)
411 return message_center()->IsQuietMode();
414 bool WebNotificationTray::IsCommandIdEnabled(int command_id
) const {
418 bool WebNotificationTray::GetAcceleratorForCommandId(
420 ui::Accelerator
* accelerator
) {
424 void WebNotificationTray::ExecuteCommand(int command_id
, int event_flags
) {
425 if (command_id
== kToggleQuietMode
) {
426 bool in_quiet_mode
= message_center()->IsQuietMode();
427 message_center()->SetQuietMode(!in_quiet_mode
);
430 base::TimeDelta expires_in
= command_id
== kEnableQuietModeDay
?
431 base::TimeDelta::FromDays(1):
432 base::TimeDelta::FromHours(1);
433 message_center()->EnterQuietModeWithExpire(expires_in
);
436 void WebNotificationTray::ButtonPressed(views::Button
* sender
,
437 const ui::Event
& event
) {
438 DCHECK_EQ(button_
, sender
);
439 PerformAction(event
);
442 void WebNotificationTray::OnMessageCenterTrayChanged() {
443 // Do not update the tray contents directly. Multiple change events can happen
444 // consecutively, and calling Update in the middle of those events will show
445 // intermediate unread counts for a moment.
446 should_update_tray_content_
= true;
447 base::MessageLoop::current()->PostTask(
449 base::Bind(&WebNotificationTray::UpdateTrayContent
, AsWeakPtr()));
452 void WebNotificationTray::UpdateTrayContent() {
453 if (!should_update_tray_content_
)
455 should_update_tray_content_
= false;
457 message_center::MessageCenter
* message_center
=
458 message_center_tray_
->message_center();
459 button_
->SetUnreadCount(message_center
->UnreadNotificationCount());
460 if (IsMessageCenterBubbleVisible())
461 button_
->SetState(views::CustomButton::STATE_PRESSED
);
463 button_
->SetState(views::CustomButton::STATE_NORMAL
);
464 bool userAddingRunning
= ash::Shell::GetInstance()
465 ->session_state_delegate()
466 ->IsInSecondaryLoginScreen();
468 SetVisible((status_area_widget()->login_status() != user::LOGGED_IN_NONE
) &&
469 (status_area_widget()->login_status() != user::LOGGED_IN_LOCKED
) &&
470 !userAddingRunning
&& (message_center
->NotificationCount() > 0));
475 bool WebNotificationTray::ClickedOutsideBubble() {
476 // Only hide the message center
477 if (!message_center_bubble())
480 message_center_tray_
->HideMessageCenterBubble();
484 message_center::MessageCenter
* WebNotificationTray::message_center() const {
485 return message_center_tray_
->message_center();
488 // Methods for testing
490 bool WebNotificationTray::IsPopupVisible() const {
491 return message_center_tray_
->popups_visible();
494 message_center::MessageCenterBubble
*
495 WebNotificationTray::GetMessageCenterBubbleForTest() {
496 if (!message_center_bubble())
498 return static_cast<message_center::MessageCenterBubble
*>(
499 message_center_bubble()->bubble());