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/popup_manager.h"
16 #include "components/web_modal/web_contents_modal_dialog_manager.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "content/public/browser/web_contents_delegate.h"
19 #include "ui/aura/window_tree_host.h"
20 #include "ui/base/l10n/l10n_util.h"
21 #include "ui/events/event_constants.h"
22 #include "ui/events/keycodes/keyboard_codes.h"
23 #include "ui/gfx/canvas.h"
24 #include "ui/native_theme/native_theme.h"
25 #include "ui/views/background.h"
26 #include "ui/views/bubble/bubble_frame_view.h"
27 #include "ui/views/controls/image_view.h"
28 #include "ui/views/controls/label.h"
29 #include "ui/views/controls/scroll_view.h"
30 #include "ui/views/layout/layout_constants.h"
31 #include "ui/views/widget/widget.h"
32 #include "ui/views/window/dialog_client_view.h"
33 #include "ui/wm/core/shadow_types.h"
35 using content::DesktopMediaID
;
39 const int kThumbnailWidth
= 160;
40 const int kThumbnailHeight
= 100;
41 const int kThumbnailMargin
= 10;
42 const int kLabelHeight
= 40;
43 const int kListItemWidth
= kThumbnailMargin
* 2 + kThumbnailWidth
;
44 const int kListItemHeight
=
45 kThumbnailMargin
* 2 + kThumbnailHeight
+ kLabelHeight
;
46 const int kListColumns
= 3;
47 const int kTotalListWidth
= kListColumns
* kListItemWidth
;
49 const int kDesktopMediaSourceViewGroupId
= 1;
51 const char kDesktopMediaSourceViewClassName
[] =
52 "DesktopMediaPicker_DesktopMediaSourceView";
54 DesktopMediaID::Id
AcceleratedWidgetToDesktopMediaId(
55 gfx::AcceleratedWidget accelerated_widget
) {
57 return reinterpret_cast<DesktopMediaID::Id
>(accelerated_widget
);
59 return static_cast<DesktopMediaID::Id
>(accelerated_widget
);
63 int GetMediaListViewHeightForRows(size_t rows
) {
64 return kListItemHeight
* rows
;
69 DesktopMediaSourceView::DesktopMediaSourceView(
70 DesktopMediaListView
* parent
,
71 DesktopMediaID source_id
)
73 source_id_(source_id
),
74 image_view_(new views::ImageView()),
75 label_(new views::Label()),
77 AddChildView(image_view_
);
82 DesktopMediaSourceView::~DesktopMediaSourceView() {}
84 void DesktopMediaSourceView::SetName(const base::string16
& name
) {
85 label_
->SetText(name
);
88 void DesktopMediaSourceView::SetThumbnail(const gfx::ImageSkia
& thumbnail
) {
89 image_view_
->SetImage(thumbnail
);
92 void DesktopMediaSourceView::SetSelected(bool selected
) {
93 if (selected
== selected_
)
98 // Unselect all other sources.
100 parent()->GetViewsInGroup(GetGroup(), &neighbours
);
101 for (Views::iterator
i(neighbours
.begin()); i
!= neighbours
.end(); ++i
) {
103 DCHECK_EQ((*i
)->GetClassName(), kDesktopMediaSourceViewClassName
);
104 DesktopMediaSourceView
* source_view
=
105 static_cast<DesktopMediaSourceView
*>(*i
);
106 source_view
->SetSelected(false);
110 const SkColor bg_color
= GetNativeTheme()->GetSystemColor(
111 ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor
);
112 set_background(views::Background::CreateSolidBackground(bg_color
));
114 parent_
->OnSelectionChanged();
116 set_background(NULL
);
122 const char* DesktopMediaSourceView::GetClassName() const {
123 return kDesktopMediaSourceViewClassName
;
126 void DesktopMediaSourceView::Layout() {
127 image_view_
->SetBounds(kThumbnailMargin
, kThumbnailMargin
,
128 kThumbnailWidth
, kThumbnailHeight
);
129 label_
->SetBounds(kThumbnailMargin
, kThumbnailHeight
+ kThumbnailMargin
,
130 kThumbnailWidth
, kLabelHeight
);
133 views::View
* DesktopMediaSourceView::GetSelectedViewForGroup(int group
) {
135 parent()->GetViewsInGroup(group
, &neighbours
);
136 if (neighbours
.empty())
139 for (Views::iterator
i(neighbours
.begin()); i
!= neighbours
.end(); ++i
) {
140 DCHECK_EQ((*i
)->GetClassName(), kDesktopMediaSourceViewClassName
);
141 DesktopMediaSourceView
* source_view
=
142 static_cast<DesktopMediaSourceView
*>(*i
);
143 if (source_view
->selected_
)
149 bool DesktopMediaSourceView::IsGroupFocusTraversable() const {
153 void DesktopMediaSourceView::OnPaint(gfx::Canvas
* canvas
) {
154 View::OnPaint(canvas
);
156 gfx::Rect
bounds(GetLocalBounds());
157 bounds
.Inset(kThumbnailMargin
/ 2, kThumbnailMargin
/ 2);
158 canvas
->DrawFocusRect(bounds
);
162 void DesktopMediaSourceView::OnFocus() {
165 ScrollRectToVisible(gfx::Rect(size()));
166 // We paint differently when focused.
170 void DesktopMediaSourceView::OnBlur() {
172 // We paint differently when focused.
176 bool DesktopMediaSourceView::OnMousePressed(const ui::MouseEvent
& event
) {
177 if (event
.GetClickCount() == 1) {
179 } else if (event
.GetClickCount() == 2) {
181 parent_
->OnDoubleClick();
186 void DesktopMediaSourceView::OnGestureEvent(ui::GestureEvent
* event
) {
187 if (event
->type() == ui::ET_GESTURE_TAP
&&
188 event
->details().tap_count() == 2) {
190 parent_
->OnDoubleClick();
195 // Detect tap gesture using ET_GESTURE_TAP_DOWN so the view also gets focused
196 // on the long tap (when the tap gesture starts).
197 if (event
->type() == ui::ET_GESTURE_TAP_DOWN
) {
203 DesktopMediaListView::DesktopMediaListView(
204 DesktopMediaPickerDialogView
* parent
,
205 scoped_ptr
<DesktopMediaList
> media_list
)
207 media_list_(media_list
.Pass()),
208 weak_factory_(this) {
209 media_list_
->SetThumbnailSize(gfx::Size(kThumbnailWidth
, kThumbnailHeight
));
212 DesktopMediaListView::~DesktopMediaListView() {}
214 void DesktopMediaListView::StartUpdating(DesktopMediaID::Id dialog_window_id
) {
215 media_list_
->SetViewDialogWindowId(dialog_window_id
);
216 media_list_
->StartUpdating(this);
219 void DesktopMediaListView::OnSelectionChanged() {
220 parent_
->OnSelectionChanged();
223 void DesktopMediaListView::OnDoubleClick() {
224 parent_
->OnDoubleClick();
227 DesktopMediaSourceView
* DesktopMediaListView::GetSelection() {
228 for (int i
= 0; i
< child_count(); ++i
) {
229 DesktopMediaSourceView
* source_view
=
230 static_cast<DesktopMediaSourceView
*>(child_at(i
));
231 DCHECK_EQ(source_view
->GetClassName(), kDesktopMediaSourceViewClassName
);
232 if (source_view
->is_selected())
238 gfx::Size
DesktopMediaListView::GetPreferredSize() const {
239 int total_rows
= (child_count() + kListColumns
- 1) / kListColumns
;
240 return gfx::Size(kTotalListWidth
, GetMediaListViewHeightForRows(total_rows
));
243 void DesktopMediaListView::Layout() {
247 for (int i
= 0; i
< child_count(); ++i
) {
248 if (x
+ kListItemWidth
> kTotalListWidth
) {
250 y
+= kListItemHeight
;
253 View
* source_view
= child_at(i
);
254 source_view
->SetBounds(x
, y
, kListItemWidth
, kListItemHeight
);
259 y
+= kListItemHeight
;
260 SetSize(gfx::Size(kTotalListWidth
, y
));
263 bool DesktopMediaListView::OnKeyPressed(const ui::KeyEvent
& event
) {
264 int position_increment
= 0;
265 switch (event
.key_code()) {
267 position_increment
= -kListColumns
;
270 position_increment
= kListColumns
;
273 position_increment
= -1;
276 position_increment
= 1;
282 if (position_increment
!= 0) {
283 DesktopMediaSourceView
* selected
= GetSelection();
284 DesktopMediaSourceView
* new_selected
= NULL
;
287 int index
= GetIndexOf(selected
);
288 int new_index
= index
+ position_increment
;
289 if (new_index
>= child_count())
290 new_index
= child_count() - 1;
291 else if (new_index
< 0)
293 if (index
!= new_index
) {
295 static_cast<DesktopMediaSourceView
*>(child_at(new_index
));
297 } else if (has_children()) {
298 new_selected
= static_cast<DesktopMediaSourceView
*>(child_at(0));
302 GetFocusManager()->SetFocusedView(new_selected
);
311 void DesktopMediaListView::OnSourceAdded(int index
) {
312 const DesktopMediaList::Source
& source
= media_list_
->GetSource(index
);
313 DesktopMediaSourceView
* source_view
=
314 new DesktopMediaSourceView(this, source
.id
);
315 source_view
->SetName(source
.name
);
316 source_view
->SetGroup(kDesktopMediaSourceViewGroupId
);
317 AddChildViewAt(source_view
, index
);
319 PreferredSizeChanged();
321 if (child_count() % kListColumns
== 1)
322 parent_
->OnMediaListRowsChanged();
324 std::string autoselect_source
=
325 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
326 switches::kAutoSelectDesktopCaptureSource
);
327 if (!autoselect_source
.empty() &&
328 base::ASCIIToUTF16(autoselect_source
) == source
.name
) {
329 // Select, then accept and close the dialog when we're done adding sources.
330 source_view
->OnFocus();
331 content::BrowserThread::PostTask(
332 content::BrowserThread::UI
, FROM_HERE
,
333 base::Bind(&DesktopMediaListView::AcceptSelection
,
334 weak_factory_
.GetWeakPtr()));
338 void DesktopMediaListView::OnSourceRemoved(int index
) {
339 DesktopMediaSourceView
* view
=
340 static_cast<DesktopMediaSourceView
*>(child_at(index
));
342 DCHECK_EQ(view
->GetClassName(), kDesktopMediaSourceViewClassName
);
343 bool was_selected
= view
->is_selected();
344 RemoveChildView(view
);
348 OnSelectionChanged();
350 PreferredSizeChanged();
352 if (child_count() % kListColumns
== 0)
353 parent_
->OnMediaListRowsChanged();
356 void DesktopMediaListView::OnSourceMoved(int old_index
, int new_index
) {
357 DesktopMediaSourceView
* view
=
358 static_cast<DesktopMediaSourceView
*>(child_at(old_index
));
359 ReorderChildView(view
, new_index
);
360 PreferredSizeChanged();
363 void DesktopMediaListView::OnSourceNameChanged(int index
) {
364 const DesktopMediaList::Source
& source
= media_list_
->GetSource(index
);
365 DesktopMediaSourceView
* source_view
=
366 static_cast<DesktopMediaSourceView
*>(child_at(index
));
367 source_view
->SetName(source
.name
);
370 void DesktopMediaListView::OnSourceThumbnailChanged(int index
) {
371 const DesktopMediaList::Source
& source
= media_list_
->GetSource(index
);
372 DesktopMediaSourceView
* source_view
=
373 static_cast<DesktopMediaSourceView
*>(child_at(index
));
374 source_view
->SetThumbnail(source
.thumbnail
);
377 void DesktopMediaListView::AcceptSelection() {
378 OnSelectionChanged();
382 DesktopMediaPickerDialogView::DesktopMediaPickerDialogView(
383 content::WebContents
* parent_web_contents
,
384 gfx::NativeWindow context
,
385 DesktopMediaPickerViews
* parent
,
386 const base::string16
& app_name
,
387 const base::string16
& target_name
,
388 scoped_ptr
<DesktopMediaList
> media_list
)
391 label_(new views::Label()),
392 scroll_view_(views::ScrollView::CreateScrollViewWithBorder()),
393 list_view_(new DesktopMediaListView(this, media_list
.Pass())) {
394 if (app_name
== target_name
) {
396 l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TEXT
, app_name
));
398 label_
->SetText(l10n_util::GetStringFUTF16(
399 IDS_DESKTOP_MEDIA_PICKER_TEXT_DELEGATED
, app_name
, target_name
));
401 label_
->SetMultiLine(true);
402 label_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
403 AddChildView(label_
);
405 scroll_view_
->SetContents(list_view_
);
406 scroll_view_
->ClipHeightTo(
407 GetMediaListViewHeightForRows(1), GetMediaListViewHeightForRows(2));
408 AddChildView(scroll_view_
);
410 // If |parent_web_contents| is set and it's not a background page then the
411 // picker will be shown modal to the web contents. Otherwise the picker is
412 // shown in a separate window.
413 views::Widget
* widget
= NULL
;
415 parent_web_contents
&&
416 !parent_web_contents
->GetDelegate()->IsNeverVisible(parent_web_contents
);
418 widget
= constrained_window::CreateWebModalDialogViews(this,
419 parent_web_contents
);
421 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(widget
->GetNativeWindow()).id
;
434 DCHECK_NE(dialog_window_id
, 0);
438 if (dialog_window_id
== 0) {
439 dialog_window_id
= AcceleratedWidgetToDesktopMediaId(
440 widget
->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
444 list_view_
->StartUpdating(dialog_window_id
);
447 web_modal::PopupManager
* popup_manager
=
448 web_modal::PopupManager::FromWebContents(parent_web_contents
);
449 popup_manager
->ShowModalDialog(GetWidget()->GetNativeView(),
450 parent_web_contents
);
456 DesktopMediaPickerDialogView::~DesktopMediaPickerDialogView() {}
458 void DesktopMediaPickerDialogView::DetachParent() {
462 gfx::Size
DesktopMediaPickerDialogView::GetPreferredSize() const {
463 static const size_t kDialogViewWidth
= 600;
464 const gfx::Insets title_insets
= views::BubbleFrameView::GetTitleInsets();
465 size_t label_height
=
466 label_
->GetHeightForWidth(kDialogViewWidth
- title_insets
.height() * 2);
468 return gfx::Size(kDialogViewWidth
,
469 views::kPanelVertMargin
* 2 + label_height
+
470 views::kPanelVerticalSpacing
+
471 scroll_view_
->GetPreferredSize().height());
474 void DesktopMediaPickerDialogView::Layout() {
475 // DialogDelegate uses the bubble style frame.
476 const gfx::Insets title_insets
= views::BubbleFrameView::GetTitleInsets();
477 gfx::Rect rect
= GetLocalBounds();
479 rect
.Inset(title_insets
.left(), views::kPanelVertMargin
);
481 gfx::Rect
label_rect(rect
.x(), rect
.y(), rect
.width(),
482 label_
->GetHeightForWidth(rect
.width()));
483 label_
->SetBoundsRect(label_rect
);
485 int scroll_view_top
= label_rect
.bottom() + views::kPanelVerticalSpacing
;
486 scroll_view_
->SetBounds(
487 rect
.x(), scroll_view_top
,
488 rect
.width(), rect
.height() - scroll_view_top
);
491 ui::ModalType
DesktopMediaPickerDialogView::GetModalType() const {
492 return ui::MODAL_TYPE_CHILD
;
495 base::string16
DesktopMediaPickerDialogView::GetWindowTitle() const {
496 return l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TITLE
, app_name_
);
499 bool DesktopMediaPickerDialogView::IsDialogButtonEnabled(
500 ui::DialogButton button
) const {
501 if (button
== ui::DIALOG_BUTTON_OK
)
502 return list_view_
->GetSelection() != NULL
;
506 base::string16
DesktopMediaPickerDialogView::GetDialogButtonLabel(
507 ui::DialogButton button
) const {
508 return l10n_util::GetStringUTF16(button
== ui::DIALOG_BUTTON_OK
?
509 IDS_DESKTOP_MEDIA_PICKER_SHARE
: IDS_CANCEL
);
512 bool DesktopMediaPickerDialogView::Accept() {
513 DesktopMediaSourceView
* selection
= list_view_
->GetSelection();
515 // Ok button should only be enabled when a source is selected.
518 DesktopMediaID source
;
520 source
= selection
->source_id();
523 parent_
->NotifyDialogResult(source
);
525 // Return true to close the window.
529 void DesktopMediaPickerDialogView::DeleteDelegate() {
530 // If the dialog is being closed then notify the parent about it.
532 parent_
->NotifyDialogResult(DesktopMediaID());
536 void DesktopMediaPickerDialogView::OnSelectionChanged() {
537 GetDialogClientView()->UpdateDialogButtons();
540 void DesktopMediaPickerDialogView::OnDoubleClick() {
541 // This will call Accept() and close the dialog.
542 GetDialogClientView()->AcceptWindow();
545 void DesktopMediaPickerDialogView::OnMediaListRowsChanged() {
546 gfx::Rect widget_bound
= GetWidget()->GetWindowBoundsInScreen();
548 int new_height
= widget_bound
.height() - scroll_view_
->height() +
549 scroll_view_
->GetPreferredSize().height();
551 GetWidget()->CenterWindow(gfx::Size(widget_bound
.width(), new_height
));
554 DesktopMediaSourceView
*
555 DesktopMediaPickerDialogView::GetMediaSourceViewForTesting(int index
) const {
556 if (list_view_
->child_count() <= index
)
559 return reinterpret_cast<DesktopMediaSourceView
*>(list_view_
->child_at(index
));
562 DesktopMediaPickerViews::DesktopMediaPickerViews() : dialog_(NULL
) {
565 DesktopMediaPickerViews::~DesktopMediaPickerViews() {
567 dialog_
->DetachParent();
568 dialog_
->GetWidget()->Close();
572 void DesktopMediaPickerViews::Show(content::WebContents
* web_contents
,
573 gfx::NativeWindow context
,
574 gfx::NativeWindow parent
,
575 const base::string16
& app_name
,
576 const base::string16
& target_name
,
577 scoped_ptr
<DesktopMediaList
> media_list
,
578 const DoneCallback
& done_callback
) {
579 callback_
= done_callback
;
580 dialog_
= new DesktopMediaPickerDialogView(
581 web_contents
, context
, this, app_name
, target_name
, media_list
.Pass());
584 void DesktopMediaPickerViews::NotifyDialogResult(DesktopMediaID source
) {
585 // Once this method is called the |dialog_| will close and destroy itself.
586 dialog_
->DetachParent();
589 DCHECK(!callback_
.is_null());
591 // Notify the |callback_| asynchronously because it may need to destroy
592 // DesktopMediaPicker.
593 content::BrowserThread::PostTask(
594 content::BrowserThread::UI
, FROM_HERE
,
595 base::Bind(callback_
, source
));
600 scoped_ptr
<DesktopMediaPicker
> DesktopMediaPicker::Create() {
601 return scoped_ptr
<DesktopMediaPicker
>(new DesktopMediaPickerViews());