NaCl docs: add sanitizers to GSoC ideas
[chromium-blink-merge.git] / chrome / browser / ui / views / desktop_media_picker_views.cc
blob7f60bbd7fc6070f13aa74c9941f722cc4816c9a9
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 "base/command_line.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/media/desktop_media_list.h"
11 #include "chrome/browser/ui/ash/ash_util.h"
12 #include "chrome/common/chrome_switches.h"
13 #include "chrome/grit/generated_resources.h"
14 #include "components/constrained_window/constrained_window_views.h"
15 #include "components/web_modal/popup_manager.h"
16 #include "components/web_modal/web_contents_modal_dialog_manager.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "content/public/browser/web_contents_delegate.h"
19 #include "ui/aura/window_tree_host.h"
20 #include "ui/base/l10n/l10n_util.h"
21 #include "ui/events/event_constants.h"
22 #include "ui/events/keycodes/keyboard_codes.h"
23 #include "ui/gfx/canvas.h"
24 #include "ui/native_theme/native_theme.h"
25 #include "ui/views/background.h"
26 #include "ui/views/bubble/bubble_frame_view.h"
27 #include "ui/views/controls/image_view.h"
28 #include "ui/views/controls/label.h"
29 #include "ui/views/controls/scroll_view.h"
30 #include "ui/views/layout/layout_constants.h"
31 #include "ui/views/widget/widget.h"
32 #include "ui/views/window/dialog_client_view.h"
33 #include "ui/wm/core/shadow_types.h"
35 using content::DesktopMediaID;
37 namespace {
39 const int kThumbnailWidth = 160;
40 const int kThumbnailHeight = 100;
41 const int kThumbnailMargin = 10;
42 const int kLabelHeight = 40;
43 const int kListItemWidth = kThumbnailMargin * 2 + kThumbnailWidth;
44 const int kListItemHeight =
45 kThumbnailMargin * 2 + kThumbnailHeight + kLabelHeight;
46 const int kListColumns = 3;
47 const int kTotalListWidth = kListColumns * kListItemWidth;
49 const int kDesktopMediaSourceViewGroupId = 1;
51 const char kDesktopMediaSourceViewClassName[] =
52 "DesktopMediaPicker_DesktopMediaSourceView";
54 DesktopMediaID::Id AcceleratedWidgetToDesktopMediaId(
55 gfx::AcceleratedWidget accelerated_widget) {
56 #if defined(OS_WIN)
57 return reinterpret_cast<DesktopMediaID::Id>(accelerated_widget);
58 #else
59 return static_cast<DesktopMediaID::Id>(accelerated_widget);
60 #endif
63 int GetMediaListViewHeightForRows(size_t rows) {
64 return kListItemHeight * rows;
67 } // namespace
69 DesktopMediaSourceView::DesktopMediaSourceView(
70 DesktopMediaListView* parent,
71 DesktopMediaID source_id)
72 : parent_(parent),
73 source_id_(source_id),
74 image_view_(new views::ImageView()),
75 label_(new views::Label()),
76 selected_(false) {
77 AddChildView(image_view_);
78 AddChildView(label_);
79 SetFocusable(true);
82 DesktopMediaSourceView::~DesktopMediaSourceView() {}
84 void DesktopMediaSourceView::SetName(const base::string16& name) {
85 label_->SetText(name);
88 void DesktopMediaSourceView::SetThumbnail(const gfx::ImageSkia& thumbnail) {
89 image_view_->SetImage(thumbnail);
92 void DesktopMediaSourceView::SetSelected(bool selected) {
93 if (selected == selected_)
94 return;
95 selected_ = selected;
97 if (selected) {
98 // Unselect all other sources.
99 Views neighbours;
100 parent()->GetViewsInGroup(GetGroup(), &neighbours);
101 for (Views::iterator i(neighbours.begin()); i != neighbours.end(); ++i) {
102 if (*i != this) {
103 DCHECK_EQ((*i)->GetClassName(), kDesktopMediaSourceViewClassName);
104 DesktopMediaSourceView* source_view =
105 static_cast<DesktopMediaSourceView*>(*i);
106 source_view->SetSelected(false);
110 const SkColor bg_color = GetNativeTheme()->GetSystemColor(
111 ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor);
112 set_background(views::Background::CreateSolidBackground(bg_color));
114 parent_->OnSelectionChanged();
115 } else {
116 set_background(NULL);
119 SchedulePaint();
122 const char* DesktopMediaSourceView::GetClassName() const {
123 return kDesktopMediaSourceViewClassName;
126 void DesktopMediaSourceView::Layout() {
127 image_view_->SetBounds(kThumbnailMargin, kThumbnailMargin,
128 kThumbnailWidth, kThumbnailHeight);
129 label_->SetBounds(kThumbnailMargin, kThumbnailHeight + kThumbnailMargin,
130 kThumbnailWidth, kLabelHeight);
133 views::View* DesktopMediaSourceView::GetSelectedViewForGroup(int group) {
134 Views neighbours;
135 parent()->GetViewsInGroup(group, &neighbours);
136 if (neighbours.empty())
137 return NULL;
139 for (Views::iterator i(neighbours.begin()); i != neighbours.end(); ++i) {
140 DCHECK_EQ((*i)->GetClassName(), kDesktopMediaSourceViewClassName);
141 DesktopMediaSourceView* source_view =
142 static_cast<DesktopMediaSourceView*>(*i);
143 if (source_view->selected_)
144 return source_view;
146 return NULL;
149 bool DesktopMediaSourceView::IsGroupFocusTraversable() const {
150 return false;
153 void DesktopMediaSourceView::OnPaint(gfx::Canvas* canvas) {
154 View::OnPaint(canvas);
155 if (HasFocus()) {
156 gfx::Rect bounds(GetLocalBounds());
157 bounds.Inset(kThumbnailMargin / 2, kThumbnailMargin / 2);
158 canvas->DrawFocusRect(bounds);
162 void DesktopMediaSourceView::OnFocus() {
163 View::OnFocus();
164 SetSelected(true);
165 ScrollRectToVisible(gfx::Rect(size()));
166 // We paint differently when focused.
167 SchedulePaint();
170 void DesktopMediaSourceView::OnBlur() {
171 View::OnBlur();
172 // We paint differently when focused.
173 SchedulePaint();
176 bool DesktopMediaSourceView::OnMousePressed(const ui::MouseEvent& event) {
177 if (event.GetClickCount() == 1) {
178 RequestFocus();
179 } else if (event.GetClickCount() == 2) {
180 RequestFocus();
181 parent_->OnDoubleClick();
183 return true;
186 void DesktopMediaSourceView::OnGestureEvent(ui::GestureEvent* event) {
187 if (event->type() == ui::ET_GESTURE_TAP &&
188 event->details().tap_count() == 2) {
189 RequestFocus();
190 parent_->OnDoubleClick();
191 event->SetHandled();
192 return;
195 // Detect tap gesture using ET_GESTURE_TAP_DOWN so the view also gets focused
196 // on the long tap (when the tap gesture starts).
197 if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
198 RequestFocus();
199 event->SetHandled();
203 DesktopMediaListView::DesktopMediaListView(
204 DesktopMediaPickerDialogView* parent,
205 scoped_ptr<DesktopMediaList> media_list)
206 : parent_(parent),
207 media_list_(media_list.Pass()),
208 weak_factory_(this) {
209 media_list_->SetThumbnailSize(gfx::Size(kThumbnailWidth, kThumbnailHeight));
212 DesktopMediaListView::~DesktopMediaListView() {}
214 void DesktopMediaListView::StartUpdating(DesktopMediaID::Id dialog_window_id) {
215 media_list_->SetViewDialogWindowId(dialog_window_id);
216 media_list_->StartUpdating(this);
219 void DesktopMediaListView::OnSelectionChanged() {
220 parent_->OnSelectionChanged();
223 void DesktopMediaListView::OnDoubleClick() {
224 parent_->OnDoubleClick();
227 DesktopMediaSourceView* DesktopMediaListView::GetSelection() {
228 for (int i = 0; i < child_count(); ++i) {
229 DesktopMediaSourceView* source_view =
230 static_cast<DesktopMediaSourceView*>(child_at(i));
231 DCHECK_EQ(source_view->GetClassName(), kDesktopMediaSourceViewClassName);
232 if (source_view->is_selected())
233 return source_view;
235 return NULL;
238 gfx::Size DesktopMediaListView::GetPreferredSize() const {
239 int total_rows = (child_count() + kListColumns - 1) / kListColumns;
240 return gfx::Size(kTotalListWidth, GetMediaListViewHeightForRows(total_rows));
243 void DesktopMediaListView::Layout() {
244 int x = 0;
245 int y = 0;
247 for (int i = 0; i < child_count(); ++i) {
248 if (x + kListItemWidth > kTotalListWidth) {
249 x = 0;
250 y += kListItemHeight;
253 View* source_view = child_at(i);
254 source_view->SetBounds(x, y, kListItemWidth, kListItemHeight);
256 x += kListItemWidth;
259 y += kListItemHeight;
260 SetSize(gfx::Size(kTotalListWidth, y));
263 bool DesktopMediaListView::OnKeyPressed(const ui::KeyEvent& event) {
264 int position_increment = 0;
265 switch (event.key_code()) {
266 case ui::VKEY_UP:
267 position_increment = -kListColumns;
268 break;
269 case ui::VKEY_DOWN:
270 position_increment = kListColumns;
271 break;
272 case ui::VKEY_LEFT:
273 position_increment = -1;
274 break;
275 case ui::VKEY_RIGHT:
276 position_increment = 1;
277 break;
278 default:
279 return false;
282 if (position_increment != 0) {
283 DesktopMediaSourceView* selected = GetSelection();
284 DesktopMediaSourceView* new_selected = NULL;
286 if (selected) {
287 int index = GetIndexOf(selected);
288 int new_index = index + position_increment;
289 if (new_index >= child_count())
290 new_index = child_count() - 1;
291 else if (new_index < 0)
292 new_index = 0;
293 if (index != new_index) {
294 new_selected =
295 static_cast<DesktopMediaSourceView*>(child_at(new_index));
297 } else if (has_children()) {
298 new_selected = static_cast<DesktopMediaSourceView*>(child_at(0));
301 if (new_selected) {
302 GetFocusManager()->SetFocusedView(new_selected);
305 return true;
308 return false;
311 void DesktopMediaListView::OnSourceAdded(int index) {
312 const DesktopMediaList::Source& source = media_list_->GetSource(index);
313 DesktopMediaSourceView* source_view =
314 new DesktopMediaSourceView(this, source.id);
315 source_view->SetName(source.name);
316 source_view->SetGroup(kDesktopMediaSourceViewGroupId);
317 AddChildViewAt(source_view, index);
319 PreferredSizeChanged();
321 if (child_count() % kListColumns == 1)
322 parent_->OnMediaListRowsChanged();
324 std::string autoselect_source =
325 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
326 switches::kAutoSelectDesktopCaptureSource);
327 if (!autoselect_source.empty() &&
328 base::ASCIIToUTF16(autoselect_source) == source.name) {
329 // Select, then accept and close the dialog when we're done adding sources.
330 source_view->OnFocus();
331 content::BrowserThread::PostTask(
332 content::BrowserThread::UI, FROM_HERE,
333 base::Bind(&DesktopMediaListView::AcceptSelection,
334 weak_factory_.GetWeakPtr()));
338 void DesktopMediaListView::OnSourceRemoved(int index) {
339 DesktopMediaSourceView* view =
340 static_cast<DesktopMediaSourceView*>(child_at(index));
341 DCHECK(view);
342 DCHECK_EQ(view->GetClassName(), kDesktopMediaSourceViewClassName);
343 bool was_selected = view->is_selected();
344 RemoveChildView(view);
345 delete view;
347 if (was_selected)
348 OnSelectionChanged();
350 PreferredSizeChanged();
352 if (child_count() % kListColumns == 0)
353 parent_->OnMediaListRowsChanged();
356 void DesktopMediaListView::OnSourceMoved(int old_index, int new_index) {
357 DesktopMediaSourceView* view =
358 static_cast<DesktopMediaSourceView*>(child_at(old_index));
359 ReorderChildView(view, new_index);
360 PreferredSizeChanged();
363 void DesktopMediaListView::OnSourceNameChanged(int index) {
364 const DesktopMediaList::Source& source = media_list_->GetSource(index);
365 DesktopMediaSourceView* source_view =
366 static_cast<DesktopMediaSourceView*>(child_at(index));
367 source_view->SetName(source.name);
370 void DesktopMediaListView::OnSourceThumbnailChanged(int index) {
371 const DesktopMediaList::Source& source = media_list_->GetSource(index);
372 DesktopMediaSourceView* source_view =
373 static_cast<DesktopMediaSourceView*>(child_at(index));
374 source_view->SetThumbnail(source.thumbnail);
377 void DesktopMediaListView::AcceptSelection() {
378 OnSelectionChanged();
379 OnDoubleClick();
382 DesktopMediaPickerDialogView::DesktopMediaPickerDialogView(
383 content::WebContents* parent_web_contents,
384 gfx::NativeWindow context,
385 DesktopMediaPickerViews* parent,
386 const base::string16& app_name,
387 const base::string16& target_name,
388 scoped_ptr<DesktopMediaList> media_list)
389 : parent_(parent),
390 app_name_(app_name),
391 label_(new views::Label()),
392 scroll_view_(views::ScrollView::CreateScrollViewWithBorder()),
393 list_view_(new DesktopMediaListView(this, media_list.Pass())) {
394 if (app_name == target_name) {
395 label_->SetText(
396 l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TEXT, app_name));
397 } else {
398 label_->SetText(l10n_util::GetStringFUTF16(
399 IDS_DESKTOP_MEDIA_PICKER_TEXT_DELEGATED, app_name, target_name));
401 label_->SetMultiLine(true);
402 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
403 AddChildView(label_);
405 scroll_view_->SetContents(list_view_);
406 scroll_view_->ClipHeightTo(
407 GetMediaListViewHeightForRows(1), GetMediaListViewHeightForRows(2));
408 AddChildView(scroll_view_);
410 // If |parent_web_contents| is set and it's not a background page then the
411 // picker will be shown modal to the web contents. Otherwise the picker is
412 // shown in a separate window.
413 views::Widget* widget = NULL;
414 bool modal_dialog =
415 parent_web_contents &&
416 !parent_web_contents->GetDelegate()->IsNeverVisible(parent_web_contents);
417 if (modal_dialog) {
418 widget = constrained_window::CreateWebModalDialogViews(this,
419 parent_web_contents);
420 } else {
421 widget = DialogDelegate::CreateDialogWidget(this, context, NULL);
424 // If the picker is not modal to the calling web contents then it is displayed
425 // in its own top-level window, so in that case it needs to be filtered out of
426 // the list of top-level windows available for capture, and to achieve that
427 // the Id is passed to DesktopMediaList.
428 DesktopMediaID::Id dialog_window_id = 0;
429 if (!modal_dialog) {
430 #if defined(USE_ASH)
431 if (chrome::IsNativeWindowInAsh(widget->GetNativeWindow())) {
432 dialog_window_id =
433 DesktopMediaID::RegisterAuraWindow(widget->GetNativeWindow()).id;
434 DCHECK_NE(dialog_window_id, 0);
436 #endif
438 if (dialog_window_id == 0) {
439 dialog_window_id = AcceleratedWidgetToDesktopMediaId(
440 widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
444 list_view_->StartUpdating(dialog_window_id);
446 if (modal_dialog) {
447 web_modal::PopupManager* popup_manager =
448 web_modal::PopupManager::FromWebContents(parent_web_contents);
449 popup_manager->ShowModalDialog(GetWidget()->GetNativeView(),
450 parent_web_contents);
451 } else {
452 widget->Show();
456 DesktopMediaPickerDialogView::~DesktopMediaPickerDialogView() {}
458 void DesktopMediaPickerDialogView::DetachParent() {
459 parent_ = NULL;
462 gfx::Size DesktopMediaPickerDialogView::GetPreferredSize() const {
463 static const size_t kDialogViewWidth = 600;
464 const gfx::Insets title_insets = views::BubbleFrameView::GetTitleInsets();
465 size_t label_height =
466 label_->GetHeightForWidth(kDialogViewWidth - title_insets.height() * 2);
468 return gfx::Size(kDialogViewWidth,
469 views::kPanelVertMargin * 2 + label_height +
470 views::kPanelVerticalSpacing +
471 scroll_view_->GetPreferredSize().height());
474 void DesktopMediaPickerDialogView::Layout() {
475 // DialogDelegate uses the bubble style frame.
476 const gfx::Insets title_insets = views::BubbleFrameView::GetTitleInsets();
477 gfx::Rect rect = GetLocalBounds();
479 rect.Inset(title_insets.left(), views::kPanelVertMargin);
481 gfx::Rect label_rect(rect.x(), rect.y(), rect.width(),
482 label_->GetHeightForWidth(rect.width()));
483 label_->SetBoundsRect(label_rect);
485 int scroll_view_top = label_rect.bottom() + views::kPanelVerticalSpacing;
486 scroll_view_->SetBounds(
487 rect.x(), scroll_view_top,
488 rect.width(), rect.height() - scroll_view_top);
491 ui::ModalType DesktopMediaPickerDialogView::GetModalType() const {
492 return ui::MODAL_TYPE_CHILD;
495 base::string16 DesktopMediaPickerDialogView::GetWindowTitle() const {
496 return l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TITLE, app_name_);
499 bool DesktopMediaPickerDialogView::IsDialogButtonEnabled(
500 ui::DialogButton button) const {
501 if (button == ui::DIALOG_BUTTON_OK)
502 return list_view_->GetSelection() != NULL;
503 return true;
506 base::string16 DesktopMediaPickerDialogView::GetDialogButtonLabel(
507 ui::DialogButton button) const {
508 return l10n_util::GetStringUTF16(button == ui::DIALOG_BUTTON_OK ?
509 IDS_DESKTOP_MEDIA_PICKER_SHARE : IDS_CANCEL);
512 bool DesktopMediaPickerDialogView::Accept() {
513 DesktopMediaSourceView* selection = list_view_->GetSelection();
515 // Ok button should only be enabled when a source is selected.
516 DCHECK(selection);
518 DesktopMediaID source;
519 if (selection)
520 source = selection->source_id();
522 if (parent_)
523 parent_->NotifyDialogResult(source);
525 // Return true to close the window.
526 return true;
529 void DesktopMediaPickerDialogView::DeleteDelegate() {
530 // If the dialog is being closed then notify the parent about it.
531 if (parent_)
532 parent_->NotifyDialogResult(DesktopMediaID());
533 delete this;
536 void DesktopMediaPickerDialogView::OnSelectionChanged() {
537 GetDialogClientView()->UpdateDialogButtons();
540 void DesktopMediaPickerDialogView::OnDoubleClick() {
541 // This will call Accept() and close the dialog.
542 GetDialogClientView()->AcceptWindow();
545 void DesktopMediaPickerDialogView::OnMediaListRowsChanged() {
546 gfx::Rect widget_bound = GetWidget()->GetWindowBoundsInScreen();
548 int new_height = widget_bound.height() - scroll_view_->height() +
549 scroll_view_->GetPreferredSize().height();
551 GetWidget()->CenterWindow(gfx::Size(widget_bound.width(), new_height));
554 DesktopMediaSourceView*
555 DesktopMediaPickerDialogView::GetMediaSourceViewForTesting(int index) const {
556 if (list_view_->child_count() <= index)
557 return NULL;
559 return reinterpret_cast<DesktopMediaSourceView*>(list_view_->child_at(index));
562 DesktopMediaPickerViews::DesktopMediaPickerViews() : dialog_(NULL) {
565 DesktopMediaPickerViews::~DesktopMediaPickerViews() {
566 if (dialog_) {
567 dialog_->DetachParent();
568 dialog_->GetWidget()->Close();
572 void DesktopMediaPickerViews::Show(content::WebContents* web_contents,
573 gfx::NativeWindow context,
574 gfx::NativeWindow parent,
575 const base::string16& app_name,
576 const base::string16& target_name,
577 scoped_ptr<DesktopMediaList> media_list,
578 const DoneCallback& done_callback) {
579 callback_ = done_callback;
580 dialog_ = new DesktopMediaPickerDialogView(
581 web_contents, context, this, app_name, target_name, media_list.Pass());
584 void DesktopMediaPickerViews::NotifyDialogResult(DesktopMediaID source) {
585 // Once this method is called the |dialog_| will close and destroy itself.
586 dialog_->DetachParent();
587 dialog_ = NULL;
589 DCHECK(!callback_.is_null());
591 // Notify the |callback_| asynchronously because it may need to destroy
592 // DesktopMediaPicker.
593 content::BrowserThread::PostTask(
594 content::BrowserThread::UI, FROM_HERE,
595 base::Bind(callback_, source));
596 callback_.Reset();
599 // static
600 scoped_ptr<DesktopMediaPicker> DesktopMediaPicker::Create() {
601 return scoped_ptr<DesktopMediaPicker>(new DesktopMediaPickerViews());