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/message_view.h"
7 #include "grit/ui_resources.h"
8 #include "grit/ui_strings.h"
9 #include "ui/base/l10n/l10n_util.h"
10 #include "ui/base/models/simple_menu_model.h"
11 #include "ui/base/resource/resource_bundle.h"
12 #include "ui/compositor/scoped_layer_animation_settings.h"
13 #include "ui/gfx/canvas.h"
14 #include "ui/gfx/shadow_value.h"
15 #include "ui/gfx/skia_util.h"
16 #include "ui/message_center/message_center_constants.h"
17 #include "ui/message_center/message_center_util.h"
18 #include "ui/message_center/notification_change_observer.h"
19 #include "ui/views/controls/button/image_button.h"
20 #include "ui/views/controls/menu/menu_runner.h"
21 #include "ui/views/controls/scroll_view.h"
22 #include "ui/views/widget/widget.h"
26 const int kCloseIconTopPadding
= 5;
27 const int kCloseIconRightPadding
= 5;
28 const int kExpandIconBottomPadding
= 8;
29 const int kExpandIconRightPadding
= 11;
31 const int kShadowOffset
= 1;
32 const int kShadowBlur
= 4;
34 const SkColor kShadowColor
= SkColorSetARGB(0.3 * 255, 0, 0, 0);
35 const SkColor kTransparentColor
= SkColorSetARGB(0, 0, 0, 0);
38 const int kTogglePermissionCommand
= 0;
39 const int kToggleExtensionCommand
= 1;
40 const int kShowSettingsCommand
= 2;
42 // ControlButtons are ImageButtons whose image can be padded within the button.
43 // This allows the creation of buttons like the notification close and expand
44 // buttons whose clickable areas extends beyond their image areas
45 // (<http://crbug.com/168822>) without the need to create and maintain
46 // corresponding resource images with alpha padding. In the future, this class
47 // will also allow for buttons whose touch areas extend beyond their clickable
48 // area (<http://crbug.com/168856>).
49 class ControlButton
: public views::ImageButton
{
51 ControlButton(views::ButtonListener
* listener
);
52 virtual ~ControlButton();
54 // Overridden from views::ImageButton:
55 virtual gfx::Size
GetPreferredSize() OVERRIDE
;
56 virtual void OnPaint(gfx::Canvas
* canvas
) OVERRIDE
;
58 // The SetPadding() method also sets the button's image alignment (positive
59 // values yield left/top alignments, negative values yield right/bottom ones,
60 // and zero values center/middle ones). ImageButton::SetImageAlignment() calls
61 // will not affect ControlButton image alignments.
62 void SetPadding(int horizontal_padding
, int vertical_padding
);
64 void SetNormalImage(int resource_id
);
65 void SetHoveredImage(int resource_id
);
66 void SetPressedImage(int resource_id
);
69 gfx::Point
ComputePaddedImagePaintPosition(const gfx::ImageSkia
& image
);
74 DISALLOW_COPY_AND_ASSIGN(ControlButton
);
77 ControlButton::ControlButton(views::ButtonListener
* listener
)
78 : views::ImageButton(listener
) {
82 ControlButton::~ControlButton() {
85 void ControlButton::SetPadding(int horizontal_padding
, int vertical_padding
) {
86 padding_
.Set(std::max(vertical_padding
, 0),
87 std::max(horizontal_padding
, 0),
88 std::max(-vertical_padding
, 0),
89 std::max(-horizontal_padding
, 0));
92 void ControlButton::SetNormalImage(int resource_id
) {
93 SetImage(views::CustomButton::STATE_NORMAL
,
94 ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
98 void ControlButton::SetHoveredImage(int resource_id
) {
99 SetImage(views::CustomButton::STATE_HOVERED
,
100 ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
104 void ControlButton::SetPressedImage(int resource_id
) {
105 SetImage(views::CustomButton::STATE_PRESSED
,
106 ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
110 gfx::Size
ControlButton::GetPreferredSize() {
111 return gfx::Size(message_center::kControlButtonSize
,
112 message_center::kControlButtonSize
);
115 void ControlButton::OnPaint(gfx::Canvas
* canvas
) {
116 // This is the same implementation as ImageButton::OnPaint except
117 // that it calls ComputePaddedImagePaintPosition() instead of
118 // ComputeImagePaintPosition(), in effect overriding that private method.
119 View::OnPaint(canvas
);
120 gfx::ImageSkia image
= GetImageToPaint();
121 if (!image
.isNull()) {
122 gfx::Point position
= ComputePaddedImagePaintPosition(image
);
123 if (!background_image_
.isNull())
124 canvas
->DrawImageInt(background_image_
, position
.x(), position
.y());
125 canvas
->DrawImageInt(image
, position
.x(), position
.y());
126 if (!overlay_image_
.isNull())
127 canvas
->DrawImageInt(overlay_image_
, position
.x(), position
.y());
129 OnPaintFocusBorder(canvas
);
132 gfx::Point
ControlButton::ComputePaddedImagePaintPosition(
133 const gfx::ImageSkia
& image
) {
134 gfx::Vector2d offset
;
135 gfx::Rect bounds
= GetContentsBounds();
136 bounds
.Inset(padding_
);
138 if (padding_
.left() == 0 && padding_
.right() == 0)
139 offset
.set_x((bounds
.width() - image
.width()) / 2); // Center align.
140 else if (padding_
.right() > 0)
141 offset
.set_x(bounds
.width() - image
.width()); // Right align.
143 if (padding_
.top() == 0 && padding_
.bottom() == 0)
144 offset
.set_y((bounds
.height() - image
.height()) / 2); // Middle align.
145 else if (padding_
.bottom() > 0)
146 offset
.set_y(bounds
.height() - image
.height()); // Bottom align.
148 return bounds
.origin() + offset
;
151 // A border to provide the shadow for each card.
152 // Current shadow should look like css box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3)
153 class ShadowBorder
: public views::Border
{
155 ShadowBorder() : views::Border() {}
156 virtual ~ShadowBorder() {}
159 // Overridden from views::Border:
160 virtual void Paint(const views::View
& view
, gfx::Canvas
* canvas
) OVERRIDE
;
161 virtual gfx::Insets
GetInsets() const OVERRIDE
;
163 DISALLOW_COPY_AND_ASSIGN(ShadowBorder
);
166 void ShadowBorder::Paint(const views::View
& view
, gfx::Canvas
* canvas
) {
168 std::vector
<gfx::ShadowValue
> shadows
;
169 shadows
.push_back(gfx::ShadowValue(gfx::Point(), kShadowBlur
, kShadowColor
));
170 skia::RefPtr
<SkDrawLooper
> looper
= gfx::CreateShadowDrawLooper(shadows
);
171 paint
.setLooper(looper
.get());
172 paint
.setColor(kTransparentColor
);
173 paint
.setStrokeJoin(SkPaint::kRound_Join
);
174 gfx::Rect
bounds(view
.size());
175 bounds
.Inset(gfx::Insets(kShadowBlur
/ 2, kShadowBlur
/ 2,
176 kShadowBlur
/ 2, kShadowBlur
/ 2));
177 canvas
->DrawRect(bounds
, paint
);
180 gfx::Insets
ShadowBorder::GetInsets() const {
181 return message_center::MessageView::GetShadowInsets();
184 // A dropdown menu for notifications.
185 class MenuModel
: public ui::SimpleMenuModel
,
186 public ui::SimpleMenuModel::Delegate
{
188 MenuModel(message_center::NotificationChangeObserver
* observer
,
189 const std::string
& notification_id
,
190 const string16
& display_source
,
191 const std::string
& extension_id
);
192 virtual ~MenuModel();
194 // Overridden from ui::SimpleMenuModel::Delegate:
195 virtual bool IsItemForCommandIdDynamic(int command_id
) const OVERRIDE
;
196 virtual bool IsCommandIdChecked(int command_id
) const OVERRIDE
;
197 virtual bool IsCommandIdEnabled(int command_id
) const OVERRIDE
;
198 virtual bool GetAcceleratorForCommandId(
200 ui::Accelerator
* accelerator
) OVERRIDE
;
201 virtual void ExecuteCommand(int command_id
, int event_flags
) OVERRIDE
;
204 message_center::NotificationChangeObserver
* observer_
; // Weak reference.
205 std::string notification_id_
;
207 DISALLOW_COPY_AND_ASSIGN(MenuModel
);
210 MenuModel::MenuModel(message_center::NotificationChangeObserver
* observer
,
211 const std::string
& notification_id
,
212 const string16
& display_source
,
213 const std::string
& extension_id
)
214 : ALLOW_THIS_IN_INITIALIZER_LIST(ui::SimpleMenuModel(this)),
216 notification_id_(notification_id
) {
217 // Add 'disable notifications' menu item.
218 if (!extension_id
.empty() && !display_source
.empty()) {
219 AddItem(kToggleExtensionCommand
,
220 l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_EXTENSIONS_DISABLE
,
222 } else if (!display_source
.empty()) {
223 AddItem(kTogglePermissionCommand
,
224 l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_SITE_DISABLE
,
227 // Add settings menu item.
228 if (message_center::IsRichNotificationEnabled() || !display_source
.empty()) {
229 AddItem(kShowSettingsCommand
,
230 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_SETTINGS
));
234 MenuModel::~MenuModel() {
237 bool MenuModel::IsItemForCommandIdDynamic(int command_id
) const {
241 bool MenuModel::IsCommandIdChecked(int command_id
) const {
245 bool MenuModel::IsCommandIdEnabled(int command_id
) const {
249 bool MenuModel::GetAcceleratorForCommandId(int command_id
,
250 ui::Accelerator
* accelerator
) {
254 void MenuModel::ExecuteCommand(int command_id
, int event_flags
) {
255 switch (command_id
) {
256 case kToggleExtensionCommand
:
257 observer_
->OnDisableNotificationsByExtension(notification_id_
);
259 case kTogglePermissionCommand
:
260 observer_
->OnDisableNotificationsByUrl(notification_id_
);
262 case kShowSettingsCommand
:
263 if (message_center::IsRichNotificationEnabled())
264 observer_
->OnShowNotificationSettingsDialog(NULL
);
266 observer_
->OnShowNotificationSettings(notification_id_
);
275 namespace message_center
{
277 MessageView::MessageView(const Notification
& notification
,
278 NotificationChangeObserver
* observer
,
280 : observer_(observer
),
281 notification_id_(notification
.id()),
282 display_source_(notification
.display_source()),
283 extension_id_(notification
.extension_id()),
285 is_expanded_(expanded
) {
286 ControlButton
*close
= new ControlButton(this);
287 close
->SetPadding(-kCloseIconRightPadding
, kCloseIconTopPadding
);
288 close
->SetNormalImage(IDR_NOTIFICATION_CLOSE
);
289 close
->SetHoveredImage(IDR_NOTIFICATION_CLOSE_HOVER
);
290 close
->SetPressedImage(IDR_NOTIFICATION_CLOSE_PRESSED
);
291 close
->set_owned_by_client();
292 close_button_
.reset(close
);
294 ControlButton
*expand
= new ControlButton(this);
295 expand
->SetPadding(-kExpandIconRightPadding
, -kExpandIconBottomPadding
);
296 expand
->SetNormalImage(IDR_NOTIFICATIONS_EXPAND
);
297 expand
->SetHoveredImage(IDR_NOTIFICATIONS_EXPAND_HOVER
);
298 expand
->SetPressedImage(IDR_NOTIFICATIONS_EXPAND_PRESSED
);
299 expand
->set_owned_by_client();
300 expand_button_
.reset(expand
);
302 if (IsRichNotificationEnabled())
303 set_border(new ShadowBorder());
306 MessageView::MessageView() {
309 MessageView::~MessageView() {
313 gfx::Insets
MessageView::GetShadowInsets() {
314 return gfx::Insets(kShadowBlur
/ 2 - kShadowOffset
,
316 kShadowBlur
/ 2 + kShadowOffset
,
320 bool MessageView::OnMousePressed(const ui::MouseEvent
& event
) {
321 if (event
.flags() & ui::EF_RIGHT_MOUSE_BUTTON
) {
322 ShowMenu(event
.location());
325 observer_
->OnClicked(notification_id_
);
329 void MessageView::OnGestureEvent(ui::GestureEvent
* event
) {
330 if (event
->type() == ui::ET_GESTURE_TAP
) {
331 observer_
->OnClicked(notification_id_
);
336 if (event
->type() == ui::ET_GESTURE_LONG_PRESS
) {
337 ShowMenu(event
->location());
342 SlideOutView::OnGestureEvent(event
);
343 // Do not return here by checking handled(). SlideOutView calls SetHandled()
344 // even though the scroll gesture doesn't make no (or little) effects on the
345 // slide-out behavior. See http://crbug.com/172991
347 if (!event
->IsScrollGestureEvent())
351 scroller_
->OnGestureEvent(event
);
355 void MessageView::ButtonPressed(views::Button
* sender
,
356 const ui::Event
& event
) {
357 if (sender
== close_button()) {
358 observer_
->OnRemoveNotification(notification_id_
, true); // By user.
359 } else if (sender
== expand_button()) {
361 observer_
->OnExpanded(notification_id_
);
365 void MessageView::ShowMenu(gfx::Point screen_location
) {
366 MenuModel
menu_model(observer_
, notification_id_
,
367 display_source_
, extension_id_
);
368 if (menu_model
.GetItemCount() == 0)
371 views::MenuRunner
menu_runner(&menu_model
);
373 views::View::ConvertPointToScreen(this, &screen_location
);
374 ignore_result(menu_runner
.RunMenuAt(
375 GetWidget()->GetTopLevelWidget(),
377 gfx::Rect(screen_location
, gfx::Size()),
378 views::MenuItemView::TOPRIGHT
,
379 views::MenuRunner::HAS_MNEMONICS
));
382 void MessageView::OnSlideOut() {
383 observer_
->OnRemoveNotification(notification_id_
, true); // By user.
386 } // namespace message_center