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/media/desktop_media_picker.h"
7 #include "base/callback.h"
8 #include "chrome/browser/media/desktop_media_list.h"
9 #include "chrome/browser/media/desktop_media_list_observer.h"
10 #include "chrome/browser/ui/ash/ash_util.h"
11 #include "content/public/browser/browser_thread.h"
12 #include "grit/generated_resources.h"
13 #include "ui/base/l10n/l10n_util.h"
14 #include "ui/events/keycodes/keyboard_codes.h"
15 #include "ui/gfx/canvas.h"
16 #include "ui/native_theme/native_theme.h"
17 #include "ui/views/background.h"
18 #include "ui/views/controls/image_view.h"
19 #include "ui/views/controls/label.h"
20 #include "ui/views/controls/scroll_view.h"
21 #include "ui/views/corewm/shadow_types.h"
22 #include "ui/views/layout/box_layout.h"
23 #include "ui/views/layout/layout_constants.h"
24 #include "ui/views/widget/widget.h"
25 #include "ui/views/window/dialog_client_view.h"
26 #include "ui/views/window/dialog_delegate.h"
29 #include "ui/aura/root_window.h"
32 using content::DesktopMediaID
;
36 const int kThumbnailWidth
= 160;
37 const int kThumbnailHeight
= 100;
38 const int kThumbnailMargin
= 10;
39 const int kLabelHeight
= 40;
40 const int kListItemWidth
= kThumbnailMargin
* 2 + kThumbnailWidth
;
41 const int kListItemHeight
=
42 kThumbnailMargin
* 2 + kThumbnailHeight
+ kLabelHeight
;
43 const int kListColumns
= 3;
44 const int kTotalListWidth
= kListColumns
* kListItemWidth
;
46 const int kDesktopMediaSourceViewGroupId
= 1;
48 const char kDesktopMediaSourceViewClassName
[] =
49 "DesktopMediaPicker_DesktopMediaSourceView";
51 content::DesktopMediaID::Id
AcceleratedWidgetToDesktopMediaId(
52 gfx::AcceleratedWidget accelerated_widget
) {
54 return reinterpret_cast<content::DesktopMediaID::Id
>(accelerated_widget
);
56 return static_cast<content::DesktopMediaID::Id
>(accelerated_widget
);
60 class DesktopMediaListView
;
61 class DesktopMediaPickerDialogView
;
62 class DesktopMediaPickerViews
;
64 // View used for each item in DesktopMediaListView. Shows a single desktop media
65 // source as a thumbnail with the title under it.
66 class DesktopMediaSourceView
: public views::View
{
68 DesktopMediaSourceView(DesktopMediaListView
* parent
,
69 DesktopMediaID source_id
);
70 virtual ~DesktopMediaSourceView();
72 // Updates thumbnail and title from |source|.
73 void SetName(const base::string16
& name
);
74 void SetThumbnail(const gfx::ImageSkia
& thumbnail
);
76 // Id for the source shown by this View.
77 const DesktopMediaID
& source_id() const {
81 // Returns true if the source is selected.
82 bool is_selected() const { return selected_
; }
84 // Updates selection state of the element. If |selected| is true then also
85 // calls SetSelected(false) for the source view that was selected before that
87 void SetSelected(bool selected
);
89 // views::View interface.
90 virtual const char* GetClassName() const OVERRIDE
;
91 virtual void Layout() OVERRIDE
;
92 virtual views::View
* GetSelectedViewForGroup(int group
) OVERRIDE
;
93 virtual bool IsGroupFocusTraversable() const OVERRIDE
;
94 virtual void OnPaint(gfx::Canvas
* canvas
) OVERRIDE
;
95 virtual void OnFocus() OVERRIDE
;
96 virtual void OnBlur() OVERRIDE
;
97 virtual bool OnMousePressed(const ui::MouseEvent
& event
) OVERRIDE
;
100 DesktopMediaListView
* parent_
;
101 DesktopMediaID source_id_
;
103 views::ImageView
* image_view_
;
104 views::Label
* label_
;
108 DISALLOW_COPY_AND_ASSIGN(DesktopMediaSourceView
);
111 // View that shows list of desktop media sources available from
113 class DesktopMediaListView
: public views::View
,
114 public DesktopMediaListObserver
{
116 DesktopMediaListView(DesktopMediaPickerDialogView
* parent
,
117 scoped_ptr
<DesktopMediaList
> media_list
);
118 virtual ~DesktopMediaListView();
120 void StartUpdating(content::DesktopMediaID::Id dialog_window_id
);
122 // Called by DesktopMediaSourceView when selection has changed.
123 void OnSelectionChanged();
125 // Called by DesktopMediaSourceView when a source has been double-clicked.
126 void OnDoubleClick();
128 // Returns currently selected source.
129 DesktopMediaSourceView
* GetSelection();
131 // views::View overrides.
132 virtual gfx::Size
GetPreferredSize() OVERRIDE
;
133 virtual void Layout() OVERRIDE
;
134 virtual bool OnKeyPressed(const ui::KeyEvent
& event
) OVERRIDE
;
137 // DesktopMediaList::Observer interface
138 virtual void OnSourceAdded(int index
) OVERRIDE
;
139 virtual void OnSourceRemoved(int index
) OVERRIDE
;
140 virtual void OnSourceNameChanged(int index
) OVERRIDE
;
141 virtual void OnSourceThumbnailChanged(int index
) OVERRIDE
;
143 DesktopMediaPickerDialogView
* parent_
;
144 scoped_ptr
<DesktopMediaList
> media_list_
;
146 DISALLOW_COPY_AND_ASSIGN(DesktopMediaListView
);
149 // Dialog view used for DesktopMediaPickerViews.
150 class DesktopMediaPickerDialogView
: public views::DialogDelegateView
{
152 DesktopMediaPickerDialogView(gfx::NativeWindow context
,
153 gfx::NativeWindow parent_window
,
154 DesktopMediaPickerViews
* parent
,
155 const base::string16
& app_name
,
156 scoped_ptr
<DesktopMediaList
> media_list
);
157 virtual ~DesktopMediaPickerDialogView();
159 // Called by parent (DesktopMediaPickerViews) when it's destroyed.
162 // Called by DesktopMediaListView.
163 void OnSelectionChanged();
164 void OnDoubleClick();
166 // views::View overrides.
167 virtual gfx::Size
GetPreferredSize() OVERRIDE
;
168 virtual void Layout() OVERRIDE
;
170 // views::DialogDelegateView overrides.
171 virtual base::string16
GetWindowTitle() const OVERRIDE
;
172 virtual bool IsDialogButtonEnabled(ui::DialogButton button
) const OVERRIDE
;
173 virtual bool Accept() OVERRIDE
;
174 virtual void DeleteDelegate() OVERRIDE
;
177 DesktopMediaPickerViews
* parent_
;
178 base::string16 app_name_
;
180 views::Label
* label_
;
181 views::ScrollView
* scroll_view_
;
182 DesktopMediaListView
* list_view_
;
184 DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerDialogView
);
187 // Implementation of DesktopMediaPicker for Views.
188 class DesktopMediaPickerViews
: public DesktopMediaPicker
{
190 DesktopMediaPickerViews();
191 virtual ~DesktopMediaPickerViews();
193 void NotifyDialogResult(DesktopMediaID source
);
195 // DesktopMediaPicker overrides.
196 virtual void Show(gfx::NativeWindow context
,
197 gfx::NativeWindow parent
,
198 const base::string16
& app_name
,
199 scoped_ptr
<DesktopMediaList
> media_list
,
200 const DoneCallback
& done_callback
) OVERRIDE
;
203 DoneCallback callback_
;
205 // The |dialog_| is owned by the corresponding views::Widget instance.
206 // When DesktopMediaPickerViews is destroyed the |dialog_| is destroyed
207 // asynchronously by closing the widget.
208 DesktopMediaPickerDialogView
* dialog_
;
210 DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerViews
);
213 DesktopMediaSourceView::DesktopMediaSourceView(
214 DesktopMediaListView
* parent
,
215 DesktopMediaID source_id
)
217 source_id_(source_id
),
218 image_view_(new views::ImageView()),
219 label_(new views::Label()),
221 AddChildView(image_view_
);
222 AddChildView(label_
);
226 DesktopMediaSourceView::~DesktopMediaSourceView() {}
228 void DesktopMediaSourceView::SetName(const base::string16
& name
) {
229 label_
->SetText(name
);
232 void DesktopMediaSourceView::SetThumbnail(const gfx::ImageSkia
& thumbnail
) {
233 image_view_
->SetImage(thumbnail
);
236 void DesktopMediaSourceView::SetSelected(bool selected
) {
237 if (selected
== selected_
)
239 selected_
= selected
;
242 // Unselect all other sources.
244 parent()->GetViewsInGroup(GetGroup(), &neighbours
);
245 for (Views::iterator
i(neighbours
.begin()); i
!= neighbours
.end(); ++i
) {
247 DCHECK_EQ((*i
)->GetClassName(), kDesktopMediaSourceViewClassName
);
248 DesktopMediaSourceView
* source_view
=
249 static_cast<DesktopMediaSourceView
*>(*i
);
250 source_view
->SetSelected(false);
254 const SkColor bg_color
= GetNativeTheme()->GetSystemColor(
255 ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor
);
256 set_background(views::Background::CreateSolidBackground(bg_color
));
258 parent_
->OnSelectionChanged();
260 set_background(NULL
);
266 const char* DesktopMediaSourceView::GetClassName() const {
267 return kDesktopMediaSourceViewClassName
;
270 void DesktopMediaSourceView::Layout() {
271 image_view_
->SetBounds(kThumbnailMargin
, kThumbnailMargin
,
272 kThumbnailWidth
, kThumbnailHeight
);
273 label_
->SetBounds(kThumbnailMargin
, kThumbnailHeight
+ kThumbnailMargin
,
274 kThumbnailWidth
, kLabelHeight
);
277 views::View
* DesktopMediaSourceView::GetSelectedViewForGroup(int group
) {
279 parent()->GetViewsInGroup(group
, &neighbours
);
280 if (neighbours
.empty())
283 for (Views::iterator
i(neighbours
.begin()); i
!= neighbours
.end(); ++i
) {
284 DCHECK_EQ((*i
)->GetClassName(), kDesktopMediaSourceViewClassName
);
285 DesktopMediaSourceView
* source_view
=
286 static_cast<DesktopMediaSourceView
*>(*i
);
287 if (source_view
->selected_
)
293 bool DesktopMediaSourceView::IsGroupFocusTraversable() const {
297 void DesktopMediaSourceView::OnPaint(gfx::Canvas
* canvas
) {
298 View::OnPaint(canvas
);
300 gfx::Rect
bounds(GetLocalBounds());
301 bounds
.Inset(kThumbnailMargin
/ 2, kThumbnailMargin
/ 2);
302 canvas
->DrawFocusRect(bounds
);
306 void DesktopMediaSourceView::OnFocus() {
309 ScrollRectToVisible(gfx::Rect(size()));
310 // We paint differently when focused.
314 void DesktopMediaSourceView::OnBlur() {
316 // We paint differently when focused.
320 bool DesktopMediaSourceView::OnMousePressed(const ui::MouseEvent
& event
) {
321 if (event
.GetClickCount() == 1) {
323 } else if (event
.GetClickCount() == 2) {
325 parent_
->OnDoubleClick();
330 DesktopMediaListView::DesktopMediaListView(
331 DesktopMediaPickerDialogView
* parent
,
332 scoped_ptr
<DesktopMediaList
> media_list
)
334 media_list_(media_list
.Pass()) {
335 media_list_
->SetThumbnailSize(gfx::Size(kThumbnailWidth
, kThumbnailHeight
));
338 DesktopMediaListView::~DesktopMediaListView() {}
340 void DesktopMediaListView::StartUpdating(
341 content::DesktopMediaID::Id dialog_window_id
) {
342 media_list_
->SetViewDialogWindowId(dialog_window_id
);
343 media_list_
->StartUpdating(this);
346 void DesktopMediaListView::OnSelectionChanged() {
347 parent_
->OnSelectionChanged();
350 void DesktopMediaListView::OnDoubleClick() {
351 parent_
->OnDoubleClick();
354 DesktopMediaSourceView
* DesktopMediaListView::GetSelection() {
355 for (int i
= 0; i
< child_count(); ++i
) {
356 DesktopMediaSourceView
* source_view
=
357 static_cast<DesktopMediaSourceView
*>(child_at(i
));
358 DCHECK_EQ(source_view
->GetClassName(), kDesktopMediaSourceViewClassName
);
359 if (source_view
->is_selected())
365 gfx::Size
DesktopMediaListView::GetPreferredSize() {
366 int total_rows
= (child_count() + kListColumns
- 1) / kListColumns
;
367 return gfx::Size(kTotalListWidth
, kListItemHeight
* total_rows
);
370 void DesktopMediaListView::Layout() {
374 for (int i
= 0; i
< child_count(); ++i
) {
375 if (x
+ kListItemWidth
> kTotalListWidth
) {
377 y
+= kListItemHeight
;
380 View
* source_view
= child_at(i
);
381 source_view
->SetBounds(x
, y
, kListItemWidth
, kListItemHeight
);
386 y
+= kListItemHeight
;
387 SetSize(gfx::Size(kTotalListWidth
, y
));
390 bool DesktopMediaListView::OnKeyPressed(const ui::KeyEvent
& event
) {
391 int position_increment
= 0;
392 switch (event
.key_code()) {
394 position_increment
= -kListColumns
;
397 position_increment
= kListColumns
;
400 position_increment
= -1;
403 position_increment
= 1;
409 if (position_increment
!= 0) {
410 DesktopMediaSourceView
* selected
= GetSelection();
411 DesktopMediaSourceView
* new_selected
= NULL
;
414 int index
= GetIndexOf(selected
);
415 int new_index
= index
+ position_increment
;
416 if (new_index
>= child_count())
417 new_index
= child_count() - 1;
418 else if (new_index
< 0)
420 if (index
!= new_index
) {
422 static_cast<DesktopMediaSourceView
*>(child_at(new_index
));
424 } else if (has_children()) {
425 new_selected
= static_cast<DesktopMediaSourceView
*>(child_at(0));
429 GetFocusManager()->SetFocusedView(new_selected
);
438 void DesktopMediaListView::OnSourceAdded(int index
) {
439 const DesktopMediaList::Source
& source
= media_list_
->GetSource(index
);
440 DesktopMediaSourceView
* source_view
=
441 new DesktopMediaSourceView(this, source
.id
);
442 source_view
->SetName(source
.name
);
443 source_view
->SetGroup(kDesktopMediaSourceViewGroupId
);
444 AddChildViewAt(source_view
, index
);
446 PreferredSizeChanged();
449 void DesktopMediaListView::OnSourceRemoved(int index
) {
450 DesktopMediaSourceView
* view
=
451 static_cast<DesktopMediaSourceView
*>(child_at(index
));
453 DCHECK_EQ(view
->GetClassName(), kDesktopMediaSourceViewClassName
);
454 bool was_selected
= view
->is_selected();
455 RemoveChildView(view
);
459 OnSelectionChanged();
461 PreferredSizeChanged();
464 void DesktopMediaListView::OnSourceNameChanged(int index
) {
465 const DesktopMediaList::Source
& source
= media_list_
->GetSource(index
);
466 DesktopMediaSourceView
* source_view
=
467 static_cast<DesktopMediaSourceView
*>(child_at(index
));
468 source_view
->SetName(source
.name
);
471 void DesktopMediaListView::OnSourceThumbnailChanged(int index
) {
472 const DesktopMediaList::Source
& source
= media_list_
->GetSource(index
);
473 DesktopMediaSourceView
* source_view
=
474 static_cast<DesktopMediaSourceView
*>(child_at(index
));
475 source_view
->SetThumbnail(source
.thumbnail
);
478 DesktopMediaPickerDialogView::DesktopMediaPickerDialogView(
479 gfx::NativeWindow context
,
480 gfx::NativeWindow parent_window
,
481 DesktopMediaPickerViews
* parent
,
482 const base::string16
& app_name
,
483 scoped_ptr
<DesktopMediaList
> media_list
)
486 label_(new views::Label()),
487 scroll_view_(views::ScrollView::CreateScrollViewWithBorder()),
488 list_view_(new DesktopMediaListView(this, media_list
.Pass())) {
490 l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TEXT
, app_name_
));
491 label_
->SetMultiLine(true);
492 label_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
493 AddChildView(label_
);
495 scroll_view_
->SetContents(list_view_
);
496 AddChildView(scroll_view_
);
498 DialogDelegate::CreateDialogWidget(this, context
, parent_window
);
500 // DesktopMediaList needs to know the ID of the picker window which
501 // matches the ID it gets from the OS. Depending on the OS and configuration
502 // we get this ID differently.
503 content::DesktopMediaID::Id dialog_window_id
= 0;
505 #if defined(USE_AURA)
508 if (chrome::IsNativeWindowInAsh(GetWidget()->GetNativeWindow())) {
509 dialog_window_id
= content::DesktopMediaID::RegisterAuraWindow(
510 GetWidget()->GetNativeWindow()).id
;
514 dialog_window_id
= AcceleratedWidgetToDesktopMediaId(
515 GetWidget()->GetNativeWindow()->GetDispatcher()->host()->
516 GetAcceleratedWidget());
519 #else // defined(USE_AURA)
520 dialog_window_id
= 0;
522 #endif // !defined(USE_AURA)
524 list_view_
->StartUpdating(dialog_window_id
);
529 DesktopMediaPickerDialogView::~DesktopMediaPickerDialogView() {}
531 void DesktopMediaPickerDialogView::DetachParent() {
535 gfx::Size
DesktopMediaPickerDialogView::GetPreferredSize() {
536 return gfx::Size(600, 500);
539 void DesktopMediaPickerDialogView::Layout() {
540 gfx::Rect rect
= GetLocalBounds();
541 rect
.Inset(views::kPanelHorizMargin
, views::kPanelVertMargin
);
543 gfx::Rect
label_rect(rect
.x(), rect
.y(), rect
.width(),
544 label_
->GetHeightForWidth(rect
.width()));
545 label_
->SetBoundsRect(label_rect
);
547 int scroll_view_top
= label_rect
.bottom() + views::kPanelVerticalSpacing
;
548 scroll_view_
->SetBounds(
549 rect
.x(), scroll_view_top
,
550 rect
.width(), rect
.height() - scroll_view_top
);
553 base::string16
DesktopMediaPickerDialogView::GetWindowTitle() const {
554 return l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TITLE
, app_name_
);
557 bool DesktopMediaPickerDialogView::IsDialogButtonEnabled(
558 ui::DialogButton button
) const {
559 if (button
== ui::DIALOG_BUTTON_OK
)
560 return list_view_
->GetSelection() != NULL
;
564 bool DesktopMediaPickerDialogView::Accept() {
565 DesktopMediaSourceView
* selection
= list_view_
->GetSelection();
567 // Ok button should only be enabled when a source is selected.
570 DesktopMediaID source
;
572 source
= selection
->source_id();
575 parent_
->NotifyDialogResult(source
);
577 // Return true to close the window.
581 void DesktopMediaPickerDialogView::DeleteDelegate() {
582 // If the dialog is being closed then notify the parent about it.
584 parent_
->NotifyDialogResult(DesktopMediaID());
588 void DesktopMediaPickerDialogView::OnSelectionChanged() {
589 GetDialogClientView()->UpdateDialogButtons();
592 void DesktopMediaPickerDialogView::OnDoubleClick() {
593 // This will call Accept() and close the dialog.
594 GetDialogClientView()->AcceptWindow();
597 DesktopMediaPickerViews::DesktopMediaPickerViews()
601 DesktopMediaPickerViews::~DesktopMediaPickerViews() {
603 dialog_
->DetachParent();
604 dialog_
->GetWidget()->Close();
608 void DesktopMediaPickerViews::Show(gfx::NativeWindow context
,
609 gfx::NativeWindow parent
,
610 const base::string16
& app_name
,
611 scoped_ptr
<DesktopMediaList
> media_list
,
612 const DoneCallback
& done_callback
) {
613 callback_
= done_callback
;
614 dialog_
= new DesktopMediaPickerDialogView(
615 context
, parent
, this, app_name
, media_list
.Pass());
618 void DesktopMediaPickerViews::NotifyDialogResult(
619 DesktopMediaID source
) {
620 // Once this method is called the |dialog_| will close and destroy itself.
621 dialog_
->DetachParent();
624 DCHECK(!callback_
.is_null());
626 // Notify the |callback_| asynchronously because it may need to destroy
627 // DesktopMediaPicker.
628 content::BrowserThread::PostTask(
629 content::BrowserThread::UI
, FROM_HERE
,
630 base::Bind(callback_
, source
));
637 scoped_ptr
<DesktopMediaPicker
> DesktopMediaPicker::Create() {
638 return scoped_ptr
<DesktopMediaPicker
>(new DesktopMediaPickerViews());