blob: 7591d9fec251bbf9af3226c23799fbc53cb24cf1 [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/message_center_bubble.h"
#include "grit/ui_strings.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/insets.h"
#include "ui/gfx/point.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/size.h"
#include "ui/gfx/text_constants.h"
#include "ui/message_center/message_center_constants.h"
#include "ui/message_center/message_center_util.h"
#include "ui/message_center/message_view.h"
#include "ui/message_center/notification_view.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/button/text_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/painter.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
namespace message_center {
namespace {
const int kMessageBubbleBaseMinHeight = 80;
const int kFooterMargin = 16;
const int kFooterHeight = 24;
const SkColor kMessageCenterBackgroundColor = SkColorSetRGB(0xe5, 0xe5, 0xe5);
const SkColor kBorderDarkColor = SkColorSetRGB(0xaa, 0xaa, 0xaa);
const SkColor kTransparentColor = SkColorSetARGB(0, 0, 0, 0);
const SkColor kFooterDelimiterColor = SkColorSetRGB(0xcc, 0xcc, 0xcc);
const SkColor kFooterTextColor = SkColorSetRGB(0x80, 0x80, 0x80);
const SkColor kButtonTextHighlightColor = SkColorSetRGB(0x32, 0x32, 0x32);
const SkColor kButtonTextHoverColor = SkColorSetRGB(0x32, 0x32, 0x32);
// The focus color and focus-border logic is copied from ash tray.
// TODO(mukai): unite those implementations.
const SkColor kFocusBorderColor = SkColorSetRGB(0x40, 0x80, 0xfa);
class WebNotificationButtonViewBase : public views::View {
public:
WebNotificationButtonViewBase(NotificationList::Delegate* list_delegate)
: list_delegate_(list_delegate),
close_all_button_(NULL) {}
void SetCloseAllVisible(bool visible) {
if (close_all_button_)
close_all_button_->SetVisible(visible);
}
void set_close_all_button(views::Button* button) {
close_all_button_ = button;
}
protected:
NotificationList::Delegate* list_delegate() { return list_delegate_; }
views::Button* close_all_button() { return close_all_button_; }
private:
NotificationList::Delegate* list_delegate_;
views::Button* close_all_button_;
DISALLOW_COPY_AND_ASSIGN(WebNotificationButtonViewBase);
};
// The view for the buttons at the bottom of the web notification tray.
class WebNotificationButtonView : public WebNotificationButtonViewBase,
public views::ButtonListener {
public:
explicit WebNotificationButtonView(NotificationList::Delegate* list_delegate)
: WebNotificationButtonViewBase(list_delegate) {
set_background(views::Background::CreateBackgroundPainter(
true,
views::Painter::CreateVerticalGradient(
MessageBubbleBase::kHeaderBackgroundColorLight,
MessageBubbleBase::kHeaderBackgroundColorDark)));
set_border(views::Border::CreateSolidSidedBorder(
2, 0, 0, 0, kBorderDarkColor));
views::GridLayout* layout = new views::GridLayout(this);
SetLayoutManager(layout);
views::ColumnSet* columns = layout->AddColumnSet(0);
columns->AddPaddingColumn(100, 0);
columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER,
0, /* resize percent */
views::GridLayout::USE_PREF, 0, 0);
columns->AddPaddingColumn(0, 4);
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
views::TextButton* close_all_button = new views::TextButton(
this, rb.GetLocalizedString(IDS_MESSAGE_CENTER_CLEAR_ALL));
close_all_button->set_alignment(views::TextButton::ALIGN_CENTER);
close_all_button->set_focusable(true);
close_all_button->set_request_focus_on_press(false);
layout->AddPaddingRow(0, 4);
layout->StartRow(0, 0);
layout->AddView(close_all_button);
set_close_all_button(close_all_button);
}
virtual ~WebNotificationButtonView() {}
// Overridden from ButtonListener.
virtual void ButtonPressed(views::Button* sender,
const ui::Event& event) OVERRIDE {
if (sender == close_all_button())
list_delegate()->SendRemoveAllNotifications();
}
private:
DISALLOW_COPY_AND_ASSIGN(WebNotificationButtonView);
};
class WebNotificationButton : public views::TextButton {
public:
WebNotificationButton(views::ButtonListener* listener, const string16& text)
: views::TextButton(listener, text) {
set_border(views::Border::CreateEmptyBorder(0, 16, 0, 16));
set_min_height(kFooterHeight);
SetEnabledColor(kFooterTextColor);
SetHighlightColor(kButtonTextHighlightColor);
SetHoverColor(kButtonTextHoverColor);
}
protected:
// views::View overrides:
virtual gfx::Size GetPreferredSize() OVERRIDE {
// Returns an empty size when invisible, to trim its space in the parent's
// GridLayout.
if (!visible())
return gfx::Size();
return views::TextButton::GetPreferredSize();
}
virtual void OnPaintBorder(gfx::Canvas* canvas) OVERRIDE {
// Just paint the left border.
canvas->DrawLine(gfx::Point(0, 0), gfx::Point(0, height()),
kFooterDelimiterColor);
}
virtual void OnPaintFocusBorder(gfx::Canvas* canvas) OVERRIDE {
if (HasFocus() && (focusable() || IsAccessibilityFocusable())) {
canvas->DrawRect(gfx::Rect(2, 1, width() - 4, height() - 3),
kFocusBorderColor);
}
}
DISALLOW_COPY_AND_ASSIGN(WebNotificationButton);
};
// TODO(mukai): remove the trailing '2' when the kEnableNewMessageCenterBubble
// flag disappears.
class WebNotificationButtonView2 : public WebNotificationButtonViewBase,
public views::ButtonListener {
public:
explicit WebNotificationButtonView2(NotificationList::Delegate* list_delegate)
: WebNotificationButtonViewBase(list_delegate) {
set_background(views::Background::CreateSolidBackground(
kMessageCenterBackgroundColor));
set_border(views::Border::CreateSolidSidedBorder(
1, 0, 0, 0, kFooterDelimiterColor));
notification_label_ = new views::Label(l10n_util::GetStringUTF16(
IDS_MESSAGE_CENTER_FOOTER_TITLE));
notification_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
notification_label_->SetElideBehavior(views::Label::ELIDE_AT_END);
notification_label_->SetEnabledColor(kFooterTextColor);
AddChildView(notification_label_);
settings_button_ = new WebNotificationButton(
this, l10n_util::GetStringUTF16(
IDS_MESSAGE_CENTER_SETTINGS_BUTTON_LABEL));
AddChildView(settings_button_);
WebNotificationButton* close_all_button = new WebNotificationButton(
this, l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_CLEAR_ALL));
AddChildView(close_all_button);
views::GridLayout* layout = new views::GridLayout(this);
SetLayoutManager(layout);
layout->SetInsets(
kMarginBetweenItems, kFooterMargin, kMarginBetweenItems, 0);
views::ColumnSet* column = layout->AddColumnSet(0);
column->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
1.0f, views::GridLayout::USE_PREF, 0, 0);
column->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL,
0, views::GridLayout::FIXED,
settings_button_->GetPreferredSize().width(), 0);
column->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL,
0, views::GridLayout::USE_PREF, 0, 0);
layout->StartRow(0, 0);
layout->AddView(notification_label_);
layout->AddView(settings_button_);
layout->AddView(close_all_button);
set_close_all_button(close_all_button);
}
private:
// views::ButtonListener overrides:
virtual void ButtonPressed(views::Button* sender,
const ui::Event& event) OVERRIDE {
if (sender == close_all_button())
list_delegate()->SendRemoveAllNotifications();
else if (sender == settings_button_)
list_delegate()->ShowNotificationSettingsDialog(
GetWidget()->GetNativeView());
else
NOTREACHED();
}
views::Label* notification_label_;
views::Button* settings_button_;
DISALLOW_COPY_AND_ASSIGN(WebNotificationButtonView2);
};
// A custom scroll-view that has a specified size.
class FixedSizedScrollView : public views::ScrollView {
public:
FixedSizedScrollView() {
set_focusable(true);
set_notify_enter_exit_on_child(true);
if (IsRichNotificationEnabled()) {
set_background(views::Background::CreateSolidBackground(
kMessageCenterBackgroundColor));
}
}
virtual ~FixedSizedScrollView() {}
void SetFixedSize(const gfx::Size& size) {
if (fixed_size_ == size)
return;
fixed_size_ = size;
PreferredSizeChanged();
}
// views::View overrides.
virtual gfx::Size GetPreferredSize() OVERRIDE {
gfx::Size size = fixed_size_.IsEmpty() ?
contents()->GetPreferredSize() : fixed_size_;
gfx::Insets insets = GetInsets();
size.Enlarge(insets.width(), insets.height());
return size;
}
virtual void Layout() OVERRIDE {
gfx::Rect bounds = gfx::Rect(contents()->GetPreferredSize());
bounds.set_width(std::max(0, width() - GetScrollBarWidth()));
contents()->SetBoundsRect(bounds);
views::ScrollView::Layout();
if (!vertical_scroll_bar()->visible()) {
gfx::Rect bounds = contents()->bounds();
bounds.set_width(bounds.width() + GetScrollBarWidth());
contents()->SetBoundsRect(bounds);
}
}
virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE {
gfx::Rect bounds = gfx::Rect(contents()->GetPreferredSize());
bounds.set_width(std::max(0, width() - GetScrollBarWidth()));
contents()->SetBoundsRect(bounds);
}
private:
gfx::Size fixed_size_;
DISALLOW_COPY_AND_ASSIGN(FixedSizedScrollView);
};
// Container for the messages.
class ScrollContentView : public views::View {
public:
ScrollContentView() {
if (IsRichNotificationEnabled()) {
// Set the margin to 0 for the layout. BoxLayout assumes the same margin
// for top and bottom, but the bottom margin here should be smaller
// because of the shadow of message view. Use an empty border instead
// to provide this margin.
gfx::Insets shadow_insets = MessageView::GetShadowInsets();
SetLayoutManager(
new views::BoxLayout(views::BoxLayout::kVertical,
0,
0,
kMarginBetweenItems - shadow_insets.bottom()));
set_background(views::Background::CreateSolidBackground(
kMessageCenterBackgroundColor));
set_border(views::Border::CreateEmptyBorder(
kMarginBetweenItems - shadow_insets.top(), /* top */
kMarginBetweenItems - shadow_insets.left(), /* left */
0, /* bottom */
kMarginBetweenItems - shadow_insets.right() /* right */ ));
} else {
views::BoxLayout* layout =
new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1);
layout->set_spread_blank_space(true);
SetLayoutManager(layout);
}
}
virtual ~ScrollContentView() {
}
virtual gfx::Size GetPreferredSize() OVERRIDE {
if (!preferred_size_.IsEmpty())
return preferred_size_;
return views::View::GetPreferredSize();
}
void set_preferred_size(const gfx::Size& size) { preferred_size_ = size; }
private:
gfx::Size preferred_size_;
DISALLOW_COPY_AND_ASSIGN(ScrollContentView);
};
} // namespace
// Message Center contents.
class MessageCenterContentsView : public views::View {
public:
explicit MessageCenterContentsView(MessageCenterBubble* bubble,
NotificationList::Delegate* list_delegate)
: list_delegate_(list_delegate),
bubble_(bubble) {
int between_child = IsRichNotificationEnabled() ? 0 : 1;
SetLayoutManager(
new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, between_child));
scroll_content_ = new ScrollContentView;
scroller_ = new FixedSizedScrollView;
scroller_->SetContents(scroll_content_);
AddChildView(scroller_);
if (get_use_acceleration_when_possible()) {
scroller_->SetPaintToLayer(true);
scroller_->SetFillsBoundsOpaquely(false);
scroller_->layer()->SetMasksToBounds(true);
}
if (IsRichNotificationEnabled())
button_view_ = new WebNotificationButtonView2(list_delegate);
else
button_view_ = new WebNotificationButtonView(list_delegate);
AddChildView(button_view_);
}
void FocusContents() {
scroller_->RequestFocus();
}
void Update(const NotificationList::Notifications& notifications) {
scroll_content_->RemoveAllChildViews(true);
scroll_content_->set_preferred_size(gfx::Size());
size_t num_children = 0;
for (NotificationList::Notifications::const_iterator iter =
notifications.begin(); iter != notifications.end(); ++iter) {
MessageView* view =
NotificationView::ViewForNotification(*(*iter), list_delegate_);
view->set_scroller(scroller_);
if (IsRichNotificationEnabled())
view->SetUpShadow();
scroll_content_->AddChildView(view);
if (++num_children >=
NotificationList::kMaxVisibleMessageCenterNotifications) {
break;
}
}
if (num_children == 0) {
views::Label* label = new views::Label(l10n_util::GetStringUTF16(
IDS_MESSAGE_CENTER_NO_MESSAGES));
label->SetFont(label->font().DeriveFont(1));
label->SetEnabledColor(SK_ColorGRAY);
// Set transparent background to ensure that subpixel rendering
// is disabled. See crbug.com/169056
label->SetBackgroundColor(kTransparentColor);
scroll_content_->AddChildView(label);
button_view_->SetCloseAllVisible(false);
scroller_->set_focusable(false);
} else {
button_view_->SetCloseAllVisible(true);
scroller_->set_focusable(true);
}
SizeScrollContent();
Layout();
if (GetWidget())
GetWidget()->GetRootView()->SchedulePaint();
}
size_t NumMessageViews() const {
return scroll_content_->child_count();
}
private:
void SizeScrollContent() {
gfx::Size scroll_size = scroll_content_->GetPreferredSize();
const int button_height = button_view_->GetPreferredSize().height();
const int min_height = kMessageBubbleBaseMinHeight - button_height;
const int max_height = bubble_->max_height() - button_height;
int scroll_height = std::min(std::max(
scroll_size.height(), min_height), max_height);
scroll_size.set_height(scroll_height);
if (scroll_height == min_height)
scroll_content_->set_preferred_size(scroll_size);
else
scroll_content_->set_preferred_size(gfx::Size());
scroller_->SetFixedSize(scroll_size);
scroller_->SizeToPreferredSize();
scroll_content_->InvalidateLayout();
}
NotificationList::Delegate* list_delegate_;
FixedSizedScrollView* scroller_;
ScrollContentView* scroll_content_;
WebNotificationButtonViewBase* button_view_;
MessageCenterBubble* bubble_;
DISALLOW_COPY_AND_ASSIGN(MessageCenterContentsView);
};
// Message Center Bubble.
MessageCenterBubble::MessageCenterBubble(NotificationList::Delegate* delegate)
: MessageBubbleBase(delegate),
contents_view_(NULL) {
}
MessageCenterBubble::~MessageCenterBubble() {}
views::TrayBubbleView::InitParams MessageCenterBubble::GetInitParams(
views::TrayBubbleView::AnchorAlignment anchor_alignment) {
views::TrayBubbleView::InitParams init_params =
GetDefaultInitParams(anchor_alignment);
if (IsRichNotificationEnabled()) {
init_params.min_width += kMarginBetweenItems * 2;
init_params.max_width += kMarginBetweenItems * 2;
}
init_params.max_height = max_height();
init_params.can_activate = true;
return init_params;
}
void MessageCenterBubble::InitializeContents(
views::TrayBubbleView* new_bubble_view) {
set_bubble_view(new_bubble_view);
contents_view_ = new MessageCenterContentsView(this, list_delegate());
bubble_view()->AddChildView(contents_view_);
// Resize the content of the bubble view to the given bubble size. This is
// necessary in case of the bubble border forcing a bigger size then the
// |new_bubble_view| actually wants. See crbug.com/169390.
bubble_view()->Layout();
UpdateBubbleView();
contents_view_->FocusContents();
}
void MessageCenterBubble::OnBubbleViewDestroyed() {
contents_view_ = NULL;
}
void MessageCenterBubble::UpdateBubbleView() {
if (!bubble_view())
return; // Could get called after view is closed
const NotificationList::Notifications& notifications =
list_delegate()->GetNotificationList()->GetNotifications();
contents_view_->Update(notifications);
bubble_view()->Show();
bubble_view()->UpdateBubble();
}
void MessageCenterBubble::OnMouseEnteredView() {
}
void MessageCenterBubble::OnMouseExitedView() {
}
size_t MessageCenterBubble::NumMessageViewsForTest() const {
return contents_view_->NumMessageViews();
}
} // namespace message_center