Separate Simple Backend creation from initialization.
[chromium-blink-merge.git] / ui / message_center / views / message_view.cc
blob07f87b1e60fd21222b97a7a3ee18104351497c1f
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/l10n/l10n_util.h"
10 #include "ui/base/models/simple_menu_model.h"
11 #include "ui/base/resource/resource_bundle.h"
12 #include "ui/compositor/scoped_layer_animation_settings.h"
13 #include "ui/gfx/canvas.h"
14 #include "ui/gfx/shadow_value.h"
15 #include "ui/gfx/skia_util.h"
16 #include "ui/message_center/message_center_constants.h"
17 #include "ui/message_center/message_center_util.h"
18 #include "ui/message_center/notification_change_observer.h"
19 #include "ui/views/controls/button/image_button.h"
20 #include "ui/views/controls/menu/menu_runner.h"
21 #include "ui/views/controls/scroll_view.h"
22 #include "ui/views/widget/widget.h"
24 namespace {
26 const int kCloseIconTopPadding = 5;
27 const int kCloseIconRightPadding = 5;
28 const int kExpandIconBottomPadding = 8;
29 const int kExpandIconRightPadding = 11;
31 const int kShadowOffset = 1;
32 const int kShadowBlur = 4;
34 const SkColor kShadowColor = SkColorSetARGB(0.3 * 255, 0, 0, 0);
35 const SkColor kTransparentColor = SkColorSetARGB(0, 0, 0, 0);
37 // Menu constants
38 const int kTogglePermissionCommand = 0;
39 const int kToggleExtensionCommand = 1;
40 const int kShowSettingsCommand = 2;
42 // ControlButtons are ImageButtons whose image can be padded within the button.
43 // This allows the creation of buttons like the notification close and expand
44 // buttons whose clickable areas extends beyond their image areas
45 // (<http://crbug.com/168822>) without the need to create and maintain
46 // corresponding resource images with alpha padding. In the future, this class
47 // will also allow for buttons whose touch areas extend beyond their clickable
48 // area (<http://crbug.com/168856>).
49 class ControlButton : public views::ImageButton {
50 public:
51 ControlButton(views::ButtonListener* listener);
52 virtual ~ControlButton();
54 // Overridden from views::ImageButton:
55 virtual gfx::Size GetPreferredSize() OVERRIDE;
56 virtual void OnPaint(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);
68 protected:
69 gfx::Point ComputePaddedImagePaintPosition(const gfx::ImageSkia& image);
71 private:
72 gfx::Insets padding_;
74 DISALLOW_COPY_AND_ASSIGN(ControlButton);
77 ControlButton::ControlButton(views::ButtonListener* listener)
78 : views::ImageButton(listener) {
79 set_focusable(true);
82 ControlButton::~ControlButton() {
85 void ControlButton::SetPadding(int horizontal_padding, int vertical_padding) {
86 padding_.Set(std::max(vertical_padding, 0),
87 std::max(horizontal_padding, 0),
88 std::max(-vertical_padding, 0),
89 std::max(-horizontal_padding, 0));
92 void ControlButton::SetNormalImage(int resource_id) {
93 SetImage(views::CustomButton::STATE_NORMAL,
94 ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
95 resource_id));
98 void ControlButton::SetHoveredImage(int resource_id) {
99 SetImage(views::CustomButton::STATE_HOVERED,
100 ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
101 resource_id));
104 void ControlButton::SetPressedImage(int resource_id) {
105 SetImage(views::CustomButton::STATE_PRESSED,
106 ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
107 resource_id));
110 gfx::Size ControlButton::GetPreferredSize() {
111 return gfx::Size(message_center::kControlButtonSize,
112 message_center::kControlButtonSize);
115 void ControlButton::OnPaint(gfx::Canvas* canvas) {
116 // This is the same implementation as ImageButton::OnPaint except
117 // that it calls ComputePaddedImagePaintPosition() instead of
118 // ComputeImagePaintPosition(), in effect overriding that private method.
119 View::OnPaint(canvas);
120 gfx::ImageSkia image = GetImageToPaint();
121 if (!image.isNull()) {
122 gfx::Point position = ComputePaddedImagePaintPosition(image);
123 if (!background_image_.isNull())
124 canvas->DrawImageInt(background_image_, position.x(), position.y());
125 canvas->DrawImageInt(image, position.x(), position.y());
126 if (!overlay_image_.isNull())
127 canvas->DrawImageInt(overlay_image_, position.x(), position.y());
129 OnPaintFocusBorder(canvas);
132 gfx::Point ControlButton::ComputePaddedImagePaintPosition(
133 const gfx::ImageSkia& image) {
134 gfx::Vector2d offset;
135 gfx::Rect bounds = GetContentsBounds();
136 bounds.Inset(padding_);
138 if (padding_.left() == 0 && padding_.right() == 0)
139 offset.set_x((bounds.width() - image.width()) / 2); // Center align.
140 else if (padding_.right() > 0)
141 offset.set_x(bounds.width() - image.width()); // Right align.
143 if (padding_.top() == 0 && padding_.bottom() == 0)
144 offset.set_y((bounds.height() - image.height()) / 2); // Middle align.
145 else if (padding_.bottom() > 0)
146 offset.set_y(bounds.height() - image.height()); // Bottom align.
148 return bounds.origin() + offset;
151 // A border to provide the shadow for each card.
152 // Current shadow should look like css box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3)
153 class ShadowBorder : public views::Border {
154 public:
155 ShadowBorder() : views::Border() {}
156 virtual ~ShadowBorder() {}
158 protected:
159 // Overridden from views::Border:
160 virtual void Paint(const views::View& view, gfx::Canvas* canvas) OVERRIDE;
161 virtual gfx::Insets GetInsets() const OVERRIDE;
163 DISALLOW_COPY_AND_ASSIGN(ShadowBorder);
166 void ShadowBorder::Paint(const views::View& view, gfx::Canvas* canvas) {
167 SkPaint paint;
168 std::vector<gfx::ShadowValue> shadows;
169 shadows.push_back(gfx::ShadowValue(gfx::Point(), kShadowBlur, kShadowColor));
170 skia::RefPtr<SkDrawLooper> looper = gfx::CreateShadowDrawLooper(shadows);
171 paint.setLooper(looper.get());
172 paint.setColor(kTransparentColor);
173 paint.setStrokeJoin(SkPaint::kRound_Join);
174 gfx::Rect bounds(view.size());
175 bounds.Inset(gfx::Insets(kShadowBlur / 2, kShadowBlur / 2,
176 kShadowBlur / 2, kShadowBlur / 2));
177 canvas->DrawRect(bounds, paint);
180 gfx::Insets ShadowBorder::GetInsets() const {
181 return message_center::MessageView::GetShadowInsets();
184 // A dropdown menu for notifications.
185 class MenuModel : public ui::SimpleMenuModel,
186 public ui::SimpleMenuModel::Delegate {
187 public:
188 MenuModel(message_center::NotificationChangeObserver* observer,
189 const std::string& notification_id,
190 const string16& display_source,
191 const std::string& extension_id);
192 virtual ~MenuModel();
194 // Overridden from ui::SimpleMenuModel::Delegate:
195 virtual bool IsItemForCommandIdDynamic(int command_id) const OVERRIDE;
196 virtual bool IsCommandIdChecked(int command_id) const OVERRIDE;
197 virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE;
198 virtual bool GetAcceleratorForCommandId(
199 int command_id,
200 ui::Accelerator* accelerator) OVERRIDE;
201 virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE;
203 private:
204 message_center::NotificationChangeObserver* observer_; // Weak reference.
205 std::string notification_id_;
207 DISALLOW_COPY_AND_ASSIGN(MenuModel);
210 MenuModel::MenuModel(message_center::NotificationChangeObserver* observer,
211 const std::string& notification_id,
212 const string16& display_source,
213 const std::string& extension_id)
214 : ALLOW_THIS_IN_INITIALIZER_LIST(ui::SimpleMenuModel(this)),
215 observer_(observer),
216 notification_id_(notification_id) {
217 // Add 'disable notifications' menu item.
218 if (!extension_id.empty() && !display_source.empty()) {
219 AddItem(kToggleExtensionCommand,
220 l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_EXTENSIONS_DISABLE,
221 display_source));
222 } else if (!display_source.empty()) {
223 AddItem(kTogglePermissionCommand,
224 l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_SITE_DISABLE,
225 display_source));
227 // Add settings menu item.
228 if (message_center::IsRichNotificationEnabled() || !display_source.empty()) {
229 AddItem(kShowSettingsCommand,
230 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_SETTINGS));
234 MenuModel::~MenuModel() {
237 bool MenuModel::IsItemForCommandIdDynamic(int command_id) const {
238 return false;
241 bool MenuModel::IsCommandIdChecked(int command_id) const {
242 return false;
245 bool MenuModel::IsCommandIdEnabled(int command_id) const {
246 return true;
249 bool MenuModel::GetAcceleratorForCommandId(int command_id,
250 ui::Accelerator* accelerator) {
251 return false;
254 void MenuModel::ExecuteCommand(int command_id, int event_flags) {
255 switch (command_id) {
256 case kToggleExtensionCommand:
257 observer_->OnDisableNotificationsByExtension(notification_id_);
258 break;
259 case kTogglePermissionCommand:
260 observer_->OnDisableNotificationsByUrl(notification_id_);
261 break;
262 case kShowSettingsCommand:
263 if (message_center::IsRichNotificationEnabled())
264 observer_->OnShowNotificationSettingsDialog(NULL);
265 else
266 observer_->OnShowNotificationSettings(notification_id_);
267 break;
268 default:
269 NOTREACHED();
273 } // namespace
275 namespace message_center {
277 MessageView::MessageView(const Notification& notification,
278 NotificationChangeObserver* observer,
279 bool expanded)
280 : observer_(observer),
281 notification_id_(notification.id()),
282 display_source_(notification.display_source()),
283 extension_id_(notification.extension_id()),
284 scroller_(NULL),
285 is_expanded_(expanded) {
286 ControlButton *close = new ControlButton(this);
287 close->SetPadding(-kCloseIconRightPadding, kCloseIconTopPadding);
288 close->SetNormalImage(IDR_NOTIFICATION_CLOSE);
289 close->SetHoveredImage(IDR_NOTIFICATION_CLOSE_HOVER);
290 close->SetPressedImage(IDR_NOTIFICATION_CLOSE_PRESSED);
291 close->set_owned_by_client();
292 close_button_.reset(close);
294 ControlButton *expand = new ControlButton(this);
295 expand->SetPadding(-kExpandIconRightPadding, -kExpandIconBottomPadding);
296 expand->SetNormalImage(IDR_NOTIFICATIONS_EXPAND);
297 expand->SetHoveredImage(IDR_NOTIFICATIONS_EXPAND_HOVER);
298 expand->SetPressedImage(IDR_NOTIFICATIONS_EXPAND_PRESSED);
299 expand->set_owned_by_client();
300 expand_button_.reset(expand);
302 if (IsRichNotificationEnabled())
303 set_border(new ShadowBorder());
306 MessageView::MessageView() {
309 MessageView::~MessageView() {
312 // static
313 gfx::Insets MessageView::GetShadowInsets() {
314 return gfx::Insets(kShadowBlur / 2 - kShadowOffset,
315 kShadowBlur / 2,
316 kShadowBlur / 2 + kShadowOffset,
317 kShadowBlur / 2);
320 bool MessageView::OnMousePressed(const ui::MouseEvent& event) {
321 if (event.flags() & ui::EF_RIGHT_MOUSE_BUTTON) {
322 ShowMenu(event.location());
323 return true;
325 observer_->OnClicked(notification_id_);
326 return true;
329 void MessageView::OnGestureEvent(ui::GestureEvent* event) {
330 if (event->type() == ui::ET_GESTURE_TAP) {
331 observer_->OnClicked(notification_id_);
332 event->SetHandled();
333 return;
336 if (event->type() == ui::ET_GESTURE_LONG_PRESS) {
337 ShowMenu(event->location());
338 event->SetHandled();
339 return;
342 SlideOutView::OnGestureEvent(event);
343 // Do not return here by checking handled(). SlideOutView calls SetHandled()
344 // even though the scroll gesture doesn't make no (or little) effects on the
345 // slide-out behavior. See http://crbug.com/172991
347 if (!event->IsScrollGestureEvent())
348 return;
350 if (scroller_)
351 scroller_->OnGestureEvent(event);
352 event->SetHandled();
355 void MessageView::ButtonPressed(views::Button* sender,
356 const ui::Event& event) {
357 if (sender == close_button()) {
358 observer_->OnRemoveNotification(notification_id_, true); // By user.
359 } else if (sender == expand_button()) {
360 is_expanded_ = true;
361 observer_->OnExpanded(notification_id_);
365 void MessageView::ShowMenu(gfx::Point screen_location) {
366 MenuModel menu_model(observer_, notification_id_,
367 display_source_, extension_id_);
368 if (menu_model.GetItemCount() == 0)
369 return;
371 views::MenuRunner menu_runner(&menu_model);
373 views::View::ConvertPointToScreen(this, &screen_location);
374 ignore_result(menu_runner.RunMenuAt(
375 GetWidget()->GetTopLevelWidget(),
376 NULL,
377 gfx::Rect(screen_location, gfx::Size()),
378 views::MenuItemView::TOPRIGHT,
379 views::MenuRunner::HAS_MNEMONICS));
382 void MessageView::OnSlideOut() {
383 observer_->OnRemoveNotification(notification_id_, true); // By user.
386 } // namespace message_center