blob: 8e87df5b060c5b0419061dee21c2bf7d6cdb42a3 [file] [log] [blame]
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ui/message_center/views/message_center_view.h"
6
7#include <map>
8
9#include "base/stl_util.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010010#include "grit/ui_resources.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010011#include "grit/ui_strings.h"
12#include "ui/base/animation/slide_animation.h"
13#include "ui/base/l10n/l10n_util.h"
14#include "ui/base/resource/resource_bundle.h"
15#include "ui/gfx/canvas.h"
16#include "ui/gfx/insets.h"
17#include "ui/gfx/point.h"
18#include "ui/gfx/rect.h"
19#include "ui/gfx/size.h"
20#include "ui/gfx/text_constants.h"
21#include "ui/message_center/message_center.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010022#include "ui/message_center/message_center_style.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010023#include "ui/message_center/message_center_util.h"
24#include "ui/message_center/views/message_view.h"
25#include "ui/message_center/views/notification_view.h"
26#include "ui/views/animation/bounds_animator.h"
27#include "ui/views/animation/bounds_animator_observer.h"
28#include "ui/views/background.h"
29#include "ui/views/border.h"
30#include "ui/views/controls/button/button.h"
31#include "ui/views/controls/button/label_button.h"
32#include "ui/views/controls/label.h"
33#include "ui/views/controls/scroll_view.h"
34#include "ui/views/controls/scrollbar/kennedy_scroll_bar.h"
35#include "ui/views/layout/box_layout.h"
36#include "ui/views/layout/grid_layout.h"
37#include "ui/views/painter.h"
38#include "ui/views/widget/widget.h"
39
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010040namespace message_center {
41
42namespace {
43
44const int kMinScrollViewHeight = 100;
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010045const int kFooterLeftMargin = 17;
46const int kFooterRightMargin = 14;
47const int kButtonSize = 40;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010048const SkColor kNoNotificationsTextColor = SkColorSetRGB(0xb4, 0xb4, 0xb4);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010049const SkColor kBorderDarkColor = SkColorSetRGB(0xaa, 0xaa, 0xaa);
50const SkColor kTransparentColor = SkColorSetARGB(0, 0, 0, 0);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010051const SkColor kButtonTextHighlightColor = SkColorSetRGB(0x2a, 0x2a, 0x2a);
52const SkColor kButtonTextHoverColor = SkColorSetRGB(0x2a, 0x2a, 0x2a);
53
54// PoorMessageCenterButtonBar //////////////////////////////////////////////////
55
56// The view for the buttons at the bottom of the message center window used
57// when kEnableRichNotifications is false (hence the "poor" in the name :-).
58class PoorMessageCenterButtonBar : public MessageCenterButtonBar,
59 public views::ButtonListener {
60 public:
61 explicit PoorMessageCenterButtonBar(MessageCenter* message_center);
62
63 // Overridden from views::ButtonListener:
64 virtual void ButtonPressed(views::Button* sender,
65 const ui::Event& event) OVERRIDE;
66
67 private:
68 DISALLOW_COPY_AND_ASSIGN(PoorMessageCenterButtonBar);
69};
70
71PoorMessageCenterButtonBar::PoorMessageCenterButtonBar(
72 MessageCenter* message_center)
73 : MessageCenterButtonBar(message_center) {
74 set_background(views::Background::CreateBackgroundPainter(
75 true,
76 views::Painter::CreateVerticalGradient(kBackgroundLightColor,
77 kBackgroundDarkColor)));
78 set_border(views::Border::CreateSolidSidedBorder(
79 2, 0, 0, 0, kBorderDarkColor));
80
81 views::GridLayout* layout = new views::GridLayout(this);
82 SetLayoutManager(layout);
83 views::ColumnSet* columns = layout->AddColumnSet(0);
84 columns->AddPaddingColumn(100, 0);
85 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER,
86 0, /* resize percent */
87 views::GridLayout::USE_PREF, 0, 0);
88 columns->AddPaddingColumn(0, 4);
89
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010090 ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010091 views::LabelButton* close_all_button = new views::LabelButton(
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010092 this, resource_bundle.GetLocalizedString(IDS_MESSAGE_CENTER_CLEAR_ALL));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010093 close_all_button->SetHorizontalAlignment(gfx::ALIGN_CENTER);
94 close_all_button->set_request_focus_on_press(false);
95 close_all_button->SetTextColor(views::Button::STATE_NORMAL, kFooterTextColor);
96 close_all_button->SetTextColor(views::Button::STATE_HOVERED,
97 kButtonTextHoverColor);
98
99 layout->AddPaddingRow(0, 4);
100 layout->StartRow(0, 0);
101 layout->AddView(close_all_button);
102 set_close_all_button(close_all_button);
103}
104
105void PoorMessageCenterButtonBar::ButtonPressed(views::Button* sender,
106 const ui::Event& event) {
107 if (sender == close_all_button())
108 message_center()->RemoveAllNotifications(true); // Action by user.
109}
110
111// NotificationCenterButton ////////////////////////////////////////////////////
112
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100113class NotificationCenterButton : public views::ToggleImageButton {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100114 public:
115 NotificationCenterButton(views::ButtonListener* listener,
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100116 int normal_id,
117 int hover_id,
118 int pressed_id,
119 int text_id);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100120
121 protected:
122 // Overridden from views::View:
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100123 virtual gfx::Size GetPreferredSize() OVERRIDE;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100124 virtual void OnPaintFocusBorder(gfx::Canvas* canvas) OVERRIDE;
125
126 private:
127 DISALLOW_COPY_AND_ASSIGN(NotificationCenterButton);
128};
129
130NotificationCenterButton::NotificationCenterButton(
131 views::ButtonListener* listener,
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100132 int normal_id,
133 int hover_id,
134 int pressed_id,
135 int text_id)
136 : views::ToggleImageButton(listener) {
137 ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance();
138 SetImage(STATE_NORMAL, resource_bundle.GetImageSkiaNamed(normal_id));
139 SetImage(STATE_HOVERED, resource_bundle.GetImageSkiaNamed(hover_id));
140 SetImage(STATE_PRESSED, resource_bundle.GetImageSkiaNamed(pressed_id));
141 SetImageAlignment(views::ImageButton::ALIGN_CENTER,
142 views::ImageButton::ALIGN_MIDDLE);
143 SetTooltipText(resource_bundle.GetLocalizedString(text_id));
144 set_focusable(true);
145 set_request_focus_on_press(false);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100146}
147
148gfx::Size NotificationCenterButton::GetPreferredSize() {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100149 return gfx::Size(kButtonSize, kButtonSize);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100150}
151
152void NotificationCenterButton::OnPaintFocusBorder(gfx::Canvas* canvas) {
153 if (HasFocus() && (focusable() || IsAccessibilityFocusable())) {
154 canvas->DrawRect(gfx::Rect(2, 1, width() - 4, height() - 3),
155 kFocusBorderColor);
156 }
157}
158
159// RichMessageCenterButtonBar //////////////////////////////////////////////////
160
161// TODO(mukai): Merge this into MessageCenterButtonBar and get rid of
162// PoorMessageCenterButtonBar when the kEnableRichNotifications flag disappears.
163class RichMessageCenterButtonBar : public MessageCenterButtonBar,
164 public views::ButtonListener {
165 public:
166 explicit RichMessageCenterButtonBar(MessageCenter* message_center);
167
168 private:
169 // Overridden from views::View:
170 virtual void ChildVisibilityChanged(views::View* child) OVERRIDE;
171
172 // Overridden from views::ButtonListener:
173 virtual void ButtonPressed(views::Button* sender,
174 const ui::Event& event) OVERRIDE;
175
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100176 NotificationCenterButton* settings_button_;
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100177 NotificationCenterButton* quiet_mode_button_;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100178
179 DISALLOW_COPY_AND_ASSIGN(RichMessageCenterButtonBar);
180};
181
182RichMessageCenterButtonBar::RichMessageCenterButtonBar(
183 MessageCenter* message_center)
184 : MessageCenterButtonBar(message_center) {
185 set_background(views::Background::CreateSolidBackground(
186 kMessageCenterBackgroundColor));
187 set_border(views::Border::CreateSolidSidedBorder(
188 1, 0, 0, 0, kFooterDelimiterColor));
189
190
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100191 views::Label* notification_label = new views::Label(l10n_util::GetStringUTF16(
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100192 IDS_MESSAGE_CENTER_FOOTER_TITLE));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100193 notification_label->SetAutoColorReadabilityEnabled(false);
194 notification_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
195 notification_label->SetEnabledColor(kRegularTextColor);
196 AddChildView(notification_label);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100197
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100198 views::View* button_container = new views::View;
199 button_container->SetLayoutManager(
200 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
201 quiet_mode_button_ = new NotificationCenterButton(
202 this,
203 IDR_NOTIFICATION_PAUSE,
204 IDR_NOTIFICATION_PAUSE_HOVER,
205 IDR_NOTIFICATION_PAUSE_PRESSED,
206 IDS_MESSAGE_CENTER_QUIET_MODE_BUTTON_TOOLTIP);
207 ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance();
208 quiet_mode_button_->SetToggledImage(
209 views::Button::STATE_NORMAL,
210 resource_bundle.GetImageSkiaNamed(IDR_NOTIFICATION_PAUSE_PRESSED));
211 quiet_mode_button_->SetToggledImage(
212 views::Button::STATE_HOVERED,
213 resource_bundle.GetImageSkiaNamed(IDR_NOTIFICATION_PAUSE_PRESSED));
214 quiet_mode_button_->SetToggledImage(
215 views::Button::STATE_PRESSED,
216 resource_bundle.GetImageSkiaNamed(IDR_NOTIFICATION_PAUSE_PRESSED));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100217 quiet_mode_button_->SetToggled(message_center->IsQuietMode());
218 button_container->AddChildView(quiet_mode_button_);
219
220 NotificationCenterButton* close_all_button = new NotificationCenterButton(
221 this,
222 IDR_NOTIFICATION_CLEAR_ALL,
223 IDR_NOTIFICATION_CLEAR_ALL_HOVER,
224 IDR_NOTIFICATION_CLEAR_ALL_PRESSED,
225 IDS_MESSAGE_CENTER_CLEAR_ALL);
226 button_container->AddChildView(close_all_button);
227 set_close_all_button(close_all_button);
228 settings_button_ = new NotificationCenterButton(
229 this,
230 IDR_NOTIFICATION_SETTINGS,
231 IDR_NOTIFICATION_SETTINGS_HOVER,
232 IDR_NOTIFICATION_SETTINGS_PRESSED,
233 IDS_MESSAGE_CENTER_SETTINGS_BUTTON_LABEL);
234 button_container->AddChildView(settings_button_);
235
236 gfx::ImageSkia* settings_image =
237 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
238 IDR_NOTIFICATION_SETTINGS);
239 int image_margin = std::max(0, (kButtonSize - settings_image->width()) / 2);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100240 views::GridLayout* layout = new views::GridLayout(this);
241 SetLayoutManager(layout);
242 layout->SetInsets(
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100243 0, kFooterLeftMargin, 0, std::max(0, kFooterRightMargin - image_margin));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100244 views::ColumnSet* column = layout->AddColumnSet(0);
245 column->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
246 1.0f, views::GridLayout::USE_PREF, 0, 0);
247 column->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL,
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100248 0, views::GridLayout::USE_PREF, 0, 0);
249 layout->StartRow(0, 0);
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100250 layout->AddView(notification_label);
251 layout->AddView(button_container);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100252}
253
254// Overridden from views::View:
255void RichMessageCenterButtonBar::ChildVisibilityChanged(views::View* child) {
256 InvalidateLayout();
257}
258
259// Overridden from views::ButtonListener:
260void RichMessageCenterButtonBar::ButtonPressed(views::Button* sender,
261 const ui::Event& event) {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100262 if (sender == close_all_button()) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100263 message_center()->RemoveAllNotifications(true); // Action by user.
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100264 } else if (sender == settings_button_) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100265 message_center()->ShowNotificationSettingsDialog(
266 GetWidget()->GetNativeView());
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100267 } else if (sender == quiet_mode_button_) {
268 if (message_center()->IsQuietMode())
269 message_center()->SetQuietMode(false);
270 else
271 message_center()->EnterQuietModeWithExpire(base::TimeDelta::FromDays(1));
272 quiet_mode_button_->SetToggled(message_center()->IsQuietMode());
273 } else {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100274 NOTREACHED();
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100275 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100276}
277
278// BoundedScrollView ///////////////////////////////////////////////////////////
279
280// A custom scroll view whose height has a minimum and maximum value and whose
281// scroll bar disappears when not needed.
282class BoundedScrollView : public views::ScrollView {
283 public:
284 BoundedScrollView(int min_height, int max_height);
285
286 // Overridden from views::View:
287 virtual gfx::Size GetPreferredSize() OVERRIDE;
288 virtual int GetHeightForWidth(int width) OVERRIDE;
289 virtual void Layout() OVERRIDE;
290
291 private:
292 int min_height_;
293 int max_height_;
294
295 DISALLOW_COPY_AND_ASSIGN(BoundedScrollView);
296};
297
298BoundedScrollView::BoundedScrollView(int min_height, int max_height)
299 : min_height_(min_height),
300 max_height_(max_height) {
301 set_notify_enter_exit_on_child(true);
302 // Cancels the default dashed focus border.
303 set_focus_border(NULL);
304 if (IsRichNotificationEnabled()) {
305 set_background(views::Background::CreateSolidBackground(
306 kMessageCenterBackgroundColor));
307 SetVerticalScrollBar(new views::KennedyScrollBar(false));
308 }
309}
310
311gfx::Size BoundedScrollView::GetPreferredSize() {
312 gfx::Size size = contents()->GetPreferredSize();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100313 size.SetToMax(gfx::Size(size.width(), min_height_));
314 size.SetToMin(gfx::Size(size.width(), max_height_));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100315 gfx::Insets insets = GetInsets();
316 size.Enlarge(insets.width(), insets.height());
317 return size;
318}
319
320int BoundedScrollView::GetHeightForWidth(int width) {
321 gfx::Insets insets = GetInsets();
322 width = std::max(0, width - insets.width());
323 int height = contents()->GetHeightForWidth(width) + insets.height();
324 return std::min(std::max(height, min_height_), max_height_);
325}
326
327void BoundedScrollView::Layout() {
328 int content_width = width();
329 int content_height = contents()->GetHeightForWidth(content_width);
330 if (content_height > height()) {
331 content_width = std::max(content_width - GetScrollBarWidth(), 0);
332 content_height = contents()->GetHeightForWidth(content_width);
333 }
334 if (contents()->bounds().size() != gfx::Size(content_width, content_height))
335 contents()->SetBounds(0, 0, content_width, content_height);
336 views::ScrollView::Layout();
337}
338
339class NoNotificationMessageView : public views::View {
340 public:
341 NoNotificationMessageView();
342 virtual ~NoNotificationMessageView();
343
344 // Overridden from views::View.
345 virtual gfx::Size GetPreferredSize() OVERRIDE;
346 virtual int GetHeightForWidth(int width) OVERRIDE;
347 virtual void Layout() OVERRIDE;
348
349 private:
350 views::Label* label_;
351
352 DISALLOW_COPY_AND_ASSIGN(NoNotificationMessageView);
353};
354
355NoNotificationMessageView::NoNotificationMessageView() {
356 label_ = new views::Label(l10n_util::GetStringUTF16(
357 IDS_MESSAGE_CENTER_NO_MESSAGES));
358 label_->SetAutoColorReadabilityEnabled(false);
359 label_->SetEnabledColor(kNoNotificationsTextColor);
360 // Set transparent background to ensure that subpixel rendering
361 // is disabled. See crbug.com/169056
362#if defined(OS_LINUX) && defined(OS_CHROMEOS)
363 label_->SetBackgroundColor(kTransparentColor);
364#endif
365 AddChildView(label_);
366}
367
368NoNotificationMessageView::~NoNotificationMessageView() {
369}
370
371gfx::Size NoNotificationMessageView::GetPreferredSize() {
372 return gfx::Size(kMinScrollViewHeight, label_->GetPreferredSize().width());
373}
374
375int NoNotificationMessageView::GetHeightForWidth(int width) {
376 return kMinScrollViewHeight;
377}
378
379void NoNotificationMessageView::Layout() {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100380 int text_height = label_->GetHeightForWidth(width());
381 int margin = (height() - text_height) / 2;
382 label_->SetBounds(0, margin, width(), text_height);
383}
384
385} // namespace
386
387// MessageListView /////////////////////////////////////////////////////////////
388
389// Displays a list of messages.
390class MessageListView : public views::View {
391 public:
392 MessageListView();
393
394 // The interface for repositioning.
395 virtual void AddNotificationAt(views::View* view, int i);
396 virtual void RemoveNotificationAt(int i);
397 virtual void UpdateNotificationAt(views::View* view, int i);
398 virtual void SetRepositionTarget(const gfx::Rect& target_rect) {}
399 virtual void ResetRepositionSession() {}
400
401 private:
402 DISALLOW_COPY_AND_ASSIGN(MessageListView);
403};
404
405MessageListView::MessageListView() {
406 views::BoxLayout* layout =
407 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1);
408 layout->set_spread_blank_space(true);
409 SetLayoutManager(layout);
410}
411
412void MessageListView::AddNotificationAt(views::View* view, int i) {
413 AddChildViewAt(view, i);
414}
415
416void MessageListView::RemoveNotificationAt(int i) {
417 delete child_at(i);
418}
419
420void MessageListView::UpdateNotificationAt(views::View* view, int i) {
421 delete child_at(i);
422 AddChildViewAt(view, i);
423}
424
425// Displays a list of messages for rich notifications. It also supports
426// repositioning.
427class RichMessageListView : public MessageListView,
428 public views::BoundsAnimatorObserver {
429 public:
430 RichMessageListView();
431 virtual ~RichMessageListView();
432
433 protected:
434 // Overridden from views::View.
435 virtual void Layout() OVERRIDE;
436 virtual gfx::Size GetPreferredSize() OVERRIDE;
437 virtual int GetHeightForWidth(int width) OVERRIDE;
438
439 // Overridden from MessageListView.
440 virtual void AddNotificationAt(views::View* view, int i) OVERRIDE;
441 virtual void RemoveNotificationAt(int i) OVERRIDE;
442 virtual void UpdateNotificationAt(views::View* view, int i) OVERRIDE;
443 virtual void SetRepositionTarget(const gfx::Rect& target_rect) OVERRIDE;
444 virtual void ResetRepositionSession() OVERRIDE;
445
446 // Overridden from views::BoundsAnimatorObserver.
447 virtual void OnBoundsAnimatorProgressed(
448 views::BoundsAnimator* animator) OVERRIDE;
449 virtual void OnBoundsAnimatorDone(views::BoundsAnimator* animator) OVERRIDE;
450
451 private:
452 int GetActualIndex(int index);
453 bool IsValidChild(views::View* child);
454 void DoUpdateIfPossible();
455
456 // Schedules animation for a child to the specified position.
457 void AnimateChild(views::View* child, int top, int height);
458
459 // The top position of the reposition target rectangle.
460 int reposition_top_;
461
462 int fixed_height_;
463
464 bool has_deferred_task_;
465 std::set<views::View*> adding_views_;
466 std::set<views::View*> deleting_views_;
467 std::set<views::View*> deleted_when_done_;
468 scoped_ptr<views::BoundsAnimator> animator_;
469
470 DISALLOW_COPY_AND_ASSIGN(RichMessageListView);
471};
472
473RichMessageListView::RichMessageListView()
474 : reposition_top_(-1),
475 fixed_height_(0),
476 has_deferred_task_(false) {
477 // Set the margin to 0 for the layout. BoxLayout assumes the same margin
478 // for top and bottom, but the bottom margin here should be smaller
479 // because of the shadow of message view. Use an empty border instead
480 // to provide this margin.
481 gfx::Insets shadow_insets = MessageView::GetShadowInsets();
482 set_background(views::Background::CreateSolidBackground(
483 kMessageCenterBackgroundColor));
484 set_border(views::Border::CreateEmptyBorder(
485 kMarginBetweenItems - shadow_insets.top(), /* top */
486 kMarginBetweenItems - shadow_insets.left(), /* left */
487 kMarginBetweenItems - shadow_insets.bottom(), /* bottom */
488 kMarginBetweenItems - shadow_insets.right() /* right */ ));
489}
490
491RichMessageListView::~RichMessageListView() {
492 if (animator_.get())
493 animator_->RemoveObserver(this);
494}
495
496void RichMessageListView::Layout() {
497 if (animator_.get())
498 return;
499
500 gfx::Rect child_area = GetContentsBounds();
501 int top = child_area.y();
502 int between_items =
503 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
504
505 for (int i = 0; i < child_count(); ++i) {
506 views::View* child = child_at(i);
507 if (!child->visible())
508 continue;
509 int height = child->GetHeightForWidth(child_area.width());
510 child->SetBounds(child_area.x(), top, child_area.width(), height);
511 top += height + between_items;
512 }
513}
514
515void RichMessageListView::AddNotificationAt(views::View* view, int i) {
516 AddChildViewAt(view, GetActualIndex(i));
517 if (GetContentsBounds().IsEmpty())
518 return;
519
520 adding_views_.insert(view);
521 DoUpdateIfPossible();
522}
523
524void RichMessageListView::RemoveNotificationAt(int i) {
525 views::View* child = child_at(GetActualIndex(i));
526 if (GetContentsBounds().IsEmpty()) {
527 delete child;
528 } else {
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100529 if (child->layer()) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100530 deleting_views_.insert(child);
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100531 } else {
532 if (animator_.get())
533 animator_->StopAnimatingView(child);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100534 delete child;
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100535 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100536 DoUpdateIfPossible();
537 }
538}
539
540void RichMessageListView::UpdateNotificationAt(views::View* view, int i) {
541 views::View* child = child_at(GetActualIndex(i));
542 if (animator_.get())
543 animator_->StopAnimatingView(child);
544 gfx::Rect old_bounds = child->bounds();
545 if (deleting_views_.find(child) != deleting_views_.end())
546 deleting_views_.erase(child);
547 if (deleted_when_done_.find(child) != deleted_when_done_.end())
548 deleted_when_done_.erase(child);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100549 delete child;
550 AddChildViewAt(view, i);
551 view->SetBounds(old_bounds.x(), old_bounds.y(), old_bounds.width(),
552 view->GetHeightForWidth(old_bounds.width()));
553 DoUpdateIfPossible();
554}
555
556gfx::Size RichMessageListView::GetPreferredSize() {
557 int width = 0;
558 for (int i = 0; i < child_count(); i++) {
559 views::View* child = child_at(i);
560 if (IsValidChild(child))
561 width = std::max(width, child->GetPreferredSize().width());
562 }
563
564 return gfx::Size(width + GetInsets().width(),
565 GetHeightForWidth(width + GetInsets().width()));
566}
567
568int RichMessageListView::GetHeightForWidth(int width) {
569 if (fixed_height_ > 0)
570 return fixed_height_;
571
572 width -= GetInsets().width();
573 int height = 0;
574 int padding = 0;
575 for (int i = 0; i < child_count(); ++i) {
576 views::View* child = child_at(i);
577 if (!IsValidChild(child))
578 continue;
579 height += child->GetHeightForWidth(width) + padding;
580 padding = kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
581 }
582
583 return height + GetInsets().height();
584}
585
586void RichMessageListView::SetRepositionTarget(const gfx::Rect& target) {
587 reposition_top_ = target.y();
588 fixed_height_ = GetHeightForWidth(width());
589}
590
591void RichMessageListView::ResetRepositionSession() {
592 // Don't call DoUpdateIfPossible(), but let Layout() do the task without
593 // animation. Reset will cause the change of the bubble size itself, and
594 // animation from the old location will look weird.
595 if (reposition_top_ >= 0 && animator_.get()) {
596 has_deferred_task_ = false;
597 // cancel cause OnBoundsAnimatorDone which deletes |deleted_when_done_|.
598 animator_->Cancel();
599 STLDeleteContainerPointers(deleting_views_.begin(), deleting_views_.end());
600 deleting_views_.clear();
601 adding_views_.clear();
602 animator_.reset();
603 }
604
605 reposition_top_ = -1;
606 fixed_height_ = 0;
607}
608
609void RichMessageListView::OnBoundsAnimatorProgressed(
610 views::BoundsAnimator* animator) {
611 DCHECK_EQ(animator_.get(), animator);
612 for (std::set<views::View*>::iterator iter = deleted_when_done_.begin();
613 iter != deleted_when_done_.end(); ++iter) {
614 const ui::SlideAnimation* animation = animator->GetAnimationForView(*iter);
615 if (animation)
616 (*iter)->layer()->SetOpacity(animation->CurrentValueBetween(1.0, 0.0));
617 }
618}
619
620void RichMessageListView::OnBoundsAnimatorDone(
621 views::BoundsAnimator* animator) {
622 STLDeleteContainerPointers(
623 deleted_when_done_.begin(), deleted_when_done_.end());
624 deleted_when_done_.clear();
625
626 if (has_deferred_task_) {
627 has_deferred_task_ = false;
628 DoUpdateIfPossible();
629 }
630
631 if (GetWidget())
632 GetWidget()->SynthesizeMouseMoveEvent();
633}
634
635int RichMessageListView::GetActualIndex(int index) {
636 for (int i = 0; i < child_count() && i <= index; ++i)
637 index += IsValidChild(child_at(i)) ? 0 : 1;
638 return std::min(index, child_count());
639}
640
641bool RichMessageListView::IsValidChild(views::View* child) {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100642 return child->visible() &&
643 deleting_views_.find(child) == deleting_views_.end() &&
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100644 deleted_when_done_.find(child) == deleted_when_done_.end();
645}
646
647void RichMessageListView::DoUpdateIfPossible() {
648 gfx::Rect child_area = GetContentsBounds();
649 if (child_area.IsEmpty())
650 return;
651
652 if (animator_.get() && animator_->IsAnimating()) {
653 has_deferred_task_ = true;
654 return;
655 }
656
657 if (!animator_.get()) {
658 animator_.reset(new views::BoundsAnimator(this));
659 animator_->AddObserver(this);
660 }
661
662 int between_items =
663 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
664 int width = child_area.width();
665 views::View* last_child = NULL;
666 for (int i = child_count() - 1; i >= 0; --i) {
667 views::View* child = child_at(i);
668 if (IsValidChild(child)) {
669 last_child = child;
670 break;
671 }
672 }
673
674 if (!last_child || reposition_top_ < last_child->bounds().y()) {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100675 const int initial_top = std::max(reposition_top_, child_area.y());
676 int top = initial_top;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100677 for (int i = 0; i < child_count(); ++i) {
678 views::View* child = child_at(i);
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100679 if (adding_views_.find(child) == adding_views_.end() &&
680 child->bounds().y() < initial_top) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100681 continue;
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100682 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100683 int height = child->GetHeightForWidth(width);
684 AnimateChild(child, top, height);
685 if (IsValidChild(child))
686 top += height + between_items;
687 }
688 } else {
689 int bottom = reposition_top_ + last_child->GetHeightForWidth(width);
690 for (int i = child_count() - 1; i >= 0; --i) {
691 views::View* child = child_at(i);
692 int height = child->GetHeightForWidth(child_area.width());
693 AnimateChild(child, bottom - height, height);
694 if (IsValidChild(child))
695 bottom -= height + between_items;
696 }
697 }
698 adding_views_.clear();
699 deleting_views_.clear();
700}
701
702void RichMessageListView::AnimateChild(views::View* child,
703 int top,
704 int height) {
705 gfx::Rect child_area = GetContentsBounds();
706 if (adding_views_.find(child) != adding_views_.end()) {
707 child->SetBounds(child_area.right(), top, child_area.width(), height);
708 animator_->AnimateViewTo(
709 child, gfx::Rect(child_area.x(), top, child_area.width(), height));
710 } else if (deleting_views_.find(child) != deleting_views_.end()) {
711 DCHECK(child->layer());
712 // No moves, but animate to fade-out.
713 animator_->AnimateViewTo(child, child->bounds());
714 deleted_when_done_.insert(child);
715 } else {
716 gfx::Rect target(child_area.x(), top, child_area.width(), height);
717 if (child->bounds().origin() != target.origin())
718 animator_->AnimateViewTo(child, target);
719 else
720 child->SetBoundsRect(target);
721 }
722}
723
724// MessageCenterButtonBar //////////////////////////////////////////////////////
725
726MessageCenterButtonBar::MessageCenterButtonBar(MessageCenter* message_center)
727 : message_center_(message_center),
728 close_all_button_(NULL) {
729}
730
731MessageCenterButtonBar::~MessageCenterButtonBar() {
732}
733
734void MessageCenterButtonBar::SetCloseAllVisible(bool visible) {
735 if (close_all_button_)
736 close_all_button_->SetVisible(visible);
737}
738
739// MessageCenterView ///////////////////////////////////////////////////////////
740
741MessageCenterView::MessageCenterView(MessageCenter* message_center,
742 int max_height)
743 : message_center_(message_center) {
744 message_center_->AddObserver(this);
745 set_notify_enter_exit_on_child(true);
746 int between_child = IsRichNotificationEnabled() ? 0 : 1;
747 SetLayoutManager(
748 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, between_child));
749
750
751 if (IsRichNotificationEnabled())
752 button_bar_ = new RichMessageCenterButtonBar(message_center);
753 else
754 button_bar_ = new PoorMessageCenterButtonBar(message_center);
755
756 const int button_height = button_bar_->GetPreferredSize().height();
757 scroller_ = new BoundedScrollView(kMinScrollViewHeight,
758 max_height - button_height);
759
760 if (get_use_acceleration_when_possible()) {
761 scroller_->SetPaintToLayer(true);
762 scroller_->SetFillsBoundsOpaquely(false);
763 scroller_->layer()->SetMasksToBounds(true);
764 }
765
766 message_list_view_ = IsRichNotificationEnabled() ?
767 new RichMessageListView() : new MessageListView();
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100768 no_notifications_message_view_ = new NoNotificationMessageView();
769 // Set the default visibility to false, otherwise the notification has slide
770 // in animation when the center is shown.
771 no_notifications_message_view_->SetVisible(false);
772 message_list_view_->AddChildView(no_notifications_message_view_);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100773 scroller_->SetContents(message_list_view_);
774
775 AddChildView(scroller_);
776 AddChildView(button_bar_);
777}
778
779MessageCenterView::~MessageCenterView() {
780 message_center_->RemoveObserver(this);
781}
782
783void MessageCenterView::SetNotifications(
784 const NotificationList::Notifications& notifications) {
785 message_views_.clear();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100786 int index = 0;
787 for (NotificationList::Notifications::const_iterator iter =
788 notifications.begin(); iter != notifications.end();
789 ++iter, ++index) {
790 AddNotificationAt(*(*iter), index);
791 if (message_views_.size() >= kMaxVisibleMessageCenterNotifications)
792 break;
793 }
794 NotificationsChanged();
795 scroller_->RequestFocus();
796}
797
798size_t MessageCenterView::NumMessageViewsForTest() const {
799 return message_list_view_->child_count();
800}
801
802void MessageCenterView::Layout() {
803 scroller_->SetBounds(0, 0, width(), scroller_->GetHeightForWidth(width()));
804 views::View::Layout();
805 if (GetWidget())
806 GetWidget()->GetRootView()->SchedulePaint();
807}
808
809bool MessageCenterView::OnMouseWheel(const ui::MouseWheelEvent& event) {
810 // Do not rely on the default scroll event handler of ScrollView because
811 // the scroll happens only when the focus is on the ScrollView. The
812 // notification center will allow the scrolling even when the focus is on
813 // the buttons.
814 if (scroller_->bounds().Contains(event.location()))
815 return scroller_->OnMouseWheel(event);
816 return views::View::OnMouseWheel(event);
817}
818
819void MessageCenterView::OnMouseExited(const ui::MouseEvent& event) {
820 message_list_view_->ResetRepositionSession();
821 NotificationsChanged();
822}
823
824void MessageCenterView::OnNotificationAdded(const std::string& id) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100825 int index = 0;
826 const NotificationList::Notifications& notifications =
827 message_center_->GetNotifications();
828 for (NotificationList::Notifications::const_iterator iter =
829 notifications.begin(); iter != notifications.end();
830 ++iter, ++index) {
831 if ((*iter)->id() == id) {
832 AddNotificationAt(*(*iter), index);
833 break;
834 }
835 if (message_views_.size() >= kMaxVisibleMessageCenterNotifications)
836 break;
837 }
838 NotificationsChanged();
839}
840
841void MessageCenterView::OnNotificationRemoved(const std::string& id,
842 bool by_user) {
843 for (size_t i = 0; i < message_views_.size(); ++i) {
844 if (message_views_[i]->notification_id() == id) {
845 if (by_user) {
846 message_list_view_->SetRepositionTarget(message_views_[i]->bounds());
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100847 // Moves the keyboard focus to the next notification if the removed
848 // notification is focused so that the user can dismiss notifications
849 // without re-focusing by tab key.
850 if (message_views_.size() > 1) {
851 views::View* focused_view = GetFocusManager()->GetFocusedView();
852 if (message_views_[i]->IsCloseButtonFocused() ||
853 focused_view == message_views_[i]) {
854 size_t next_index = i + 1;
855 if (next_index >= message_views_.size())
856 next_index = message_views_.size() - 2;
857 if (focused_view == message_views_[i])
858 message_views_[next_index]->RequestFocus();
859 else
860 message_views_[next_index]->RequestFocusOnCloseButton();
861 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100862 }
863 }
864 message_list_view_->RemoveNotificationAt(i);
865 message_views_.erase(message_views_.begin() + i);
866 NotificationsChanged();
867 break;
868 }
869 }
870}
871
872void MessageCenterView::OnNotificationUpdated(const std::string& id) {
873 const NotificationList::Notifications& notifications =
874 message_center_->GetNotifications();
875 size_t index = 0;
876 for (NotificationList::Notifications::const_iterator iter =
877 notifications.begin();
878 iter != notifications.end() && index < message_views_.size();
879 ++iter, ++index) {
880 DCHECK((*iter)->id() == message_views_[index]->notification_id());
881 if ((*iter)->id() == id) {
882 MessageView* view = NotificationView::Create(
883 *(*iter), message_center_, true);
884 view->set_scroller(scroller_);
885 message_list_view_->UpdateNotificationAt(view, index);
886 message_views_[index] = view;
887 NotificationsChanged();
888 break;
889 }
890 }
891}
892
893void MessageCenterView::AddNotificationAt(const Notification& notification,
894 int index) {
895 // NotificationViews are expanded by default here until
896 // http://crbug.com/217902 is fixed. TODO(dharcourt): Fix.
897 MessageView* view = NotificationView::Create(
898 notification, message_center_, true);
899 view->set_scroller(scroller_);
900 message_views_.insert(message_views_.begin() + index, view);
901 message_list_view_->AddNotificationAt(view, index);
902 message_center_->DisplayedNotification(notification.id());
903}
904
905void MessageCenterView::NotificationsChanged() {
906 if (!message_views_.empty()) {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100907 no_notifications_message_view_->SetVisible(false);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100908 button_bar_->SetCloseAllVisible(true);
909 scroller_->set_focusable(true);
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100910 } else {
911 no_notifications_message_view_->SetVisible(true);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100912 button_bar_->SetCloseAllVisible(false);
913 scroller_->set_focusable(false);
914 }
915 scroller_->InvalidateLayout();
916 PreferredSizeChanged();
917 Layout();
918}
919
920void MessageCenterView::SetNotificationViewForTest(views::View* view) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100921 message_list_view_->AddNotificationAt(view, 0);
922}
923
924} // namespace message_center