blob: a36af5359fd815d0719523dd0a6e96dca9587181 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/message_center/views/notification_view.h"
#include "base/command_line.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "grit/ui_resources.h"
#include "ui/base/layout.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/size.h"
#include "ui/gfx/skia_util.h"
#include "ui/gfx/text_elider.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/message_center_style.h"
#include "ui/message_center/message_center_switches.h"
#include "ui/message_center/message_center_util.h"
#include "ui/message_center/notification.h"
#include "ui/message_center/notification_types.h"
#include "ui/message_center/views/bounded_label.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/progress_bar.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/widget/widget.h"
#if defined(USE_AURA)
#include "ui/base/cursor/cursor.h"
#endif
namespace {
// Dimensions.
const int kIconSize = message_center::kNotificationIconSize;
const int kLegacyIconSize = 40;
const int kTextLeftPadding = kIconSize + message_center::kIconToTextPadding;
const int kTextBottomPadding = 12;
const int kTextRightPadding = 23;
const int kItemTitleToMessagePadding = 3;
const int kProgressBarWidth = message_center::kNotificationWidth -
kTextLeftPadding - kTextRightPadding;
const int kProgressBarBottomPadding = 0;
const int kButtonVecticalPadding = 0;
const int kButtonTitleTopPadding = 0;
// Character limits: Displayed text will be subject to the line limits above,
// but we also remove trailing characters from text to reduce processing cost.
// Character limit = pixels per line * line limit / min. pixels per character.
const size_t kTitleCharacterLimit =
message_center::kNotificationWidth * message_center::kTitleLineLimit / 4;
const size_t kMessageCharacterLimit =
message_center::kNotificationWidth *
message_center::kMessageExpandedLineLimit / 3;
const size_t kContextMessageCharacterLimit =
message_center::kNotificationWidth *
message_center::kContextMessageLineLimit / 3;
// Notification colors. The text background colors below are used only to keep
// view::Label from modifying the text color and will not actually be drawn.
// See view::Label's RecalculateColors() for details.
const SkColor kRegularTextBackgroundColor = SK_ColorWHITE;
const SkColor kDimTextBackgroundColor = SK_ColorWHITE;
const SkColor kContextTextBackgroundColor = SK_ColorWHITE;
// static
views::Background* MakeBackground(
SkColor color = message_center::kNotificationBackgroundColor) {
return views::Background::CreateSolidBackground(color);
}
// static
views::Border* MakeEmptyBorder(int top, int left, int bottom, int right) {
return views::Border::CreateEmptyBorder(top, left, bottom, right);
}
// static
views::Border* MakeTextBorder(int padding, int top, int bottom) {
// Split the padding between the top and the bottom, then add the extra space.
return MakeEmptyBorder(padding / 2 + top, kTextLeftPadding,
(padding + 1) / 2 + bottom, kTextRightPadding);
}
// static
views::Border* MakeProgressBarBorder(int top, int bottom) {
return MakeEmptyBorder(top, kTextLeftPadding, bottom, kTextRightPadding);
}
// static
views::Border* MakeSeparatorBorder(int top, int left, SkColor color) {
return views::Border::CreateSolidSidedBorder(top, left, 0, 0, color);
}
// static
// Return true if and only if the image is null or has alpha.
bool HasAlpha(gfx::ImageSkia& image, views::Widget* widget) {
// Determine which bitmap to use.
ui::ScaleFactor factor = ui::SCALE_FACTOR_100P;
if (widget) {
factor = ui::GetScaleFactorForNativeView(widget->GetNativeView());
if (factor == ui::SCALE_FACTOR_NONE)
factor = ui::SCALE_FACTOR_100P;
}
// Extract that bitmap's alpha and look for a non-opaque pixel there.
SkBitmap bitmap =
image.GetRepresentation(ui::GetImageScale(factor)).sk_bitmap();
if (!bitmap.isNull()) {
SkBitmap alpha;
alpha.setConfig(SkBitmap::kA1_Config, bitmap.width(), bitmap.height(), 0);
bitmap.extractAlpha(&alpha);
for (int y = 0; y < bitmap.height(); ++y) {
for (int x = 0; x < bitmap.width(); ++x) {
if (alpha.getColor(x, y) != SK_ColorBLACK) {
return true;
}
}
}
}
// If no opaque pixel was found, return false unless the bitmap is empty.
return bitmap.isNull();
}
// ItemView ////////////////////////////////////////////////////////////////////
// ItemViews are responsible for drawing each list notification item's title and
// message next to each other within a single column.
class ItemView : public views::View {
public:
ItemView(const message_center::NotificationItem& item);
virtual ~ItemView();
// Overridden from views::View:
virtual void SetVisible(bool visible) OVERRIDE;
private:
DISALLOW_COPY_AND_ASSIGN(ItemView);
};
ItemView::ItemView(const message_center::NotificationItem& item) {
SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal,
0, 0, kItemTitleToMessagePadding));
views::Label* title = new views::Label(item.title);
title->set_collapse_when_hidden(true);
title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
title->SetEnabledColor(message_center::kRegularTextColor);
title->SetBackgroundColor(kRegularTextBackgroundColor);
AddChildView(title);
views::Label* message = new views::Label(item.message);
message->set_collapse_when_hidden(true);
message->SetHorizontalAlignment(gfx::ALIGN_LEFT);
message->SetEnabledColor(message_center::kDimTextColor);
message->SetBackgroundColor(kDimTextBackgroundColor);
AddChildView(message);
PreferredSizeChanged();
SchedulePaint();
}
ItemView::~ItemView() {
}
void ItemView::SetVisible(bool visible) {
views::View::SetVisible(visible);
for (int i = 0; i < child_count(); ++i)
child_at(i)->SetVisible(visible);
}
// ProportionalImageView ///////////////////////////////////////////////////////
// ProportionalImageViews center their images to preserve their proportion.
class ProportionalImageView : public views::View {
public:
ProportionalImageView(const gfx::ImageSkia& image);
virtual ~ProportionalImageView();
// Overridden from views::View:
virtual gfx::Size GetPreferredSize() OVERRIDE;
virtual int GetHeightForWidth(int width) OVERRIDE;
virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
private:
gfx::Size GetImageSizeForWidth(int width);
gfx::ImageSkia image_;
DISALLOW_COPY_AND_ASSIGN(ProportionalImageView);
};
ProportionalImageView::ProportionalImageView(const gfx::ImageSkia& image)
: image_(image) {
}
ProportionalImageView::~ProportionalImageView() {
}
gfx::Size ProportionalImageView::GetPreferredSize() {
gfx::Size size = GetImageSizeForWidth(image_.width());
return gfx::Size(size.width() + GetInsets().width(),
size.height() + GetInsets().height());
}
int ProportionalImageView::GetHeightForWidth(int width) {
return GetImageSizeForWidth(width).height();
}
void ProportionalImageView::OnPaint(gfx::Canvas* canvas) {
views::View::OnPaint(canvas);
gfx::Size draw_size(GetImageSizeForWidth(width()));
if (!draw_size.IsEmpty()) {
gfx::Rect draw_bounds = GetContentsBounds();
draw_bounds.ClampToCenteredSize(draw_size);
gfx::Size image_size(image_.size());
if (image_size == draw_size) {
canvas->DrawImageInt(image_, draw_bounds.x(), draw_bounds.y());
} else {
// Resize case
SkPaint paint;
paint.setFilterBitmap(true);
canvas->DrawImageInt(image_, 0, 0,
image_size.width(), image_size.height(),
draw_bounds.x(), draw_bounds.y(),
draw_size.width(), draw_size.height(),
true, paint);
}
}
}
gfx::Size ProportionalImageView::GetImageSizeForWidth(int width) {
gfx::Size size = visible() ? image_.size() : gfx::Size();
return message_center::GetImageSizeForWidth(width, size);
}
// NotificationProgressBar /////////////////////////////////////////////////////
class NotificationProgressBar : public views::ProgressBar {
public:
NotificationProgressBar();
virtual ~NotificationProgressBar();
private:
// Overriden from View
virtual gfx::Size GetPreferredSize() OVERRIDE;
virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
DISALLOW_COPY_AND_ASSIGN(NotificationProgressBar);
};
NotificationProgressBar::NotificationProgressBar() {
}
NotificationProgressBar::~NotificationProgressBar() {
}
gfx::Size NotificationProgressBar::GetPreferredSize() {
gfx::Size pref_size(kProgressBarWidth, message_center::kProgressBarThickness);
gfx::Insets insets = GetInsets();
pref_size.Enlarge(insets.width(), insets.height());
return pref_size;
}
void NotificationProgressBar::OnPaint(gfx::Canvas* canvas) {
gfx::Rect content_bounds = GetContentsBounds();
// Draw background.
SkPath background_path;
background_path.addRoundRect(gfx::RectToSkRect(content_bounds),
message_center::kProgressBarCornerRadius,
message_center::kProgressBarCornerRadius);
SkPaint background_paint;
background_paint.setStyle(SkPaint::kFill_Style);
background_paint.setFlags(SkPaint::kAntiAlias_Flag);
background_paint.setColor(message_center::kProgressBarBackgroundColor);
canvas->DrawPath(background_path, background_paint);
// Draw slice.
const int slice_width =
static_cast<int>(content_bounds.width() * GetNormalizedValue() + 0.5);
if (slice_width < 1)
return;
gfx::Rect slice_bounds = content_bounds;
slice_bounds.set_width(slice_width);
SkPath slice_path;
slice_path.addRoundRect(gfx::RectToSkRect(slice_bounds),
message_center::kProgressBarCornerRadius,
message_center::kProgressBarCornerRadius);
SkPaint slice_paint;
slice_paint.setStyle(SkPaint::kFill_Style);
slice_paint.setFlags(SkPaint::kAntiAlias_Flag);
slice_paint.setColor(message_center::kProgressBarSliceColor);
canvas->DrawPath(slice_path, slice_paint);
}
// NotificationButton //////////////////////////////////////////////////////////
// NotificationButtons render the action buttons of notifications.
class NotificationButton : public views::CustomButton {
public:
NotificationButton(views::ButtonListener* listener);
virtual ~NotificationButton();
void SetIcon(const gfx::ImageSkia& icon);
void SetTitle(const string16& title);
// Overridden from views::View:
virtual gfx::Size GetPreferredSize() OVERRIDE;
virtual int GetHeightForWidth(int width) OVERRIDE;
virtual void OnFocus() OVERRIDE;
virtual void OnPaintFocusBorder(gfx::Canvas* canvas) OVERRIDE;
// Overridden from views::CustomButton:
virtual void StateChanged() OVERRIDE;
private:
views::ImageView* icon_;
views::Label* title_;
};
NotificationButton::NotificationButton(views::ButtonListener* listener)
: views::CustomButton(listener),
icon_(NULL),
title_(NULL) {
set_focusable(true);
set_request_focus_on_press(false);
SetLayoutManager(
new views::BoxLayout(views::BoxLayout::kHorizontal,
message_center::kButtonHorizontalPadding,
kButtonVecticalPadding,
message_center::kButtonIconToTitlePadding));
}
NotificationButton::~NotificationButton() {
}
void NotificationButton::SetIcon(const gfx::ImageSkia& image) {
if (icon_ != NULL)
delete icon_; // This removes the icon from this view's children.
if (image.isNull()) {
icon_ = NULL;
} else {
icon_ = new views::ImageView();
icon_->SetImageSize(gfx::Size(message_center::kNotificationButtonIconSize,
message_center::kNotificationButtonIconSize));
icon_->SetImage(image);
icon_->SetHorizontalAlignment(views::ImageView::LEADING);
icon_->SetVerticalAlignment(views::ImageView::LEADING);
icon_->set_border(
MakeEmptyBorder(message_center::kButtonIconTopPadding, 0, 0, 0));
AddChildViewAt(icon_, 0);
}
}
void NotificationButton::SetTitle(const string16& title) {
if (title_ != NULL)
delete title_; // This removes the title from this view's children.
if (title.empty()) {
title_ = NULL;
} else {
title_ = new views::Label(title);
title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
title_->SetEnabledColor(message_center::kRegularTextColor);
title_->SetBackgroundColor(kRegularTextBackgroundColor);
title_->set_border(MakeEmptyBorder(kButtonTitleTopPadding, 0, 0, 0));
AddChildView(title_);
}
SetAccessibleName(title);
}
gfx::Size NotificationButton::GetPreferredSize() {
return gfx::Size(message_center::kNotificationWidth,
message_center::kButtonHeight);
}
int NotificationButton::GetHeightForWidth(int width) {
return message_center::kButtonHeight;
}
void NotificationButton::OnFocus() {
views::CustomButton::OnFocus();
ScrollRectToVisible(GetLocalBounds());
}
void NotificationButton::OnPaintFocusBorder(gfx::Canvas* canvas) {
if (HasFocus() && (focusable() || IsAccessibilityFocusable())) {
canvas->DrawRect(gfx::Rect(2, 1, width() - 4, height() - 3),
message_center::kFocusBorderColor);
}
}
void NotificationButton::StateChanged() {
if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
set_background(
MakeBackground(message_center::kHoveredButtonBackgroundColor));
} else {
set_background(NULL);
}
}
} // namespace
namespace message_center {
// NotificationView ////////////////////////////////////////////////////////////
// static
MessageView* NotificationView::Create(const Notification& notification,
MessageCenter* message_center,
MessageCenterTray* tray,
bool expanded,
bool top_level) {
switch (notification.type()) {
case NOTIFICATION_TYPE_BASE_FORMAT:
case NOTIFICATION_TYPE_IMAGE:
case NOTIFICATION_TYPE_MULTIPLE:
case NOTIFICATION_TYPE_SIMPLE:
case NOTIFICATION_TYPE_PROGRESS:
break;
default:
// If the caller asks for an unrecognized kind of view (entirely possible
// if an application is running on an older version of this code that
// doesn't have the requested kind of notification template), we'll fall
// back to a notification instance that will provide at least basic
// functionality.
LOG(WARNING) << "Unable to fulfill request for unrecognized "
<< "notification type " << notification.type() << ". "
<< "Falling back to simple notification type.";
}
// Currently all roads lead to the generic NotificationView.
MessageView* notification_view =
new NotificationView(notification, message_center, tray, expanded);
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
// Don't create shadows for notification toasts on linux wih aura.
if (top_level)
return notification_view;
#endif
notification_view->CreateShadowBorder();
return notification_view;
}
NotificationView::NotificationView(const Notification& notification,
MessageCenter* message_center,
MessageCenterTray* tray,
bool expanded)
: MessageView(notification, message_center, tray, expanded),
clickable_(notification.clickable()){
std::vector<string16> accessible_lines;
// Create the opaque background that's above the view's shadow.
background_view_ = new views::View();
background_view_->set_background(MakeBackground());
// Create the top_view_, which collects into a vertical box all content
// at the top of the notification (to the right of the icon) except for the
// close button.
top_view_ = new views::View();
top_view_->SetLayoutManager(
new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
top_view_->set_border(MakeEmptyBorder(
kTextTopPadding - 8, 0, kTextBottomPadding - 5, 0));
const gfx::FontList default_label_font_list = views::Label().font_list();
// Create the title view if appropriate.
title_view_ = NULL;
if (!notification.title().empty()) {
const gfx::FontList& font_list =
default_label_font_list.DeriveFontListWithSizeDelta(2);
int padding = kTitleLineHeight - font_list.GetHeight();
title_view_ = new BoundedLabel(
gfx::TruncateString(notification.title(), kTitleCharacterLimit),
font_list);
title_view_->SetLineHeight(kTitleLineHeight);
title_view_->SetLineLimit(message_center::kTitleLineLimit);
title_view_->SetColors(message_center::kRegularTextColor,
kRegularTextBackgroundColor);
title_view_->set_border(MakeTextBorder(padding, 3, 0));
top_view_->AddChildView(title_view_);
accessible_lines.push_back(notification.title());
}
// Create the message view if appropriate.
message_view_ = NULL;
if (!notification.message().empty()) {
int padding = kMessageLineHeight - default_label_font_list.GetHeight();
message_view_ = new BoundedLabel(
gfx::TruncateString(notification.message(), kMessageCharacterLimit));
message_view_->SetLineHeight(kMessageLineHeight);
message_view_->SetVisible(!is_expanded() || !notification.items().size());
message_view_->SetColors(message_center::kRegularTextColor,
kDimTextBackgroundColor);
message_view_->set_border(MakeTextBorder(padding, 4, 0));
top_view_->AddChildView(message_view_);
accessible_lines.push_back(notification.message());
}
// Create the context message view if appropriate.
context_message_view_ = NULL;
if (!notification.context_message().empty()) {
int padding = kMessageLineHeight - default_label_font_list.GetHeight();
context_message_view_ =
new BoundedLabel(gfx::TruncateString(notification.context_message(),
kContextMessageCharacterLimit),
default_label_font_list);
context_message_view_->SetLineLimit(
message_center::kContextMessageLineLimit);
context_message_view_->SetLineHeight(kMessageLineHeight);
context_message_view_->SetColors(message_center::kDimTextColor,
kContextTextBackgroundColor);
context_message_view_->set_border(MakeTextBorder(padding, 4, 0));
top_view_->AddChildView(context_message_view_);
accessible_lines.push_back(notification.context_message());
}
// Create the progress bar view.
progress_bar_view_ = NULL;
if (notification.type() == NOTIFICATION_TYPE_PROGRESS) {
progress_bar_view_ = new NotificationProgressBar();
progress_bar_view_->set_border(MakeProgressBarBorder(
message_center::kProgressBarTopPadding, kProgressBarBottomPadding));
progress_bar_view_->SetValue(notification.progress() / 100.0);
top_view_->AddChildView(progress_bar_view_);
}
// Create the list item views (up to a maximum).
int padding = kMessageLineHeight - default_label_font_list.GetHeight();
std::vector<NotificationItem> items = notification.items();
for (size_t i = 0; i < items.size() && i < kNotificationMaximumItems; ++i) {
ItemView* item_view = new ItemView(items[i]);
item_view->SetVisible(is_expanded());
item_view->set_border(MakeTextBorder(padding, i ? 0 : 4, 0));
item_views_.push_back(item_view);
top_view_->AddChildView(item_view);
accessible_lines.push_back(
items[i].title + base::ASCIIToUTF16(" ") + items[i].message);
}
// Create the notification icon view.
gfx::ImageSkia icon = notification.icon().AsImageSkia();
if (notification.type() == NOTIFICATION_TYPE_SIMPLE &&
(icon.width() != kIconSize ||
icon.height() != kIconSize ||
HasAlpha(icon, GetWidget()))) {
views::ImageView* icon_view = new views::ImageView();
icon_view->SetImage(icon);
icon_view->SetImageSize(gfx::Size(kLegacyIconSize, kLegacyIconSize));
icon_view->SetHorizontalAlignment(views::ImageView::CENTER);
icon_view->SetVerticalAlignment(views::ImageView::CENTER);
icon_view_ = icon_view;
} else {
icon_view_ = new ProportionalImageView(icon);
}
icon_view_->set_background(MakeBackground(kIconBackgroundColor));
// Create the bottom_view_, which collects into a vertical box all content
// below the notification icon except for the expand button.
bottom_view_ = new views::View();
bottom_view_->SetLayoutManager(
new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
// Create the image view if appropriate.
image_view_ = NULL;
if (!notification.image().IsEmpty()) {
image_view_ = new ProportionalImageView(notification.image().AsImageSkia());
image_view_->SetVisible(is_expanded());
bottom_view_->AddChildView(image_view_);
}
// Create action buttons if appropriate.
std::vector<ButtonInfo> buttons = notification.buttons();
for (size_t i = 0; i < buttons.size(); ++i) {
views::View* separator = new views::ImageView();
separator->set_border(MakeSeparatorBorder(1, 0, kButtonSeparatorColor));
bottom_view_->AddChildView(separator);
NotificationButton* button = new NotificationButton(this);
ButtonInfo button_info = buttons[i];
button->SetTitle(button_info.title);
button->SetIcon(button_info.icon.AsImageSkia());
action_buttons_.push_back(button);
bottom_view_->AddChildView(button);
}
// Put together the different content and control views. Layering those allows
// for proper layout logic and it also allows the close and expand buttons to
// overlap the content as needed to provide large enough click and touch areas
// (<http://crbug.com/168822> and <http://crbug.com/168856>).
AddChildView(background_view_);
AddChildView(top_view_);
AddChildView(icon_view_);
AddChildView(bottom_view_);
AddChildView(close_button());
AddChildView(expand_button());
set_accessible_name(JoinString(accessible_lines, '\n'));
}
NotificationView::~NotificationView() {
}
gfx::Size NotificationView::GetPreferredSize() {
int top_width = top_view_->GetPreferredSize().width();
int bottom_width = bottom_view_->GetPreferredSize().width();
int preferred_width = std::max(top_width, bottom_width) + GetInsets().width();
return gfx::Size(preferred_width, GetHeightForWidth(preferred_width));
}
int NotificationView::GetHeightForWidth(int width) {
// Get the height assuming no line limit changes.
int content_width = width - GetInsets().width();
int top_height = top_view_->GetHeightForWidth(content_width);
int bottom_height = bottom_view_->GetHeightForWidth(content_width);
int content_height = std::max(top_height, kIconSize) + bottom_height;
// <http://crbug.com/230448> Fix: Adjust the height when the message_view's
// line limit would be different for the specified width than it currently is.
// TODO(dharcourt): Avoid BoxLayout and directly compute the correct height.
if (message_view_) {
int used_limit = message_view_->GetLineLimit();
int correct_limit = GetMessageLineLimit(width);
if (used_limit != correct_limit) {
content_height -= GetMessageHeight(content_width, used_limit);
content_height += GetMessageHeight(content_width, correct_limit);
}
}
// Adjust the height to make sure there is at least 16px of space below the
// icon if there is any space there (<http://crbug.com/232966>).
if (content_height > kIconSize)
content_height = std::max(content_height,
kIconSize + message_center::kIconBottomPadding);
return content_height + GetInsets().height();
}
void NotificationView::Layout() {
gfx::Insets insets = GetInsets();
int content_width = width() - insets.width();
int content_right = width() - insets.right();
// Before any resizing, set or adjust the number of message lines.
if (message_view_)
message_view_->SetLineLimit(GetMessageLineLimit(width()));
// Background.
background_view_->SetBounds(insets.left(), insets.top(),
content_width, height() - insets.height());
// Top views.
int top_height = top_view_->GetHeightForWidth(content_width);
top_view_->SetBounds(insets.left(), insets.top(), content_width, top_height);
// Icon.
icon_view_->SetBounds(insets.left(), insets.top(), kIconSize, kIconSize);
// Bottom views.
int bottom_y = insets.top() + std::max(top_height, kIconSize);
int bottom_height = bottom_view_->GetHeightForWidth(content_width);
bottom_view_->SetBounds(insets.left(), bottom_y,
content_width, bottom_height);
// Close button.
gfx::Size close_size(close_button()->GetPreferredSize());
close_button()->SetBounds(content_right - close_size.width(), insets.top(),
close_size.width(), close_size.height());
// Expand button.
gfx::Size expand_size(expand_button()->GetPreferredSize());
int expand_y = bottom_y - expand_size.height();
expand_button()->SetVisible(IsExpansionNeeded(width()));
expand_button()->SetBounds(content_right - expand_size.width(), expand_y,
expand_size.width(), expand_size.height());
}
void NotificationView::OnFocus() {
MessageView::OnFocus();
ScrollRectToVisible(GetLocalBounds());
}
void NotificationView::ScrollRectToVisible(const gfx::Rect& rect) {
// Notification want to show the whole notification when a part of it (like
// a button) gets focused.
views::View::ScrollRectToVisible(GetLocalBounds());
}
views::View* NotificationView::GetEventHandlerForPoint(
const gfx::Point& point) {
// Want to return this for underlying views, otherwise GetCursor is not
// called. But buttons are exceptions, they'll have their own event handlings.
std::vector<views::View*> buttons(action_buttons_);
buttons.push_back(close_button());
buttons.push_back(expand_button());
for (size_t i = 0; i < buttons.size(); ++i) {
gfx::Point point_in_child = point;
ConvertPointToTarget(this, buttons[i], &point_in_child);
if (buttons[i]->HitTestPoint(point_in_child))
return buttons[i]->GetEventHandlerForPoint(point_in_child);
}
return this;
}
gfx::NativeCursor NotificationView::GetCursor(const ui::MouseEvent& event) {
if (!clickable_ || !message_center()->HasClickedListener(notification_id()))
return views::View::GetCursor(event);
#if defined(USE_AURA)
return ui::kCursorHand;
#elif defined(OS_WIN)
static HCURSOR g_hand_cursor = LoadCursor(NULL, IDC_HAND);
return g_hand_cursor;
#endif
}
void NotificationView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
// See if the button pressed was an action button.
for (size_t i = 0; i < action_buttons_.size(); ++i) {
if (sender == action_buttons_[i]) {
message_center()->ClickOnNotificationButton(notification_id(), i);
return;
}
}
// Adjust notification subviews for expansion.
if (sender == expand_button()) {
if (message_view_ && item_views_.size())
message_view_->SetVisible(false);
for (size_t i = 0; i < item_views_.size(); ++i)
item_views_[i]->SetVisible(true);
if (image_view_)
image_view_->SetVisible(true);
expand_button()->SetVisible(false);
PreferredSizeChanged();
SchedulePaint();
return;
}
// Let the superclass handled anything other than action buttons.
// Warning: This may cause the NotificationView itself to be deleted,
// so don't do anything afterwards.
MessageView::ButtonPressed(sender, event);
}
bool NotificationView::IsExpansionNeeded(int width) {
return (!is_expanded() &&
(image_view_ ||
item_views_.size() ||
IsMessageExpansionNeeded(width)));
}
bool NotificationView::IsMessageExpansionNeeded(int width) {
int current = GetMessageLines(width, GetMessageLineLimit(width));
int expanded = GetMessageLines(width,
message_center::kMessageExpandedLineLimit);
return current < expanded;
}
int NotificationView::GetMessageLineLimit(int width) {
// Expanded notifications get a larger limit, except for image notifications,
// whose images must be kept flush against their icons.
if (is_expanded() && !image_view_)
return message_center::kMessageExpandedLineLimit;
// If there's a title ensure title + message lines <= collapsed line limit.
if (title_view_) {
int title_lines = title_view_->GetLinesForWidthAndLimit(width, -1);
return std::max(message_center::kMessageCollapsedLineLimit - title_lines,
0);
}
return message_center::kMessageCollapsedLineLimit;
}
int NotificationView::GetMessageLines(int width, int limit) {
return message_view_ ?
message_view_->GetLinesForWidthAndLimit(width, limit) : 0;
}
int NotificationView::GetMessageHeight(int width, int limit) {
return message_view_ ?
message_view_->GetSizeForWidthAndLines(width, limit).height() : 0;
}
} // namespace message_center