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/accessibility/ax_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/views/padded_button.h"
18#include "ui/views/background.h"
19#include "ui/views/controls/button/image_button.h"
20#include "ui/views/controls/image_view.h"
21#include "ui/views/controls/scroll_view.h"
22#include "ui/views/focus/focus_manager.h"
23#include "ui/views/painter.h"
24#include "ui/views/shadow_border.h"
25
26namespace {
27
28const int kCloseIconTopPadding = 5;
29const int kCloseIconRightPadding = 5;
30
31const int kShadowOffset = 1;
32const int kShadowBlur = 4;
33
34}  // namespace
35
36namespace message_center {
37
38MessageView::MessageView(MessageViewController* controller,
39                         const std::string& notification_id,
40                         const NotifierId& notifier_id,
41                         const gfx::ImageSkia& small_image,
42                         const base::string16& display_source)
43    : controller_(controller),
44      notification_id_(notification_id),
45      notifier_id_(notifier_id),
46      background_view_(NULL),
47      scroller_(NULL),
48      display_source_(display_source) {
49  SetFocusable(true);
50
51  // Create the opaque background that's above the view's shadow.
52  background_view_ = new views::View();
53  background_view_->set_background(
54      views::Background::CreateSolidBackground(kNotificationBackgroundColor));
55  AddChildView(background_view_);
56
57  views::ImageView* small_image_view = new views::ImageView();
58  small_image_view->SetImage(small_image);
59  small_image_view->SetImageSize(gfx::Size(kSmallImageSize, kSmallImageSize));
60  // The small image view should be added to view hierarchy by the derived
61  // class. This ensures that it is on top of other views.
62  small_image_view->set_owned_by_client();
63  small_image_view_.reset(small_image_view);
64
65  PaddedButton *close = new PaddedButton(this);
66  close->SetPadding(-kCloseIconRightPadding, kCloseIconTopPadding);
67  close->SetNormalImage(IDR_NOTIFICATION_CLOSE);
68  close->SetHoveredImage(IDR_NOTIFICATION_CLOSE_HOVER);
69  close->SetPressedImage(IDR_NOTIFICATION_CLOSE_PRESSED);
70  close->set_animate_on_state_change(false);
71  close->SetAccessibleName(l10n_util::GetStringUTF16(
72      IDS_MESSAGE_CENTER_CLOSE_NOTIFICATION_BUTTON_ACCESSIBLE_NAME));
73  // The close button should be added to view hierarchy by the derived class.
74  // This ensures that it is on top of other views.
75  close->set_owned_by_client();
76  close_button_.reset(close);
77
78  focus_painter_ = views::Painter::CreateSolidFocusPainter(
79      kFocusBorderColor,
80      gfx::Insets(0, 1, 3, 2)).Pass();
81}
82
83MessageView::~MessageView() {
84}
85
86void MessageView::UpdateWithNotification(const Notification& notification) {
87  small_image_view_->SetImage(notification.small_image().AsImageSkia());
88  display_source_ = notification.display_source();
89}
90
91// static
92gfx::Insets MessageView::GetShadowInsets() {
93  return gfx::Insets(kShadowBlur / 2 - kShadowOffset,
94                     kShadowBlur / 2,
95                     kShadowBlur / 2 + kShadowOffset,
96                     kShadowBlur / 2);
97}
98
99void MessageView::CreateShadowBorder() {
100  SetBorder(scoped_ptr<views::Border>(
101      new views::ShadowBorder(kShadowBlur,
102                              message_center::kShadowColor,
103                              kShadowOffset,  // Vertical offset.
104                              0)));           // Horizontal offset.
105}
106
107bool MessageView::IsCloseButtonFocused() {
108  views::FocusManager* focus_manager = GetFocusManager();
109  return focus_manager && focus_manager->GetFocusedView() == close_button();
110}
111
112void MessageView::RequestFocusOnCloseButton() {
113  close_button_->RequestFocus();
114}
115
116void MessageView::GetAccessibleState(ui::AXViewState* state) {
117  state->role = ui::AX_ROLE_BUTTON;
118  state->name = accessible_name_;
119}
120
121bool MessageView::OnMousePressed(const ui::MouseEvent& event) {
122  if (!event.IsOnlyLeftMouseButton())
123    return false;
124
125  controller_->ClickOnNotification(notification_id_);
126  return true;
127}
128
129bool MessageView::OnKeyPressed(const ui::KeyEvent& event) {
130  if (event.flags() != ui::EF_NONE)
131    return false;
132
133  if (event.key_code() == ui::VKEY_RETURN) {
134    controller_->ClickOnNotification(notification_id_);
135    return true;
136  } else if ((event.key_code() == ui::VKEY_DELETE ||
137              event.key_code() == ui::VKEY_BACK)) {
138    controller_->RemoveNotification(notification_id_, true);  // By user.
139    return true;
140  }
141
142  return false;
143}
144
145bool MessageView::OnKeyReleased(const ui::KeyEvent& event) {
146  // Space key handling is triggerred at key-release timing. See
147  // ui/views/controls/buttons/custom_button.cc for why.
148  if (event.flags() != ui::EF_NONE || event.flags() != ui::VKEY_SPACE)
149    return false;
150
151  controller_->ClickOnNotification(notification_id_);
152  return true;
153}
154
155void MessageView::OnPaint(gfx::Canvas* canvas) {
156  DCHECK_EQ(this, close_button_->parent());
157  DCHECK_EQ(this, small_image_view_->parent());
158  SlideOutView::OnPaint(canvas);
159  views::Painter::PaintFocusPainter(this, canvas, focus_painter_.get());
160}
161
162void MessageView::OnFocus() {
163  SlideOutView::OnFocus();
164  // We paint a focus indicator.
165  SchedulePaint();
166}
167
168void MessageView::OnBlur() {
169  SlideOutView::OnBlur();
170  // We paint a focus indicator.
171  SchedulePaint();
172}
173
174void MessageView::Layout() {
175  gfx::Rect content_bounds = GetContentsBounds();
176
177  // Background.
178  background_view_->SetBoundsRect(content_bounds);
179
180  // Close button.
181  gfx::Size close_size(close_button_->GetPreferredSize());
182  gfx::Rect close_rect(content_bounds.right() - close_size.width(),
183                       content_bounds.y(),
184                       close_size.width(),
185                       close_size.height());
186  close_button_->SetBoundsRect(close_rect);
187
188  gfx::Size small_image_size(small_image_view_->GetPreferredSize());
189  gfx::Rect small_image_rect(small_image_size);
190  small_image_rect.set_origin(gfx::Point(
191      content_bounds.right() - small_image_size.width() - kSmallImagePadding,
192      content_bounds.bottom() - small_image_size.height() -
193          kSmallImagePadding));
194  small_image_view_->SetBoundsRect(small_image_rect);
195}
196
197void MessageView::OnGestureEvent(ui::GestureEvent* event) {
198  if (event->type() == ui::ET_GESTURE_TAP) {
199    controller_->ClickOnNotification(notification_id_);
200    event->SetHandled();
201    return;
202  }
203
204  SlideOutView::OnGestureEvent(event);
205  // Do not return here by checking handled(). SlideOutView calls SetHandled()
206  // even though the scroll gesture doesn't make no (or little) effects on the
207  // slide-out behavior. See http://crbug.com/172991
208
209  if (!event->IsScrollGestureEvent() && !event->IsFlingScrollEvent())
210    return;
211
212  if (scroller_)
213    scroller_->OnGestureEvent(event);
214  event->SetHandled();
215}
216
217void MessageView::ButtonPressed(views::Button* sender,
218                                const ui::Event& event) {
219  if (sender == close_button()) {
220    controller_->RemoveNotification(notification_id_, true);  // By user.
221  }
222}
223
224void MessageView::OnSlideOut() {
225  controller_->RemoveNotification(notification_id_, true);  // By user.
226}
227
228}  // namespace message_center
229