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