Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 1 | // 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) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 10 | #include "grit/ui_resources.h" |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 11 | #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) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 22 | #include "ui/message_center/message_center_style.h" |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 23 | #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) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 40 | namespace message_center { |
| 41 | |
| 42 | namespace { |
| 43 | |
| 44 | const int kMinScrollViewHeight = 100; |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 45 | const int kFooterLeftMargin = 17; |
| 46 | const int kFooterRightMargin = 14; |
| 47 | const int kButtonSize = 40; |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 48 | const SkColor kNoNotificationsTextColor = SkColorSetRGB(0xb4, 0xb4, 0xb4); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 49 | const SkColor kBorderDarkColor = SkColorSetRGB(0xaa, 0xaa, 0xaa); |
| 50 | const SkColor kTransparentColor = SkColorSetARGB(0, 0, 0, 0); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 51 | const SkColor kButtonTextHighlightColor = SkColorSetRGB(0x2a, 0x2a, 0x2a); |
| 52 | const 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 :-). |
| 58 | class 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 | |
| 71 | PoorMessageCenterButtonBar::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) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 90 | ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance(); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 91 | views::LabelButton* close_all_button = new views::LabelButton( |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 92 | this, resource_bundle.GetLocalizedString(IDS_MESSAGE_CENTER_CLEAR_ALL)); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 93 | 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 | |
| 105 | void 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) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 113 | class NotificationCenterButton : public views::ToggleImageButton { |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 114 | public: |
| 115 | NotificationCenterButton(views::ButtonListener* listener, |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 116 | int normal_id, |
| 117 | int hover_id, |
| 118 | int pressed_id, |
| 119 | int text_id); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 120 | |
| 121 | protected: |
| 122 | // Overridden from views::View: |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 123 | virtual gfx::Size GetPreferredSize() OVERRIDE; |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 124 | virtual void OnPaintFocusBorder(gfx::Canvas* canvas) OVERRIDE; |
| 125 | |
| 126 | private: |
| 127 | DISALLOW_COPY_AND_ASSIGN(NotificationCenterButton); |
| 128 | }; |
| 129 | |
| 130 | NotificationCenterButton::NotificationCenterButton( |
| 131 | views::ButtonListener* listener, |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 132 | 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) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 146 | } |
| 147 | |
| 148 | gfx::Size NotificationCenterButton::GetPreferredSize() { |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 149 | return gfx::Size(kButtonSize, kButtonSize); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 150 | } |
| 151 | |
| 152 | void 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. |
| 163 | class 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) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 176 | NotificationCenterButton* settings_button_; |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 177 | NotificationCenterButton* quiet_mode_button_; |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 178 | |
| 179 | DISALLOW_COPY_AND_ASSIGN(RichMessageCenterButtonBar); |
| 180 | }; |
| 181 | |
| 182 | RichMessageCenterButtonBar::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) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 191 | views::Label* notification_label = new views::Label(l10n_util::GetStringUTF16( |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 192 | IDS_MESSAGE_CENTER_FOOTER_TITLE)); |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 193 | notification_label->SetAutoColorReadabilityEnabled(false); |
| 194 | notification_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| 195 | notification_label->SetEnabledColor(kRegularTextColor); |
| 196 | AddChildView(notification_label); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 197 | |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 198 | 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) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 217 | 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) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 240 | views::GridLayout* layout = new views::GridLayout(this); |
| 241 | SetLayoutManager(layout); |
| 242 | layout->SetInsets( |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 243 | 0, kFooterLeftMargin, 0, std::max(0, kFooterRightMargin - image_margin)); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 244 | 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) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 248 | 0, views::GridLayout::USE_PREF, 0, 0); |
| 249 | layout->StartRow(0, 0); |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 250 | layout->AddView(notification_label); |
| 251 | layout->AddView(button_container); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 252 | } |
| 253 | |
| 254 | // Overridden from views::View: |
| 255 | void RichMessageCenterButtonBar::ChildVisibilityChanged(views::View* child) { |
| 256 | InvalidateLayout(); |
| 257 | } |
| 258 | |
| 259 | // Overridden from views::ButtonListener: |
| 260 | void RichMessageCenterButtonBar::ButtonPressed(views::Button* sender, |
| 261 | const ui::Event& event) { |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 262 | if (sender == close_all_button()) { |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 263 | message_center()->RemoveAllNotifications(true); // Action by user. |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 264 | } else if (sender == settings_button_) { |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 265 | message_center()->ShowNotificationSettingsDialog( |
| 266 | GetWidget()->GetNativeView()); |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 267 | } 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) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 274 | NOTREACHED(); |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 275 | } |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 276 | } |
| 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. |
| 282 | class 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 | |
| 298 | BoundedScrollView::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 | |
| 311 | gfx::Size BoundedScrollView::GetPreferredSize() { |
| 312 | gfx::Size size = contents()->GetPreferredSize(); |
Torne (Richard Coles) | 868fa2f | 2013-06-11 10:57:03 +0100 | [diff] [blame^] | 313 | size.SetToMax(gfx::Size(size.width(), min_height_)); |
| 314 | size.SetToMin(gfx::Size(size.width(), max_height_)); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 315 | gfx::Insets insets = GetInsets(); |
| 316 | size.Enlarge(insets.width(), insets.height()); |
| 317 | return size; |
| 318 | } |
| 319 | |
| 320 | int 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 | |
| 327 | void 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 | |
| 339 | class 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 | |
| 355 | NoNotificationMessageView::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 | |
| 368 | NoNotificationMessageView::~NoNotificationMessageView() { |
| 369 | } |
| 370 | |
| 371 | gfx::Size NoNotificationMessageView::GetPreferredSize() { |
| 372 | return gfx::Size(kMinScrollViewHeight, label_->GetPreferredSize().width()); |
| 373 | } |
| 374 | |
| 375 | int NoNotificationMessageView::GetHeightForWidth(int width) { |
| 376 | return kMinScrollViewHeight; |
| 377 | } |
| 378 | |
| 379 | void NoNotificationMessageView::Layout() { |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 380 | 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. |
| 390 | class 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 | |
| 405 | MessageListView::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 | |
| 412 | void MessageListView::AddNotificationAt(views::View* view, int i) { |
| 413 | AddChildViewAt(view, i); |
| 414 | } |
| 415 | |
| 416 | void MessageListView::RemoveNotificationAt(int i) { |
| 417 | delete child_at(i); |
| 418 | } |
| 419 | |
| 420 | void 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. |
| 427 | class 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 | |
| 473 | RichMessageListView::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 | |
| 491 | RichMessageListView::~RichMessageListView() { |
| 492 | if (animator_.get()) |
| 493 | animator_->RemoveObserver(this); |
| 494 | } |
| 495 | |
| 496 | void 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 | |
| 515 | void 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 | |
| 524 | void RichMessageListView::RemoveNotificationAt(int i) { |
| 525 | views::View* child = child_at(GetActualIndex(i)); |
| 526 | if (GetContentsBounds().IsEmpty()) { |
| 527 | delete child; |
| 528 | } else { |
Torne (Richard Coles) | b2df76e | 2013-05-13 16:52:09 +0100 | [diff] [blame] | 529 | if (child->layer()) { |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 530 | deleting_views_.insert(child); |
Torne (Richard Coles) | b2df76e | 2013-05-13 16:52:09 +0100 | [diff] [blame] | 531 | } else { |
| 532 | if (animator_.get()) |
| 533 | animator_->StopAnimatingView(child); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 534 | delete child; |
Torne (Richard Coles) | b2df76e | 2013-05-13 16:52:09 +0100 | [diff] [blame] | 535 | } |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 536 | DoUpdateIfPossible(); |
| 537 | } |
| 538 | } |
| 539 | |
| 540 | void 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) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 549 | 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 | |
| 556 | gfx::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 | |
| 568 | int 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 | |
| 586 | void RichMessageListView::SetRepositionTarget(const gfx::Rect& target) { |
| 587 | reposition_top_ = target.y(); |
| 588 | fixed_height_ = GetHeightForWidth(width()); |
| 589 | } |
| 590 | |
| 591 | void 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 | |
| 609 | void 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 | |
| 620 | void 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 | |
| 635 | int 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 | |
| 641 | bool RichMessageListView::IsValidChild(views::View* child) { |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 642 | return child->visible() && |
| 643 | deleting_views_.find(child) == deleting_views_.end() && |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 644 | deleted_when_done_.find(child) == deleted_when_done_.end(); |
| 645 | } |
| 646 | |
| 647 | void 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) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 675 | const int initial_top = std::max(reposition_top_, child_area.y()); |
| 676 | int top = initial_top; |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 677 | for (int i = 0; i < child_count(); ++i) { |
| 678 | views::View* child = child_at(i); |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 679 | if (adding_views_.find(child) == adding_views_.end() && |
| 680 | child->bounds().y() < initial_top) { |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 681 | continue; |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 682 | } |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 683 | 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 | |
| 702 | void 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 | |
| 726 | MessageCenterButtonBar::MessageCenterButtonBar(MessageCenter* message_center) |
| 727 | : message_center_(message_center), |
| 728 | close_all_button_(NULL) { |
| 729 | } |
| 730 | |
| 731 | MessageCenterButtonBar::~MessageCenterButtonBar() { |
| 732 | } |
| 733 | |
| 734 | void MessageCenterButtonBar::SetCloseAllVisible(bool visible) { |
| 735 | if (close_all_button_) |
| 736 | close_all_button_->SetVisible(visible); |
| 737 | } |
| 738 | |
| 739 | // MessageCenterView /////////////////////////////////////////////////////////// |
| 740 | |
| 741 | MessageCenterView::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) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 768 | 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) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 773 | scroller_->SetContents(message_list_view_); |
| 774 | |
| 775 | AddChildView(scroller_); |
| 776 | AddChildView(button_bar_); |
| 777 | } |
| 778 | |
| 779 | MessageCenterView::~MessageCenterView() { |
| 780 | message_center_->RemoveObserver(this); |
| 781 | } |
| 782 | |
| 783 | void MessageCenterView::SetNotifications( |
| 784 | const NotificationList::Notifications& notifications) { |
| 785 | message_views_.clear(); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 786 | 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 | |
| 798 | size_t MessageCenterView::NumMessageViewsForTest() const { |
| 799 | return message_list_view_->child_count(); |
| 800 | } |
| 801 | |
| 802 | void MessageCenterView::Layout() { |
| 803 | scroller_->SetBounds(0, 0, width(), scroller_->GetHeightForWidth(width())); |
| 804 | views::View::Layout(); |
| 805 | if (GetWidget()) |
| 806 | GetWidget()->GetRootView()->SchedulePaint(); |
| 807 | } |
| 808 | |
| 809 | bool 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 | |
| 819 | void MessageCenterView::OnMouseExited(const ui::MouseEvent& event) { |
| 820 | message_list_view_->ResetRepositionSession(); |
| 821 | NotificationsChanged(); |
| 822 | } |
| 823 | |
| 824 | void MessageCenterView::OnNotificationAdded(const std::string& id) { |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 825 | 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 | |
| 841 | void 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) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 847 | // 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) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 862 | } |
| 863 | } |
| 864 | message_list_view_->RemoveNotificationAt(i); |
| 865 | message_views_.erase(message_views_.begin() + i); |
| 866 | NotificationsChanged(); |
| 867 | break; |
| 868 | } |
| 869 | } |
| 870 | } |
| 871 | |
| 872 | void 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 | |
| 893 | void 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 | |
| 905 | void MessageCenterView::NotificationsChanged() { |
| 906 | if (!message_views_.empty()) { |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 907 | no_notifications_message_view_->SetVisible(false); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 908 | button_bar_->SetCloseAllVisible(true); |
| 909 | scroller_->set_focusable(true); |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 910 | } else { |
| 911 | no_notifications_message_view_->SetVisible(true); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 912 | button_bar_->SetCloseAllVisible(false); |
| 913 | scroller_->set_focusable(false); |
| 914 | } |
| 915 | scroller_->InvalidateLayout(); |
| 916 | PreferredSizeChanged(); |
| 917 | Layout(); |
| 918 | } |
| 919 | |
| 920 | void MessageCenterView::SetNotificationViewForTest(views::View* view) { |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 921 | message_list_view_->AddNotificationAt(view, 0); |
| 922 | } |
| 923 | |
| 924 | } // namespace message_center |