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_util.h"
18#include "ui/message_center/views/padded_button.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/painter.h"
24#include "ui/views/shadow_border.h"
25#include "ui/views/widget/widget.h"
26
27namespace {
28
29const int kCloseIconTopPadding = 5;
30const int kCloseIconRightPadding = 5;
31
32const int kShadowOffset = 1;
33const int kShadowBlur = 4;
34
35// Menu constants
36const int kTogglePermissionCommand = 0;
37const int kShowSettingsCommand = 1;
38
39// A dropdown menu for notifications.
40class MenuModel : public ui::SimpleMenuModel,
41                  public ui::SimpleMenuModel::Delegate {
42 public:
43  MenuModel(message_center::MessageViewController* controller,
44            message_center::NotifierId notifier_id,
45            const string16& display_source);
46  virtual ~MenuModel();
47
48  // Overridden from ui::SimpleMenuModel::Delegate:
49  virtual bool IsItemForCommandIdDynamic(int command_id) const OVERRIDE;
50  virtual bool IsCommandIdChecked(int command_id) const OVERRIDE;
51  virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE;
52  virtual bool GetAcceleratorForCommandId(
53      int command_id,
54      ui::Accelerator* accelerator) OVERRIDE;
55  virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE;
56
57 private:
58  message_center::MessageViewController* controller_;
59  message_center::NotifierId notifier_id_;
60  DISALLOW_COPY_AND_ASSIGN(MenuModel);
61};
62
63MenuModel::MenuModel(message_center::MessageViewController* controller,
64                     message_center::NotifierId notifier_id,
65                     const string16& display_source)
66    : ui::SimpleMenuModel(this),
67      controller_(controller),
68      notifier_id_(notifier_id) {
69  // Add 'disable notifications' menu item.
70  if (!display_source.empty()) {
71    AddItem(kTogglePermissionCommand,
72            l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_NOTIFIER_DISABLE,
73                                       display_source));
74  }
75  // Add settings menu item.
76  AddItem(kShowSettingsCommand,
77          l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_SETTINGS));
78}
79
80MenuModel::~MenuModel() {
81}
82
83bool MenuModel::IsItemForCommandIdDynamic(int command_id) const {
84  return false;
85}
86
87bool MenuModel::IsCommandIdChecked(int command_id) const {
88  return false;
89}
90
91bool MenuModel::IsCommandIdEnabled(int command_id) const {
92  return true;
93}
94
95bool MenuModel::GetAcceleratorForCommandId(int command_id,
96                                           ui::Accelerator* accelerator) {
97  return false;
98}
99
100void MenuModel::ExecuteCommand(int command_id, int event_flags) {
101  switch (command_id) {
102    case kTogglePermissionCommand:
103      controller_->DisableNotificationsFromThisSource(notifier_id_);
104      break;
105    case kShowSettingsCommand:
106        controller_->ShowNotifierSettingsBubble();
107      break;
108    default:
109      NOTREACHED();
110  }
111}
112
113}  // namespace
114
115namespace message_center {
116
117class MessageViewContextMenuController : public views::ContextMenuController {
118 public:
119  MessageViewContextMenuController(MessageViewController* controller,
120                                   const NotifierId& notifier_id,
121                                   const string16& display_source);
122  virtual ~MessageViewContextMenuController();
123
124 protected:
125  // Overridden from views::ContextMenuController:
126  virtual void ShowContextMenuForView(views::View* source,
127                                      const gfx::Point& point,
128                                      ui::MenuSourceType source_type) OVERRIDE;
129
130  MessageViewController* controller_;  // Weak, owns us.
131  NotifierId notifier_id_;
132  string16 display_source_;
133};
134
135MessageViewContextMenuController::MessageViewContextMenuController(
136    MessageViewController* controller,
137    const NotifierId& notifier_id,
138    const string16& display_source)
139    : controller_(controller),
140      notifier_id_(notifier_id),
141      display_source_(display_source) {
142}
143
144MessageViewContextMenuController::~MessageViewContextMenuController() {
145}
146
147void MessageViewContextMenuController::ShowContextMenuForView(
148    views::View* source,
149    const gfx::Point& point,
150    ui::MenuSourceType source_type) {
151  MenuModel menu_model(controller_, notifier_id_, display_source_);
152  if (menu_model.GetItemCount() == 0)
153    return;
154
155  views::MenuRunner menu_runner(&menu_model);
156
157  ignore_result(menu_runner.RunMenuAt(
158      source->GetWidget()->GetTopLevelWidget(),
159      NULL,
160      gfx::Rect(point, gfx::Size()),
161      views::MenuItemView::TOPRIGHT,
162      source_type,
163      views::MenuRunner::HAS_MNEMONICS));
164}
165
166MessageView::MessageView(MessageViewController* controller,
167                         const std::string& notification_id,
168                         const NotifierId& notifier_id,
169                         const string16& display_source)
170    : controller_(controller),
171      notification_id_(notification_id),
172      notifier_id_(notifier_id),
173      context_menu_controller_(
174        new MessageViewContextMenuController(controller,
175                                             notifier_id,
176                                             display_source)),
177      scroller_(NULL) {
178  SetFocusable(true);
179  set_context_menu_controller(context_menu_controller_.get());
180
181  PaddedButton *close = new PaddedButton(this);
182  close->SetPadding(-kCloseIconRightPadding, kCloseIconTopPadding);
183  close->SetNormalImage(IDR_NOTIFICATION_CLOSE);
184  close->SetHoveredImage(IDR_NOTIFICATION_CLOSE_HOVER);
185  close->SetPressedImage(IDR_NOTIFICATION_CLOSE_PRESSED);
186  close->set_owned_by_client();
187  close->set_animate_on_state_change(false);
188  close->SetAccessibleName(l10n_util::GetStringUTF16(
189      IDS_MESSAGE_CENTER_CLOSE_NOTIFICATION_BUTTON_ACCESSIBLE_NAME));
190  close_button_.reset(close);
191
192  focus_painter_ = views::Painter::CreateSolidFocusPainter(
193      kFocusBorderColor,
194      gfx::Insets(0, 1, 3, 2)).Pass();
195}
196
197MessageView::~MessageView() {
198}
199
200// static
201gfx::Insets MessageView::GetShadowInsets() {
202  return gfx::Insets(kShadowBlur / 2 - kShadowOffset,
203                     kShadowBlur / 2,
204                     kShadowBlur / 2 + kShadowOffset,
205                     kShadowBlur / 2);
206}
207
208void MessageView::CreateShadowBorder() {
209  set_border(new views::ShadowBorder(kShadowBlur,
210                                     message_center::kShadowColor,
211                                     kShadowOffset,  // Vertical offset.
212                                     0));            // Horizontal offset.
213}
214
215bool MessageView::IsCloseButtonFocused() {
216  views::FocusManager* focus_manager = GetFocusManager();
217  return focus_manager && focus_manager->GetFocusedView() == close_button();
218}
219
220void MessageView::RequestFocusOnCloseButton() {
221  close_button_->RequestFocus();
222}
223
224void MessageView::GetAccessibleState(ui::AccessibleViewState* state) {
225  state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
226  state->name = accessible_name_;
227}
228
229bool MessageView::OnMousePressed(const ui::MouseEvent& event) {
230  if (!event.IsOnlyLeftMouseButton())
231    return false;
232
233  controller_->ClickOnNotification(notification_id_);
234  return true;
235}
236
237bool MessageView::OnKeyPressed(const ui::KeyEvent& event) {
238  if (event.flags() != ui::EF_NONE)
239    return false;
240
241  if (event.key_code() == ui::VKEY_RETURN) {
242    controller_->ClickOnNotification(notification_id_);
243    return true;
244  } else if ((event.key_code() == ui::VKEY_DELETE ||
245              event.key_code() == ui::VKEY_BACK)) {
246    controller_->RemoveNotification(notification_id_, true);  // By user.
247    return true;
248  }
249
250  return false;
251}
252
253bool MessageView::OnKeyReleased(const ui::KeyEvent& event) {
254  // Space key handling is triggerred at key-release timing. See
255  // ui/views/controls/buttons/custom_button.cc for why.
256  if (event.flags() != ui::EF_NONE || event.flags() != ui::VKEY_SPACE)
257    return false;
258
259  controller_->ClickOnNotification(notification_id_);
260  return true;
261}
262
263void MessageView::OnPaint(gfx::Canvas* canvas) {
264  SlideOutView::OnPaint(canvas);
265  views::Painter::PaintFocusPainter(this, canvas, focus_painter_.get());
266}
267
268void MessageView::OnFocus() {
269  SlideOutView::OnFocus();
270  // We paint a focus indicator.
271  SchedulePaint();
272}
273
274void MessageView::OnBlur() {
275  SlideOutView::OnBlur();
276  // We paint a focus indicator.
277  SchedulePaint();
278}
279
280void MessageView::OnGestureEvent(ui::GestureEvent* event) {
281  if (event->type() == ui::ET_GESTURE_TAP) {
282    controller_->ClickOnNotification(notification_id_);
283    event->SetHandled();
284    return;
285  }
286
287  SlideOutView::OnGestureEvent(event);
288  // Do not return here by checking handled(). SlideOutView calls SetHandled()
289  // even though the scroll gesture doesn't make no (or little) effects on the
290  // slide-out behavior. See http://crbug.com/172991
291
292  if (!event->IsScrollGestureEvent() && !event->IsFlingScrollEvent())
293    return;
294
295  if (scroller_)
296    scroller_->OnGestureEvent(event);
297  event->SetHandled();
298}
299
300void MessageView::ButtonPressed(views::Button* sender,
301                                const ui::Event& event) {
302  if (sender == close_button()) {
303    controller_->RemoveNotification(notification_id_, true);  // By user.
304  }
305}
306
307void MessageView::OnSlideOut() {
308  controller_->RemoveNotification(notification_id_, true);  // By user.
309}
310
311}  // namespace message_center
312