- add sources.
[platform/framework/web/crosswalk.git] / src / ui / message_center / views / message_view.cc
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.
4
5 #include "ui/message_center/views/message_view.h"
6
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"
25
26 namespace {
27
28 const int kCloseIconTopPadding = 5;
29 const int kCloseIconRightPadding = 5;
30 const int kExpandIconBottomPadding = 8;
31 const int kExpandIconRightPadding = 11;
32
33 const int kShadowOffset = 1;
34 const int kShadowBlur = 4;
35
36 // Menu constants
37 const int kTogglePermissionCommand = 0;
38 const int kShowSettingsCommand = 1;
39
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 {
48  public:
49   ControlButton(views::ButtonListener* listener);
50   virtual ~ControlButton();
51
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;
57
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);
63
64   void SetNormalImage(int resource_id);
65   void SetHoveredImage(int resource_id);
66   void SetPressedImage(int resource_id);
67
68  protected:
69   gfx::Point ComputePaddedImagePaintPosition(const gfx::ImageSkia& image);
70
71  private:
72   gfx::Insets padding_;
73
74   DISALLOW_COPY_AND_ASSIGN(ControlButton);
75 };
76
77 ControlButton::ControlButton(views::ButtonListener* listener)
78   : views::ImageButton(listener) {
79   set_focusable(true);
80   set_request_focus_on_press(false);
81 }
82
83 ControlButton::~ControlButton() {
84 }
85
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));
91 }
92
93 void ControlButton::SetNormalImage(int resource_id) {
94   SetImage(views::CustomButton::STATE_NORMAL,
95            ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
96                resource_id));
97 }
98
99 void ControlButton::SetHoveredImage(int resource_id) {
100   SetImage(views::CustomButton::STATE_HOVERED,
101            ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
102                resource_id));
103 }
104
105 void ControlButton::SetPressedImage(int resource_id) {
106   SetImage(views::CustomButton::STATE_PRESSED,
107            ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
108                resource_id));
109 }
110
111 gfx::Size ControlButton::GetPreferredSize() {
112   return gfx::Size(message_center::kControlButtonSize,
113                    message_center::kControlButtonSize);
114 }
115
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());
129   }
130   OnPaintFocusBorder(canvas);
131 }
132
133 void ControlButton::OnFocus() {
134   views::ImageButton::OnFocus();
135   ScrollRectToVisible(GetLocalBounds());
136 }
137
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);
142   }
143 }
144
145 gfx::Point ControlButton::ComputePaddedImagePaintPosition(
146     const gfx::ImageSkia& image) {
147   gfx::Vector2d offset;
148   gfx::Rect bounds = GetContentsBounds();
149   bounds.Inset(padding_);
150
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.
155
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.
160
161   return bounds.origin() + offset;
162 }
163
164 // A dropdown menu for notifications.
165 class MenuModel : public ui::SimpleMenuModel,
166                   public ui::SimpleMenuModel::Delegate {
167  public:
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();
174
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(
180       int command_id,
181       ui::Accelerator* accelerator) OVERRIDE;
182   virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE;
183
184  private:
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_;
189
190   DISALLOW_COPY_AND_ASSIGN(MenuModel);
191 };
192
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),
200       tray_(tray),
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,
207                                        display_source));
208   }
209   // Add settings menu item.
210   AddItem(kShowSettingsCommand,
211           l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_SETTINGS));
212 }
213
214 MenuModel::~MenuModel() {
215 }
216
217 bool MenuModel::IsItemForCommandIdDynamic(int command_id) const {
218   return false;
219 }
220
221 bool MenuModel::IsCommandIdChecked(int command_id) const {
222   return false;
223 }
224
225 bool MenuModel::IsCommandIdEnabled(int command_id) const {
226   return true;
227 }
228
229 bool MenuModel::GetAcceleratorForCommandId(int command_id,
230                                            ui::Accelerator* accelerator) {
231   return false;
232 }
233
234 void MenuModel::ExecuteCommand(int command_id, int event_flags) {
235   switch (command_id) {
236     case kTogglePermissionCommand:
237       message_center_->DisableNotificationsByNotifier(notifier_id_);
238       break;
239     case kShowSettingsCommand:
240       // |tray_| may be NULL in tests.
241       if (tray_)
242         tray_->ShowNotifierSettingsBubble();
243       break;
244     default:
245       NOTREACHED();
246   }
247 }
248
249 }  // namespace
250
251 namespace message_center {
252
253 class MessageViewContextMenuController : public views::ContextMenuController {
254  public:
255   MessageViewContextMenuController(
256       MessageCenter* message_center,
257       MessageCenterTray* tray,
258       const Notification& notification);
259   virtual ~MessageViewContextMenuController();
260
261  protected:
262   // Overridden from views::ContextMenuController:
263   virtual void ShowContextMenuForView(views::View* source,
264                                       const gfx::Point& point,
265                                       ui::MenuSourceType source_type) OVERRIDE;
266
267   MessageCenter* message_center_;  // Weak reference.
268   MessageCenterTray* tray_;  // Weak reference.
269   std::string notification_id_;
270   string16 display_source_;
271   NotifierId notifier_id_;
272 };
273
274 MessageViewContextMenuController::MessageViewContextMenuController(
275     MessageCenter* message_center,
276     MessageCenterTray* tray,
277     const Notification& notification)
278     : message_center_(message_center),
279       tray_(tray),
280       notification_id_(notification.id()),
281       display_source_(notification.display_source()),
282       notifier_id_(notification.notifier_id()) {
283 }
284
285 MessageViewContextMenuController::~MessageViewContextMenuController() {
286 }
287
288 void MessageViewContextMenuController::ShowContextMenuForView(
289     views::View* source,
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)
295     return;
296
297   views::MenuRunner menu_runner(&menu_model);
298
299   ignore_result(menu_runner.RunMenuAt(
300       source->GetWidget()->GetTopLevelWidget(),
301       NULL,
302       gfx::Rect(point, gfx::Size()),
303       views::MenuItemView::TOPRIGHT,
304       source_type,
305       views::MenuRunner::HAS_MNEMONICS));
306 }
307
308 MessageView::MessageView(const Notification& notification,
309                          MessageCenter* message_center,
310                          MessageCenterTray* tray,
311                          bool expanded)
312     : message_center_(message_center),
313       notification_id_(notification.id()),
314       context_menu_controller_(new MessageViewContextMenuController(
315           message_center, tray, notification)),
316       scroller_(NULL),
317       is_expanded_(expanded) {
318   set_focusable(true);
319   set_context_menu_controller(context_menu_controller_.get());
320
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);
331
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);
342 }
343
344 MessageView::MessageView() {
345 }
346
347 MessageView::~MessageView() {
348 }
349
350 // static
351 gfx::Insets MessageView::GetShadowInsets() {
352   return gfx::Insets(kShadowBlur / 2 - kShadowOffset,
353                      kShadowBlur / 2,
354                      kShadowBlur / 2 + kShadowOffset,
355                      kShadowBlur / 2);
356 }
357
358 void MessageView::CreateShadowBorder() {
359   set_border(new views::ShadowBorder(kShadowBlur,
360                                      message_center::kShadowColor,
361                                      kShadowOffset,  // Vertical offset.
362                                      0));            // Horizontal offset.
363 }
364
365 bool MessageView::IsCloseButtonFocused() {
366   views::FocusManager* focus_manager = GetFocusManager();
367   return focus_manager && focus_manager->GetFocusedView() == close_button();
368 }
369
370 void MessageView::RequestFocusOnCloseButton() {
371   close_button_->RequestFocus();
372 }
373
374 void MessageView::GetAccessibleState(ui::AccessibleViewState* state) {
375   state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
376   state->name = accessible_name_;
377 }
378
379 bool MessageView::OnMousePressed(const ui::MouseEvent& event) {
380   if (event.IsOnlyLeftMouseButton()) {
381     message_center_->ClickOnNotification(notification_id_);
382     return true;
383   }
384   return false;
385 }
386
387 bool MessageView::OnKeyPressed(const ui::KeyEvent& event) {
388   if (event.flags() != ui::EF_NONE)
389     return false;
390
391   if (event.key_code() == ui::VKEY_RETURN) {
392     message_center_->ClickOnNotification(notification_id_);
393     return true;
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.
397     return true;
398   }
399
400   return false;
401 }
402
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)
407     return false;
408
409   message_center_->ClickOnNotification(notification_id_);
410   return true;
411 }
412
413 void MessageView::OnGestureEvent(ui::GestureEvent* event) {
414   if (event->type() == ui::ET_GESTURE_TAP) {
415     message_center_->ClickOnNotification(notification_id_);
416     event->SetHandled();
417     return;
418   }
419
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
424
425   if (!event->IsScrollGestureEvent() && !event->IsFlingScrollEvent())
426     return;
427
428   if (scroller_)
429     scroller_->OnGestureEvent(event);
430   event->SetHandled();
431 }
432
433 void MessageView::OnPaintFocusBorder(gfx::Canvas* canvas) {
434   if (HasFocus()) {
435     canvas->DrawRect(gfx::Rect(1, 0, width() - 2, height() - 2),
436                      message_center::kFocusBorderColor);
437   }
438 }
439
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()) {
445     is_expanded_ = true;
446     message_center_->ExpandNotification(notification_id_);
447   }
448 }
449
450 void MessageView::OnSlideOut() {
451   message_center_->RemoveNotification(notification_id_, true);  // By user.
452 }
453
454 }  // namespace message_center