Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / views / desktop_media_picker_views.cc
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.
4
5 #include "chrome/browser/ui/views/desktop_media_picker_views.h"
6
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"
34
35 using content::DesktopMediaID;
36
37 namespace {
38
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;
48
49 const int kDesktopMediaSourceViewGroupId = 1;
50
51 const char kDesktopMediaSourceViewClassName[] =
52     "DesktopMediaPicker_DesktopMediaSourceView";
53
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
61 }
62
63 int GetMediaListViewHeightForRows(size_t rows) {
64   return kListItemHeight * rows;
65 }
66
67 }  // namespace
68
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);
80 }
81
82 DesktopMediaSourceView::~DesktopMediaSourceView() {}
83
84 void DesktopMediaSourceView::SetName(const base::string16& name) {
85   label_->SetText(name);
86 }
87
88 void DesktopMediaSourceView::SetThumbnail(const gfx::ImageSkia& thumbnail) {
89   image_view_->SetImage(thumbnail);
90 }
91
92 void DesktopMediaSourceView::SetSelected(bool selected) {
93   if (selected == selected_)
94     return;
95   selected_ = selected;
96
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);
107       }
108     }
109
110     const SkColor bg_color = GetNativeTheme()->GetSystemColor(
111         ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor);
112     set_background(views::Background::CreateSolidBackground(bg_color));
113
114     parent_->OnSelectionChanged();
115   } else {
116     set_background(NULL);
117   }
118
119   SchedulePaint();
120 }
121
122 const char* DesktopMediaSourceView::GetClassName() const {
123   return kDesktopMediaSourceViewClassName;
124 }
125
126 void DesktopMediaSourceView::Layout() {
127   image_view_->SetBounds(kThumbnailMargin, kThumbnailMargin,
128                          kThumbnailWidth, kThumbnailHeight);
129   label_->SetBounds(kThumbnailMargin, kThumbnailHeight + kThumbnailMargin,
130                     kThumbnailWidth, kLabelHeight);
131 }
132
133 views::View* DesktopMediaSourceView::GetSelectedViewForGroup(int group) {
134   Views neighbours;
135   parent()->GetViewsInGroup(group, &neighbours);
136   if (neighbours.empty())
137     return NULL;
138
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;
145   }
146   return NULL;
147 }
148
149 bool DesktopMediaSourceView::IsGroupFocusTraversable() const {
150   return false;
151 }
152
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);
159   }
160 }
161
162 void DesktopMediaSourceView::OnFocus() {
163   View::OnFocus();
164   SetSelected(true);
165   ScrollRectToVisible(gfx::Rect(size()));
166   // We paint differently when focused.
167   SchedulePaint();
168 }
169
170 void DesktopMediaSourceView::OnBlur() {
171   View::OnBlur();
172   // We paint differently when focused.
173   SchedulePaint();
174 }
175
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();
182   }
183   return true;
184 }
185
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;
193   }
194
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();
200   }
201 }
202
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));
210 }
211
212 DesktopMediaListView::~DesktopMediaListView() {}
213
214 void DesktopMediaListView::StartUpdating(DesktopMediaID::Id dialog_window_id) {
215   media_list_->SetViewDialogWindowId(dialog_window_id);
216   media_list_->StartUpdating(this);
217 }
218
219 void DesktopMediaListView::OnSelectionChanged() {
220   parent_->OnSelectionChanged();
221 }
222
223 void DesktopMediaListView::OnDoubleClick() {
224   parent_->OnDoubleClick();
225 }
226
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;
234   }
235   return NULL;
236 }
237
238 gfx::Size DesktopMediaListView::GetPreferredSize() const {
239   int total_rows = (child_count() + kListColumns - 1) / kListColumns;
240   return gfx::Size(kTotalListWidth, GetMediaListViewHeightForRows(total_rows));
241 }
242
243 void DesktopMediaListView::Layout() {
244   int x = 0;
245   int y = 0;
246
247   for (int i = 0; i < child_count(); ++i) {
248     if (x + kListItemWidth > kTotalListWidth) {
249       x = 0;
250       y += kListItemHeight;
251     }
252
253     View* source_view = child_at(i);
254     source_view->SetBounds(x, y, kListItemWidth, kListItemHeight);
255
256     x += kListItemWidth;
257   }
258
259   y += kListItemHeight;
260   SetSize(gfx::Size(kTotalListWidth, y));
261 }
262
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;
280   }
281
282   if (position_increment != 0) {
283     DesktopMediaSourceView* selected = GetSelection();
284     DesktopMediaSourceView* new_selected = NULL;
285
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));
296       }
297     } else if (has_children()) {
298       new_selected = static_cast<DesktopMediaSourceView*>(child_at(0));
299     }
300
301     if (new_selected) {
302       GetFocusManager()->SetFocusedView(new_selected);
303     }
304
305     return true;
306   }
307
308   return false;
309 }
310
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);
318
319   PreferredSizeChanged();
320
321   if (child_count() % kListColumns == 1)
322     parent_->OnMediaListRowsChanged();
323
324   std::string autoselect_source =
325       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()));
335   }
336 }
337
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;
346
347   if (was_selected)
348     OnSelectionChanged();
349
350   PreferredSizeChanged();
351
352   if (child_count() % kListColumns == 0)
353     parent_->OnMediaListRowsChanged();
354 }
355
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();
361 }
362
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);
368 }
369
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);
375 }
376
377 void DesktopMediaListView::AcceptSelection() {
378   OnSelectionChanged();
379   OnDoubleClick();
380 }
381
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));
400   }
401   label_->SetMultiLine(true);
402   label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
403   AddChildView(label_);
404
405   scroll_view_->SetContents(list_view_);
406   scroll_view_->ClipHeightTo(
407       GetMediaListViewHeightForRows(1), GetMediaListViewHeightForRows(2));
408   AddChildView(scroll_view_);
409
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 = CreateWebModalDialogViews(this, parent_web_contents);
419   } else {
420     widget = DialogDelegate::CreateDialogWidget(this, context, NULL);
421   }
422
423   // If the picker is not modal to the calling web contents then it is displayed
424   // in its own top-level window, so in that case it needs to be filtered out of
425   // the list of top-level windows available for capture, and to achieve that
426   // the Id is passed to DesktopMediaList.
427   DesktopMediaID::Id dialog_window_id = 0;
428   if (!modal_dialog) {
429 #if defined(USE_ASH)
430     if (chrome::IsNativeWindowInAsh(widget->GetNativeWindow())) {
431       dialog_window_id =
432           DesktopMediaID::RegisterAuraWindow(widget->GetNativeWindow()).id;
433       DCHECK_NE(dialog_window_id, 0);
434     }
435 #endif
436
437     if (dialog_window_id == 0) {
438       dialog_window_id = AcceleratedWidgetToDesktopMediaId(
439           widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
440     }
441   }
442
443   list_view_->StartUpdating(dialog_window_id);
444
445   if (modal_dialog) {
446     web_modal::PopupManager* popup_manager =
447         web_modal::PopupManager::FromWebContents(parent_web_contents);
448     popup_manager->ShowModalDialog(GetWidget()->GetNativeView(),
449                                    parent_web_contents);
450   } else {
451     widget->Show();
452   }
453 }
454
455 DesktopMediaPickerDialogView::~DesktopMediaPickerDialogView() {}
456
457 void DesktopMediaPickerDialogView::DetachParent() {
458   parent_ = NULL;
459 }
460
461 gfx::Size DesktopMediaPickerDialogView::GetPreferredSize() const {
462   static const size_t kDialogViewWidth = 600;
463   const gfx::Insets title_insets = views::BubbleFrameView::GetTitleInsets();
464   size_t label_height =
465       label_->GetHeightForWidth(kDialogViewWidth - title_insets.height() * 2);
466
467   return gfx::Size(kDialogViewWidth,
468                    views::kPanelVertMargin * 2 + label_height +
469                        views::kPanelVerticalSpacing +
470                        scroll_view_->GetPreferredSize().height());
471 }
472
473 void DesktopMediaPickerDialogView::Layout() {
474   // DialogDelegate uses the bubble style frame.
475   const gfx::Insets title_insets = views::BubbleFrameView::GetTitleInsets();
476   gfx::Rect rect = GetLocalBounds();
477
478   rect.Inset(title_insets.left(), views::kPanelVertMargin);
479
480   gfx::Rect label_rect(rect.x(), rect.y(), rect.width(),
481                        label_->GetHeightForWidth(rect.width()));
482   label_->SetBoundsRect(label_rect);
483
484   int scroll_view_top = label_rect.bottom() + views::kPanelVerticalSpacing;
485   scroll_view_->SetBounds(
486       rect.x(), scroll_view_top,
487       rect.width(), rect.height() - scroll_view_top);
488 }
489
490 ui::ModalType DesktopMediaPickerDialogView::GetModalType() const {
491   return ui::MODAL_TYPE_CHILD;
492 }
493
494 base::string16 DesktopMediaPickerDialogView::GetWindowTitle() const {
495   return l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TITLE, app_name_);
496 }
497
498 bool DesktopMediaPickerDialogView::IsDialogButtonEnabled(
499     ui::DialogButton button) const {
500   if (button == ui::DIALOG_BUTTON_OK)
501     return list_view_->GetSelection() != NULL;
502   return true;
503 }
504
505 base::string16 DesktopMediaPickerDialogView::GetDialogButtonLabel(
506     ui::DialogButton button) const {
507   return l10n_util::GetStringUTF16(button == ui::DIALOG_BUTTON_OK ?
508       IDS_DESKTOP_MEDIA_PICKER_SHARE : IDS_CANCEL);
509 }
510
511 bool DesktopMediaPickerDialogView::Accept() {
512   DesktopMediaSourceView* selection = list_view_->GetSelection();
513
514   // Ok button should only be enabled when a source is selected.
515   DCHECK(selection);
516
517   DesktopMediaID source;
518   if (selection)
519     source = selection->source_id();
520
521   if (parent_)
522     parent_->NotifyDialogResult(source);
523
524   // Return true to close the window.
525   return true;
526 }
527
528 void DesktopMediaPickerDialogView::DeleteDelegate() {
529   // If the dialog is being closed then notify the parent about it.
530   if (parent_)
531     parent_->NotifyDialogResult(DesktopMediaID());
532   delete this;
533 }
534
535 void DesktopMediaPickerDialogView::OnSelectionChanged() {
536   GetDialogClientView()->UpdateDialogButtons();
537 }
538
539 void DesktopMediaPickerDialogView::OnDoubleClick() {
540   // This will call Accept() and close the dialog.
541   GetDialogClientView()->AcceptWindow();
542 }
543
544 void DesktopMediaPickerDialogView::OnMediaListRowsChanged() {
545   gfx::Rect widget_bound = GetWidget()->GetWindowBoundsInScreen();
546
547   int new_height = widget_bound.height() - scroll_view_->height() +
548       scroll_view_->GetPreferredSize().height();
549
550   GetWidget()->CenterWindow(gfx::Size(widget_bound.width(), new_height));
551 }
552
553 DesktopMediaSourceView*
554 DesktopMediaPickerDialogView::GetMediaSourceViewForTesting(int index) const {
555   if (list_view_->child_count() <= index)
556     return NULL;
557
558   return reinterpret_cast<DesktopMediaSourceView*>(list_view_->child_at(index));
559 }
560
561 DesktopMediaPickerViews::DesktopMediaPickerViews() : dialog_(NULL) {
562 }
563
564 DesktopMediaPickerViews::~DesktopMediaPickerViews() {
565   if (dialog_) {
566     dialog_->DetachParent();
567     dialog_->GetWidget()->Close();
568   }
569 }
570
571 void DesktopMediaPickerViews::Show(content::WebContents* web_contents,
572                                    gfx::NativeWindow context,
573                                    gfx::NativeWindow parent,
574                                    const base::string16& app_name,
575                                    const base::string16& target_name,
576                                    scoped_ptr<DesktopMediaList> media_list,
577                                    const DoneCallback& done_callback) {
578   callback_ = done_callback;
579   dialog_ = new DesktopMediaPickerDialogView(
580       web_contents, context, this, app_name, target_name, media_list.Pass());
581 }
582
583 void DesktopMediaPickerViews::NotifyDialogResult(DesktopMediaID source) {
584   // Once this method is called the |dialog_| will close and destroy itself.
585   dialog_->DetachParent();
586   dialog_ = NULL;
587
588   DCHECK(!callback_.is_null());
589
590   // Notify the |callback_| asynchronously because it may need to destroy
591   // DesktopMediaPicker.
592   content::BrowserThread::PostTask(
593       content::BrowserThread::UI, FROM_HERE,
594       base::Bind(callback_, source));
595   callback_.Reset();
596 }
597
598 // static
599 scoped_ptr<DesktopMediaPicker> DesktopMediaPicker::Create() {
600   return scoped_ptr<DesktopMediaPicker>(new DesktopMediaPickerViews());
601 }