Upstream version 9.38.198.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 "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"
29
30 using content::DesktopMediaID;
31
32 namespace {
33
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;
43
44 const int kDesktopMediaSourceViewGroupId = 1;
45
46 const char kDesktopMediaSourceViewClassName[] =
47     "DesktopMediaPicker_DesktopMediaSourceView";
48
49 DesktopMediaID::Id AcceleratedWidgetToDesktopMediaId(
50     gfx::AcceleratedWidget accelerated_widget) {
51 #if defined(OS_WIN)
52   return reinterpret_cast<DesktopMediaID::Id>(accelerated_widget);
53 #else
54   return static_cast<DesktopMediaID::Id>(accelerated_widget);
55 #endif
56 }
57
58 int GetMediaListViewHeightForRows(size_t rows) {
59   return kListItemHeight * rows;
60 }
61
62 }  // namespace
63
64 DesktopMediaSourceView::DesktopMediaSourceView(
65     DesktopMediaListView* parent,
66     DesktopMediaID source_id)
67     : parent_(parent),
68       source_id_(source_id),
69       image_view_(new views::ImageView()),
70       label_(new views::Label()),
71       selected_(false)  {
72   AddChildView(image_view_);
73   AddChildView(label_);
74   SetFocusable(true);
75 }
76
77 DesktopMediaSourceView::~DesktopMediaSourceView() {}
78
79 void DesktopMediaSourceView::SetName(const base::string16& name) {
80   label_->SetText(name);
81 }
82
83 void DesktopMediaSourceView::SetThumbnail(const gfx::ImageSkia& thumbnail) {
84   image_view_->SetImage(thumbnail);
85 }
86
87 void DesktopMediaSourceView::SetSelected(bool selected) {
88   if (selected == selected_)
89     return;
90   selected_ = selected;
91
92   if (selected) {
93     // Unselect all other sources.
94     Views neighbours;
95     parent()->GetViewsInGroup(GetGroup(), &neighbours);
96     for (Views::iterator i(neighbours.begin()); i != neighbours.end(); ++i) {
97       if (*i != this) {
98         DCHECK_EQ((*i)->GetClassName(), kDesktopMediaSourceViewClassName);
99         DesktopMediaSourceView* source_view =
100             static_cast<DesktopMediaSourceView*>(*i);
101         source_view->SetSelected(false);
102       }
103     }
104
105     const SkColor bg_color = GetNativeTheme()->GetSystemColor(
106         ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor);
107     set_background(views::Background::CreateSolidBackground(bg_color));
108
109     parent_->OnSelectionChanged();
110   } else {
111     set_background(NULL);
112   }
113
114   SchedulePaint();
115 }
116
117 const char* DesktopMediaSourceView::GetClassName() const {
118   return kDesktopMediaSourceViewClassName;
119 }
120
121 void DesktopMediaSourceView::Layout() {
122   image_view_->SetBounds(kThumbnailMargin, kThumbnailMargin,
123                          kThumbnailWidth, kThumbnailHeight);
124   label_->SetBounds(kThumbnailMargin, kThumbnailHeight + kThumbnailMargin,
125                     kThumbnailWidth, kLabelHeight);
126 }
127
128 views::View* DesktopMediaSourceView::GetSelectedViewForGroup(int group) {
129   Views neighbours;
130   parent()->GetViewsInGroup(group, &neighbours);
131   if (neighbours.empty())
132     return NULL;
133
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_)
139       return source_view;
140   }
141   return NULL;
142 }
143
144 bool DesktopMediaSourceView::IsGroupFocusTraversable() const {
145   return false;
146 }
147
148 void DesktopMediaSourceView::OnPaint(gfx::Canvas* canvas) {
149   View::OnPaint(canvas);
150   if (HasFocus()) {
151     gfx::Rect bounds(GetLocalBounds());
152     bounds.Inset(kThumbnailMargin / 2, kThumbnailMargin / 2);
153     canvas->DrawFocusRect(bounds);
154   }
155 }
156
157 void DesktopMediaSourceView::OnFocus() {
158   View::OnFocus();
159   SetSelected(true);
160   ScrollRectToVisible(gfx::Rect(size()));
161   // We paint differently when focused.
162   SchedulePaint();
163 }
164
165 void DesktopMediaSourceView::OnBlur() {
166   View::OnBlur();
167   // We paint differently when focused.
168   SchedulePaint();
169 }
170
171 bool DesktopMediaSourceView::OnMousePressed(const ui::MouseEvent& event) {
172   if (event.GetClickCount() == 1) {
173     RequestFocus();
174   } else if (event.GetClickCount() == 2) {
175     RequestFocus();
176     parent_->OnDoubleClick();
177   }
178   return true;
179 }
180
181 void DesktopMediaSourceView::OnGestureEvent(ui::GestureEvent* event) {
182   if (event->type() == ui::ET_GESTURE_TAP &&
183       event->details().tap_count() == 2) {
184     RequestFocus();
185     parent_->OnDoubleClick();
186     event->SetHandled();
187     return;
188   }
189
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) {
193     RequestFocus();
194     event->SetHandled();
195   }
196 }
197
198 DesktopMediaListView::DesktopMediaListView(
199     DesktopMediaPickerDialogView* parent,
200     scoped_ptr<DesktopMediaList> media_list)
201     : parent_(parent),
202       media_list_(media_list.Pass()) {
203   media_list_->SetThumbnailSize(gfx::Size(kThumbnailWidth, kThumbnailHeight));
204 }
205
206 DesktopMediaListView::~DesktopMediaListView() {}
207
208 void DesktopMediaListView::StartUpdating(DesktopMediaID::Id dialog_window_id) {
209   media_list_->SetViewDialogWindowId(dialog_window_id);
210   media_list_->StartUpdating(this);
211 }
212
213 void DesktopMediaListView::OnSelectionChanged() {
214   parent_->OnSelectionChanged();
215 }
216
217 void DesktopMediaListView::OnDoubleClick() {
218   parent_->OnDoubleClick();
219 }
220
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())
227       return source_view;
228   }
229   return NULL;
230 }
231
232 gfx::Size DesktopMediaListView::GetPreferredSize() const {
233   int total_rows = (child_count() + kListColumns - 1) / kListColumns;
234   return gfx::Size(kTotalListWidth, GetMediaListViewHeightForRows(total_rows));
235 }
236
237 void DesktopMediaListView::Layout() {
238   int x = 0;
239   int y = 0;
240
241   for (int i = 0; i < child_count(); ++i) {
242     if (x + kListItemWidth > kTotalListWidth) {
243       x = 0;
244       y += kListItemHeight;
245     }
246
247     View* source_view = child_at(i);
248     source_view->SetBounds(x, y, kListItemWidth, kListItemHeight);
249
250     x += kListItemWidth;
251   }
252
253   y += kListItemHeight;
254   SetSize(gfx::Size(kTotalListWidth, y));
255 }
256
257 bool DesktopMediaListView::OnKeyPressed(const ui::KeyEvent& event) {
258   int position_increment = 0;
259   switch (event.key_code()) {
260     case ui::VKEY_UP:
261       position_increment = -kListColumns;
262       break;
263     case ui::VKEY_DOWN:
264       position_increment = kListColumns;
265       break;
266     case ui::VKEY_LEFT:
267       position_increment = -1;
268       break;
269     case ui::VKEY_RIGHT:
270       position_increment = 1;
271       break;
272     default:
273       return false;
274   }
275
276   if (position_increment != 0) {
277     DesktopMediaSourceView* selected = GetSelection();
278     DesktopMediaSourceView* new_selected = NULL;
279
280     if (selected) {
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)
286         new_index = 0;
287       if (index != new_index) {
288         new_selected =
289             static_cast<DesktopMediaSourceView*>(child_at(new_index));
290       }
291     } else if (has_children()) {
292       new_selected = static_cast<DesktopMediaSourceView*>(child_at(0));
293     }
294
295     if (new_selected) {
296       GetFocusManager()->SetFocusedView(new_selected);
297     }
298
299     return true;
300   }
301
302   return false;
303 }
304
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);
312
313   PreferredSizeChanged();
314
315   if (child_count() % kListColumns == 1)
316     parent_->OnMediaListRowsChanged();
317 }
318
319 void DesktopMediaListView::OnSourceRemoved(int index) {
320   DesktopMediaSourceView* view =
321       static_cast<DesktopMediaSourceView*>(child_at(index));
322   DCHECK(view);
323   DCHECK_EQ(view->GetClassName(), kDesktopMediaSourceViewClassName);
324   bool was_selected = view->is_selected();
325   RemoveChildView(view);
326   delete view;
327
328   if (was_selected)
329     OnSelectionChanged();
330
331   PreferredSizeChanged();
332
333   if (child_count() % kListColumns == 0)
334     parent_->OnMediaListRowsChanged();
335 }
336
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();
342 }
343
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);
349 }
350
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);
356 }
357
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)
366     : parent_(parent),
367       app_name_(app_name),
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) {
372     label_->SetText(
373         l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TEXT, app_name));
374   } else {
375     label_->SetText(l10n_util::GetStringFUTF16(
376         IDS_DESKTOP_MEDIA_PICKER_TEXT_DELEGATED, app_name, target_name));
377   }
378   label_->SetMultiLine(true);
379   label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
380   AddChildView(label_);
381
382   scroll_view_->SetContents(list_view_);
383   scroll_view_->ClipHeightTo(
384       GetMediaListViewHeightForRows(1), GetMediaListViewHeightForRows(2));
385   AddChildView(scroll_view_);
386
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
391   // by a root window.
392   views::Widget* widget = NULL;
393   if (parent_web_contents)
394     widget = CreateWebModalDialogViews(this, parent_web_contents);
395   else
396     widget = DialogDelegate::CreateDialogWidget(this, context, parent_window);
397
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;
402
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) {
407 #if defined(USE_ASH)
408     if (chrome::IsNativeWindowInAsh(widget->GetNativeWindow())) {
409       dialog_window_id =
410           DesktopMediaID::RegisterAuraWindow(widget->GetNativeWindow()).id;
411       DCHECK_NE(dialog_window_id, 0);
412     }
413 #endif
414
415     if (dialog_window_id == 0) {
416       dialog_window_id = AcceleratedWidgetToDesktopMediaId(
417           widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
418     }
419   }
420
421   list_view_->StartUpdating(dialog_window_id);
422
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);
428   } else {
429     widget->Show();
430   }
431 }
432
433 DesktopMediaPickerDialogView::~DesktopMediaPickerDialogView() {}
434
435 void DesktopMediaPickerDialogView::DetachParent() {
436   parent_ = NULL;
437 }
438
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);
444
445   return gfx::Size(kDialogViewWidth,
446                    views::kPanelVertMargin * 2 + label_height +
447                        views::kPanelVerticalSpacing +
448                        scroll_view_->GetPreferredSize().height());
449 }
450
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();
455
456   rect.Inset(title_insets.left(), views::kPanelVertMargin);
457
458   gfx::Rect label_rect(rect.x(), rect.y(), rect.width(),
459                        label_->GetHeightForWidth(rect.width()));
460   label_->SetBoundsRect(label_rect);
461
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);
466 }
467
468 ui::ModalType DesktopMediaPickerDialogView::GetModalType() const {
469   return ui::MODAL_TYPE_CHILD;
470 }
471
472 base::string16 DesktopMediaPickerDialogView::GetWindowTitle() const {
473   return l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TITLE, app_name_);
474 }
475
476 bool DesktopMediaPickerDialogView::IsDialogButtonEnabled(
477     ui::DialogButton button) const {
478   if (button == ui::DIALOG_BUTTON_OK)
479     return list_view_->GetSelection() != NULL;
480   return true;
481 }
482
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);
487 }
488
489 bool DesktopMediaPickerDialogView::Accept() {
490   DesktopMediaSourceView* selection = list_view_->GetSelection();
491
492   // Ok button should only be enabled when a source is selected.
493   DCHECK(selection);
494
495   DesktopMediaID source;
496   if (selection)
497     source = selection->source_id();
498
499   if (parent_)
500     parent_->NotifyDialogResult(source);
501
502   // Return true to close the window.
503   return true;
504 }
505
506 void DesktopMediaPickerDialogView::DeleteDelegate() {
507   // If the dialog is being closed then notify the parent about it.
508   if (parent_)
509     parent_->NotifyDialogResult(DesktopMediaID());
510   delete this;
511 }
512
513 void DesktopMediaPickerDialogView::OnSelectionChanged() {
514   GetDialogClientView()->UpdateDialogButtons();
515 }
516
517 void DesktopMediaPickerDialogView::OnDoubleClick() {
518   // This will call Accept() and close the dialog.
519   GetDialogClientView()->AcceptWindow();
520 }
521
522 void DesktopMediaPickerDialogView::OnMediaListRowsChanged() {
523   gfx::Rect widget_bound = GetWidget()->GetWindowBoundsInScreen();
524
525   int new_height = widget_bound.height() - scroll_view_->height() +
526       scroll_view_->GetPreferredSize().height();
527
528   GetWidget()->CenterWindow(gfx::Size(widget_bound.width(), new_height));
529 }
530
531 DesktopMediaSourceView*
532 DesktopMediaPickerDialogView::GetMediaSourceViewForTesting(int index) const {
533   if (list_view_->child_count() <= index)
534     return NULL;
535
536   return reinterpret_cast<DesktopMediaSourceView*>(list_view_->child_at(index));
537 }
538
539 DesktopMediaPickerViews::DesktopMediaPickerViews() : dialog_(NULL) {
540 }
541
542 DesktopMediaPickerViews::~DesktopMediaPickerViews() {
543   if (dialog_) {
544     dialog_->DetachParent();
545     dialog_->GetWidget()->Close();
546   }
547 }
548
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,
559       media_list.Pass());
560 }
561
562 void DesktopMediaPickerViews::NotifyDialogResult(DesktopMediaID source) {
563   // Once this method is called the |dialog_| will close and destroy itself.
564   dialog_->DetachParent();
565   dialog_ = NULL;
566
567   DCHECK(!callback_.is_null());
568
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));
574   callback_.Reset();
575 }
576
577 // static
578 scoped_ptr<DesktopMediaPicker> DesktopMediaPicker::Create() {
579   return scoped_ptr<DesktopMediaPicker>(new DesktopMediaPickerViews());
580 }