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 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 = CreateWebModalDialogViews(this, parent_web_contents);
420 widget = DialogDelegate::CreateDialogWidget(this, context, NULL);
423 // If the picker is not modal to the calling web contents then it is displayed
424 // in its own top-level window, so in that case it needs to be filtered out of
425 // the list of top-level windows available for capture, and to achieve that
426 // the Id is passed to DesktopMediaList.
427 DesktopMediaID::Id dialog_window_id = 0;
430 if (chrome::IsNativeWindowInAsh(widget->GetNativeWindow())) {
432 DesktopMediaID::RegisterAuraWindow(widget->GetNativeWindow()).id;
433 DCHECK_NE(dialog_window_id, 0);
437 if (dialog_window_id == 0) {
438 dialog_window_id = AcceleratedWidgetToDesktopMediaId(
439 widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
443 list_view_->StartUpdating(dialog_window_id);
446 web_modal::PopupManager* popup_manager =
447 web_modal::PopupManager::FromWebContents(parent_web_contents);
448 popup_manager->ShowModalDialog(GetWidget()->GetNativeView(),
449 parent_web_contents);
455 DesktopMediaPickerDialogView::~DesktopMediaPickerDialogView() {}
457 void DesktopMediaPickerDialogView::DetachParent() {
461 gfx::Size DesktopMediaPickerDialogView::GetPreferredSize() const {
462 static const size_t kDialogViewWidth = 600;
463 const gfx::Insets title_insets = views::BubbleFrameView::GetTitleInsets();
464 size_t label_height =
465 label_->GetHeightForWidth(kDialogViewWidth - title_insets.height() * 2);
467 return gfx::Size(kDialogViewWidth,
468 views::kPanelVertMargin * 2 + label_height +
469 views::kPanelVerticalSpacing +
470 scroll_view_->GetPreferredSize().height());
473 void DesktopMediaPickerDialogView::Layout() {
474 // DialogDelegate uses the bubble style frame.
475 const gfx::Insets title_insets = views::BubbleFrameView::GetTitleInsets();
476 gfx::Rect rect = GetLocalBounds();
478 rect.Inset(title_insets.left(), views::kPanelVertMargin);
480 gfx::Rect label_rect(rect.x(), rect.y(), rect.width(),
481 label_->GetHeightForWidth(rect.width()));
482 label_->SetBoundsRect(label_rect);
484 int scroll_view_top = label_rect.bottom() + views::kPanelVerticalSpacing;
485 scroll_view_->SetBounds(
486 rect.x(), scroll_view_top,
487 rect.width(), rect.height() - scroll_view_top);
490 ui::ModalType DesktopMediaPickerDialogView::GetModalType() const {
491 return ui::MODAL_TYPE_CHILD;
494 base::string16 DesktopMediaPickerDialogView::GetWindowTitle() const {
495 return l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TITLE, app_name_);
498 bool DesktopMediaPickerDialogView::IsDialogButtonEnabled(
499 ui::DialogButton button) const {
500 if (button == ui::DIALOG_BUTTON_OK)
501 return list_view_->GetSelection() != NULL;
505 base::string16 DesktopMediaPickerDialogView::GetDialogButtonLabel(
506 ui::DialogButton button) const {
507 return l10n_util::GetStringUTF16(button == ui::DIALOG_BUTTON_OK ?
508 IDS_DESKTOP_MEDIA_PICKER_SHARE : IDS_CANCEL);
511 bool DesktopMediaPickerDialogView::Accept() {
512 DesktopMediaSourceView* selection = list_view_->GetSelection();
514 // Ok button should only be enabled when a source is selected.
517 DesktopMediaID source;
519 source = selection->source_id();
522 parent_->NotifyDialogResult(source);
524 // Return true to close the window.
528 void DesktopMediaPickerDialogView::DeleteDelegate() {
529 // If the dialog is being closed then notify the parent about it.
531 parent_->NotifyDialogResult(DesktopMediaID());
535 void DesktopMediaPickerDialogView::OnSelectionChanged() {
536 GetDialogClientView()->UpdateDialogButtons();
539 void DesktopMediaPickerDialogView::OnDoubleClick() {
540 // This will call Accept() and close the dialog.
541 GetDialogClientView()->AcceptWindow();
544 void DesktopMediaPickerDialogView::OnMediaListRowsChanged() {
545 gfx::Rect widget_bound = GetWidget()->GetWindowBoundsInScreen();
547 int new_height = widget_bound.height() - scroll_view_->height() +
548 scroll_view_->GetPreferredSize().height();
550 GetWidget()->CenterWindow(gfx::Size(widget_bound.width(), new_height));
553 DesktopMediaSourceView*
554 DesktopMediaPickerDialogView::GetMediaSourceViewForTesting(int index) const {
555 if (list_view_->child_count() <= index)
558 return reinterpret_cast<DesktopMediaSourceView*>(list_view_->child_at(index));
561 DesktopMediaPickerViews::DesktopMediaPickerViews() : dialog_(NULL) {
564 DesktopMediaPickerViews::~DesktopMediaPickerViews() {
566 dialog_->DetachParent();
567 dialog_->GetWidget()->Close();
571 void DesktopMediaPickerViews::Show(content::WebContents* web_contents,
572 gfx::NativeWindow context,
573 gfx::NativeWindow parent,
574 const base::string16& app_name,
575 const base::string16& target_name,
576 scoped_ptr<DesktopMediaList> media_list,
577 const DoneCallback& done_callback) {
578 callback_ = done_callback;
579 dialog_ = new DesktopMediaPickerDialogView(
580 web_contents, context, this, app_name, target_name, media_list.Pass());
583 void DesktopMediaPickerViews::NotifyDialogResult(DesktopMediaID source) {
584 // Once this method is called the |dialog_| will close and destroy itself.
585 dialog_->DetachParent();
588 DCHECK(!callback_.is_null());
590 // Notify the |callback_| asynchronously because it may need to destroy
591 // DesktopMediaPicker.
592 content::BrowserThread::PostTask(
593 content::BrowserThread::UI, FROM_HERE,
594 base::Bind(callback_, source));
599 scoped_ptr<DesktopMediaPicker> DesktopMediaPicker::Create() {
600 return scoped_ptr<DesktopMediaPicker>(new DesktopMediaPickerViews());