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 "ui/message_center/views/notification_view.h"
7 #include "base/command_line.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "grit/ui_resources.h"
11 #include "ui/base/layout.h"
12 #include "ui/base/resource/resource_bundle.h"
13 #include "ui/gfx/canvas.h"
14 #include "ui/gfx/size.h"
15 #include "ui/gfx/skia_util.h"
16 #include "ui/gfx/text_elider.h"
17 #include "ui/message_center/message_center.h"
18 #include "ui/message_center/message_center_style.h"
19 #include "ui/message_center/message_center_switches.h"
20 #include "ui/message_center/message_center_util.h"
21 #include "ui/message_center/notification.h"
22 #include "ui/message_center/notification_types.h"
23 #include "ui/message_center/views/bounded_label.h"
24 #include "ui/native_theme/native_theme.h"
25 #include "ui/views/controls/button/image_button.h"
26 #include "ui/views/controls/image_view.h"
27 #include "ui/views/controls/label.h"
28 #include "ui/views/controls/progress_bar.h"
29 #include "ui/views/layout/box_layout.h"
30 #include "ui/views/layout/fill_layout.h"
31 #include "ui/views/widget/widget.h"
34 #include "ui/base/cursor/cursor.h"
40 const int kIconSize = message_center::kNotificationIconSize;
41 const int kLegacyIconSize = 40;
42 const int kTextLeftPadding = kIconSize + message_center::kIconToTextPadding;
43 const int kTextBottomPadding = 12;
44 const int kTextRightPadding = 23;
45 const int kItemTitleToMessagePadding = 3;
46 const int kProgressBarWidth = message_center::kNotificationWidth -
47 kTextLeftPadding - kTextRightPadding;
48 const int kProgressBarBottomPadding = 0;
49 const int kButtonVecticalPadding = 0;
50 const int kButtonTitleTopPadding = 0;
52 // Character limits: Displayed text will be subject to the line limits above,
53 // but we also remove trailing characters from text to reduce processing cost.
54 // Character limit = pixels per line * line limit / min. pixels per character.
55 const size_t kTitleCharacterLimit =
56 message_center::kNotificationWidth * message_center::kTitleLineLimit / 4;
57 const size_t kMessageCharacterLimit =
58 message_center::kNotificationWidth *
59 message_center::kMessageExpandedLineLimit / 3;
60 const size_t kContextMessageCharacterLimit =
61 message_center::kNotificationWidth *
62 message_center::kContextMessageLineLimit / 3;
64 // Notification colors. The text background colors below are used only to keep
65 // view::Label from modifying the text color and will not actually be drawn.
66 // See view::Label's RecalculateColors() for details.
67 const SkColor kRegularTextBackgroundColor = SK_ColorWHITE;
68 const SkColor kDimTextBackgroundColor = SK_ColorWHITE;
69 const SkColor kContextTextBackgroundColor = SK_ColorWHITE;
72 views::Background* MakeBackground(
73 SkColor color = message_center::kNotificationBackgroundColor) {
74 return views::Background::CreateSolidBackground(color);
78 views::Border* MakeEmptyBorder(int top, int left, int bottom, int right) {
79 return views::Border::CreateEmptyBorder(top, left, bottom, right);
83 views::Border* MakeTextBorder(int padding, int top, int bottom) {
84 // Split the padding between the top and the bottom, then add the extra space.
85 return MakeEmptyBorder(padding / 2 + top, kTextLeftPadding,
86 (padding + 1) / 2 + bottom, kTextRightPadding);
90 views::Border* MakeProgressBarBorder(int top, int bottom) {
91 return MakeEmptyBorder(top, kTextLeftPadding, bottom, kTextRightPadding);
95 views::Border* MakeSeparatorBorder(int top, int left, SkColor color) {
96 return views::Border::CreateSolidSidedBorder(top, left, 0, 0, color);
100 // Return true if and only if the image is null or has alpha.
101 bool HasAlpha(gfx::ImageSkia& image, views::Widget* widget) {
102 // Determine which bitmap to use.
103 ui::ScaleFactor factor = ui::SCALE_FACTOR_100P;
105 factor = ui::GetScaleFactorForNativeView(widget->GetNativeView());
106 if (factor == ui::SCALE_FACTOR_NONE)
107 factor = ui::SCALE_FACTOR_100P;
110 // Extract that bitmap's alpha and look for a non-opaque pixel there.
112 image.GetRepresentation(ui::GetImageScale(factor)).sk_bitmap();
113 if (!bitmap.isNull()) {
115 alpha.setConfig(SkBitmap::kA1_Config, bitmap.width(), bitmap.height(), 0);
116 bitmap.extractAlpha(&alpha);
117 for (int y = 0; y < bitmap.height(); ++y) {
118 for (int x = 0; x < bitmap.width(); ++x) {
119 if (alpha.getColor(x, y) != SK_ColorBLACK) {
126 // If no opaque pixel was found, return false unless the bitmap is empty.
127 return bitmap.isNull();
130 // ItemView ////////////////////////////////////////////////////////////////////
132 // ItemViews are responsible for drawing each list notification item's title and
133 // message next to each other within a single column.
134 class ItemView : public views::View {
136 ItemView(const message_center::NotificationItem& item);
139 // Overridden from views::View:
140 virtual void SetVisible(bool visible) OVERRIDE;
143 DISALLOW_COPY_AND_ASSIGN(ItemView);
146 ItemView::ItemView(const message_center::NotificationItem& item) {
147 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal,
148 0, 0, kItemTitleToMessagePadding));
150 views::Label* title = new views::Label(item.title);
151 title->set_collapse_when_hidden(true);
152 title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
153 title->SetEnabledColor(message_center::kRegularTextColor);
154 title->SetBackgroundColor(kRegularTextBackgroundColor);
157 views::Label* message = new views::Label(item.message);
158 message->set_collapse_when_hidden(true);
159 message->SetHorizontalAlignment(gfx::ALIGN_LEFT);
160 message->SetEnabledColor(message_center::kDimTextColor);
161 message->SetBackgroundColor(kDimTextBackgroundColor);
162 AddChildView(message);
164 PreferredSizeChanged();
168 ItemView::~ItemView() {
171 void ItemView::SetVisible(bool visible) {
172 views::View::SetVisible(visible);
173 for (int i = 0; i < child_count(); ++i)
174 child_at(i)->SetVisible(visible);
177 // ProportionalImageView ///////////////////////////////////////////////////////
179 // ProportionalImageViews center their images to preserve their proportion.
180 class ProportionalImageView : public views::View {
182 ProportionalImageView(const gfx::ImageSkia& image);
183 virtual ~ProportionalImageView();
185 // Overridden from views::View:
186 virtual gfx::Size GetPreferredSize() OVERRIDE;
187 virtual int GetHeightForWidth(int width) OVERRIDE;
188 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
191 gfx::Size GetImageSizeForWidth(int width);
193 gfx::ImageSkia image_;
195 DISALLOW_COPY_AND_ASSIGN(ProportionalImageView);
198 ProportionalImageView::ProportionalImageView(const gfx::ImageSkia& image)
202 ProportionalImageView::~ProportionalImageView() {
205 gfx::Size ProportionalImageView::GetPreferredSize() {
206 gfx::Size size = GetImageSizeForWidth(image_.width());
207 return gfx::Size(size.width() + GetInsets().width(),
208 size.height() + GetInsets().height());
211 int ProportionalImageView::GetHeightForWidth(int width) {
212 return GetImageSizeForWidth(width).height();
215 void ProportionalImageView::OnPaint(gfx::Canvas* canvas) {
216 views::View::OnPaint(canvas);
218 gfx::Size draw_size(GetImageSizeForWidth(width()));
219 if (!draw_size.IsEmpty()) {
220 gfx::Rect draw_bounds = GetContentsBounds();
221 draw_bounds.ClampToCenteredSize(draw_size);
223 gfx::Size image_size(image_.size());
224 if (image_size == draw_size) {
225 canvas->DrawImageInt(image_, draw_bounds.x(), draw_bounds.y());
229 paint.setFilterBitmap(true);
230 canvas->DrawImageInt(image_, 0, 0,
231 image_size.width(), image_size.height(),
232 draw_bounds.x(), draw_bounds.y(),
233 draw_size.width(), draw_size.height(),
239 gfx::Size ProportionalImageView::GetImageSizeForWidth(int width) {
240 gfx::Size size = visible() ? image_.size() : gfx::Size();
241 return message_center::GetImageSizeForWidth(width, size);
244 // NotificationProgressBar /////////////////////////////////////////////////////
246 class NotificationProgressBar : public views::ProgressBar {
248 NotificationProgressBar();
249 virtual ~NotificationProgressBar();
252 // Overriden from View
253 virtual gfx::Size GetPreferredSize() OVERRIDE;
254 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
256 DISALLOW_COPY_AND_ASSIGN(NotificationProgressBar);
259 NotificationProgressBar::NotificationProgressBar() {
262 NotificationProgressBar::~NotificationProgressBar() {
265 gfx::Size NotificationProgressBar::GetPreferredSize() {
266 gfx::Size pref_size(kProgressBarWidth, message_center::kProgressBarThickness);
267 gfx::Insets insets = GetInsets();
268 pref_size.Enlarge(insets.width(), insets.height());
272 void NotificationProgressBar::OnPaint(gfx::Canvas* canvas) {
273 gfx::Rect content_bounds = GetContentsBounds();
276 SkPath background_path;
277 background_path.addRoundRect(gfx::RectToSkRect(content_bounds),
278 message_center::kProgressBarCornerRadius,
279 message_center::kProgressBarCornerRadius);
280 SkPaint background_paint;
281 background_paint.setStyle(SkPaint::kFill_Style);
282 background_paint.setFlags(SkPaint::kAntiAlias_Flag);
283 background_paint.setColor(message_center::kProgressBarBackgroundColor);
284 canvas->DrawPath(background_path, background_paint);
287 const int slice_width =
288 static_cast<int>(content_bounds.width() * GetNormalizedValue() + 0.5);
292 gfx::Rect slice_bounds = content_bounds;
293 slice_bounds.set_width(slice_width);
295 slice_path.addRoundRect(gfx::RectToSkRect(slice_bounds),
296 message_center::kProgressBarCornerRadius,
297 message_center::kProgressBarCornerRadius);
299 slice_paint.setStyle(SkPaint::kFill_Style);
300 slice_paint.setFlags(SkPaint::kAntiAlias_Flag);
301 slice_paint.setColor(message_center::kProgressBarSliceColor);
302 canvas->DrawPath(slice_path, slice_paint);
305 // NotificationButton //////////////////////////////////////////////////////////
307 // NotificationButtons render the action buttons of notifications.
308 class NotificationButton : public views::CustomButton {
310 NotificationButton(views::ButtonListener* listener);
311 virtual ~NotificationButton();
313 void SetIcon(const gfx::ImageSkia& icon);
314 void SetTitle(const string16& title);
316 // Overridden from views::View:
317 virtual gfx::Size GetPreferredSize() OVERRIDE;
318 virtual int GetHeightForWidth(int width) OVERRIDE;
319 virtual void OnFocus() OVERRIDE;
320 virtual void OnPaintFocusBorder(gfx::Canvas* canvas) OVERRIDE;
322 // Overridden from views::CustomButton:
323 virtual void StateChanged() OVERRIDE;
326 views::ImageView* icon_;
327 views::Label* title_;
330 NotificationButton::NotificationButton(views::ButtonListener* listener)
331 : views::CustomButton(listener),
335 set_request_focus_on_press(false);
337 new views::BoxLayout(views::BoxLayout::kHorizontal,
338 message_center::kButtonHorizontalPadding,
339 kButtonVecticalPadding,
340 message_center::kButtonIconToTitlePadding));
343 NotificationButton::~NotificationButton() {
346 void NotificationButton::SetIcon(const gfx::ImageSkia& image) {
348 delete icon_; // This removes the icon from this view's children.
349 if (image.isNull()) {
352 icon_ = new views::ImageView();
353 icon_->SetImageSize(gfx::Size(message_center::kNotificationButtonIconSize,
354 message_center::kNotificationButtonIconSize));
355 icon_->SetImage(image);
356 icon_->SetHorizontalAlignment(views::ImageView::LEADING);
357 icon_->SetVerticalAlignment(views::ImageView::LEADING);
359 MakeEmptyBorder(message_center::kButtonIconTopPadding, 0, 0, 0));
360 AddChildViewAt(icon_, 0);
364 void NotificationButton::SetTitle(const string16& title) {
366 delete title_; // This removes the title from this view's children.
370 title_ = new views::Label(title);
371 title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
372 title_->SetEnabledColor(message_center::kRegularTextColor);
373 title_->SetBackgroundColor(kRegularTextBackgroundColor);
374 title_->set_border(MakeEmptyBorder(kButtonTitleTopPadding, 0, 0, 0));
375 AddChildView(title_);
377 SetAccessibleName(title);
380 gfx::Size NotificationButton::GetPreferredSize() {
381 return gfx::Size(message_center::kNotificationWidth,
382 message_center::kButtonHeight);
385 int NotificationButton::GetHeightForWidth(int width) {
386 return message_center::kButtonHeight;
389 void NotificationButton::OnFocus() {
390 views::CustomButton::OnFocus();
391 ScrollRectToVisible(GetLocalBounds());
394 void NotificationButton::OnPaintFocusBorder(gfx::Canvas* canvas) {
395 if (HasFocus() && (focusable() || IsAccessibilityFocusable())) {
396 canvas->DrawRect(gfx::Rect(2, 1, width() - 4, height() - 3),
397 message_center::kFocusBorderColor);
401 void NotificationButton::StateChanged() {
402 if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
404 MakeBackground(message_center::kHoveredButtonBackgroundColor));
406 set_background(NULL);
412 namespace message_center {
414 // NotificationView ////////////////////////////////////////////////////////////
417 MessageView* NotificationView::Create(const Notification& notification,
418 MessageCenter* message_center,
419 MessageCenterTray* tray,
422 switch (notification.type()) {
423 case NOTIFICATION_TYPE_BASE_FORMAT:
424 case NOTIFICATION_TYPE_IMAGE:
425 case NOTIFICATION_TYPE_MULTIPLE:
426 case NOTIFICATION_TYPE_SIMPLE:
427 case NOTIFICATION_TYPE_PROGRESS:
430 // If the caller asks for an unrecognized kind of view (entirely possible
431 // if an application is running on an older version of this code that
432 // doesn't have the requested kind of notification template), we'll fall
433 // back to a notification instance that will provide at least basic
435 LOG(WARNING) << "Unable to fulfill request for unrecognized "
436 << "notification type " << notification.type() << ". "
437 << "Falling back to simple notification type.";
440 // Currently all roads lead to the generic NotificationView.
441 MessageView* notification_view =
442 new NotificationView(notification, message_center, tray, expanded);
444 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
445 // Don't create shadows for notification toasts on linux wih aura.
447 return notification_view;
450 notification_view->CreateShadowBorder();
451 return notification_view;
454 NotificationView::NotificationView(const Notification& notification,
455 MessageCenter* message_center,
456 MessageCenterTray* tray,
458 : MessageView(notification, message_center, tray, expanded),
459 clickable_(notification.clickable()){
460 std::vector<string16> accessible_lines;
462 // Create the opaque background that's above the view's shadow.
463 background_view_ = new views::View();
464 background_view_->set_background(MakeBackground());
466 // Create the top_view_, which collects into a vertical box all content
467 // at the top of the notification (to the right of the icon) except for the
469 top_view_ = new views::View();
470 top_view_->SetLayoutManager(
471 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
472 top_view_->set_border(MakeEmptyBorder(
473 kTextTopPadding - 8, 0, kTextBottomPadding - 5, 0));
475 const gfx::FontList default_label_font_list = views::Label().font_list();
477 // Create the title view if appropriate.
479 if (!notification.title().empty()) {
480 const gfx::FontList& font_list =
481 default_label_font_list.DeriveFontListWithSizeDelta(2);
482 int padding = kTitleLineHeight - font_list.GetHeight();
483 title_view_ = new BoundedLabel(
484 gfx::TruncateString(notification.title(), kTitleCharacterLimit),
486 title_view_->SetLineHeight(kTitleLineHeight);
487 title_view_->SetLineLimit(message_center::kTitleLineLimit);
488 title_view_->SetColors(message_center::kRegularTextColor,
489 kRegularTextBackgroundColor);
490 title_view_->set_border(MakeTextBorder(padding, 3, 0));
491 top_view_->AddChildView(title_view_);
492 accessible_lines.push_back(notification.title());
495 // Create the message view if appropriate.
496 message_view_ = NULL;
497 if (!notification.message().empty()) {
498 int padding = kMessageLineHeight - default_label_font_list.GetHeight();
499 message_view_ = new BoundedLabel(
500 gfx::TruncateString(notification.message(), kMessageCharacterLimit));
501 message_view_->SetLineHeight(kMessageLineHeight);
502 message_view_->SetVisible(!is_expanded() || !notification.items().size());
503 message_view_->SetColors(message_center::kRegularTextColor,
504 kDimTextBackgroundColor);
505 message_view_->set_border(MakeTextBorder(padding, 4, 0));
506 top_view_->AddChildView(message_view_);
507 accessible_lines.push_back(notification.message());
510 // Create the context message view if appropriate.
511 context_message_view_ = NULL;
512 if (!notification.context_message().empty()) {
513 int padding = kMessageLineHeight - default_label_font_list.GetHeight();
514 context_message_view_ =
515 new BoundedLabel(gfx::TruncateString(notification.context_message(),
516 kContextMessageCharacterLimit),
517 default_label_font_list);
518 context_message_view_->SetLineLimit(
519 message_center::kContextMessageLineLimit);
520 context_message_view_->SetLineHeight(kMessageLineHeight);
521 context_message_view_->SetColors(message_center::kDimTextColor,
522 kContextTextBackgroundColor);
523 context_message_view_->set_border(MakeTextBorder(padding, 4, 0));
524 top_view_->AddChildView(context_message_view_);
525 accessible_lines.push_back(notification.context_message());
528 // Create the progress bar view.
529 progress_bar_view_ = NULL;
530 if (notification.type() == NOTIFICATION_TYPE_PROGRESS) {
531 progress_bar_view_ = new NotificationProgressBar();
532 progress_bar_view_->set_border(MakeProgressBarBorder(
533 message_center::kProgressBarTopPadding, kProgressBarBottomPadding));
534 progress_bar_view_->SetValue(notification.progress() / 100.0);
535 top_view_->AddChildView(progress_bar_view_);
538 // Create the list item views (up to a maximum).
539 int padding = kMessageLineHeight - default_label_font_list.GetHeight();
540 std::vector<NotificationItem> items = notification.items();
541 for (size_t i = 0; i < items.size() && i < kNotificationMaximumItems; ++i) {
542 ItemView* item_view = new ItemView(items[i]);
543 item_view->SetVisible(is_expanded());
544 item_view->set_border(MakeTextBorder(padding, i ? 0 : 4, 0));
545 item_views_.push_back(item_view);
546 top_view_->AddChildView(item_view);
547 accessible_lines.push_back(
548 items[i].title + base::ASCIIToUTF16(" ") + items[i].message);
551 // Create the notification icon view.
552 gfx::ImageSkia icon = notification.icon().AsImageSkia();
553 if (notification.type() == NOTIFICATION_TYPE_SIMPLE &&
554 (icon.width() != kIconSize ||
555 icon.height() != kIconSize ||
556 HasAlpha(icon, GetWidget()))) {
557 views::ImageView* icon_view = new views::ImageView();
558 icon_view->SetImage(icon);
559 icon_view->SetImageSize(gfx::Size(kLegacyIconSize, kLegacyIconSize));
560 icon_view->SetHorizontalAlignment(views::ImageView::CENTER);
561 icon_view->SetVerticalAlignment(views::ImageView::CENTER);
562 icon_view_ = icon_view;
564 icon_view_ = new ProportionalImageView(icon);
567 icon_view_->set_background(MakeBackground(kIconBackgroundColor));
569 // Create the bottom_view_, which collects into a vertical box all content
570 // below the notification icon except for the expand button.
571 bottom_view_ = new views::View();
572 bottom_view_->SetLayoutManager(
573 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
575 // Create the image view if appropriate.
577 if (!notification.image().IsEmpty()) {
578 image_view_ = new ProportionalImageView(notification.image().AsImageSkia());
579 image_view_->SetVisible(is_expanded());
580 bottom_view_->AddChildView(image_view_);
583 // Create action buttons if appropriate.
584 std::vector<ButtonInfo> buttons = notification.buttons();
585 for (size_t i = 0; i < buttons.size(); ++i) {
586 views::View* separator = new views::ImageView();
587 separator->set_border(MakeSeparatorBorder(1, 0, kButtonSeparatorColor));
588 bottom_view_->AddChildView(separator);
589 NotificationButton* button = new NotificationButton(this);
590 ButtonInfo button_info = buttons[i];
591 button->SetTitle(button_info.title);
592 button->SetIcon(button_info.icon.AsImageSkia());
593 action_buttons_.push_back(button);
594 bottom_view_->AddChildView(button);
597 // Put together the different content and control views. Layering those allows
598 // for proper layout logic and it also allows the close and expand buttons to
599 // overlap the content as needed to provide large enough click and touch areas
600 // (<http://crbug.com/168822> and <http://crbug.com/168856>).
601 AddChildView(background_view_);
602 AddChildView(top_view_);
603 AddChildView(icon_view_);
604 AddChildView(bottom_view_);
605 AddChildView(close_button());
606 AddChildView(expand_button());
607 set_accessible_name(JoinString(accessible_lines, '\n'));
610 NotificationView::~NotificationView() {
613 gfx::Size NotificationView::GetPreferredSize() {
614 int top_width = top_view_->GetPreferredSize().width();
615 int bottom_width = bottom_view_->GetPreferredSize().width();
616 int preferred_width = std::max(top_width, bottom_width) + GetInsets().width();
617 return gfx::Size(preferred_width, GetHeightForWidth(preferred_width));
620 int NotificationView::GetHeightForWidth(int width) {
621 // Get the height assuming no line limit changes.
622 int content_width = width - GetInsets().width();
623 int top_height = top_view_->GetHeightForWidth(content_width);
624 int bottom_height = bottom_view_->GetHeightForWidth(content_width);
625 int content_height = std::max(top_height, kIconSize) + bottom_height;
627 // <http://crbug.com/230448> Fix: Adjust the height when the message_view's
628 // line limit would be different for the specified width than it currently is.
629 // TODO(dharcourt): Avoid BoxLayout and directly compute the correct height.
631 int used_limit = message_view_->GetLineLimit();
632 int correct_limit = GetMessageLineLimit(width);
633 if (used_limit != correct_limit) {
634 content_height -= GetMessageHeight(content_width, used_limit);
635 content_height += GetMessageHeight(content_width, correct_limit);
639 // Adjust the height to make sure there is at least 16px of space below the
640 // icon if there is any space there (<http://crbug.com/232966>).
641 if (content_height > kIconSize)
642 content_height = std::max(content_height,
643 kIconSize + message_center::kIconBottomPadding);
645 return content_height + GetInsets().height();
648 void NotificationView::Layout() {
649 gfx::Insets insets = GetInsets();
650 int content_width = width() - insets.width();
651 int content_right = width() - insets.right();
653 // Before any resizing, set or adjust the number of message lines.
655 message_view_->SetLineLimit(GetMessageLineLimit(width()));
658 background_view_->SetBounds(insets.left(), insets.top(),
659 content_width, height() - insets.height());
662 int top_height = top_view_->GetHeightForWidth(content_width);
663 top_view_->SetBounds(insets.left(), insets.top(), content_width, top_height);
666 icon_view_->SetBounds(insets.left(), insets.top(), kIconSize, kIconSize);
669 int bottom_y = insets.top() + std::max(top_height, kIconSize);
670 int bottom_height = bottom_view_->GetHeightForWidth(content_width);
671 bottom_view_->SetBounds(insets.left(), bottom_y,
672 content_width, bottom_height);
675 gfx::Size close_size(close_button()->GetPreferredSize());
676 close_button()->SetBounds(content_right - close_size.width(), insets.top(),
677 close_size.width(), close_size.height());
680 gfx::Size expand_size(expand_button()->GetPreferredSize());
681 int expand_y = bottom_y - expand_size.height();
682 expand_button()->SetVisible(IsExpansionNeeded(width()));
683 expand_button()->SetBounds(content_right - expand_size.width(), expand_y,
684 expand_size.width(), expand_size.height());
687 void NotificationView::OnFocus() {
688 MessageView::OnFocus();
689 ScrollRectToVisible(GetLocalBounds());
692 void NotificationView::ScrollRectToVisible(const gfx::Rect& rect) {
693 // Notification want to show the whole notification when a part of it (like
694 // a button) gets focused.
695 views::View::ScrollRectToVisible(GetLocalBounds());
698 views::View* NotificationView::GetEventHandlerForPoint(
699 const gfx::Point& point) {
700 // Want to return this for underlying views, otherwise GetCursor is not
701 // called. But buttons are exceptions, they'll have their own event handlings.
702 std::vector<views::View*> buttons(action_buttons_);
703 buttons.push_back(close_button());
704 buttons.push_back(expand_button());
706 for (size_t i = 0; i < buttons.size(); ++i) {
707 gfx::Point point_in_child = point;
708 ConvertPointToTarget(this, buttons[i], &point_in_child);
709 if (buttons[i]->HitTestPoint(point_in_child))
710 return buttons[i]->GetEventHandlerForPoint(point_in_child);
716 gfx::NativeCursor NotificationView::GetCursor(const ui::MouseEvent& event) {
717 if (!clickable_ || !message_center()->HasClickedListener(notification_id()))
718 return views::View::GetCursor(event);
720 #if defined(USE_AURA)
721 return ui::kCursorHand;
722 #elif defined(OS_WIN)
723 static HCURSOR g_hand_cursor = LoadCursor(NULL, IDC_HAND);
724 return g_hand_cursor;
728 void NotificationView::ButtonPressed(views::Button* sender,
729 const ui::Event& event) {
730 // See if the button pressed was an action button.
731 for (size_t i = 0; i < action_buttons_.size(); ++i) {
732 if (sender == action_buttons_[i]) {
733 message_center()->ClickOnNotificationButton(notification_id(), i);
738 // Adjust notification subviews for expansion.
739 if (sender == expand_button()) {
740 if (message_view_ && item_views_.size())
741 message_view_->SetVisible(false);
742 for (size_t i = 0; i < item_views_.size(); ++i)
743 item_views_[i]->SetVisible(true);
745 image_view_->SetVisible(true);
746 expand_button()->SetVisible(false);
747 PreferredSizeChanged();
753 // Let the superclass handled anything other than action buttons.
754 // Warning: This may cause the NotificationView itself to be deleted,
755 // so don't do anything afterwards.
756 MessageView::ButtonPressed(sender, event);
759 bool NotificationView::IsExpansionNeeded(int width) {
760 return (!is_expanded() &&
762 item_views_.size() ||
763 IsMessageExpansionNeeded(width)));
766 bool NotificationView::IsMessageExpansionNeeded(int width) {
767 int current = GetMessageLines(width, GetMessageLineLimit(width));
768 int expanded = GetMessageLines(width,
769 message_center::kMessageExpandedLineLimit);
770 return current < expanded;
773 int NotificationView::GetMessageLineLimit(int width) {
774 // Expanded notifications get a larger limit, except for image notifications,
775 // whose images must be kept flush against their icons.
776 if (is_expanded() && !image_view_)
777 return message_center::kMessageExpandedLineLimit;
779 // If there's a title ensure title + message lines <= collapsed line limit.
781 int title_lines = title_view_->GetLinesForWidthAndLimit(width, -1);
782 return std::max(message_center::kMessageCollapsedLineLimit - title_lines,
786 return message_center::kMessageCollapsedLineLimit;
789 int NotificationView::GetMessageLines(int width, int limit) {
790 return message_view_ ?
791 message_view_->GetLinesForWidthAndLimit(width, limit) : 0;
794 int NotificationView::GetMessageHeight(int width, int limit) {
795 return message_view_ ?
796 message_view_->GetSizeForWidthAndLines(width, limit).height() : 0;
799 } // namespace message_center