NaCl: Update revision in DEPS, r12770 -> r12773
[chromium-blink-merge.git] / chrome / browser / ui / views / desktop_media_picker_views.cc
blob951a27de4b33ba4f6ad56e89572e545af9f6488f
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"
28 #if defined(USE_AURA)
29 #include "ui/aura/root_window.h"
30 #endif
32 using content::DesktopMediaID;
34 namespace {
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) {
53 #if defined(OS_WIN)
54 return reinterpret_cast<content::DesktopMediaID::Id>(accelerated_widget);
55 #else
56 return static_cast<content::DesktopMediaID::Id>(accelerated_widget);
57 #endif
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 {
67 public:
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 {
78 return source_id_;
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
86 // (if any).
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;
99 private:
100 DesktopMediaListView* parent_;
101 DesktopMediaID source_id_;
103 views::ImageView* image_view_;
104 views::Label* label_;
106 bool selected_;
108 DISALLOW_COPY_AND_ASSIGN(DesktopMediaSourceView);
111 // View that shows list of desktop media sources available from
112 // DesktopMediaList.
113 class DesktopMediaListView : public views::View,
114 public DesktopMediaListObserver {
115 public:
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;
136 private:
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 {
152 public:
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.
161 void DetachParent();
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;
177 private:
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 {
190 public:
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;
203 private:
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)
217 : parent_(parent),
218 source_id_(source_id),
219 image_view_(new views::ImageView()),
220 label_(new views::Label()),
221 selected_(false) {
222 AddChildView(image_view_);
223 AddChildView(label_);
224 SetFocusable(true);
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_)
239 return;
240 selected_ = selected;
242 if (selected) {
243 // Unselect all other sources.
244 Views neighbours;
245 parent()->GetViewsInGroup(GetGroup(), &neighbours);
246 for (Views::iterator i(neighbours.begin()); i != neighbours.end(); ++i) {
247 if (*i != this) {
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();
260 } else {
261 set_background(NULL);
264 SchedulePaint();
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) {
279 Views neighbours;
280 parent()->GetViewsInGroup(group, &neighbours);
281 if (neighbours.empty())
282 return NULL;
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_)
289 return source_view;
291 return NULL;
294 bool DesktopMediaSourceView::IsGroupFocusTraversable() const {
295 return false;
298 void DesktopMediaSourceView::OnPaint(gfx::Canvas* canvas) {
299 View::OnPaint(canvas);
300 if (HasFocus()) {
301 gfx::Rect bounds(GetLocalBounds());
302 bounds.Inset(kThumbnailMargin / 2, kThumbnailMargin / 2);
303 canvas->DrawFocusRect(bounds);
307 void DesktopMediaSourceView::OnFocus() {
308 View::OnFocus();
309 SetSelected(true);
310 ScrollRectToVisible(gfx::Rect(size()));
311 // We paint differently when focused.
312 SchedulePaint();
315 void DesktopMediaSourceView::OnBlur() {
316 View::OnBlur();
317 // We paint differently when focused.
318 SchedulePaint();
321 bool DesktopMediaSourceView::OnMousePressed(const ui::MouseEvent& event) {
322 if (event.GetClickCount() == 1) {
323 RequestFocus();
324 } else if (event.GetClickCount() == 2) {
325 RequestFocus();
326 parent_->OnDoubleClick();
328 return true;
331 DesktopMediaListView::DesktopMediaListView(
332 DesktopMediaPickerDialogView* parent,
333 scoped_ptr<DesktopMediaList> media_list)
334 : parent_(parent),
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())
361 return source_view;
363 return NULL;
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() {
372 int x = 0;
373 int y = 0;
375 for (int i = 0; i < child_count(); ++i) {
376 if (x + kListItemWidth > kTotalListWidth) {
377 x = 0;
378 y += kListItemHeight;
381 View* source_view = child_at(i);
382 source_view->SetBounds(x, y, kListItemWidth, kListItemHeight);
384 x += kListItemWidth;
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()) {
394 case ui::VKEY_UP:
395 position_increment = -kListColumns;
396 break;
397 case ui::VKEY_DOWN:
398 position_increment = kListColumns;
399 break;
400 case ui::VKEY_LEFT:
401 position_increment = -1;
402 break;
403 case ui::VKEY_RIGHT:
404 position_increment = 1;
405 break;
406 default:
407 return false;
410 if (position_increment != 0) {
411 DesktopMediaSourceView* selected = GetSelection();
412 DesktopMediaSourceView* new_selected = NULL;
414 if (selected) {
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)
420 new_index = 0;
421 if (index != new_index) {
422 new_selected =
423 static_cast<DesktopMediaSourceView*>(child_at(new_index));
425 } else if (has_children()) {
426 new_selected = static_cast<DesktopMediaSourceView*>(child_at(0));
429 if (new_selected) {
430 GetFocusManager()->SetFocusedView(new_selected);
433 return true;
436 return false;
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));
453 DCHECK(view);
454 DCHECK_EQ(view->GetClassName(), kDesktopMediaSourceViewClassName);
455 bool was_selected = view->is_selected();
456 RemoveChildView(view);
457 delete view;
459 if (was_selected)
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)
492 : parent_(parent),
493 app_name_(app_name),
494 label_(new views::Label()),
495 scroll_view_(views::ScrollView::CreateScrollViewWithBorder()),
496 list_view_(new DesktopMediaListView(this, media_list.Pass())) {
497 label_->SetText(
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)
515 #if defined(USE_ASH)
516 if (chrome::IsNativeWindowInAsh(GetWidget()->GetNativeWindow())) {
517 dialog_window_id = content::DesktopMediaID::RegisterAuraWindow(
518 GetWidget()->GetNativeWindow()).id;
519 } else
520 #endif
522 dialog_window_id = AcceleratedWidgetToDesktopMediaId(
523 GetWidget()->GetNativeWindow()->GetDispatcher()->host()->
524 GetAcceleratedWidget());
527 #else // defined(USE_AURA)
528 dialog_window_id = 0;
529 NOTIMPLEMENTED();
530 #endif // !defined(USE_AURA)
532 list_view_->StartUpdating(dialog_window_id);
534 GetWidget()->Show();
537 DesktopMediaPickerDialogView::~DesktopMediaPickerDialogView() {}
539 void DesktopMediaPickerDialogView::DetachParent() {
540 parent_ = NULL;
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;
569 return true;
572 bool DesktopMediaPickerDialogView::Accept() {
573 DesktopMediaSourceView* selection = list_view_->GetSelection();
575 // Ok button should only be enabled when a source is selected.
576 DCHECK(selection);
578 DesktopMediaID source;
579 if (selection)
580 source = selection->source_id();
582 if (parent_)
583 parent_->NotifyDialogResult(source);
585 // Return true to close the window.
586 return true;
589 void DesktopMediaPickerDialogView::DeleteDelegate() {
590 // If the dialog is being closed then notify the parent about it.
591 if (parent_)
592 parent_->NotifyDialogResult(DesktopMediaID());
593 delete this;
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()
606 : dialog_(NULL) {
609 DesktopMediaPickerViews::~DesktopMediaPickerViews() {
610 if (dialog_) {
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();
630 dialog_ = NULL;
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));
639 callback_.Reset();
642 } // namespace
644 // static
645 scoped_ptr<DesktopMediaPicker> DesktopMediaPicker::Create() {
646 return scoped_ptr<DesktopMediaPicker>(new DesktopMediaPickerViews());