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"
8 #include "base/callback.h"
9 #include "chrome/browser/media/desktop_media_picker_model.h"
10 #include "content/public/browser/browser_thread.h"
11 #include "grit/generated_resources.h"
12 #include "ui/base/keycodes/keyboard_codes.h"
13 #include "ui/base/l10n/l10n_util.h"
14 #include "ui/native_theme/native_theme.h"
15 #include "ui/views/controls/image_view.h"
16 #include "ui/views/controls/label.h"
17 #include "ui/views/controls/scroll_view.h"
18 #include "ui/views/corewm/shadow_types.h"
19 #include "ui/views/focus_border.h"
20 #include "ui/views/layout/box_layout.h"
21 #include "ui/views/layout/layout_constants.h"
22 #include "ui/views/widget/widget.h"
23 #include "ui/views/window/dialog_client_view.h"
24 #include "ui/views/window/dialog_delegate.h"
26 using content::DesktopMediaID
;
30 const int kThumbnailWidth
= 160;
31 const int kThumbnailHeight
= 100;
32 const int kThumbnailMargin
= 10;
33 const int kLabelHeight
= 40;
34 const int kListItemWidth
= kThumbnailMargin
* 2 + kThumbnailWidth
;
35 const int kListItemHeight
=
36 kThumbnailMargin
* 2 + kThumbnailHeight
+ kLabelHeight
;
37 const int kListColumns
= 3;
38 const int kTotalListWidth
= kListColumns
* kListItemWidth
;
40 const int kDesktopMediaSourceViewGroupId
= 1;
42 const char kDesktopMediaSourceViewClassName
[] =
43 "DesktopMediaPicker_DesktopMediaSourceView";
45 class DesktopMediaListView
;
46 class DesktopMediaPickerDialogView
;
47 class DesktopMediaPickerViews
;
49 // View used for each item in DesktopMediaListView. Shows a single desktop media
50 // source as a thumbnail with the title under it.
51 class DesktopMediaSourceView
: public views::View
{
53 DesktopMediaSourceView(DesktopMediaListView
* parent
,
54 DesktopMediaID source_id
);
55 virtual ~DesktopMediaSourceView();
57 // Updates thumbnail and title from |source|.
58 void SetName(const string16
& name
);
59 void SetThumbnail(const gfx::ImageSkia
& thumbnail
);
61 // Id for the source shown by this View.
62 const DesktopMediaID
& source_id() const {
66 // Returns true if the source is selected.
67 bool is_selected() const { return selected_
; }
69 // Updates selection state of the element. If |selected| is true then also
70 // calls SetSelected(false) for the source view that was selected before that
72 void SetSelected(bool selected
);
74 // views::View interface.
75 virtual const char* GetClassName() const OVERRIDE
;
76 virtual void Layout() OVERRIDE
;
77 virtual views::View
* GetSelectedViewForGroup(int group
) OVERRIDE
;
78 virtual bool IsGroupFocusTraversable() const OVERRIDE
;
79 virtual void OnFocus() OVERRIDE
;
80 virtual bool OnMousePressed(const ui::MouseEvent
& event
) OVERRIDE
;
83 DesktopMediaListView
* parent_
;
84 DesktopMediaID source_id_
;
86 views::ImageView
* image_view_
;
91 DISALLOW_COPY_AND_ASSIGN(DesktopMediaSourceView
);
94 // View that shows list of desktop media sources available from
95 // DesktopMediaPickerModel.
96 class DesktopMediaListView
: public views::View
,
97 public DesktopMediaPickerModel::Observer
{
99 DesktopMediaListView(DesktopMediaPickerDialogView
* parent
,
100 scoped_ptr
<DesktopMediaPickerModel
> model
);
101 virtual ~DesktopMediaListView();
103 // Called by DesktopMediaSourceView when selection has changed.
104 void OnSelectionChanged();
106 // Returns currently selected source.
107 DesktopMediaSourceView
* GetSelection();
109 // views::View overrides.
110 virtual gfx::Size
GetPreferredSize() OVERRIDE
;
111 virtual void Layout() OVERRIDE
;
112 virtual bool OnKeyPressed(const ui::KeyEvent
& event
) OVERRIDE
;
115 // DesktopMediaPickerModel::Observer interface
116 virtual void OnSourceAdded(int index
) OVERRIDE
;
117 virtual void OnSourceRemoved(int index
) OVERRIDE
;
118 virtual void OnSourceNameChanged(int index
) OVERRIDE
;
119 virtual void OnSourceThumbnailChanged(int index
) OVERRIDE
;
121 DesktopMediaPickerDialogView
* parent_
;
122 scoped_ptr
<DesktopMediaPickerModel
> model_
;
124 DISALLOW_COPY_AND_ASSIGN(DesktopMediaListView
);
127 // Dialog view used for DesktopMediaPickerViews.
128 class DesktopMediaPickerDialogView
: public views::DialogDelegateView
{
130 DesktopMediaPickerDialogView(gfx::NativeWindow context
,
131 gfx::NativeWindow parent_window
,
132 DesktopMediaPickerViews
* parent
,
133 const string16
& app_name
,
134 scoped_ptr
<DesktopMediaPickerModel
> model
);
135 virtual ~DesktopMediaPickerDialogView();
137 // Called by parent (DesktopMediaPickerViews) when it's destroyed.
140 // Called by DesktopMediaListView.
141 void OnSelectionChanged();
143 // views::View overrides.
144 virtual gfx::Size
GetPreferredSize() OVERRIDE
;
145 virtual void Layout() OVERRIDE
;
147 // views::DialogDelegateView overrides.
148 virtual base::string16
GetWindowTitle() const OVERRIDE
;
149 virtual bool IsDialogButtonEnabled(ui::DialogButton button
) const OVERRIDE
;
150 virtual bool Accept() OVERRIDE
;
151 virtual void DeleteDelegate() OVERRIDE
;
154 DesktopMediaPickerViews
* parent_
;
157 views::Label
* label_
;
158 views::ScrollView
* scroll_view_
;
159 DesktopMediaListView
* list_view_
;
161 DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerDialogView
);
164 // Implementation of DesktopMediaPicker for Views.
165 class DesktopMediaPickerViews
: public DesktopMediaPicker
{
167 DesktopMediaPickerViews();
168 virtual ~DesktopMediaPickerViews();
170 void NotifyDialogResult(DesktopMediaID source
);
172 // DesktopMediaPicker overrides.
173 virtual void Show(gfx::NativeWindow context
,
174 gfx::NativeWindow parent
,
175 const string16
& app_name
,
176 scoped_ptr
<DesktopMediaPickerModel
> model
,
177 const DoneCallback
& done_callback
) OVERRIDE
;
180 DoneCallback callback_
;
182 // The |dialog_| is owned by the corresponding views::Widget instance.
183 // When DesktopMediaPickerViews is destroyed the |dialog_| is destroyed
184 // asynchronously by closing the widget.
185 DesktopMediaPickerDialogView
* dialog_
;
187 DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerViews
);
190 DesktopMediaSourceView::DesktopMediaSourceView(
191 DesktopMediaListView
* parent
,
192 DesktopMediaID source_id
)
194 source_id_(source_id
),
195 image_view_(new views::ImageView()),
196 label_(new views::Label()),
198 AddChildView(image_view_
);
199 AddChildView(label_
);
203 DesktopMediaSourceView::~DesktopMediaSourceView() {}
205 void DesktopMediaSourceView::SetName(const string16
& name
) {
206 label_
->SetText(name
);
209 void DesktopMediaSourceView::SetThumbnail(const gfx::ImageSkia
& thumbnail
) {
210 image_view_
->SetImage(thumbnail
);
213 void DesktopMediaSourceView::SetSelected(bool selected
) {
214 if (selected
== selected_
)
216 selected_
= selected
;
219 // Unselect all other sources.
221 parent()->GetViewsInGroup(GetGroup(), &neighbours
);
222 for (Views::iterator
i(neighbours
.begin()); i
!= neighbours
.end(); ++i
) {
224 DCHECK_EQ((*i
)->GetClassName(), kDesktopMediaSourceViewClassName
);
225 DesktopMediaSourceView
* source_view
=
226 static_cast<DesktopMediaSourceView
*>(*i
);
227 source_view
->SetSelected(false);
231 const SkColor bg_color
= GetNativeTheme()->GetSystemColor(
232 ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor
);
233 set_background(views::Background::CreateSolidBackground(bg_color
));
235 parent_
->OnSelectionChanged();
237 set_background(NULL
);
243 const char* DesktopMediaSourceView::GetClassName() const {
244 return kDesktopMediaSourceViewClassName
;
247 void DesktopMediaSourceView::Layout() {
248 image_view_
->SetBounds(kThumbnailMargin
, kThumbnailMargin
,
249 kThumbnailWidth
, kThumbnailHeight
);
250 label_
->SetBounds(kThumbnailMargin
, kThumbnailHeight
+ kThumbnailMargin
,
251 kThumbnailWidth
, kLabelHeight
);
253 set_focus_border(views::FocusBorder::CreateDashedFocusBorder(
254 kThumbnailMargin
/ 2, kThumbnailMargin
/ 2,
255 kThumbnailMargin
/ 2, kThumbnailMargin
/ 2));
258 views::View
* DesktopMediaSourceView::GetSelectedViewForGroup(int group
) {
260 parent()->GetViewsInGroup(group
, &neighbours
);
261 if (neighbours
.empty())
264 for (Views::iterator
i(neighbours
.begin()); i
!= neighbours
.end(); ++i
) {
265 DCHECK_EQ((*i
)->GetClassName(), kDesktopMediaSourceViewClassName
);
266 DesktopMediaSourceView
* source_view
=
267 static_cast<DesktopMediaSourceView
*>(*i
);
268 if (source_view
->selected_
)
274 bool DesktopMediaSourceView::IsGroupFocusTraversable() const {
278 void DesktopMediaSourceView::OnFocus() {
281 ScrollRectToVisible(gfx::Rect(size()));
284 bool DesktopMediaSourceView::OnMousePressed(const ui::MouseEvent
& event
) {
289 DesktopMediaListView::DesktopMediaListView(
290 DesktopMediaPickerDialogView
* parent
,
291 scoped_ptr
<DesktopMediaPickerModel
> model
)
293 model_(model
.Pass()) {
294 model_
->SetThumbnailSize(gfx::Size(kThumbnailWidth
, kThumbnailHeight
));
295 model_
->StartUpdating(this);
298 DesktopMediaListView::~DesktopMediaListView() {
301 void DesktopMediaListView::OnSelectionChanged() {
302 parent_
->OnSelectionChanged();
305 DesktopMediaSourceView
* DesktopMediaListView::GetSelection() {
306 for (int i
= 0; i
< child_count(); ++i
) {
307 DesktopMediaSourceView
* source_view
=
308 static_cast<DesktopMediaSourceView
*>(child_at(i
));
309 DCHECK_EQ(source_view
->GetClassName(), kDesktopMediaSourceViewClassName
);
310 if (source_view
->is_selected())
316 gfx::Size
DesktopMediaListView::GetPreferredSize() {
317 int total_rows
= (child_count() + kListColumns
- 1) / kListColumns
;
318 return gfx::Size(kTotalListWidth
, kListItemHeight
* total_rows
);
321 void DesktopMediaListView::Layout() {
325 for (int i
= 0; i
< child_count(); ++i
) {
326 if (x
+ kListItemWidth
> kTotalListWidth
) {
328 y
+= kListItemHeight
;
331 View
* source_view
= child_at(i
);
332 source_view
->SetBounds(x
, y
, kListItemWidth
, kListItemHeight
);
338 y
+= kListItemHeight
;
339 SetSize(gfx::Size(kTotalListWidth
, y
));
342 bool DesktopMediaListView::OnKeyPressed(const ui::KeyEvent
& event
) {
343 int position_increment
= 0;
344 switch (event
.key_code()) {
346 position_increment
= -kListColumns
;
349 position_increment
= kListColumns
;
352 position_increment
= -1;
355 position_increment
= 1;
362 if (position_increment
!= 0) {
363 DesktopMediaSourceView
* selected
= GetSelection();
364 DesktopMediaSourceView
* new_selected
= NULL
;
367 int index
= GetIndexOf(selected
);
368 int new_index
= index
+ position_increment
;
369 if (new_index
>= child_count())
370 new_index
= child_count() - 1;
371 else if (new_index
< 0)
373 if (index
!= new_index
) {
375 static_cast<DesktopMediaSourceView
*>(child_at(new_index
));
377 } else if (has_children()) {
378 new_selected
= static_cast<DesktopMediaSourceView
*>(child_at(0));
382 GetFocusManager()->SetFocusedView(new_selected
);
391 void DesktopMediaListView::OnSourceAdded(int index
) {
392 const DesktopMediaPickerModel::Source
& source
= model_
->source(index
);
393 DesktopMediaSourceView
* source_view
=
394 new DesktopMediaSourceView(this, source
.id
);
395 source_view
->SetName(source
.name
);
396 source_view
->SetGroup(kDesktopMediaSourceViewGroupId
);
397 AddChildViewAt(source_view
, index
);
401 void DesktopMediaListView::OnSourceRemoved(int index
) {
402 DesktopMediaSourceView
* view
=
403 static_cast<DesktopMediaSourceView
*>(child_at(index
));
405 DCHECK_EQ(view
->GetClassName(), kDesktopMediaSourceViewClassName
);
406 bool was_selected
= view
->is_selected();
407 RemoveChildView(view
);
411 OnSelectionChanged();
413 PreferredSizeChanged();
416 void DesktopMediaListView::OnSourceNameChanged(int index
) {
417 const DesktopMediaPickerModel::Source
& source
= model_
->source(index
);
418 DesktopMediaSourceView
* source_view
=
419 static_cast<DesktopMediaSourceView
*>(child_at(index
));
420 source_view
->SetName(source
.name
);
423 void DesktopMediaListView::OnSourceThumbnailChanged(int index
) {
424 const DesktopMediaPickerModel::Source
& source
= model_
->source(index
);
425 DesktopMediaSourceView
* source_view
=
426 static_cast<DesktopMediaSourceView
*>(child_at(index
));
427 source_view
->SetThumbnail(source
.thumbnail
);
430 DesktopMediaPickerDialogView::DesktopMediaPickerDialogView(
431 gfx::NativeWindow context
,
432 gfx::NativeWindow parent_window
,
433 DesktopMediaPickerViews
* parent
,
434 const string16
& app_name
,
435 scoped_ptr
<DesktopMediaPickerModel
> model
)
438 label_(new views::Label()),
439 scroll_view_(views::ScrollView::CreateScrollViewWithBorder()),
440 list_view_(new DesktopMediaListView(this, model
.Pass())) {
442 l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TEXT
, app_name_
));
443 label_
->SetMultiLine(true);
444 label_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
445 AddChildView(label_
);
447 scroll_view_
->SetContents(list_view_
);
448 AddChildView(scroll_view_
);
450 DialogDelegate::CreateDialogWidget(this, context
, parent_window
);
454 DesktopMediaPickerDialogView::~DesktopMediaPickerDialogView() {}
456 void DesktopMediaPickerDialogView::DetachParent() {
460 gfx::Size
DesktopMediaPickerDialogView::GetPreferredSize() {
461 return gfx::Size(600, 500);
464 void DesktopMediaPickerDialogView::Layout() {
465 gfx::Rect rect
= GetLocalBounds();
466 rect
.Inset(views::kPanelHorizMargin
, views::kPanelVertMargin
);
468 gfx::Rect
label_rect(rect
.x(), rect
.y(), rect
.width(),
469 label_
->GetHeightForWidth(rect
.width()));
470 label_
->SetBoundsRect(label_rect
);
472 int scroll_view_top
= label_rect
.bottom() + views::kPanelVerticalSpacing
;
473 scroll_view_
->SetBounds(
474 rect
.x(), scroll_view_top
,
475 rect
.width(), rect
.height() - scroll_view_top
);
478 base::string16
DesktopMediaPickerDialogView::GetWindowTitle() const {
479 return l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TITLE
, app_name_
);
482 bool DesktopMediaPickerDialogView::IsDialogButtonEnabled(
483 ui::DialogButton button
) const {
484 if (button
== ui::DIALOG_BUTTON_OK
)
485 return list_view_
->GetSelection() != NULL
;
489 bool DesktopMediaPickerDialogView::Accept() {
490 DesktopMediaSourceView
* selection
= list_view_
->GetSelection();
492 // Ok button should only be enabled when a source is selected.
495 DesktopMediaID source
;
497 source
= selection
->source_id();
500 parent_
->NotifyDialogResult(source
);
502 // Return true to close the window.
506 void DesktopMediaPickerDialogView::DeleteDelegate() {
507 // If the dialog is being closed then notify the parent about it.
509 parent_
->NotifyDialogResult(DesktopMediaID());
513 void DesktopMediaPickerDialogView::OnSelectionChanged() {
514 GetDialogClientView()->UpdateDialogButtons();
517 DesktopMediaPickerViews::DesktopMediaPickerViews()
521 DesktopMediaPickerViews::~DesktopMediaPickerViews() {
523 dialog_
->DetachParent();
524 dialog_
->GetWidget()->Close();
528 void DesktopMediaPickerViews::Show(gfx::NativeWindow context
,
529 gfx::NativeWindow parent
,
530 const string16
& app_name
,
531 scoped_ptr
<DesktopMediaPickerModel
> model
,
532 const DoneCallback
& done_callback
) {
533 callback_
= done_callback
;
534 dialog_
= new DesktopMediaPickerDialogView(
535 context
, parent
, this, app_name
, model
.Pass());
538 void DesktopMediaPickerViews::NotifyDialogResult(
539 DesktopMediaID source
) {
540 // Once this method is called the |dialog_| will close and destroy itself.
541 dialog_
->DetachParent();
544 DCHECK(!callback_
.is_null());
546 // Notify the |callback_| asynchronously because it may need to destroy
547 // DesktopMediaPicker.
548 content::BrowserThread::PostTask(
549 content::BrowserThread::UI
, FROM_HERE
,
550 base::Bind(callback_
, source
));
557 scoped_ptr
<DesktopMediaPicker
> DesktopMediaPicker::Create() {
558 return scoped_ptr
<DesktopMediaPicker
>(new DesktopMediaPickerViews());