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.
5 #include "chrome/browser/ui/views/desktop_media_picker_views.h"
7 #include "base/callback.h"
8 #include "base/command_line.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/media/desktop_media_list.h"
11 #include "chrome/browser/ui/ash/ash_util.h"
12 #include "chrome/common/chrome_switches.h"
13 #include "chrome/grit/generated_resources.h"
14 #include "components/constrained_window/constrained_window_views.h"
15 #include "components/web_modal/web_contents_modal_dialog_manager.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "content/public/browser/web_contents_delegate.h"
18 #include "ui/aura/window_tree_host.h"
19 #include "ui/base/l10n/l10n_util.h"
20 #include "ui/events/event_constants.h"
21 #include "ui/events/keycodes/keyboard_codes.h"
22 #include "ui/gfx/canvas.h"
23 #include "ui/native_theme/native_theme.h"
24 #include "ui/views/background.h"
25 #include "ui/views/bubble/bubble_frame_view.h"
26 #include "ui/views/controls/image_view.h"
27 #include "ui/views/controls/label.h"
28 #include "ui/views/controls/scroll_view.h"
29 #include "ui/views/layout/layout_constants.h"
30 #include "ui/views/widget/widget.h"
31 #include "ui/views/window/dialog_client_view.h"
32 #include "ui/wm/core/shadow_types.h"
34 using content::DesktopMediaID
;
38 const int kThumbnailWidth
= 160;
39 const int kThumbnailHeight
= 100;
40 const int kThumbnailMargin
= 10;
41 const int kLabelHeight
= 40;
42 const int kListItemWidth
= kThumbnailMargin
* 2 + kThumbnailWidth
;
43 const int kListItemHeight
=
44 kThumbnailMargin
* 2 + kThumbnailHeight
+ kLabelHeight
;
45 const int kListColumns
= 3;
46 const int kTotalListWidth
= kListColumns
* kListItemWidth
;
48 const int kDesktopMediaSourceViewGroupId
= 1;
50 const char kDesktopMediaSourceViewClassName
[] =
51 "DesktopMediaPicker_DesktopMediaSourceView";
53 DesktopMediaID::Id
AcceleratedWidgetToDesktopMediaId(
54 gfx::AcceleratedWidget accelerated_widget
) {
56 return reinterpret_cast<DesktopMediaID::Id
>(accelerated_widget
);
58 return static_cast<DesktopMediaID::Id
>(accelerated_widget
);
62 int GetMediaListViewHeightForRows(size_t rows
) {
63 return kListItemHeight
* rows
;
68 DesktopMediaSourceView::DesktopMediaSourceView(
69 DesktopMediaListView
* parent
,
70 DesktopMediaID source_id
)
72 source_id_(source_id
),
73 image_view_(new views::ImageView()),
74 label_(new views::Label()),
76 AddChildView(image_view_
);
81 DesktopMediaSourceView::~DesktopMediaSourceView() {}
83 void DesktopMediaSourceView::SetName(const base::string16
& name
) {
84 label_
->SetText(name
);
87 void DesktopMediaSourceView::SetThumbnail(const gfx::ImageSkia
& thumbnail
) {
88 image_view_
->SetImage(thumbnail
);
91 void DesktopMediaSourceView::SetSelected(bool selected
) {
92 if (selected
== selected_
)
97 // Unselect all other sources.
99 parent()->GetViewsInGroup(GetGroup(), &neighbours
);
100 for (Views::iterator
i(neighbours
.begin()); i
!= neighbours
.end(); ++i
) {
102 DCHECK_EQ((*i
)->GetClassName(), kDesktopMediaSourceViewClassName
);
103 DesktopMediaSourceView
* source_view
=
104 static_cast<DesktopMediaSourceView
*>(*i
);
105 source_view
->SetSelected(false);
109 const SkColor bg_color
= GetNativeTheme()->GetSystemColor(
110 ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor
);
111 set_background(views::Background::CreateSolidBackground(bg_color
));
113 parent_
->OnSelectionChanged();
115 set_background(NULL
);
121 const char* DesktopMediaSourceView::GetClassName() const {
122 return kDesktopMediaSourceViewClassName
;
125 void DesktopMediaSourceView::Layout() {
126 image_view_
->SetBounds(kThumbnailMargin
, kThumbnailMargin
,
127 kThumbnailWidth
, kThumbnailHeight
);
128 label_
->SetBounds(kThumbnailMargin
, kThumbnailHeight
+ kThumbnailMargin
,
129 kThumbnailWidth
, kLabelHeight
);
132 views::View
* DesktopMediaSourceView::GetSelectedViewForGroup(int group
) {
134 parent()->GetViewsInGroup(group
, &neighbours
);
135 if (neighbours
.empty())
138 for (Views::iterator
i(neighbours
.begin()); i
!= neighbours
.end(); ++i
) {
139 DCHECK_EQ((*i
)->GetClassName(), kDesktopMediaSourceViewClassName
);
140 DesktopMediaSourceView
* source_view
=
141 static_cast<DesktopMediaSourceView
*>(*i
);
142 if (source_view
->selected_
)
148 bool DesktopMediaSourceView::IsGroupFocusTraversable() const {
152 void DesktopMediaSourceView::OnPaint(gfx::Canvas
* canvas
) {
153 View::OnPaint(canvas
);
155 gfx::Rect
bounds(GetLocalBounds());
156 bounds
.Inset(kThumbnailMargin
/ 2, kThumbnailMargin
/ 2);
157 canvas
->DrawFocusRect(bounds
);
161 void DesktopMediaSourceView::OnFocus() {
164 ScrollRectToVisible(gfx::Rect(size()));
165 // We paint differently when focused.
169 void DesktopMediaSourceView::OnBlur() {
171 // We paint differently when focused.
175 bool DesktopMediaSourceView::OnMousePressed(const ui::MouseEvent
& event
) {
176 if (event
.GetClickCount() == 1) {
178 } else if (event
.GetClickCount() == 2) {
180 parent_
->OnDoubleClick();
185 void DesktopMediaSourceView::OnGestureEvent(ui::GestureEvent
* event
) {
186 if (event
->type() == ui::ET_GESTURE_TAP
&&
187 event
->details().tap_count() == 2) {
189 parent_
->OnDoubleClick();
194 // Detect tap gesture using ET_GESTURE_TAP_DOWN so the view also gets focused
195 // on the long tap (when the tap gesture starts).
196 if (event
->type() == ui::ET_GESTURE_TAP_DOWN
) {
202 DesktopMediaListView::DesktopMediaListView(
203 DesktopMediaPickerDialogView
* parent
,
204 scoped_ptr
<DesktopMediaList
> media_list
)
206 media_list_(media_list
.Pass()),
207 weak_factory_(this) {
208 media_list_
->SetThumbnailSize(gfx::Size(kThumbnailWidth
, kThumbnailHeight
));
211 DesktopMediaListView::~DesktopMediaListView() {}
213 void DesktopMediaListView::StartUpdating(DesktopMediaID::Id dialog_window_id
) {
214 media_list_
->SetViewDialogWindowId(dialog_window_id
);
215 media_list_
->StartUpdating(this);
218 void DesktopMediaListView::OnSelectionChanged() {
219 parent_
->OnSelectionChanged();
222 void DesktopMediaListView::OnDoubleClick() {
223 parent_
->OnDoubleClick();
226 DesktopMediaSourceView
* DesktopMediaListView::GetSelection() {
227 for (int i
= 0; i
< child_count(); ++i
) {
228 DesktopMediaSourceView
* source_view
=
229 static_cast<DesktopMediaSourceView
*>(child_at(i
));
230 DCHECK_EQ(source_view
->GetClassName(), kDesktopMediaSourceViewClassName
);
231 if (source_view
->is_selected())
237 gfx::Size
DesktopMediaListView::GetPreferredSize() const {
238 int total_rows
= (child_count() + kListColumns
- 1) / kListColumns
;
239 return gfx::Size(kTotalListWidth
, GetMediaListViewHeightForRows(total_rows
));
242 void DesktopMediaListView::Layout() {
246 for (int i
= 0; i
< child_count(); ++i
) {
247 if (x
+ kListItemWidth
> kTotalListWidth
) {
249 y
+= kListItemHeight
;
252 View
* source_view
= child_at(i
);
253 source_view
->SetBounds(x
, y
, kListItemWidth
, kListItemHeight
);
258 y
+= kListItemHeight
;
259 SetSize(gfx::Size(kTotalListWidth
, y
));
262 bool DesktopMediaListView::OnKeyPressed(const ui::KeyEvent
& event
) {
263 int position_increment
= 0;
264 switch (event
.key_code()) {
266 position_increment
= -kListColumns
;
269 position_increment
= kListColumns
;
272 position_increment
= -1;
275 position_increment
= 1;
281 if (position_increment
!= 0) {
282 DesktopMediaSourceView
* selected
= GetSelection();
283 DesktopMediaSourceView
* new_selected
= NULL
;
286 int index
= GetIndexOf(selected
);
287 int new_index
= index
+ position_increment
;
288 if (new_index
>= child_count())
289 new_index
= child_count() - 1;
290 else if (new_index
< 0)
292 if (index
!= new_index
) {
294 static_cast<DesktopMediaSourceView
*>(child_at(new_index
));
296 } else if (has_children()) {
297 new_selected
= static_cast<DesktopMediaSourceView
*>(child_at(0));
301 GetFocusManager()->SetFocusedView(new_selected
);
310 void DesktopMediaListView::OnSourceAdded(int index
) {
311 const DesktopMediaList::Source
& source
= media_list_
->GetSource(index
);
312 DesktopMediaSourceView
* source_view
=
313 new DesktopMediaSourceView(this, source
.id
);
314 source_view
->SetName(source
.name
);
315 source_view
->SetGroup(kDesktopMediaSourceViewGroupId
);
316 AddChildViewAt(source_view
, index
);
318 PreferredSizeChanged();
320 if (child_count() % kListColumns
== 1)
321 parent_
->OnMediaListRowsChanged();
323 std::string autoselect_source
=
324 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
325 switches::kAutoSelectDesktopCaptureSource
);
326 if (!autoselect_source
.empty() &&
327 base::ASCIIToUTF16(autoselect_source
) == source
.name
) {
328 // Select, then accept and close the dialog when we're done adding sources.
329 source_view
->OnFocus();
330 content::BrowserThread::PostTask(
331 content::BrowserThread::UI
, FROM_HERE
,
332 base::Bind(&DesktopMediaListView::AcceptSelection
,
333 weak_factory_
.GetWeakPtr()));
337 void DesktopMediaListView::OnSourceRemoved(int index
) {
338 DesktopMediaSourceView
* view
=
339 static_cast<DesktopMediaSourceView
*>(child_at(index
));
341 DCHECK_EQ(view
->GetClassName(), kDesktopMediaSourceViewClassName
);
342 bool was_selected
= view
->is_selected();
343 RemoveChildView(view
);
347 OnSelectionChanged();
349 PreferredSizeChanged();
351 if (child_count() % kListColumns
== 0)
352 parent_
->OnMediaListRowsChanged();
355 void DesktopMediaListView::OnSourceMoved(int old_index
, int new_index
) {
356 DesktopMediaSourceView
* view
=
357 static_cast<DesktopMediaSourceView
*>(child_at(old_index
));
358 ReorderChildView(view
, new_index
);
359 PreferredSizeChanged();
362 void DesktopMediaListView::OnSourceNameChanged(int index
) {
363 const DesktopMediaList::Source
& source
= media_list_
->GetSource(index
);
364 DesktopMediaSourceView
* source_view
=
365 static_cast<DesktopMediaSourceView
*>(child_at(index
));
366 source_view
->SetName(source
.name
);
369 void DesktopMediaListView::OnSourceThumbnailChanged(int index
) {
370 const DesktopMediaList::Source
& source
= media_list_
->GetSource(index
);
371 DesktopMediaSourceView
* source_view
=
372 static_cast<DesktopMediaSourceView
*>(child_at(index
));
373 source_view
->SetThumbnail(source
.thumbnail
);
376 void DesktopMediaListView::AcceptSelection() {
377 OnSelectionChanged();
381 DesktopMediaPickerDialogView::DesktopMediaPickerDialogView(
382 content::WebContents
* parent_web_contents
,
383 gfx::NativeWindow context
,
384 DesktopMediaPickerViews
* parent
,
385 const base::string16
& app_name
,
386 const base::string16
& target_name
,
387 scoped_ptr
<DesktopMediaList
> media_list
)
390 label_(new views::Label()),
391 scroll_view_(views::ScrollView::CreateScrollViewWithBorder()),
392 list_view_(new DesktopMediaListView(this, media_list
.Pass())) {
393 if (app_name
== target_name
) {
395 l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TEXT
, app_name
));
397 label_
->SetText(l10n_util::GetStringFUTF16(
398 IDS_DESKTOP_MEDIA_PICKER_TEXT_DELEGATED
, app_name
, target_name
));
400 label_
->SetMultiLine(true);
401 label_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
402 AddChildView(label_
);
404 scroll_view_
->SetContents(list_view_
);
405 scroll_view_
->ClipHeightTo(
406 GetMediaListViewHeightForRows(1), GetMediaListViewHeightForRows(2));
407 AddChildView(scroll_view_
);
409 // If |parent_web_contents| is set and it's not a background page then the
410 // picker will be shown modal to the web contents. Otherwise the picker is
411 // shown in a separate window.
412 views::Widget
* widget
= NULL
;
414 parent_web_contents
&&
415 !parent_web_contents
->GetDelegate()->IsNeverVisible(parent_web_contents
);
417 widget
= constrained_window::ShowWebModalDialogViews(this,
418 parent_web_contents
);
420 widget
= DialogDelegate::CreateDialogWidget(this, context
, NULL
);
424 // If the picker is not modal to the calling web contents then it is displayed
425 // in its own top-level window, so in that case it needs to be filtered out of
426 // the list of top-level windows available for capture, and to achieve that
427 // the Id is passed to DesktopMediaList.
428 DesktopMediaID::Id dialog_window_id
= 0;
431 if (chrome::IsNativeWindowInAsh(widget
->GetNativeWindow())) {
433 DesktopMediaID::RegisterAuraWindow(
434 DesktopMediaID::TYPE_WINDOW
, widget
->GetNativeWindow()).aura_id
;
435 DCHECK_NE(dialog_window_id
, 0);
439 if (dialog_window_id
== 0) {
440 dialog_window_id
= AcceleratedWidgetToDesktopMediaId(
441 widget
->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
445 list_view_
->StartUpdating(dialog_window_id
);
448 DesktopMediaPickerDialogView::~DesktopMediaPickerDialogView() {}
450 void DesktopMediaPickerDialogView::DetachParent() {
454 gfx::Size
DesktopMediaPickerDialogView::GetPreferredSize() const {
455 static const size_t kDialogViewWidth
= 600;
456 const gfx::Insets title_insets
= views::BubbleFrameView::GetTitleInsets();
457 size_t label_height
=
458 label_
->GetHeightForWidth(kDialogViewWidth
- title_insets
.height() * 2);
460 return gfx::Size(kDialogViewWidth
,
461 views::kPanelVertMargin
* 2 + label_height
+
462 views::kPanelVerticalSpacing
+
463 scroll_view_
->GetPreferredSize().height());
466 void DesktopMediaPickerDialogView::Layout() {
467 // DialogDelegate uses the bubble style frame.
468 const gfx::Insets title_insets
= views::BubbleFrameView::GetTitleInsets();
469 gfx::Rect rect
= GetLocalBounds();
471 rect
.Inset(title_insets
.left(), views::kPanelVertMargin
);
473 gfx::Rect
label_rect(rect
.x(), rect
.y(), rect
.width(),
474 label_
->GetHeightForWidth(rect
.width()));
475 label_
->SetBoundsRect(label_rect
);
477 int scroll_view_top
= label_rect
.bottom() + views::kPanelVerticalSpacing
;
478 scroll_view_
->SetBounds(
479 rect
.x(), scroll_view_top
,
480 rect
.width(), rect
.height() - scroll_view_top
);
483 ui::ModalType
DesktopMediaPickerDialogView::GetModalType() const {
484 return ui::MODAL_TYPE_CHILD
;
487 base::string16
DesktopMediaPickerDialogView::GetWindowTitle() const {
488 return l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TITLE
, app_name_
);
491 bool DesktopMediaPickerDialogView::IsDialogButtonEnabled(
492 ui::DialogButton button
) const {
493 if (button
== ui::DIALOG_BUTTON_OK
)
494 return list_view_
->GetSelection() != NULL
;
498 base::string16
DesktopMediaPickerDialogView::GetDialogButtonLabel(
499 ui::DialogButton button
) const {
500 return l10n_util::GetStringUTF16(button
== ui::DIALOG_BUTTON_OK
?
501 IDS_DESKTOP_MEDIA_PICKER_SHARE
: IDS_CANCEL
);
504 bool DesktopMediaPickerDialogView::Accept() {
505 DesktopMediaSourceView
* selection
= list_view_
->GetSelection();
507 // Ok button should only be enabled when a source is selected.
510 DesktopMediaID source
;
512 source
= selection
->source_id();
515 parent_
->NotifyDialogResult(source
);
517 // Return true to close the window.
521 void DesktopMediaPickerDialogView::DeleteDelegate() {
522 // If the dialog is being closed then notify the parent about it.
524 parent_
->NotifyDialogResult(DesktopMediaID());
528 void DesktopMediaPickerDialogView::OnSelectionChanged() {
529 GetDialogClientView()->UpdateDialogButtons();
532 void DesktopMediaPickerDialogView::OnDoubleClick() {
533 // This will call Accept() and close the dialog.
534 GetDialogClientView()->AcceptWindow();
537 void DesktopMediaPickerDialogView::OnMediaListRowsChanged() {
538 gfx::Rect widget_bound
= GetWidget()->GetWindowBoundsInScreen();
540 int new_height
= widget_bound
.height() - scroll_view_
->height() +
541 scroll_view_
->GetPreferredSize().height();
543 GetWidget()->CenterWindow(gfx::Size(widget_bound
.width(), new_height
));
546 DesktopMediaSourceView
*
547 DesktopMediaPickerDialogView::GetMediaSourceViewForTesting(int index
) const {
548 if (list_view_
->child_count() <= index
)
551 return reinterpret_cast<DesktopMediaSourceView
*>(list_view_
->child_at(index
));
554 DesktopMediaPickerViews::DesktopMediaPickerViews() : dialog_(NULL
) {
557 DesktopMediaPickerViews::~DesktopMediaPickerViews() {
559 dialog_
->DetachParent();
560 dialog_
->GetWidget()->Close();
564 void DesktopMediaPickerViews::Show(content::WebContents
* web_contents
,
565 gfx::NativeWindow context
,
566 gfx::NativeWindow parent
,
567 const base::string16
& app_name
,
568 const base::string16
& target_name
,
569 scoped_ptr
<DesktopMediaList
> media_list
,
570 const DoneCallback
& done_callback
) {
571 callback_
= done_callback
;
572 dialog_
= new DesktopMediaPickerDialogView(
573 web_contents
, context
, this, app_name
, target_name
, media_list
.Pass());
576 void DesktopMediaPickerViews::NotifyDialogResult(DesktopMediaID source
) {
577 // Once this method is called the |dialog_| will close and destroy itself.
578 dialog_
->DetachParent();
581 DCHECK(!callback_
.is_null());
583 // Notify the |callback_| asynchronously because it may need to destroy
584 // DesktopMediaPicker.
585 content::BrowserThread::PostTask(
586 content::BrowserThread::UI
, FROM_HERE
,
587 base::Bind(callback_
, source
));
592 scoped_ptr
<DesktopMediaPicker
> DesktopMediaPicker::Create() {
593 return scoped_ptr
<DesktopMediaPicker
>(new DesktopMediaPickerViews());