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 OnSourceMoved(int old_index
, int new_index
) OVERRIDE
;
141 virtual void OnSourceNameChanged(int index
) OVERRIDE
;
142 virtual void OnSourceThumbnailChanged(int index
) OVERRIDE
;
144 DesktopMediaPickerDialogView
* parent_
;
145 scoped_ptr
<DesktopMediaList
> media_list_
;
147 DISALLOW_COPY_AND_ASSIGN(DesktopMediaListView
);
150 // Dialog view used for DesktopMediaPickerViews.
151 class DesktopMediaPickerDialogView
: public views::DialogDelegateView
{
153 DesktopMediaPickerDialogView(gfx::NativeWindow context
,
154 gfx::NativeWindow parent_window
,
155 DesktopMediaPickerViews
* parent
,
156 const base::string16
& app_name
,
157 scoped_ptr
<DesktopMediaList
> media_list
);
158 virtual ~DesktopMediaPickerDialogView();
160 // Called by parent (DesktopMediaPickerViews) when it's destroyed.
163 // Called by DesktopMediaListView.
164 void OnSelectionChanged();
165 void OnDoubleClick();
167 // views::View overrides.
168 virtual gfx::Size
GetPreferredSize() OVERRIDE
;
169 virtual void Layout() OVERRIDE
;
171 // views::DialogDelegateView overrides.
172 virtual base::string16
GetWindowTitle() const OVERRIDE
;
173 virtual bool IsDialogButtonEnabled(ui::DialogButton button
) const OVERRIDE
;
174 virtual bool Accept() OVERRIDE
;
175 virtual void DeleteDelegate() OVERRIDE
;
178 DesktopMediaPickerViews
* parent_
;
179 base::string16 app_name_
;
181 views::Label
* label_
;
182 views::ScrollView
* scroll_view_
;
183 DesktopMediaListView
* list_view_
;
185 DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerDialogView
);
188 // Implementation of DesktopMediaPicker for Views.
189 class DesktopMediaPickerViews
: public DesktopMediaPicker
{
191 DesktopMediaPickerViews();
192 virtual ~DesktopMediaPickerViews();
194 void NotifyDialogResult(DesktopMediaID source
);
196 // DesktopMediaPicker overrides.
197 virtual void Show(gfx::NativeWindow context
,
198 gfx::NativeWindow parent
,
199 const base::string16
& app_name
,
200 scoped_ptr
<DesktopMediaList
> media_list
,
201 const DoneCallback
& done_callback
) OVERRIDE
;
204 DoneCallback callback_
;
206 // The |dialog_| is owned by the corresponding views::Widget instance.
207 // When DesktopMediaPickerViews is destroyed the |dialog_| is destroyed
208 // asynchronously by closing the widget.
209 DesktopMediaPickerDialogView
* dialog_
;
211 DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerViews
);
214 DesktopMediaSourceView::DesktopMediaSourceView(
215 DesktopMediaListView
* parent
,
216 DesktopMediaID source_id
)
218 source_id_(source_id
),
219 image_view_(new views::ImageView()),
220 label_(new views::Label()),
222 AddChildView(image_view_
);
223 AddChildView(label_
);
227 DesktopMediaSourceView::~DesktopMediaSourceView() {}
229 void DesktopMediaSourceView::SetName(const base::string16
& name
) {
230 label_
->SetText(name
);
233 void DesktopMediaSourceView::SetThumbnail(const gfx::ImageSkia
& thumbnail
) {
234 image_view_
->SetImage(thumbnail
);
237 void DesktopMediaSourceView::SetSelected(bool selected
) {
238 if (selected
== selected_
)
240 selected_
= selected
;
243 // Unselect all other sources.
245 parent()->GetViewsInGroup(GetGroup(), &neighbours
);
246 for (Views::iterator
i(neighbours
.begin()); i
!= neighbours
.end(); ++i
) {
248 DCHECK_EQ((*i
)->GetClassName(), kDesktopMediaSourceViewClassName
);
249 DesktopMediaSourceView
* source_view
=
250 static_cast<DesktopMediaSourceView
*>(*i
);
251 source_view
->SetSelected(false);
255 const SkColor bg_color
= GetNativeTheme()->GetSystemColor(
256 ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor
);
257 set_background(views::Background::CreateSolidBackground(bg_color
));
259 parent_
->OnSelectionChanged();
261 set_background(NULL
);
267 const char* DesktopMediaSourceView::GetClassName() const {
268 return kDesktopMediaSourceViewClassName
;
271 void DesktopMediaSourceView::Layout() {
272 image_view_
->SetBounds(kThumbnailMargin
, kThumbnailMargin
,
273 kThumbnailWidth
, kThumbnailHeight
);
274 label_
->SetBounds(kThumbnailMargin
, kThumbnailHeight
+ kThumbnailMargin
,
275 kThumbnailWidth
, kLabelHeight
);
278 views::View
* DesktopMediaSourceView::GetSelectedViewForGroup(int group
) {
280 parent()->GetViewsInGroup(group
, &neighbours
);
281 if (neighbours
.empty())
284 for (Views::iterator
i(neighbours
.begin()); i
!= neighbours
.end(); ++i
) {
285 DCHECK_EQ((*i
)->GetClassName(), kDesktopMediaSourceViewClassName
);
286 DesktopMediaSourceView
* source_view
=
287 static_cast<DesktopMediaSourceView
*>(*i
);
288 if (source_view
->selected_
)
294 bool DesktopMediaSourceView::IsGroupFocusTraversable() const {
298 void DesktopMediaSourceView::OnPaint(gfx::Canvas
* canvas
) {
299 View::OnPaint(canvas
);
301 gfx::Rect
bounds(GetLocalBounds());
302 bounds
.Inset(kThumbnailMargin
/ 2, kThumbnailMargin
/ 2);
303 canvas
->DrawFocusRect(bounds
);
307 void DesktopMediaSourceView::OnFocus() {
310 ScrollRectToVisible(gfx::Rect(size()));
311 // We paint differently when focused.
315 void DesktopMediaSourceView::OnBlur() {
317 // We paint differently when focused.
321 bool DesktopMediaSourceView::OnMousePressed(const ui::MouseEvent
& event
) {
322 if (event
.GetClickCount() == 1) {
324 } else if (event
.GetClickCount() == 2) {
326 parent_
->OnDoubleClick();
331 DesktopMediaListView::DesktopMediaListView(
332 DesktopMediaPickerDialogView
* parent
,
333 scoped_ptr
<DesktopMediaList
> media_list
)
335 media_list_(media_list
.Pass()) {
336 media_list_
->SetThumbnailSize(gfx::Size(kThumbnailWidth
, kThumbnailHeight
));
339 DesktopMediaListView::~DesktopMediaListView() {}
341 void DesktopMediaListView::StartUpdating(
342 content::DesktopMediaID::Id dialog_window_id
) {
343 media_list_
->SetViewDialogWindowId(dialog_window_id
);
344 media_list_
->StartUpdating(this);
347 void DesktopMediaListView::OnSelectionChanged() {
348 parent_
->OnSelectionChanged();
351 void DesktopMediaListView::OnDoubleClick() {
352 parent_
->OnDoubleClick();
355 DesktopMediaSourceView
* DesktopMediaListView::GetSelection() {
356 for (int i
= 0; i
< child_count(); ++i
) {
357 DesktopMediaSourceView
* source_view
=
358 static_cast<DesktopMediaSourceView
*>(child_at(i
));
359 DCHECK_EQ(source_view
->GetClassName(), kDesktopMediaSourceViewClassName
);
360 if (source_view
->is_selected())
366 gfx::Size
DesktopMediaListView::GetPreferredSize() {
367 int total_rows
= (child_count() + kListColumns
- 1) / kListColumns
;
368 return gfx::Size(kTotalListWidth
, kListItemHeight
* total_rows
);
371 void DesktopMediaListView::Layout() {
375 for (int i
= 0; i
< child_count(); ++i
) {
376 if (x
+ kListItemWidth
> kTotalListWidth
) {
378 y
+= kListItemHeight
;
381 View
* source_view
= child_at(i
);
382 source_view
->SetBounds(x
, y
, kListItemWidth
, kListItemHeight
);
387 y
+= kListItemHeight
;
388 SetSize(gfx::Size(kTotalListWidth
, y
));
391 bool DesktopMediaListView::OnKeyPressed(const ui::KeyEvent
& event
) {
392 int position_increment
= 0;
393 switch (event
.key_code()) {
395 position_increment
= -kListColumns
;
398 position_increment
= kListColumns
;
401 position_increment
= -1;
404 position_increment
= 1;
410 if (position_increment
!= 0) {
411 DesktopMediaSourceView
* selected
= GetSelection();
412 DesktopMediaSourceView
* new_selected
= NULL
;
415 int index
= GetIndexOf(selected
);
416 int new_index
= index
+ position_increment
;
417 if (new_index
>= child_count())
418 new_index
= child_count() - 1;
419 else if (new_index
< 0)
421 if (index
!= new_index
) {
423 static_cast<DesktopMediaSourceView
*>(child_at(new_index
));
425 } else if (has_children()) {
426 new_selected
= static_cast<DesktopMediaSourceView
*>(child_at(0));
430 GetFocusManager()->SetFocusedView(new_selected
);
439 void DesktopMediaListView::OnSourceAdded(int index
) {
440 const DesktopMediaList::Source
& source
= media_list_
->GetSource(index
);
441 DesktopMediaSourceView
* source_view
=
442 new DesktopMediaSourceView(this, source
.id
);
443 source_view
->SetName(source
.name
);
444 source_view
->SetGroup(kDesktopMediaSourceViewGroupId
);
445 AddChildViewAt(source_view
, index
);
447 PreferredSizeChanged();
450 void DesktopMediaListView::OnSourceRemoved(int index
) {
451 DesktopMediaSourceView
* view
=
452 static_cast<DesktopMediaSourceView
*>(child_at(index
));
454 DCHECK_EQ(view
->GetClassName(), kDesktopMediaSourceViewClassName
);
455 bool was_selected
= view
->is_selected();
456 RemoveChildView(view
);
460 OnSelectionChanged();
462 PreferredSizeChanged();
465 void DesktopMediaListView::OnSourceMoved(int old_index
, int new_index
) {
466 DesktopMediaSourceView
* view
=
467 static_cast<DesktopMediaSourceView
*>(child_at(old_index
));
468 ReorderChildView(view
, new_index
);
469 PreferredSizeChanged();
472 void DesktopMediaListView::OnSourceNameChanged(int index
) {
473 const DesktopMediaList::Source
& source
= media_list_
->GetSource(index
);
474 DesktopMediaSourceView
* source_view
=
475 static_cast<DesktopMediaSourceView
*>(child_at(index
));
476 source_view
->SetName(source
.name
);
479 void DesktopMediaListView::OnSourceThumbnailChanged(int index
) {
480 const DesktopMediaList::Source
& source
= media_list_
->GetSource(index
);
481 DesktopMediaSourceView
* source_view
=
482 static_cast<DesktopMediaSourceView
*>(child_at(index
));
483 source_view
->SetThumbnail(source
.thumbnail
);
486 DesktopMediaPickerDialogView::DesktopMediaPickerDialogView(
487 gfx::NativeWindow context
,
488 gfx::NativeWindow parent_window
,
489 DesktopMediaPickerViews
* parent
,
490 const base::string16
& app_name
,
491 scoped_ptr
<DesktopMediaList
> media_list
)
494 label_(new views::Label()),
495 scroll_view_(views::ScrollView::CreateScrollViewWithBorder()),
496 list_view_(new DesktopMediaListView(this, media_list
.Pass())) {
498 l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TEXT
, app_name_
));
499 label_
->SetMultiLine(true);
500 label_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
501 AddChildView(label_
);
503 scroll_view_
->SetContents(list_view_
);
504 AddChildView(scroll_view_
);
506 DialogDelegate::CreateDialogWidget(this, context
, parent_window
);
508 // DesktopMediaList needs to know the ID of the picker window which
509 // matches the ID it gets from the OS. Depending on the OS and configuration
510 // we get this ID differently.
511 content::DesktopMediaID::Id dialog_window_id
= 0;
513 #if defined(USE_AURA)
516 if (chrome::IsNativeWindowInAsh(GetWidget()->GetNativeWindow())) {
517 dialog_window_id
= content::DesktopMediaID::RegisterAuraWindow(
518 GetWidget()->GetNativeWindow()).id
;
522 dialog_window_id
= AcceleratedWidgetToDesktopMediaId(
523 GetWidget()->GetNativeWindow()->GetDispatcher()->host()->
524 GetAcceleratedWidget());
527 #else // defined(USE_AURA)
528 dialog_window_id
= 0;
530 #endif // !defined(USE_AURA)
532 list_view_
->StartUpdating(dialog_window_id
);
537 DesktopMediaPickerDialogView::~DesktopMediaPickerDialogView() {}
539 void DesktopMediaPickerDialogView::DetachParent() {
543 gfx::Size
DesktopMediaPickerDialogView::GetPreferredSize() {
544 return gfx::Size(600, 500);
547 void DesktopMediaPickerDialogView::Layout() {
548 gfx::Rect rect
= GetLocalBounds();
549 rect
.Inset(views::kPanelHorizMargin
, views::kPanelVertMargin
);
551 gfx::Rect
label_rect(rect
.x(), rect
.y(), rect
.width(),
552 label_
->GetHeightForWidth(rect
.width()));
553 label_
->SetBoundsRect(label_rect
);
555 int scroll_view_top
= label_rect
.bottom() + views::kPanelVerticalSpacing
;
556 scroll_view_
->SetBounds(
557 rect
.x(), scroll_view_top
,
558 rect
.width(), rect
.height() - scroll_view_top
);
561 base::string16
DesktopMediaPickerDialogView::GetWindowTitle() const {
562 return l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TITLE
, app_name_
);
565 bool DesktopMediaPickerDialogView::IsDialogButtonEnabled(
566 ui::DialogButton button
) const {
567 if (button
== ui::DIALOG_BUTTON_OK
)
568 return list_view_
->GetSelection() != NULL
;
572 bool DesktopMediaPickerDialogView::Accept() {
573 DesktopMediaSourceView
* selection
= list_view_
->GetSelection();
575 // Ok button should only be enabled when a source is selected.
578 DesktopMediaID source
;
580 source
= selection
->source_id();
583 parent_
->NotifyDialogResult(source
);
585 // Return true to close the window.
589 void DesktopMediaPickerDialogView::DeleteDelegate() {
590 // If the dialog is being closed then notify the parent about it.
592 parent_
->NotifyDialogResult(DesktopMediaID());
596 void DesktopMediaPickerDialogView::OnSelectionChanged() {
597 GetDialogClientView()->UpdateDialogButtons();
600 void DesktopMediaPickerDialogView::OnDoubleClick() {
601 // This will call Accept() and close the dialog.
602 GetDialogClientView()->AcceptWindow();
605 DesktopMediaPickerViews::DesktopMediaPickerViews()
609 DesktopMediaPickerViews::~DesktopMediaPickerViews() {
611 dialog_
->DetachParent();
612 dialog_
->GetWidget()->Close();
616 void DesktopMediaPickerViews::Show(gfx::NativeWindow context
,
617 gfx::NativeWindow parent
,
618 const base::string16
& app_name
,
619 scoped_ptr
<DesktopMediaList
> media_list
,
620 const DoneCallback
& done_callback
) {
621 callback_
= done_callback
;
622 dialog_
= new DesktopMediaPickerDialogView(
623 context
, parent
, this, app_name
, media_list
.Pass());
626 void DesktopMediaPickerViews::NotifyDialogResult(
627 DesktopMediaID source
) {
628 // Once this method is called the |dialog_| will close and destroy itself.
629 dialog_
->DetachParent();
632 DCHECK(!callback_
.is_null());
634 // Notify the |callback_| asynchronously because it may need to destroy
635 // DesktopMediaPicker.
636 content::BrowserThread::PostTask(
637 content::BrowserThread::UI
, FROM_HERE
,
638 base::Bind(callback_
, source
));
645 scoped_ptr
<DesktopMediaPicker
> DesktopMediaPicker::Create() {
646 return scoped_ptr
<DesktopMediaPicker
>(new DesktopMediaPickerViews());