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/aura/window_tree_host.h"
14 #include "ui/base/l10n/l10n_util.h"
15 #include "ui/events/keycodes/keyboard_codes.h"
16 #include "ui/gfx/canvas.h"
17 #include "ui/native_theme/native_theme.h"
18 #include "ui/views/background.h"
19 #include "ui/views/controls/image_view.h"
20 #include "ui/views/controls/label.h"
21 #include "ui/views/controls/scroll_view.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"
27 #include "ui/wm/core/shadow_types.h"
29 using content::DesktopMediaID
;
33 const int kThumbnailWidth
= 160;
34 const int kThumbnailHeight
= 100;
35 const int kThumbnailMargin
= 10;
36 const int kLabelHeight
= 40;
37 const int kListItemWidth
= kThumbnailMargin
* 2 + kThumbnailWidth
;
38 const int kListItemHeight
=
39 kThumbnailMargin
* 2 + kThumbnailHeight
+ kLabelHeight
;
40 const int kListColumns
= 3;
41 const int kTotalListWidth
= kListColumns
* kListItemWidth
;
43 const int kDesktopMediaSourceViewGroupId
= 1;
45 const char kDesktopMediaSourceViewClassName
[] =
46 "DesktopMediaPicker_DesktopMediaSourceView";
48 content::DesktopMediaID::Id
AcceleratedWidgetToDesktopMediaId(
49 gfx::AcceleratedWidget accelerated_widget
) {
51 return reinterpret_cast<content::DesktopMediaID::Id
>(accelerated_widget
);
53 return static_cast<content::DesktopMediaID::Id
>(accelerated_widget
);
57 class DesktopMediaListView
;
58 class DesktopMediaPickerDialogView
;
59 class DesktopMediaPickerViews
;
61 // View used for each item in DesktopMediaListView. Shows a single desktop media
62 // source as a thumbnail with the title under it.
63 class DesktopMediaSourceView
: public views::View
{
65 DesktopMediaSourceView(DesktopMediaListView
* parent
,
66 DesktopMediaID source_id
);
67 virtual ~DesktopMediaSourceView();
69 // Updates thumbnail and title from |source|.
70 void SetName(const base::string16
& name
);
71 void SetThumbnail(const gfx::ImageSkia
& thumbnail
);
73 // Id for the source shown by this View.
74 const DesktopMediaID
& source_id() const {
78 // Returns true if the source is selected.
79 bool is_selected() const { return selected_
; }
81 // Updates selection state of the element. If |selected| is true then also
82 // calls SetSelected(false) for the source view that was selected before that
84 void SetSelected(bool selected
);
86 // views::View interface.
87 virtual const char* GetClassName() const OVERRIDE
;
88 virtual void Layout() OVERRIDE
;
89 virtual views::View
* GetSelectedViewForGroup(int group
) OVERRIDE
;
90 virtual bool IsGroupFocusTraversable() const OVERRIDE
;
91 virtual void OnPaint(gfx::Canvas
* canvas
) OVERRIDE
;
92 virtual void OnFocus() OVERRIDE
;
93 virtual void OnBlur() OVERRIDE
;
94 virtual bool OnMousePressed(const ui::MouseEvent
& event
) OVERRIDE
;
97 DesktopMediaListView
* parent_
;
98 DesktopMediaID source_id_
;
100 views::ImageView
* image_view_
;
101 views::Label
* label_
;
105 DISALLOW_COPY_AND_ASSIGN(DesktopMediaSourceView
);
108 // View that shows list of desktop media sources available from
110 class DesktopMediaListView
: public views::View
,
111 public DesktopMediaListObserver
{
113 DesktopMediaListView(DesktopMediaPickerDialogView
* parent
,
114 scoped_ptr
<DesktopMediaList
> media_list
);
115 virtual ~DesktopMediaListView();
117 void StartUpdating(content::DesktopMediaID::Id dialog_window_id
);
119 // Called by DesktopMediaSourceView when selection has changed.
120 void OnSelectionChanged();
122 // Called by DesktopMediaSourceView when a source has been double-clicked.
123 void OnDoubleClick();
125 // Returns currently selected source.
126 DesktopMediaSourceView
* GetSelection();
128 // views::View overrides.
129 virtual gfx::Size
GetPreferredSize() const OVERRIDE
;
130 virtual void Layout() OVERRIDE
;
131 virtual bool OnKeyPressed(const ui::KeyEvent
& event
) OVERRIDE
;
134 // DesktopMediaList::Observer interface
135 virtual void OnSourceAdded(int index
) OVERRIDE
;
136 virtual void OnSourceRemoved(int index
) OVERRIDE
;
137 virtual void OnSourceMoved(int old_index
, int new_index
) OVERRIDE
;
138 virtual void OnSourceNameChanged(int index
) OVERRIDE
;
139 virtual void OnSourceThumbnailChanged(int index
) OVERRIDE
;
141 DesktopMediaPickerDialogView
* parent_
;
142 scoped_ptr
<DesktopMediaList
> media_list_
;
144 DISALLOW_COPY_AND_ASSIGN(DesktopMediaListView
);
147 // Dialog view used for DesktopMediaPickerViews.
148 class DesktopMediaPickerDialogView
: public views::DialogDelegateView
{
150 DesktopMediaPickerDialogView(gfx::NativeWindow context
,
151 gfx::NativeWindow parent_window
,
152 DesktopMediaPickerViews
* parent
,
153 const base::string16
& app_name
,
154 const base::string16
& target_name
,
155 scoped_ptr
<DesktopMediaList
> media_list
);
156 virtual ~DesktopMediaPickerDialogView();
158 // Called by parent (DesktopMediaPickerViews) when it's destroyed.
161 // Called by DesktopMediaListView.
162 void OnSelectionChanged();
163 void OnDoubleClick();
165 // views::View overrides.
166 virtual gfx::Size
GetPreferredSize() const OVERRIDE
;
167 virtual void Layout() OVERRIDE
;
169 // views::DialogDelegateView overrides.
170 virtual base::string16
GetWindowTitle() const OVERRIDE
;
171 virtual bool IsDialogButtonEnabled(ui::DialogButton button
) const OVERRIDE
;
172 virtual base::string16
GetDialogButtonLabel(
173 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 const base::string16
& target_name
,
201 scoped_ptr
<DesktopMediaList
> media_list
,
202 const DoneCallback
& done_callback
) OVERRIDE
;
205 DoneCallback callback_
;
207 // The |dialog_| is owned by the corresponding views::Widget instance.
208 // When DesktopMediaPickerViews is destroyed the |dialog_| is destroyed
209 // asynchronously by closing the widget.
210 DesktopMediaPickerDialogView
* dialog_
;
212 DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerViews
);
215 DesktopMediaSourceView::DesktopMediaSourceView(
216 DesktopMediaListView
* parent
,
217 DesktopMediaID source_id
)
219 source_id_(source_id
),
220 image_view_(new views::ImageView()),
221 label_(new views::Label()),
223 AddChildView(image_view_
);
224 AddChildView(label_
);
228 DesktopMediaSourceView::~DesktopMediaSourceView() {}
230 void DesktopMediaSourceView::SetName(const base::string16
& name
) {
231 label_
->SetText(name
);
234 void DesktopMediaSourceView::SetThumbnail(const gfx::ImageSkia
& thumbnail
) {
235 image_view_
->SetImage(thumbnail
);
238 void DesktopMediaSourceView::SetSelected(bool selected
) {
239 if (selected
== selected_
)
241 selected_
= selected
;
244 // Unselect all other sources.
246 parent()->GetViewsInGroup(GetGroup(), &neighbours
);
247 for (Views::iterator
i(neighbours
.begin()); i
!= neighbours
.end(); ++i
) {
249 DCHECK_EQ((*i
)->GetClassName(), kDesktopMediaSourceViewClassName
);
250 DesktopMediaSourceView
* source_view
=
251 static_cast<DesktopMediaSourceView
*>(*i
);
252 source_view
->SetSelected(false);
256 const SkColor bg_color
= GetNativeTheme()->GetSystemColor(
257 ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor
);
258 set_background(views::Background::CreateSolidBackground(bg_color
));
260 parent_
->OnSelectionChanged();
262 set_background(NULL
);
268 const char* DesktopMediaSourceView::GetClassName() const {
269 return kDesktopMediaSourceViewClassName
;
272 void DesktopMediaSourceView::Layout() {
273 image_view_
->SetBounds(kThumbnailMargin
, kThumbnailMargin
,
274 kThumbnailWidth
, kThumbnailHeight
);
275 label_
->SetBounds(kThumbnailMargin
, kThumbnailHeight
+ kThumbnailMargin
,
276 kThumbnailWidth
, kLabelHeight
);
279 views::View
* DesktopMediaSourceView::GetSelectedViewForGroup(int group
) {
281 parent()->GetViewsInGroup(group
, &neighbours
);
282 if (neighbours
.empty())
285 for (Views::iterator
i(neighbours
.begin()); i
!= neighbours
.end(); ++i
) {
286 DCHECK_EQ((*i
)->GetClassName(), kDesktopMediaSourceViewClassName
);
287 DesktopMediaSourceView
* source_view
=
288 static_cast<DesktopMediaSourceView
*>(*i
);
289 if (source_view
->selected_
)
295 bool DesktopMediaSourceView::IsGroupFocusTraversable() const {
299 void DesktopMediaSourceView::OnPaint(gfx::Canvas
* canvas
) {
300 View::OnPaint(canvas
);
302 gfx::Rect
bounds(GetLocalBounds());
303 bounds
.Inset(kThumbnailMargin
/ 2, kThumbnailMargin
/ 2);
304 canvas
->DrawFocusRect(bounds
);
308 void DesktopMediaSourceView::OnFocus() {
311 ScrollRectToVisible(gfx::Rect(size()));
312 // We paint differently when focused.
316 void DesktopMediaSourceView::OnBlur() {
318 // We paint differently when focused.
322 bool DesktopMediaSourceView::OnMousePressed(const ui::MouseEvent
& event
) {
323 if (event
.GetClickCount() == 1) {
325 } else if (event
.GetClickCount() == 2) {
327 parent_
->OnDoubleClick();
332 DesktopMediaListView::DesktopMediaListView(
333 DesktopMediaPickerDialogView
* parent
,
334 scoped_ptr
<DesktopMediaList
> media_list
)
336 media_list_(media_list
.Pass()) {
337 media_list_
->SetThumbnailSize(gfx::Size(kThumbnailWidth
, kThumbnailHeight
));
340 DesktopMediaListView::~DesktopMediaListView() {}
342 void DesktopMediaListView::StartUpdating(
343 content::DesktopMediaID::Id dialog_window_id
) {
344 media_list_
->SetViewDialogWindowId(dialog_window_id
);
345 media_list_
->StartUpdating(this);
348 void DesktopMediaListView::OnSelectionChanged() {
349 parent_
->OnSelectionChanged();
352 void DesktopMediaListView::OnDoubleClick() {
353 parent_
->OnDoubleClick();
356 DesktopMediaSourceView
* DesktopMediaListView::GetSelection() {
357 for (int i
= 0; i
< child_count(); ++i
) {
358 DesktopMediaSourceView
* source_view
=
359 static_cast<DesktopMediaSourceView
*>(child_at(i
));
360 DCHECK_EQ(source_view
->GetClassName(), kDesktopMediaSourceViewClassName
);
361 if (source_view
->is_selected())
367 gfx::Size
DesktopMediaListView::GetPreferredSize() const {
368 int total_rows
= (child_count() + kListColumns
- 1) / kListColumns
;
369 return gfx::Size(kTotalListWidth
, kListItemHeight
* total_rows
);
372 void DesktopMediaListView::Layout() {
376 for (int i
= 0; i
< child_count(); ++i
) {
377 if (x
+ kListItemWidth
> kTotalListWidth
) {
379 y
+= kListItemHeight
;
382 View
* source_view
= child_at(i
);
383 source_view
->SetBounds(x
, y
, kListItemWidth
, kListItemHeight
);
388 y
+= kListItemHeight
;
389 SetSize(gfx::Size(kTotalListWidth
, y
));
392 bool DesktopMediaListView::OnKeyPressed(const ui::KeyEvent
& event
) {
393 int position_increment
= 0;
394 switch (event
.key_code()) {
396 position_increment
= -kListColumns
;
399 position_increment
= kListColumns
;
402 position_increment
= -1;
405 position_increment
= 1;
411 if (position_increment
!= 0) {
412 DesktopMediaSourceView
* selected
= GetSelection();
413 DesktopMediaSourceView
* new_selected
= NULL
;
416 int index
= GetIndexOf(selected
);
417 int new_index
= index
+ position_increment
;
418 if (new_index
>= child_count())
419 new_index
= child_count() - 1;
420 else if (new_index
< 0)
422 if (index
!= new_index
) {
424 static_cast<DesktopMediaSourceView
*>(child_at(new_index
));
426 } else if (has_children()) {
427 new_selected
= static_cast<DesktopMediaSourceView
*>(child_at(0));
431 GetFocusManager()->SetFocusedView(new_selected
);
440 void DesktopMediaListView::OnSourceAdded(int index
) {
441 const DesktopMediaList::Source
& source
= media_list_
->GetSource(index
);
442 DesktopMediaSourceView
* source_view
=
443 new DesktopMediaSourceView(this, source
.id
);
444 source_view
->SetName(source
.name
);
445 source_view
->SetGroup(kDesktopMediaSourceViewGroupId
);
446 AddChildViewAt(source_view
, index
);
448 PreferredSizeChanged();
451 void DesktopMediaListView::OnSourceRemoved(int index
) {
452 DesktopMediaSourceView
* view
=
453 static_cast<DesktopMediaSourceView
*>(child_at(index
));
455 DCHECK_EQ(view
->GetClassName(), kDesktopMediaSourceViewClassName
);
456 bool was_selected
= view
->is_selected();
457 RemoveChildView(view
);
461 OnSelectionChanged();
463 PreferredSizeChanged();
466 void DesktopMediaListView::OnSourceMoved(int old_index
, int new_index
) {
467 DesktopMediaSourceView
* view
=
468 static_cast<DesktopMediaSourceView
*>(child_at(old_index
));
469 ReorderChildView(view
, new_index
);
470 PreferredSizeChanged();
473 void DesktopMediaListView::OnSourceNameChanged(int index
) {
474 const DesktopMediaList::Source
& source
= media_list_
->GetSource(index
);
475 DesktopMediaSourceView
* source_view
=
476 static_cast<DesktopMediaSourceView
*>(child_at(index
));
477 source_view
->SetName(source
.name
);
480 void DesktopMediaListView::OnSourceThumbnailChanged(int index
) {
481 const DesktopMediaList::Source
& source
= media_list_
->GetSource(index
);
482 DesktopMediaSourceView
* source_view
=
483 static_cast<DesktopMediaSourceView
*>(child_at(index
));
484 source_view
->SetThumbnail(source
.thumbnail
);
487 DesktopMediaPickerDialogView::DesktopMediaPickerDialogView(
488 gfx::NativeWindow context
,
489 gfx::NativeWindow parent_window
,
490 DesktopMediaPickerViews
* parent
,
491 const base::string16
& app_name
,
492 const base::string16
& target_name
,
493 scoped_ptr
<DesktopMediaList
> media_list
)
496 label_(new views::Label()),
497 scroll_view_(views::ScrollView::CreateScrollViewWithBorder()),
498 list_view_(new DesktopMediaListView(this, media_list
.Pass())) {
499 if (app_name
== target_name
) {
501 l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TEXT
, app_name
));
503 label_
->SetText(l10n_util::GetStringFUTF16(
504 IDS_DESKTOP_MEDIA_PICKER_TEXT_DELEGATED
, app_name
, target_name
));
506 label_
->SetMultiLine(true);
507 label_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
508 AddChildView(label_
);
510 scroll_view_
->SetContents(list_view_
);
511 AddChildView(scroll_view_
);
513 DialogDelegate::CreateDialogWidget(this, context
, parent_window
);
515 // DesktopMediaList needs to know the ID of the picker window which
516 // matches the ID it gets from the OS. Depending on the OS and configuration
517 // we get this ID differently.
518 content::DesktopMediaID::Id dialog_window_id
= 0;
521 if (chrome::IsNativeWindowInAsh(GetWidget()->GetNativeWindow())) {
522 dialog_window_id
= content::DesktopMediaID::RegisterAuraWindow(
523 GetWidget()->GetNativeWindow()).id
;
527 dialog_window_id
= AcceleratedWidgetToDesktopMediaId(
528 GetWidget()->GetNativeWindow()->GetHost()->
529 GetAcceleratedWidget());
532 list_view_
->StartUpdating(dialog_window_id
);
537 DesktopMediaPickerDialogView::~DesktopMediaPickerDialogView() {}
539 void DesktopMediaPickerDialogView::DetachParent() {
543 gfx::Size
DesktopMediaPickerDialogView::GetPreferredSize() const {
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 base::string16
DesktopMediaPickerDialogView::GetDialogButtonLabel(
573 ui::DialogButton button
) const {
574 return l10n_util::GetStringUTF16(button
== ui::DIALOG_BUTTON_OK
?
575 IDS_DESKTOP_MEDIA_PICKER_SHARE
: IDS_CANCEL
);
578 bool DesktopMediaPickerDialogView::Accept() {
579 DesktopMediaSourceView
* selection
= list_view_
->GetSelection();
581 // Ok button should only be enabled when a source is selected.
584 DesktopMediaID source
;
586 source
= selection
->source_id();
589 parent_
->NotifyDialogResult(source
);
591 // Return true to close the window.
595 void DesktopMediaPickerDialogView::DeleteDelegate() {
596 // If the dialog is being closed then notify the parent about it.
598 parent_
->NotifyDialogResult(DesktopMediaID());
602 void DesktopMediaPickerDialogView::OnSelectionChanged() {
603 GetDialogClientView()->UpdateDialogButtons();
606 void DesktopMediaPickerDialogView::OnDoubleClick() {
607 // This will call Accept() and close the dialog.
608 GetDialogClientView()->AcceptWindow();
611 DesktopMediaPickerViews::DesktopMediaPickerViews()
615 DesktopMediaPickerViews::~DesktopMediaPickerViews() {
617 dialog_
->DetachParent();
618 dialog_
->GetWidget()->Close();
622 void DesktopMediaPickerViews::Show(gfx::NativeWindow context
,
623 gfx::NativeWindow parent
,
624 const base::string16
& app_name
,
625 const base::string16
& target_name
,
626 scoped_ptr
<DesktopMediaList
> media_list
,
627 const DoneCallback
& done_callback
) {
628 callback_
= done_callback
;
629 dialog_
= new DesktopMediaPickerDialogView(
630 context
, parent
, this, app_name
, target_name
, media_list
.Pass());
633 void DesktopMediaPickerViews::NotifyDialogResult(
634 DesktopMediaID source
) {
635 // Once this method is called the |dialog_| will close and destroy itself.
636 dialog_
->DetachParent();
639 DCHECK(!callback_
.is_null());
641 // Notify the |callback_| asynchronously because it may need to destroy
642 // DesktopMediaPicker.
643 content::BrowserThread::PostTask(
644 content::BrowserThread::UI
, FROM_HERE
,
645 base::Bind(callback_
, source
));
652 scoped_ptr
<DesktopMediaPicker
> DesktopMediaPicker::Create() {
653 return scoped_ptr
<DesktopMediaPicker
>(new DesktopMediaPickerViews());