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/accessibility/accessible_view_state.h"
10 #include "ui/base/l10n/l10n_util.h"
11 #include "ui/base/models/simple_menu_model.h"
12 #include "ui/base/resource/resource_bundle.h"
13 #include "ui/compositor/scoped_layer_animation_settings.h"
14 #include "ui/gfx/canvas.h"
15 #include "ui/message_center/message_center.h"
16 #include "ui/message_center/message_center_style.h"
17 #include "ui/message_center/message_center_tray.h"
18 #include "ui/message_center/message_center_util.h"
19 #include "ui/views/context_menu_controller.h"
20 #include "ui/views/controls/button/image_button.h"
21 #include "ui/views/controls/menu/menu_runner.h"
22 #include "ui/views/controls/scroll_view.h"
23 #include "ui/views/shadow_border.h"
24 #include "ui/views/widget/widget.h"
28 const int kCloseIconTopPadding
= 5;
29 const int kCloseIconRightPadding
= 5;
30 const int kExpandIconBottomPadding
= 8;
31 const int kExpandIconRightPadding
= 11;
33 const int kShadowOffset
= 1;
34 const int kShadowBlur
= 4;
37 const int kTogglePermissionCommand
= 0;
38 const int kToggleExtensionCommand
= 1;
39 const int kShowSettingsCommand
= 2;
41 // ControlButtons are ImageButtons whose image can be padded within the button.
42 // This allows the creation of buttons like the notification close and expand
43 // buttons whose clickable areas extends beyond their image areas
44 // (<http://crbug.com/168822>) without the need to create and maintain
45 // corresponding resource images with alpha padding. In the future, this class
46 // will also allow for buttons whose touch areas extend beyond their clickable
47 // area (<http://crbug.com/168856>).
48 class ControlButton
: public views::ImageButton
{
50 ControlButton(views::ButtonListener
* listener
);
51 virtual ~ControlButton();
53 // Overridden from views::ImageButton:
54 virtual gfx::Size
GetPreferredSize() OVERRIDE
;
55 virtual void OnPaint(gfx::Canvas
* canvas
) OVERRIDE
;
56 virtual void OnFocus() OVERRIDE
;
57 virtual void OnPaintFocusBorder(gfx::Canvas
* canvas
) OVERRIDE
;
59 // The SetPadding() method also sets the button's image alignment (positive
60 // values yield left/top alignments, negative values yield right/bottom ones,
61 // and zero values center/middle ones). ImageButton::SetImageAlignment() calls
62 // will not affect ControlButton image alignments.
63 void SetPadding(int horizontal_padding
, int vertical_padding
);
65 void SetNormalImage(int resource_id
);
66 void SetHoveredImage(int resource_id
);
67 void SetPressedImage(int resource_id
);
70 gfx::Point
ComputePaddedImagePaintPosition(const gfx::ImageSkia
& image
);
75 DISALLOW_COPY_AND_ASSIGN(ControlButton
);
78 ControlButton::ControlButton(views::ButtonListener
* listener
)
79 : views::ImageButton(listener
) {
81 set_request_focus_on_press(false);
84 ControlButton::~ControlButton() {
87 void ControlButton::SetPadding(int horizontal_padding
, int vertical_padding
) {
88 padding_
.Set(std::max(vertical_padding
, 0),
89 std::max(horizontal_padding
, 0),
90 std::max(-vertical_padding
, 0),
91 std::max(-horizontal_padding
, 0));
94 void ControlButton::SetNormalImage(int resource_id
) {
95 SetImage(views::CustomButton::STATE_NORMAL
,
96 ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
100 void ControlButton::SetHoveredImage(int resource_id
) {
101 SetImage(views::CustomButton::STATE_HOVERED
,
102 ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
106 void ControlButton::SetPressedImage(int resource_id
) {
107 SetImage(views::CustomButton::STATE_PRESSED
,
108 ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
112 gfx::Size
ControlButton::GetPreferredSize() {
113 return gfx::Size(message_center::kControlButtonSize
,
114 message_center::kControlButtonSize
);
117 void ControlButton::OnPaint(gfx::Canvas
* canvas
) {
118 // This is the same implementation as ImageButton::OnPaint except
119 // that it calls ComputePaddedImagePaintPosition() instead of
120 // ComputeImagePaintPosition(), in effect overriding that private method.
121 View::OnPaint(canvas
);
122 gfx::ImageSkia image
= GetImageToPaint();
123 if (!image
.isNull()) {
124 gfx::Point position
= ComputePaddedImagePaintPosition(image
);
125 if (!background_image_
.isNull())
126 canvas
->DrawImageInt(background_image_
, position
.x(), position
.y());
127 canvas
->DrawImageInt(image
, position
.x(), position
.y());
128 if (!overlay_image_
.isNull())
129 canvas
->DrawImageInt(overlay_image_
, position
.x(), position
.y());
131 OnPaintFocusBorder(canvas
);
134 void ControlButton::OnFocus() {
135 views::ImageButton::OnFocus();
136 ScrollRectToVisible(GetLocalBounds());
139 void ControlButton::OnPaintFocusBorder(gfx::Canvas
* canvas
) {
140 if (HasFocus() && (focusable() || IsAccessibilityFocusable())) {
141 canvas
->DrawRect(gfx::Rect(2, 1, width() - 4, height() - 3),
142 message_center::kFocusBorderColor
);
146 gfx::Point
ControlButton::ComputePaddedImagePaintPosition(
147 const gfx::ImageSkia
& image
) {
148 gfx::Vector2d offset
;
149 gfx::Rect bounds
= GetContentsBounds();
150 bounds
.Inset(padding_
);
152 if (padding_
.left() == 0 && padding_
.right() == 0)
153 offset
.set_x((bounds
.width() - image
.width()) / 2); // Center align.
154 else if (padding_
.right() > 0)
155 offset
.set_x(bounds
.width() - image
.width()); // Right align.
157 if (padding_
.top() == 0 && padding_
.bottom() == 0)
158 offset
.set_y((bounds
.height() - image
.height()) / 2); // Middle align.
159 else if (padding_
.bottom() > 0)
160 offset
.set_y(bounds
.height() - image
.height()); // Bottom align.
162 return bounds
.origin() + offset
;
165 // A dropdown menu for notifications.
166 class MenuModel
: public ui::SimpleMenuModel
,
167 public ui::SimpleMenuModel::Delegate
{
169 MenuModel(message_center::MessageCenter
* message_center
,
170 message_center::MessageCenterTray
* tray
,
171 const std::string
& notification_id
,
172 const string16
& display_source
,
173 const std::string
& extension_id
);
174 virtual ~MenuModel();
176 // Overridden from ui::SimpleMenuModel::Delegate:
177 virtual bool IsItemForCommandIdDynamic(int command_id
) const OVERRIDE
;
178 virtual bool IsCommandIdChecked(int command_id
) const OVERRIDE
;
179 virtual bool IsCommandIdEnabled(int command_id
) const OVERRIDE
;
180 virtual bool GetAcceleratorForCommandId(
182 ui::Accelerator
* accelerator
) OVERRIDE
;
183 virtual void ExecuteCommand(int command_id
, int event_flags
) OVERRIDE
;
186 message_center::MessageCenter
* message_center_
; // Weak reference.
187 message_center::MessageCenterTray
* tray_
; // Weak reference.
188 std::string notification_id_
;
190 DISALLOW_COPY_AND_ASSIGN(MenuModel
);
193 MenuModel::MenuModel(message_center::MessageCenter
* message_center
,
194 message_center::MessageCenterTray
* tray
,
195 const std::string
& notification_id
,
196 const string16
& display_source
,
197 const std::string
& extension_id
)
198 : ui::SimpleMenuModel(this),
199 message_center_(message_center
),
201 notification_id_(notification_id
) {
202 // Add 'disable notifications' menu item.
203 if (!extension_id
.empty() && !display_source
.empty()) {
204 AddItem(kToggleExtensionCommand
,
205 l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_EXTENSIONS_DISABLE
,
207 } else if (!display_source
.empty()) {
208 AddItem(kTogglePermissionCommand
,
209 l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_SITE_DISABLE
,
212 // Add settings menu item.
213 AddItem(kShowSettingsCommand
,
214 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_SETTINGS
));
217 MenuModel::~MenuModel() {
220 bool MenuModel::IsItemForCommandIdDynamic(int command_id
) const {
224 bool MenuModel::IsCommandIdChecked(int command_id
) const {
228 bool MenuModel::IsCommandIdEnabled(int command_id
) const {
232 bool MenuModel::GetAcceleratorForCommandId(int command_id
,
233 ui::Accelerator
* accelerator
) {
237 void MenuModel::ExecuteCommand(int command_id
, int event_flags
) {
238 switch (command_id
) {
239 case kToggleExtensionCommand
:
240 message_center_
->DisableNotificationsByExtension(notification_id_
);
242 case kTogglePermissionCommand
:
243 message_center_
->DisableNotificationsByUrl(notification_id_
);
245 case kShowSettingsCommand
:
246 // |tray_| may be NULL in tests.
248 tray_
->ShowNotifierSettingsBubble();
250 message_center_
->ShowNotificationSettings(notification_id_
);
259 namespace message_center
{
261 class MessageViewContextMenuController
: public views::ContextMenuController
{
263 MessageViewContextMenuController(
264 MessageCenter
* message_center
,
265 MessageCenterTray
* tray
,
266 const Notification
& notification
);
267 virtual ~MessageViewContextMenuController();
270 // Overridden from views::ContextMenuController:
271 virtual void ShowContextMenuForView(views::View
* source
,
272 const gfx::Point
& point
,
273 ui::MenuSourceType source_type
) OVERRIDE
;
275 MessageCenter
* message_center_
; // Weak reference.
276 MessageCenterTray
* tray_
; // Weak reference.
277 std::string notification_id_
;
278 string16 display_source_
;
279 std::string extension_id_
;
282 MessageViewContextMenuController::MessageViewContextMenuController(
283 MessageCenter
* message_center
,
284 MessageCenterTray
* tray
,
285 const Notification
& notification
)
286 : message_center_(message_center
),
288 notification_id_(notification
.id()),
289 display_source_(notification
.display_source()),
290 extension_id_(notification
.extension_id()) {
293 MessageViewContextMenuController::~MessageViewContextMenuController() {
296 void MessageViewContextMenuController::ShowContextMenuForView(
298 const gfx::Point
& point
,
299 ui::MenuSourceType source_type
) {
300 MenuModel
menu_model(message_center_
, tray_
, notification_id_
,
301 display_source_
, extension_id_
);
302 if (menu_model
.GetItemCount() == 0)
305 views::MenuRunner
menu_runner(&menu_model
);
307 ignore_result(menu_runner
.RunMenuAt(
308 source
->GetWidget()->GetTopLevelWidget(),
310 gfx::Rect(point
, gfx::Size()),
311 views::MenuItemView::TOPRIGHT
,
313 views::MenuRunner::HAS_MNEMONICS
));
316 MessageView::MessageView(const Notification
& notification
,
317 MessageCenter
* message_center
,
318 MessageCenterTray
* tray
,
320 : message_center_(message_center
),
321 notification_id_(notification
.id()),
322 context_menu_controller_(new MessageViewContextMenuController(
323 message_center
, tray
, notification
)),
325 is_expanded_(expanded
) {
327 set_context_menu_controller(context_menu_controller_
.get());
329 ControlButton
*close
= new ControlButton(this);
330 close
->SetPadding(-kCloseIconRightPadding
, kCloseIconTopPadding
);
331 close
->SetNormalImage(IDR_NOTIFICATION_CLOSE
);
332 close
->SetHoveredImage(IDR_NOTIFICATION_CLOSE_HOVER
);
333 close
->SetPressedImage(IDR_NOTIFICATION_CLOSE_PRESSED
);
334 close
->set_owned_by_client();
335 close
->set_animate_on_state_change(false);
336 close
->SetAccessibleName(l10n_util::GetStringUTF16(
337 IDS_MESSAGE_CENTER_CLOSE_NOTIFICATION_BUTTON_ACCESSIBLE_NAME
));
338 close_button_
.reset(close
);
340 ControlButton
*expand
= new ControlButton(this);
341 expand
->SetPadding(-kExpandIconRightPadding
, -kExpandIconBottomPadding
);
342 expand
->SetNormalImage(IDR_NOTIFICATION_EXPAND
);
343 expand
->SetHoveredImage(IDR_NOTIFICATION_EXPAND_HOVER
);
344 expand
->SetPressedImage(IDR_NOTIFICATION_EXPAND_PRESSED
);
345 expand
->set_owned_by_client();
346 expand
->set_animate_on_state_change(false);
347 expand
->SetAccessibleName(l10n_util::GetStringUTF16(
348 IDS_MESSAGE_CENTER_EXPAND_NOTIFICATION_BUTTON_ACCESSIBLE_NAME
));
349 expand_button_
.reset(expand
);
352 MessageView::MessageView() {
355 MessageView::~MessageView() {
359 gfx::Insets
MessageView::GetShadowInsets() {
360 return gfx::Insets(kShadowBlur
/ 2 - kShadowOffset
,
362 kShadowBlur
/ 2 + kShadowOffset
,
366 void MessageView::CreateShadowBorder() {
367 set_border(new views::ShadowBorder(kShadowBlur
,
368 message_center::kShadowColor
,
369 kShadowOffset
, // Vertical offset.
370 0)); // Horizontal offset.
373 bool MessageView::IsCloseButtonFocused() {
374 views::FocusManager
* focus_manager
= GetFocusManager();
375 return focus_manager
&& focus_manager
->GetFocusedView() == close_button();
378 void MessageView::RequestFocusOnCloseButton() {
379 close_button_
->RequestFocus();
382 void MessageView::GetAccessibleState(ui::AccessibleViewState
* state
) {
383 state
->role
= ui::AccessibilityTypes::ROLE_PUSHBUTTON
;
384 state
->name
= accessible_name_
;
387 bool MessageView::OnMousePressed(const ui::MouseEvent
& event
) {
388 if (event
.IsOnlyLeftMouseButton()) {
389 message_center_
->ClickOnNotification(notification_id_
);
395 bool MessageView::OnKeyPressed(const ui::KeyEvent
& event
) {
396 if (event
.flags() != ui::EF_NONE
)
399 if (event
.key_code() == ui::VKEY_RETURN
) {
400 message_center_
->ClickOnNotification(notification_id_
);
402 } else if ((event
.key_code() == ui::VKEY_DELETE
||
403 event
.key_code() == ui::VKEY_BACK
)) {
404 message_center_
->RemoveNotification(notification_id_
, true); // By user.
411 bool MessageView::OnKeyReleased(const ui::KeyEvent
& event
) {
412 // Space key handling is triggerred at key-release timing. See
413 // ui/views/controls/buttons/custom_button.cc for why.
414 if (event
.flags() != ui::EF_NONE
|| event
.flags() != ui::VKEY_SPACE
)
417 message_center_
->ClickOnNotification(notification_id_
);
421 void MessageView::OnGestureEvent(ui::GestureEvent
* event
) {
422 if (event
->type() == ui::ET_GESTURE_TAP
) {
423 message_center_
->ClickOnNotification(notification_id_
);
428 SlideOutView::OnGestureEvent(event
);
429 // Do not return here by checking handled(). SlideOutView calls SetHandled()
430 // even though the scroll gesture doesn't make no (or little) effects on the
431 // slide-out behavior. See http://crbug.com/172991
433 if (!event
->IsScrollGestureEvent() && !event
->IsFlingScrollEvent())
437 scroller_
->OnGestureEvent(event
);
441 void MessageView::OnPaintFocusBorder(gfx::Canvas
* canvas
) {
443 canvas
->DrawRect(gfx::Rect(1, 0, width() - 2, height() - 2),
444 message_center::kFocusBorderColor
);
448 void MessageView::ButtonPressed(views::Button
* sender
,
449 const ui::Event
& event
) {
450 if (sender
== close_button()) {
451 message_center_
->RemoveNotification(notification_id_
, true); // By user.
452 } else if (sender
== expand_button()) {
454 message_center_
->ExpandNotification(notification_id_
);
458 void MessageView::OnSlideOut() {
459 message_center_
->RemoveNotification(notification_id_
, true); // By user.
462 } // namespace message_center