1 // Copyright 2013 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 "chrome/browser/ui/screen_capture_notification_ui.h" 6 7 #include "ash/shell.h" 8 #include "chrome/app/chrome_dll_resource.h" 9 #include "chrome/browser/ui/views/chrome_views_export.h" 10 #include "chrome/grit/generated_resources.h" 11 #include "grit/theme_resources.h" 12 #include "ui/aura/window_event_dispatcher.h" 13 #include "ui/base/hit_test.h" 14 #include "ui/base/l10n/l10n_util.h" 15 #include "ui/base/resource/resource_bundle.h" 16 #include "ui/views/bubble/bubble_border.h" 17 #include "ui/views/bubble/bubble_frame_view.h" 18 #include "ui/views/controls/button/blue_button.h" 19 #include "ui/views/controls/image_view.h" 20 #include "ui/views/controls/link.h" 21 #include "ui/views/controls/link_listener.h" 22 #include "ui/views/view.h" 23 #include "ui/views/widget/widget.h" 24 #include "ui/views/widget/widget_delegate.h" 25 #include "ui/wm/core/shadow_types.h" 26 27 #if defined(OS_WIN) 28 #include "ui/views/win/hwnd_util.h" 29 #endif 30 31 namespace { 32 33 const int kMinimumWidth = 460; 34 const int kMaximumWidth = 1000; 35 const int kHorizontalMargin = 10; 36 const float kWindowAlphaValue = 0.85f; 37 const int kPaddingVertical = 5; 38 const int kPaddingHorizontal = 10; 39 40 namespace { 41 42 // A ClientView that overrides NonClientHitTest() so that the whole window area 43 // acts as a window caption, except a rect specified using set_client_rect(). 44 // ScreenCaptureNotificationUIViews uses this class to make the notification bar 45 // draggable. 46 class NotificationBarClientView : public views::ClientView { 47 public: 48 NotificationBarClientView(views::Widget* widget, views::View* view) 49 : views::ClientView(widget, view) { 50 } 51 virtual ~NotificationBarClientView() {} 52 53 void set_client_rect(const gfx::Rect& rect) { rect_ = rect; } 54 55 // views::ClientView overrides. 56 virtual int NonClientHitTest(const gfx::Point& point) OVERRIDE { 57 if (!bounds().Contains(point)) 58 return HTNOWHERE; 59 // The whole window is HTCAPTION, except the |rect_|. 60 if (rect_.Contains(gfx::PointAtOffsetFromOrigin(point - bounds().origin()))) 61 return HTCLIENT; 62 63 return HTCAPTION; 64 } 65 66 private: 67 gfx::Rect rect_; 68 69 DISALLOW_COPY_AND_ASSIGN(NotificationBarClientView); 70 }; 71 72 } // namespace 73 74 // ScreenCaptureNotificationUI implementation using Views. 75 class ScreenCaptureNotificationUIViews 76 : public ScreenCaptureNotificationUI, 77 public views::WidgetDelegateView, 78 public views::ButtonListener, 79 public views::LinkListener { 80 public: 81 explicit ScreenCaptureNotificationUIViews(const base::string16& text); 82 virtual ~ScreenCaptureNotificationUIViews(); 83 84 // ScreenCaptureNotificationUI interface. 85 virtual gfx::NativeViewId OnStarted(const base::Closure& stop_callback) 86 OVERRIDE; 87 88 // views::View overrides. 89 virtual gfx::Size GetPreferredSize() const OVERRIDE; 90 virtual void Layout() OVERRIDE; 91 92 // views::WidgetDelegateView overrides. 93 virtual void DeleteDelegate() OVERRIDE; 94 virtual views::View* GetContentsView() OVERRIDE; 95 virtual views::ClientView* CreateClientView(views::Widget* widget) OVERRIDE; 96 virtual views::NonClientFrameView* CreateNonClientFrameView( 97 views::Widget* widget) OVERRIDE; 98 virtual base::string16 GetWindowTitle() const OVERRIDE; 99 virtual bool ShouldShowWindowTitle() const OVERRIDE; 100 virtual bool ShouldShowCloseButton() const OVERRIDE; 101 virtual bool CanActivate() const OVERRIDE; 102 103 // views::ButtonListener interface. 104 virtual void ButtonPressed(views::Button* sender, 105 const ui::Event& event) OVERRIDE; 106 107 // views::LinkListener interface. 108 virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE; 109 110 private: 111 // Helper to call |stop_callback_|. 112 void NotifyStopped(); 113 114 const base::string16 text_; 115 base::Closure stop_callback_; 116 NotificationBarClientView* client_view_; 117 views::ImageView* gripper_; 118 views::Label* label_; 119 views::BlueButton* stop_button_; 120 views::Link* hide_link_; 121 122 DISALLOW_COPY_AND_ASSIGN(ScreenCaptureNotificationUIViews); 123 }; 124 125 ScreenCaptureNotificationUIViews::ScreenCaptureNotificationUIViews( 126 const base::string16& text) 127 : text_(text), 128 client_view_(NULL), 129 gripper_(NULL), 130 label_(NULL), 131 stop_button_(NULL), 132 hide_link_(NULL) { 133 set_owned_by_client(); 134 135 gripper_ = new views::ImageView(); 136 gripper_->SetImage( 137 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 138 IDR_SCREEN_CAPTURE_NOTIFICATION_GRIP)); 139 AddChildView(gripper_); 140 141 label_ = new views::Label(); 142 AddChildView(label_); 143 144 base::string16 stop_text = 145 l10n_util::GetStringUTF16(IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_STOP); 146 stop_button_ = new views::BlueButton(this, stop_text); 147 AddChildView(stop_button_); 148 149 // TODO(jiayl): IDS_PASSWORDS_PAGE_VIEW_HIDE_BUTTON is used for the need to 150 // merge to M34. Change it to a new IDS_ after the merge. 151 hide_link_ = new views::Link( 152 l10n_util::GetStringUTF16(IDS_PASSWORDS_PAGE_VIEW_HIDE_BUTTON)); 153 hide_link_->set_listener(this); 154 hide_link_->SetUnderline(false); 155 AddChildView(hide_link_); 156 } 157 158 ScreenCaptureNotificationUIViews::~ScreenCaptureNotificationUIViews() { 159 stop_callback_.Reset(); 160 delete GetWidget(); 161 } 162 163 gfx::NativeViewId ScreenCaptureNotificationUIViews::OnStarted( 164 const base::Closure& stop_callback) { 165 stop_callback_ = stop_callback; 166 167 label_->SetElideBehavior(gfx::ELIDE_MIDDLE); 168 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 169 label_->SetText(text_); 170 171 views::Widget* widget = new views::Widget; 172 173 views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW); 174 params.delegate = this; 175 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 176 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; 177 params.remove_standard_frame = true; 178 params.keep_on_top = true; 179 180 // TODO(sergeyu): The notification bar must be shown on the monitor that's 181 // being captured. Make sure it's always the case. Currently we always capture 182 // the primary monitor. 183 if (ash::Shell::HasInstance()) 184 params.context = ash::Shell::GetPrimaryRootWindow(); 185 186 widget->set_frame_type(views::Widget::FRAME_TYPE_FORCE_CUSTOM); 187 widget->Init(params); 188 widget->SetAlwaysOnTop(true); 189 190 set_background(views::Background::CreateSolidBackground(GetNativeTheme()-> 191 GetSystemColor(ui::NativeTheme::kColorId_DialogBackground))); 192 193 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 194 // TODO(sergeyu): Move the notification to the display being captured when 195 // per-display screen capture is supported. 196 gfx::Rect work_area = screen->GetPrimaryDisplay().work_area(); 197 198 // Place the bar in the center of the bottom of the display. 199 gfx::Size size = widget->non_client_view()->GetPreferredSize(); 200 gfx::Rect bounds( 201 work_area.x() + work_area.width() / 2 - size.width() / 2, 202 work_area.y() + work_area.height() - size.height(), 203 size.width(), size.height()); 204 widget->SetBounds(bounds); 205 widget->Show(); 206 // This has to be called after Show() to have effect. 207 widget->SetOpacity(0xFF * kWindowAlphaValue); 208 209 #if defined(OS_WIN) 210 return gfx::NativeViewId(views::HWNDForWidget(widget)); 211 #else 212 return 0; 213 #endif 214 } 215 216 gfx::Size ScreenCaptureNotificationUIViews::GetPreferredSize() const { 217 gfx::Size grip_size = gripper_->GetPreferredSize(); 218 gfx::Size label_size = label_->GetPreferredSize(); 219 gfx::Size stop_button_size = stop_button_->GetPreferredSize(); 220 gfx::Size hide_link_size = hide_link_->GetPreferredSize(); 221 int width = kHorizontalMargin * 3 + grip_size.width() + label_size.width() + 222 stop_button_size.width() + hide_link_size.width(); 223 width = std::max(width, kMinimumWidth); 224 width = std::min(width, kMaximumWidth); 225 return gfx::Size(width, std::max(label_size.height(), 226 std::max(hide_link_size.height(), 227 stop_button_size.height()))); 228 } 229 230 void ScreenCaptureNotificationUIViews::Layout() { 231 gfx::Rect grip_rect(gripper_->GetPreferredSize()); 232 grip_rect.set_y((bounds().height() - grip_rect.height()) / 2); 233 gripper_->SetBoundsRect(grip_rect); 234 235 gfx::Rect stop_button_rect(stop_button_->GetPreferredSize()); 236 gfx::Rect hide_link_rect(hide_link_->GetPreferredSize()); 237 238 hide_link_rect.set_x(bounds().width() - hide_link_rect.width()); 239 hide_link_rect.set_y((bounds().height() - hide_link_rect.height()) / 2); 240 hide_link_->SetBoundsRect(hide_link_rect); 241 242 stop_button_rect.set_x( 243 hide_link_rect.x() - kHorizontalMargin - stop_button_rect.width()); 244 stop_button_->SetBoundsRect(stop_button_rect); 245 246 gfx::Rect label_rect; 247 label_rect.set_x(grip_rect.right() + kHorizontalMargin); 248 label_rect.set_width( 249 stop_button_rect.x() - kHorizontalMargin - label_rect.x()); 250 label_rect.set_height(bounds().height()); 251 label_->SetBoundsRect(label_rect); 252 253 client_view_->set_client_rect(gfx::Rect( 254 stop_button_rect.x(), stop_button_rect.y(), 255 stop_button_rect.width() + kHorizontalMargin + hide_link_rect.width(), 256 std::max(stop_button_rect.height(), hide_link_rect.height()))); 257 } 258 259 void ScreenCaptureNotificationUIViews::DeleteDelegate() { 260 NotifyStopped(); 261 } 262 263 views::View* ScreenCaptureNotificationUIViews::GetContentsView() { 264 return this; 265 } 266 267 views::ClientView* ScreenCaptureNotificationUIViews::CreateClientView( 268 views::Widget* widget) { 269 DCHECK(!client_view_); 270 client_view_ = new NotificationBarClientView(widget, this); 271 return client_view_; 272 } 273 274 views::NonClientFrameView* 275 ScreenCaptureNotificationUIViews::CreateNonClientFrameView( 276 views::Widget* widget) { 277 views::BubbleFrameView* frame = new views::BubbleFrameView( 278 gfx::Insets(kPaddingVertical, 279 kPaddingHorizontal, 280 kPaddingVertical, 281 kPaddingHorizontal)); 282 SkColor color = widget->GetNativeTheme()->GetSystemColor( 283 ui::NativeTheme::kColorId_DialogBackground); 284 frame->SetBubbleBorder(scoped_ptr<views::BubbleBorder>( 285 new views::BubbleBorder(views::BubbleBorder::NONE, 286 views::BubbleBorder::SMALL_SHADOW, 287 color))); 288 return frame; 289 } 290 291 base::string16 ScreenCaptureNotificationUIViews::GetWindowTitle() const { 292 return text_; 293 } 294 295 bool ScreenCaptureNotificationUIViews::ShouldShowWindowTitle() const { 296 return false; 297 } 298 299 bool ScreenCaptureNotificationUIViews::ShouldShowCloseButton() const { 300 return false; 301 } 302 303 bool ScreenCaptureNotificationUIViews::CanActivate() const { 304 // When the window is visible, it can be activated so the mouse clicks 305 // can be sent to the window; when the window is minimized, we don't want it 306 // to activate, otherwise it sometimes does not show properly on Windows. 307 return GetWidget() && GetWidget()->IsVisible(); 308 } 309 310 void ScreenCaptureNotificationUIViews::ButtonPressed(views::Button* sender, 311 const ui::Event& event) { 312 NotifyStopped(); 313 } 314 315 void ScreenCaptureNotificationUIViews::LinkClicked(views::Link* source, 316 int event_flags) { 317 GetWidget()->Minimize(); 318 } 319 320 void ScreenCaptureNotificationUIViews::NotifyStopped() { 321 if (!stop_callback_.is_null()) { 322 base::Closure callback = stop_callback_; 323 stop_callback_.Reset(); 324 callback.Run(); 325 } 326 } 327 328 } // namespace 329 330 scoped_ptr<ScreenCaptureNotificationUI> ScreenCaptureNotificationUI::Create( 331 const base::string16& text) { 332 return scoped_ptr<ScreenCaptureNotificationUI>( 333 new ScreenCaptureNotificationUIViews(text)); 334 } 335