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 kShowSettingsCommand
= 1;
40 // ControlButtons are ImageButtons whose image can be padded within the button.
41 // This allows the creation of buttons like the notification close and expand
42 // buttons whose clickable areas extends beyond their image areas
43 // (<http://crbug.com/168822>) without the need to create and maintain
44 // corresponding resource images with alpha padding. In the future, this class
45 // will also allow for buttons whose touch areas extend beyond their clickable
46 // area (<http://crbug.com/168856>).
47 class ControlButton
: public views::ImageButton
{
49 ControlButton(views::ButtonListener
* listener
);
50 virtual ~ControlButton();
52 // Overridden from views::ImageButton:
53 virtual gfx::Size
GetPreferredSize() OVERRIDE
;
54 virtual void OnPaint(gfx::Canvas
* canvas
) OVERRIDE
;
55 virtual void OnFocus() OVERRIDE
;
56 virtual void OnPaintFocusBorder(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
) {
80 set_request_focus_on_press(false);
83 ControlButton::~ControlButton() {
86 void ControlButton::SetPadding(int horizontal_padding
, int vertical_padding
) {
87 padding_
.Set(std::max(vertical_padding
, 0),
88 std::max(horizontal_padding
, 0),
89 std::max(-vertical_padding
, 0),
90 std::max(-horizontal_padding
, 0));
93 void ControlButton::SetNormalImage(int resource_id
) {
94 SetImage(views::CustomButton::STATE_NORMAL
,
95 ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
99 void ControlButton::SetHoveredImage(int resource_id
) {
100 SetImage(views::CustomButton::STATE_HOVERED
,
101 ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
105 void ControlButton::SetPressedImage(int resource_id
) {
106 SetImage(views::CustomButton::STATE_PRESSED
,
107 ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
111 gfx::Size
ControlButton::GetPreferredSize() {
112 return gfx::Size(message_center::kControlButtonSize
,
113 message_center::kControlButtonSize
);
116 void ControlButton::OnPaint(gfx::Canvas
* canvas
) {
117 // This is the same implementation as ImageButton::OnPaint except
118 // that it calls ComputePaddedImagePaintPosition() instead of
119 // ComputeImagePaintPosition(), in effect overriding that private method.
120 View::OnPaint(canvas
);
121 gfx::ImageSkia image
= GetImageToPaint();
122 if (!image
.isNull()) {
123 gfx::Point position
= ComputePaddedImagePaintPosition(image
);
124 if (!background_image_
.isNull())
125 canvas
->DrawImageInt(background_image_
, position
.x(), position
.y());
126 canvas
->DrawImageInt(image
, position
.x(), position
.y());
127 if (!overlay_image_
.isNull())
128 canvas
->DrawImageInt(overlay_image_
, position
.x(), position
.y());
130 OnPaintFocusBorder(canvas
);
133 void ControlButton::OnFocus() {
134 views::ImageButton::OnFocus();
135 ScrollRectToVisible(GetLocalBounds());
138 void ControlButton::OnPaintFocusBorder(gfx::Canvas
* canvas
) {
139 if (HasFocus() && (focusable() || IsAccessibilityFocusable())) {
140 canvas
->DrawRect(gfx::Rect(2, 1, width() - 4, height() - 3),
141 message_center::kFocusBorderColor
);
145 gfx::Point
ControlButton::ComputePaddedImagePaintPosition(
146 const gfx::ImageSkia
& image
) {
147 gfx::Vector2d offset
;
148 gfx::Rect bounds
= GetContentsBounds();
149 bounds
.Inset(padding_
);
151 if (padding_
.left() == 0 && padding_
.right() == 0)
152 offset
.set_x((bounds
.width() - image
.width()) / 2); // Center align.
153 else if (padding_
.right() > 0)
154 offset
.set_x(bounds
.width() - image
.width()); // Right align.
156 if (padding_
.top() == 0 && padding_
.bottom() == 0)
157 offset
.set_y((bounds
.height() - image
.height()) / 2); // Middle align.
158 else if (padding_
.bottom() > 0)
159 offset
.set_y(bounds
.height() - image
.height()); // Bottom align.
161 return bounds
.origin() + offset
;
164 // A dropdown menu for notifications.
165 class MenuModel
: public ui::SimpleMenuModel
,
166 public ui::SimpleMenuModel::Delegate
{
168 MenuModel(message_center::MessageCenter
* message_center
,
169 message_center::MessageCenterTray
* tray
,
170 const std::string
& notification_id
,
171 const string16
& display_source
,
172 const message_center::NotifierId
& notifier_id
);
173 virtual ~MenuModel();
175 // Overridden from ui::SimpleMenuModel::Delegate:
176 virtual bool IsItemForCommandIdDynamic(int command_id
) const OVERRIDE
;
177 virtual bool IsCommandIdChecked(int command_id
) const OVERRIDE
;
178 virtual bool IsCommandIdEnabled(int command_id
) const OVERRIDE
;
179 virtual bool GetAcceleratorForCommandId(
181 ui::Accelerator
* accelerator
) OVERRIDE
;
182 virtual void ExecuteCommand(int command_id
, int event_flags
) OVERRIDE
;
185 message_center::MessageCenter
* message_center_
; // Weak reference.
186 message_center::MessageCenterTray
* tray_
; // Weak reference.
187 std::string notification_id_
;
188 message_center::NotifierId notifier_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 message_center::NotifierId
& notifier_id
)
198 : ui::SimpleMenuModel(this),
199 message_center_(message_center
),
201 notification_id_(notification_id
),
202 notifier_id_(notifier_id
) {
203 // Add 'disable notifications' menu item.
204 if (!display_source
.empty()) {
205 AddItem(kTogglePermissionCommand
,
206 l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_NOTIFIER_DISABLE
,
209 // Add settings menu item.
210 AddItem(kShowSettingsCommand
,
211 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_SETTINGS
));
214 MenuModel::~MenuModel() {
217 bool MenuModel::IsItemForCommandIdDynamic(int command_id
) const {
221 bool MenuModel::IsCommandIdChecked(int command_id
) const {
225 bool MenuModel::IsCommandIdEnabled(int command_id
) const {
229 bool MenuModel::GetAcceleratorForCommandId(int command_id
,
230 ui::Accelerator
* accelerator
) {
234 void MenuModel::ExecuteCommand(int command_id
, int event_flags
) {
235 switch (command_id
) {
236 case kTogglePermissionCommand
:
237 message_center_
->DisableNotificationsByNotifier(notifier_id_
);
239 case kShowSettingsCommand
:
240 // |tray_| may be NULL in tests.
242 tray_
->ShowNotifierSettingsBubble();
251 namespace message_center
{
253 class MessageViewContextMenuController
: public views::ContextMenuController
{
255 MessageViewContextMenuController(
256 MessageCenter
* message_center
,
257 MessageCenterTray
* tray
,
258 const Notification
& notification
);
259 virtual ~MessageViewContextMenuController();
262 // Overridden from views::ContextMenuController:
263 virtual void ShowContextMenuForView(views::View
* source
,
264 const gfx::Point
& point
,
265 ui::MenuSourceType source_type
) OVERRIDE
;
267 MessageCenter
* message_center_
; // Weak reference.
268 MessageCenterTray
* tray_
; // Weak reference.
269 std::string notification_id_
;
270 string16 display_source_
;
271 NotifierId notifier_id_
;
274 MessageViewContextMenuController::MessageViewContextMenuController(
275 MessageCenter
* message_center
,
276 MessageCenterTray
* tray
,
277 const Notification
& notification
)
278 : message_center_(message_center
),
280 notification_id_(notification
.id()),
281 display_source_(notification
.display_source()),
282 notifier_id_(notification
.notifier_id()) {
285 MessageViewContextMenuController::~MessageViewContextMenuController() {
288 void MessageViewContextMenuController::ShowContextMenuForView(
290 const gfx::Point
& point
,
291 ui::MenuSourceType source_type
) {
292 MenuModel
menu_model(message_center_
, tray_
, notification_id_
,
293 display_source_
, notifier_id_
);
294 if (menu_model
.GetItemCount() == 0)
297 views::MenuRunner
menu_runner(&menu_model
);
299 ignore_result(menu_runner
.RunMenuAt(
300 source
->GetWidget()->GetTopLevelWidget(),
302 gfx::Rect(point
, gfx::Size()),
303 views::MenuItemView::TOPRIGHT
,
305 views::MenuRunner::HAS_MNEMONICS
));
308 MessageView::MessageView(const Notification
& notification
,
309 MessageCenter
* message_center
,
310 MessageCenterTray
* tray
,
312 : message_center_(message_center
),
313 notification_id_(notification
.id()),
314 context_menu_controller_(new MessageViewContextMenuController(
315 message_center
, tray
, notification
)),
317 is_expanded_(expanded
) {
319 set_context_menu_controller(context_menu_controller_
.get());
321 ControlButton
*close
= new ControlButton(this);
322 close
->SetPadding(-kCloseIconRightPadding
, kCloseIconTopPadding
);
323 close
->SetNormalImage(IDR_NOTIFICATION_CLOSE
);
324 close
->SetHoveredImage(IDR_NOTIFICATION_CLOSE_HOVER
);
325 close
->SetPressedImage(IDR_NOTIFICATION_CLOSE_PRESSED
);
326 close
->set_owned_by_client();
327 close
->set_animate_on_state_change(false);
328 close
->SetAccessibleName(l10n_util::GetStringUTF16(
329 IDS_MESSAGE_CENTER_CLOSE_NOTIFICATION_BUTTON_ACCESSIBLE_NAME
));
330 close_button_
.reset(close
);
332 ControlButton
*expand
= new ControlButton(this);
333 expand
->SetPadding(-kExpandIconRightPadding
, -kExpandIconBottomPadding
);
334 expand
->SetNormalImage(IDR_NOTIFICATION_EXPAND
);
335 expand
->SetHoveredImage(IDR_NOTIFICATION_EXPAND_HOVER
);
336 expand
->SetPressedImage(IDR_NOTIFICATION_EXPAND_PRESSED
);
337 expand
->set_owned_by_client();
338 expand
->set_animate_on_state_change(false);
339 expand
->SetAccessibleName(l10n_util::GetStringUTF16(
340 IDS_MESSAGE_CENTER_EXPAND_NOTIFICATION_BUTTON_ACCESSIBLE_NAME
));
341 expand_button_
.reset(expand
);
344 MessageView::MessageView() {
347 MessageView::~MessageView() {
351 gfx::Insets
MessageView::GetShadowInsets() {
352 return gfx::Insets(kShadowBlur
/ 2 - kShadowOffset
,
354 kShadowBlur
/ 2 + kShadowOffset
,
358 void MessageView::CreateShadowBorder() {
359 set_border(new views::ShadowBorder(kShadowBlur
,
360 message_center::kShadowColor
,
361 kShadowOffset
, // Vertical offset.
362 0)); // Horizontal offset.
365 bool MessageView::IsCloseButtonFocused() {
366 views::FocusManager
* focus_manager
= GetFocusManager();
367 return focus_manager
&& focus_manager
->GetFocusedView() == close_button();
370 void MessageView::RequestFocusOnCloseButton() {
371 close_button_
->RequestFocus();
374 void MessageView::GetAccessibleState(ui::AccessibleViewState
* state
) {
375 state
->role
= ui::AccessibilityTypes::ROLE_PUSHBUTTON
;
376 state
->name
= accessible_name_
;
379 bool MessageView::OnMousePressed(const ui::MouseEvent
& event
) {
380 if (event
.IsOnlyLeftMouseButton()) {
381 message_center_
->ClickOnNotification(notification_id_
);
387 bool MessageView::OnKeyPressed(const ui::KeyEvent
& event
) {
388 if (event
.flags() != ui::EF_NONE
)
391 if (event
.key_code() == ui::VKEY_RETURN
) {
392 message_center_
->ClickOnNotification(notification_id_
);
394 } else if ((event
.key_code() == ui::VKEY_DELETE
||
395 event
.key_code() == ui::VKEY_BACK
)) {
396 message_center_
->RemoveNotification(notification_id_
, true); // By user.
403 bool MessageView::OnKeyReleased(const ui::KeyEvent
& event
) {
404 // Space key handling is triggerred at key-release timing. See
405 // ui/views/controls/buttons/custom_button.cc for why.
406 if (event
.flags() != ui::EF_NONE
|| event
.flags() != ui::VKEY_SPACE
)
409 message_center_
->ClickOnNotification(notification_id_
);
413 void MessageView::OnGestureEvent(ui::GestureEvent
* event
) {
414 if (event
->type() == ui::ET_GESTURE_TAP
) {
415 message_center_
->ClickOnNotification(notification_id_
);
420 SlideOutView::OnGestureEvent(event
);
421 // Do not return here by checking handled(). SlideOutView calls SetHandled()
422 // even though the scroll gesture doesn't make no (or little) effects on the
423 // slide-out behavior. See http://crbug.com/172991
425 if (!event
->IsScrollGestureEvent() && !event
->IsFlingScrollEvent())
429 scroller_
->OnGestureEvent(event
);
433 void MessageView::OnPaintFocusBorder(gfx::Canvas
* canvas
) {
435 canvas
->DrawRect(gfx::Rect(1, 0, width() - 2, height() - 2),
436 message_center::kFocusBorderColor
);
440 void MessageView::ButtonPressed(views::Button
* sender
,
441 const ui::Event
& event
) {
442 if (sender
== close_button()) {
443 message_center_
->RemoveNotification(notification_id_
, true); // By user.
444 } else if (sender
== expand_button()) {
446 message_center_
->ExpandNotification(notification_id_
);
450 void MessageView::OnSlideOut() {
451 message_center_
->RemoveNotification(notification_id_
, true); // By user.
454 } // namespace message_center