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