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/ui/views/desktop_media_picker_views.h"
7 #include "base/callback.h"
8 #include "chrome/browser/media/desktop_media_list.h"
9 #include "chrome/browser/ui/ash/ash_util.h"
10 #include "chrome/browser/ui/views/constrained_window_views.h"
11 #include "chrome/grit/generated_resources.h"
12 #include "components/web_modal/popup_manager.h"
13 #include "content/public/browser/browser_thread.h"
14 #include "ui/aura/window_tree_host.h"
15 #include "ui/base/l10n/l10n_util.h"
16 #include "ui/events/event_constants.h"
17 #include "ui/events/keycodes/keyboard_codes.h"
18 #include "ui/gfx/canvas.h"
19 #include "ui/native_theme/native_theme.h"
20 #include "ui/views/background.h"
21 #include "ui/views/bubble/bubble_frame_view.h"
22 #include "ui/views/controls/image_view.h"
23 #include "ui/views/controls/label.h"
24 #include "ui/views/controls/scroll_view.h"
25 #include "ui/views/layout/layout_constants.h"
26 #include "ui/views/widget/widget.h"
27 #include "ui/views/window/dialog_client_view.h"
28 #include "ui/wm/core/shadow_types.h"
30 using content::DesktopMediaID;
34 const int kThumbnailWidth = 160;
35 const int kThumbnailHeight = 100;
36 const int kThumbnailMargin = 10;
37 const int kLabelHeight = 40;
38 const int kListItemWidth = kThumbnailMargin * 2 + kThumbnailWidth;
39 const int kListItemHeight =
40 kThumbnailMargin * 2 + kThumbnailHeight + kLabelHeight;
41 const int kListColumns = 3;
42 const int kTotalListWidth = kListColumns * kListItemWidth;
44 const int kDesktopMediaSourceViewGroupId = 1;
46 const char kDesktopMediaSourceViewClassName[] =
47 "DesktopMediaPicker_DesktopMediaSourceView";
49 DesktopMediaID::Id AcceleratedWidgetToDesktopMediaId(
50 gfx::AcceleratedWidget accelerated_widget) {
52 return reinterpret_cast<DesktopMediaID::Id>(accelerated_widget);
54 return static_cast<DesktopMediaID::Id>(accelerated_widget);
58 int GetMediaListViewHeightForRows(size_t rows) {
59 return kListItemHeight * rows;
64 DesktopMediaSourceView::DesktopMediaSourceView(
65 DesktopMediaListView* parent,
66 DesktopMediaID source_id)
68 source_id_(source_id),
69 image_view_(new views::ImageView()),
70 label_(new views::Label()),
72 AddChildView(image_view_);
77 DesktopMediaSourceView::~DesktopMediaSourceView() {}
79 void DesktopMediaSourceView::SetName(const base::string16& name) {
80 label_->SetText(name);
83 void DesktopMediaSourceView::SetThumbnail(const gfx::ImageSkia& thumbnail) {
84 image_view_->SetImage(thumbnail);
87 void DesktopMediaSourceView::SetSelected(bool selected) {
88 if (selected == selected_)
93 // Unselect all other sources.
95 parent()->GetViewsInGroup(GetGroup(), &neighbours);
96 for (Views::iterator i(neighbours.begin()); i != neighbours.end(); ++i) {
98 DCHECK_EQ((*i)->GetClassName(), kDesktopMediaSourceViewClassName);
99 DesktopMediaSourceView* source_view =
100 static_cast<DesktopMediaSourceView*>(*i);
101 source_view->SetSelected(false);
105 const SkColor bg_color = GetNativeTheme()->GetSystemColor(
106 ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor);
107 set_background(views::Background::CreateSolidBackground(bg_color));
109 parent_->OnSelectionChanged();
111 set_background(NULL);
117 const char* DesktopMediaSourceView::GetClassName() const {
118 return kDesktopMediaSourceViewClassName;
121 void DesktopMediaSourceView::Layout() {
122 image_view_->SetBounds(kThumbnailMargin, kThumbnailMargin,
123 kThumbnailWidth, kThumbnailHeight);
124 label_->SetBounds(kThumbnailMargin, kThumbnailHeight + kThumbnailMargin,
125 kThumbnailWidth, kLabelHeight);
128 views::View* DesktopMediaSourceView::GetSelectedViewForGroup(int group) {
130 parent()->GetViewsInGroup(group, &neighbours);
131 if (neighbours.empty())
134 for (Views::iterator i(neighbours.begin()); i != neighbours.end(); ++i) {
135 DCHECK_EQ((*i)->GetClassName(), kDesktopMediaSourceViewClassName);
136 DesktopMediaSourceView* source_view =
137 static_cast<DesktopMediaSourceView*>(*i);
138 if (source_view->selected_)
144 bool DesktopMediaSourceView::IsGroupFocusTraversable() const {
148 void DesktopMediaSourceView::OnPaint(gfx::Canvas* canvas) {
149 View::OnPaint(canvas);
151 gfx::Rect bounds(GetLocalBounds());
152 bounds.Inset(kThumbnailMargin / 2, kThumbnailMargin / 2);
153 canvas->DrawFocusRect(bounds);
157 void DesktopMediaSourceView::OnFocus() {
160 ScrollRectToVisible(gfx::Rect(size()));
161 // We paint differently when focused.
165 void DesktopMediaSourceView::OnBlur() {
167 // We paint differently when focused.
171 bool DesktopMediaSourceView::OnMousePressed(const ui::MouseEvent& event) {
172 if (event.GetClickCount() == 1) {
174 } else if (event.GetClickCount() == 2) {
176 parent_->OnDoubleClick();
181 void DesktopMediaSourceView::OnGestureEvent(ui::GestureEvent* event) {
182 if (event->type() == ui::ET_GESTURE_TAP &&
183 event->details().tap_count() == 2) {
185 parent_->OnDoubleClick();
190 // Detect tap gesture using ET_GESTURE_TAP_DOWN so the view also gets focused
191 // on the long tap (when the tap gesture starts).
192 if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
198 DesktopMediaListView::DesktopMediaListView(
199 DesktopMediaPickerDialogView* parent,
200 scoped_ptr<DesktopMediaList> media_list)
202 media_list_(media_list.Pass()) {
203 media_list_->SetThumbnailSize(gfx::Size(kThumbnailWidth, kThumbnailHeight));
206 DesktopMediaListView::~DesktopMediaListView() {}
208 void DesktopMediaListView::StartUpdating(DesktopMediaID::Id dialog_window_id) {
209 media_list_->SetViewDialogWindowId(dialog_window_id);
210 media_list_->StartUpdating(this);
213 void DesktopMediaListView::OnSelectionChanged() {
214 parent_->OnSelectionChanged();
217 void DesktopMediaListView::OnDoubleClick() {
218 parent_->OnDoubleClick();
221 DesktopMediaSourceView* DesktopMediaListView::GetSelection() {
222 for (int i = 0; i < child_count(); ++i) {
223 DesktopMediaSourceView* source_view =
224 static_cast<DesktopMediaSourceView*>(child_at(i));
225 DCHECK_EQ(source_view->GetClassName(), kDesktopMediaSourceViewClassName);
226 if (source_view->is_selected())
232 gfx::Size DesktopMediaListView::GetPreferredSize() const {
233 int total_rows = (child_count() + kListColumns - 1) / kListColumns;
234 return gfx::Size(kTotalListWidth, GetMediaListViewHeightForRows(total_rows));
237 void DesktopMediaListView::Layout() {
241 for (int i = 0; i < child_count(); ++i) {
242 if (x + kListItemWidth > kTotalListWidth) {
244 y += kListItemHeight;
247 View* source_view = child_at(i);
248 source_view->SetBounds(x, y, kListItemWidth, kListItemHeight);
253 y += kListItemHeight;
254 SetSize(gfx::Size(kTotalListWidth, y));
257 bool DesktopMediaListView::OnKeyPressed(const ui::KeyEvent& event) {
258 int position_increment = 0;
259 switch (event.key_code()) {
261 position_increment = -kListColumns;
264 position_increment = kListColumns;
267 position_increment = -1;
270 position_increment = 1;
276 if (position_increment != 0) {
277 DesktopMediaSourceView* selected = GetSelection();
278 DesktopMediaSourceView* new_selected = NULL;
281 int index = GetIndexOf(selected);
282 int new_index = index + position_increment;
283 if (new_index >= child_count())
284 new_index = child_count() - 1;
285 else if (new_index < 0)
287 if (index != new_index) {
289 static_cast<DesktopMediaSourceView*>(child_at(new_index));
291 } else if (has_children()) {
292 new_selected = static_cast<DesktopMediaSourceView*>(child_at(0));
296 GetFocusManager()->SetFocusedView(new_selected);
305 void DesktopMediaListView::OnSourceAdded(int index) {
306 const DesktopMediaList::Source& source = media_list_->GetSource(index);
307 DesktopMediaSourceView* source_view =
308 new DesktopMediaSourceView(this, source.id);
309 source_view->SetName(source.name);
310 source_view->SetGroup(kDesktopMediaSourceViewGroupId);
311 AddChildViewAt(source_view, index);
313 PreferredSizeChanged();
315 if (child_count() % kListColumns == 1)
316 parent_->OnMediaListRowsChanged();
319 void DesktopMediaListView::OnSourceRemoved(int index) {
320 DesktopMediaSourceView* view =
321 static_cast<DesktopMediaSourceView*>(child_at(index));
323 DCHECK_EQ(view->GetClassName(), kDesktopMediaSourceViewClassName);
324 bool was_selected = view->is_selected();
325 RemoveChildView(view);
329 OnSelectionChanged();
331 PreferredSizeChanged();
333 if (child_count() % kListColumns == 0)
334 parent_->OnMediaListRowsChanged();
337 void DesktopMediaListView::OnSourceMoved(int old_index, int new_index) {
338 DesktopMediaSourceView* view =
339 static_cast<DesktopMediaSourceView*>(child_at(old_index));
340 ReorderChildView(view, new_index);
341 PreferredSizeChanged();
344 void DesktopMediaListView::OnSourceNameChanged(int index) {
345 const DesktopMediaList::Source& source = media_list_->GetSource(index);
346 DesktopMediaSourceView* source_view =
347 static_cast<DesktopMediaSourceView*>(child_at(index));
348 source_view->SetName(source.name);
351 void DesktopMediaListView::OnSourceThumbnailChanged(int index) {
352 const DesktopMediaList::Source& source = media_list_->GetSource(index);
353 DesktopMediaSourceView* source_view =
354 static_cast<DesktopMediaSourceView*>(child_at(index));
355 source_view->SetThumbnail(source.thumbnail);
358 DesktopMediaPickerDialogView::DesktopMediaPickerDialogView(
359 content::WebContents* parent_web_contents,
360 gfx::NativeWindow context,
361 gfx::NativeWindow parent_window,
362 DesktopMediaPickerViews* parent,
363 const base::string16& app_name,
364 const base::string16& target_name,
365 scoped_ptr<DesktopMediaList> media_list)
368 label_(new views::Label()),
369 scroll_view_(views::ScrollView::CreateScrollViewWithBorder()),
370 list_view_(new DesktopMediaListView(this, media_list.Pass())) {
371 if (app_name == target_name) {
373 l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TEXT, app_name));
375 label_->SetText(l10n_util::GetStringFUTF16(
376 IDS_DESKTOP_MEDIA_PICKER_TEXT_DELEGATED, app_name, target_name));
378 label_->SetMultiLine(true);
379 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
380 AddChildView(label_);
382 scroll_view_->SetContents(list_view_);
383 scroll_view_->ClipHeightTo(
384 GetMediaListViewHeightForRows(1), GetMediaListViewHeightForRows(2));
385 AddChildView(scroll_view_);
387 // If |parent_web_contents| is set, the picker will be shown modal to the
388 // web contents. Otherwise, a new dialog widget inside |parent_window| will be
389 // created for the picker. Note that |parent_window| may also be NULL if
390 // parent web contents is not set. In this case the picker will be parented
392 views::Widget* widget = NULL;
393 if (parent_web_contents)
394 widget = CreateWebModalDialogViews(this, parent_web_contents);
396 widget = DialogDelegate::CreateDialogWidget(this, context, parent_window);
398 // DesktopMediaList needs to know the ID of the picker window which
399 // matches the ID it gets from the OS. Depending on the OS and configuration
400 // we get this ID differently.
401 DesktopMediaID::Id dialog_window_id = 0;
403 // If there is |parent_window| or |parent_web_contents|, the picker window
404 // is embedded in the parent and does not have its own native window id, so we
405 // do not filter in that case.
406 if (!parent_window && !parent_web_contents) {
408 if (chrome::IsNativeWindowInAsh(widget->GetNativeWindow())) {
410 DesktopMediaID::RegisterAuraWindow(widget->GetNativeWindow()).id;
411 DCHECK_NE(dialog_window_id, 0);
415 if (dialog_window_id == 0) {
416 dialog_window_id = AcceleratedWidgetToDesktopMediaId(
417 widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
421 list_view_->StartUpdating(dialog_window_id);
423 if (parent_web_contents) {
424 web_modal::PopupManager* popup_manager =
425 web_modal::PopupManager::FromWebContents(parent_web_contents);
426 popup_manager->ShowModalDialog(GetWidget()->GetNativeView(),
427 parent_web_contents);
433 DesktopMediaPickerDialogView::~DesktopMediaPickerDialogView() {}
435 void DesktopMediaPickerDialogView::DetachParent() {
439 gfx::Size DesktopMediaPickerDialogView::GetPreferredSize() const {
440 static const size_t kDialogViewWidth = 600;
441 const gfx::Insets title_insets = views::BubbleFrameView::GetTitleInsets();
442 size_t label_height =
443 label_->GetHeightForWidth(kDialogViewWidth - title_insets.height() * 2);
445 return gfx::Size(kDialogViewWidth,
446 views::kPanelVertMargin * 2 + label_height +
447 views::kPanelVerticalSpacing +
448 scroll_view_->GetPreferredSize().height());
451 void DesktopMediaPickerDialogView::Layout() {
452 // DialogDelegate uses the bubble style frame.
453 const gfx::Insets title_insets = views::BubbleFrameView::GetTitleInsets();
454 gfx::Rect rect = GetLocalBounds();
456 rect.Inset(title_insets.left(), views::kPanelVertMargin);
458 gfx::Rect label_rect(rect.x(), rect.y(), rect.width(),
459 label_->GetHeightForWidth(rect.width()));
460 label_->SetBoundsRect(label_rect);
462 int scroll_view_top = label_rect.bottom() + views::kPanelVerticalSpacing;
463 scroll_view_->SetBounds(
464 rect.x(), scroll_view_top,
465 rect.width(), rect.height() - scroll_view_top);
468 ui::ModalType DesktopMediaPickerDialogView::GetModalType() const {
469 return ui::MODAL_TYPE_CHILD;
472 base::string16 DesktopMediaPickerDialogView::GetWindowTitle() const {
473 return l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TITLE, app_name_);
476 bool DesktopMediaPickerDialogView::IsDialogButtonEnabled(
477 ui::DialogButton button) const {
478 if (button == ui::DIALOG_BUTTON_OK)
479 return list_view_->GetSelection() != NULL;
483 base::string16 DesktopMediaPickerDialogView::GetDialogButtonLabel(
484 ui::DialogButton button) const {
485 return l10n_util::GetStringUTF16(button == ui::DIALOG_BUTTON_OK ?
486 IDS_DESKTOP_MEDIA_PICKER_SHARE : IDS_CANCEL);
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 void DesktopMediaPickerDialogView::OnDoubleClick() {
518 // This will call Accept() and close the dialog.
519 GetDialogClientView()->AcceptWindow();
522 void DesktopMediaPickerDialogView::OnMediaListRowsChanged() {
523 gfx::Rect widget_bound = GetWidget()->GetWindowBoundsInScreen();
525 int new_height = widget_bound.height() - scroll_view_->height() +
526 scroll_view_->GetPreferredSize().height();
528 GetWidget()->CenterWindow(gfx::Size(widget_bound.width(), new_height));
531 DesktopMediaSourceView*
532 DesktopMediaPickerDialogView::GetMediaSourceViewForTesting(int index) const {
533 if (list_view_->child_count() <= index)
536 return reinterpret_cast<DesktopMediaSourceView*>(list_view_->child_at(index));
539 DesktopMediaPickerViews::DesktopMediaPickerViews() : dialog_(NULL) {
542 DesktopMediaPickerViews::~DesktopMediaPickerViews() {
544 dialog_->DetachParent();
545 dialog_->GetWidget()->Close();
549 void DesktopMediaPickerViews::Show(content::WebContents* web_contents,
550 gfx::NativeWindow context,
551 gfx::NativeWindow parent,
552 const base::string16& app_name,
553 const base::string16& target_name,
554 scoped_ptr<DesktopMediaList> media_list,
555 const DoneCallback& done_callback) {
556 callback_ = done_callback;
557 dialog_ = new DesktopMediaPickerDialogView(
558 web_contents, context, parent, this, app_name, target_name,
562 void DesktopMediaPickerViews::NotifyDialogResult(DesktopMediaID source) {
563 // Once this method is called the |dialog_| will close and destroy itself.
564 dialog_->DetachParent();
567 DCHECK(!callback_.is_null());
569 // Notify the |callback_| asynchronously because it may need to destroy
570 // DesktopMediaPicker.
571 content::BrowserThread::PostTask(
572 content::BrowserThread::UI, FROM_HERE,
573 base::Bind(callback_, source));
578 scoped_ptr<DesktopMediaPicker> DesktopMediaPicker::Create() {
579 return scoped_ptr<DesktopMediaPicker>(new DesktopMediaPickerViews());