Upstream version 7.36.149.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/media/desktop_media_picker.h"
6
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/aura/window_tree_host.h"
14 #include "ui/base/l10n/l10n_util.h"
15 #include "ui/events/keycodes/keyboard_codes.h"
16 #include "ui/gfx/canvas.h"
17 #include "ui/native_theme/native_theme.h"
18 #include "ui/views/background.h"
19 #include "ui/views/controls/image_view.h"
20 #include "ui/views/controls/label.h"
21 #include "ui/views/controls/scroll_view.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"
27 #include "ui/wm/core/shadow_types.h"
28
29 using content::DesktopMediaID;
30
31 namespace {
32
33 const int kThumbnailWidth = 160;
34 const int kThumbnailHeight = 100;
35 const int kThumbnailMargin = 10;
36 const int kLabelHeight = 40;
37 const int kListItemWidth = kThumbnailMargin * 2 + kThumbnailWidth;
38 const int kListItemHeight =
39     kThumbnailMargin * 2 + kThumbnailHeight + kLabelHeight;
40 const int kListColumns = 3;
41 const int kTotalListWidth = kListColumns * kListItemWidth;
42
43 const int kDesktopMediaSourceViewGroupId = 1;
44
45 const char kDesktopMediaSourceViewClassName[] =
46     "DesktopMediaPicker_DesktopMediaSourceView";
47
48 content::DesktopMediaID::Id AcceleratedWidgetToDesktopMediaId(
49     gfx::AcceleratedWidget accelerated_widget) {
50 #if defined(OS_WIN)
51   return reinterpret_cast<content::DesktopMediaID::Id>(accelerated_widget);
52 #else
53   return static_cast<content::DesktopMediaID::Id>(accelerated_widget);
54 #endif
55 }
56
57 class DesktopMediaListView;
58 class DesktopMediaPickerDialogView;
59 class DesktopMediaPickerViews;
60
61 // View used for each item in DesktopMediaListView. Shows a single desktop media
62 // source as a thumbnail with the title under it.
63 class DesktopMediaSourceView : public views::View {
64  public:
65   DesktopMediaSourceView(DesktopMediaListView* parent,
66                          DesktopMediaID source_id);
67   virtual ~DesktopMediaSourceView();
68
69   // Updates thumbnail and title from |source|.
70   void SetName(const base::string16& name);
71   void SetThumbnail(const gfx::ImageSkia& thumbnail);
72
73   // Id for the source shown by this View.
74   const DesktopMediaID& source_id() const {
75     return source_id_;
76   }
77
78   // Returns true if the source is selected.
79   bool is_selected() const { return selected_; }
80
81   // Updates selection state of the element. If |selected| is true then also
82   // calls SetSelected(false) for the source view that was selected before that
83   // (if any).
84   void SetSelected(bool selected);
85
86   // views::View interface.
87   virtual const char* GetClassName() const OVERRIDE;
88   virtual void Layout() OVERRIDE;
89   virtual views::View* GetSelectedViewForGroup(int group) OVERRIDE;
90   virtual bool IsGroupFocusTraversable() const OVERRIDE;
91   virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
92   virtual void OnFocus() OVERRIDE;
93   virtual void OnBlur() OVERRIDE;
94   virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE;
95
96  private:
97   DesktopMediaListView* parent_;
98   DesktopMediaID source_id_;
99
100   views::ImageView* image_view_;
101   views::Label* label_;
102
103   bool selected_;
104
105   DISALLOW_COPY_AND_ASSIGN(DesktopMediaSourceView);
106 };
107
108 // View that shows list of desktop media sources available from
109 // DesktopMediaList.
110 class DesktopMediaListView : public views::View,
111                              public DesktopMediaListObserver {
112  public:
113   DesktopMediaListView(DesktopMediaPickerDialogView* parent,
114                        scoped_ptr<DesktopMediaList> media_list);
115   virtual ~DesktopMediaListView();
116
117   void StartUpdating(content::DesktopMediaID::Id dialog_window_id);
118
119   // Called by DesktopMediaSourceView when selection has changed.
120   void OnSelectionChanged();
121
122   // Called by DesktopMediaSourceView when a source has been double-clicked.
123   void OnDoubleClick();
124
125   // Returns currently selected source.
126   DesktopMediaSourceView* GetSelection();
127
128   // views::View overrides.
129   virtual gfx::Size GetPreferredSize() OVERRIDE;
130   virtual void Layout() OVERRIDE;
131   virtual bool OnKeyPressed(const ui::KeyEvent& event) OVERRIDE;
132
133  private:
134   // DesktopMediaList::Observer interface
135   virtual void OnSourceAdded(int index) OVERRIDE;
136   virtual void OnSourceRemoved(int index) OVERRIDE;
137   virtual void OnSourceMoved(int old_index, int new_index) OVERRIDE;
138   virtual void OnSourceNameChanged(int index) OVERRIDE;
139   virtual void OnSourceThumbnailChanged(int index) OVERRIDE;
140
141   DesktopMediaPickerDialogView* parent_;
142   scoped_ptr<DesktopMediaList> media_list_;
143
144   DISALLOW_COPY_AND_ASSIGN(DesktopMediaListView);
145 };
146
147 // Dialog view used for DesktopMediaPickerViews.
148 class DesktopMediaPickerDialogView : public views::DialogDelegateView {
149  public:
150   DesktopMediaPickerDialogView(gfx::NativeWindow context,
151                                gfx::NativeWindow parent_window,
152                                DesktopMediaPickerViews* parent,
153                                const base::string16& app_name,
154                                const base::string16& target_name,
155                                scoped_ptr<DesktopMediaList> media_list);
156   virtual ~DesktopMediaPickerDialogView();
157
158   // Called by parent (DesktopMediaPickerViews) when it's destroyed.
159   void DetachParent();
160
161   // Called by DesktopMediaListView.
162   void OnSelectionChanged();
163   void OnDoubleClick();
164
165   // views::View overrides.
166   virtual gfx::Size GetPreferredSize() OVERRIDE;
167   virtual void Layout() OVERRIDE;
168
169   // views::DialogDelegateView overrides.
170   virtual base::string16 GetWindowTitle() const OVERRIDE;
171   virtual bool IsDialogButtonEnabled(ui::DialogButton button) const OVERRIDE;
172   virtual base::string16 GetDialogButtonLabel(
173       ui::DialogButton button) const OVERRIDE;
174   virtual bool Accept() OVERRIDE;
175   virtual void DeleteDelegate() OVERRIDE;
176
177  private:
178   DesktopMediaPickerViews* parent_;
179   base::string16 app_name_;
180
181   views::Label* label_;
182   views::ScrollView* scroll_view_;
183   DesktopMediaListView* list_view_;
184
185   DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerDialogView);
186 };
187
188 // Implementation of DesktopMediaPicker for Views.
189 class DesktopMediaPickerViews : public DesktopMediaPicker {
190  public:
191   DesktopMediaPickerViews();
192   virtual ~DesktopMediaPickerViews();
193
194   void NotifyDialogResult(DesktopMediaID source);
195
196   // DesktopMediaPicker overrides.
197   virtual void Show(gfx::NativeWindow context,
198                     gfx::NativeWindow parent,
199                     const base::string16& app_name,
200                     const base::string16& target_name,
201                     scoped_ptr<DesktopMediaList> media_list,
202                     const DoneCallback& done_callback) OVERRIDE;
203
204  private:
205   DoneCallback callback_;
206
207   // The |dialog_| is owned by the corresponding views::Widget instance.
208   // When DesktopMediaPickerViews is destroyed the |dialog_| is destroyed
209   // asynchronously by closing the widget.
210   DesktopMediaPickerDialogView* dialog_;
211
212   DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerViews);
213 };
214
215 DesktopMediaSourceView::DesktopMediaSourceView(
216     DesktopMediaListView* parent,
217     DesktopMediaID source_id)
218     : parent_(parent),
219       source_id_(source_id),
220       image_view_(new views::ImageView()),
221       label_(new views::Label()),
222       selected_(false)  {
223   AddChildView(image_view_);
224   AddChildView(label_);
225   SetFocusable(true);
226 }
227
228 DesktopMediaSourceView::~DesktopMediaSourceView() {}
229
230 void DesktopMediaSourceView::SetName(const base::string16& name) {
231   label_->SetText(name);
232 }
233
234 void DesktopMediaSourceView::SetThumbnail(const gfx::ImageSkia& thumbnail) {
235   image_view_->SetImage(thumbnail);
236 }
237
238 void DesktopMediaSourceView::SetSelected(bool selected) {
239   if (selected == selected_)
240     return;
241   selected_ = selected;
242
243   if (selected) {
244     // Unselect all other sources.
245     Views neighbours;
246     parent()->GetViewsInGroup(GetGroup(), &neighbours);
247     for (Views::iterator i(neighbours.begin()); i != neighbours.end(); ++i) {
248       if (*i != this) {
249         DCHECK_EQ((*i)->GetClassName(), kDesktopMediaSourceViewClassName);
250         DesktopMediaSourceView* source_view =
251             static_cast<DesktopMediaSourceView*>(*i);
252         source_view->SetSelected(false);
253       }
254     }
255
256     const SkColor bg_color = GetNativeTheme()->GetSystemColor(
257         ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor);
258     set_background(views::Background::CreateSolidBackground(bg_color));
259
260     parent_->OnSelectionChanged();
261   } else {
262     set_background(NULL);
263   }
264
265   SchedulePaint();
266 }
267
268 const char* DesktopMediaSourceView::GetClassName() const {
269   return kDesktopMediaSourceViewClassName;
270 }
271
272 void DesktopMediaSourceView::Layout() {
273   image_view_->SetBounds(kThumbnailMargin, kThumbnailMargin,
274                          kThumbnailWidth, kThumbnailHeight);
275   label_->SetBounds(kThumbnailMargin, kThumbnailHeight + kThumbnailMargin,
276                     kThumbnailWidth, kLabelHeight);
277 }
278
279 views::View* DesktopMediaSourceView::GetSelectedViewForGroup(int group) {
280   Views neighbours;
281   parent()->GetViewsInGroup(group, &neighbours);
282   if (neighbours.empty())
283     return NULL;
284
285   for (Views::iterator i(neighbours.begin()); i != neighbours.end(); ++i) {
286     DCHECK_EQ((*i)->GetClassName(), kDesktopMediaSourceViewClassName);
287     DesktopMediaSourceView* source_view =
288         static_cast<DesktopMediaSourceView*>(*i);
289     if (source_view->selected_)
290       return source_view;
291   }
292   return NULL;
293 }
294
295 bool DesktopMediaSourceView::IsGroupFocusTraversable() const {
296   return false;
297 }
298
299 void DesktopMediaSourceView::OnPaint(gfx::Canvas* canvas) {
300   View::OnPaint(canvas);
301   if (HasFocus()) {
302     gfx::Rect bounds(GetLocalBounds());
303     bounds.Inset(kThumbnailMargin / 2, kThumbnailMargin / 2);
304     canvas->DrawFocusRect(bounds);
305   }
306 }
307
308 void DesktopMediaSourceView::OnFocus() {
309   View::OnFocus();
310   SetSelected(true);
311   ScrollRectToVisible(gfx::Rect(size()));
312   // We paint differently when focused.
313   SchedulePaint();
314 }
315
316 void DesktopMediaSourceView::OnBlur() {
317   View::OnBlur();
318   // We paint differently when focused.
319   SchedulePaint();
320 }
321
322 bool DesktopMediaSourceView::OnMousePressed(const ui::MouseEvent& event) {
323   if (event.GetClickCount() == 1) {
324     RequestFocus();
325   } else if (event.GetClickCount() == 2) {
326     RequestFocus();
327     parent_->OnDoubleClick();
328   }
329   return true;
330 }
331
332 DesktopMediaListView::DesktopMediaListView(
333     DesktopMediaPickerDialogView* parent,
334     scoped_ptr<DesktopMediaList> media_list)
335     : parent_(parent),
336       media_list_(media_list.Pass()) {
337   media_list_->SetThumbnailSize(gfx::Size(kThumbnailWidth, kThumbnailHeight));
338 }
339
340 DesktopMediaListView::~DesktopMediaListView() {}
341
342 void DesktopMediaListView::StartUpdating(
343     content::DesktopMediaID::Id dialog_window_id) {
344   media_list_->SetViewDialogWindowId(dialog_window_id);
345   media_list_->StartUpdating(this);
346 }
347
348 void DesktopMediaListView::OnSelectionChanged() {
349   parent_->OnSelectionChanged();
350 }
351
352 void DesktopMediaListView::OnDoubleClick() {
353   parent_->OnDoubleClick();
354 }
355
356 DesktopMediaSourceView* DesktopMediaListView::GetSelection() {
357   for (int i = 0; i < child_count(); ++i) {
358     DesktopMediaSourceView* source_view =
359         static_cast<DesktopMediaSourceView*>(child_at(i));
360     DCHECK_EQ(source_view->GetClassName(), kDesktopMediaSourceViewClassName);
361     if (source_view->is_selected())
362       return source_view;
363   }
364   return NULL;
365 }
366
367 gfx::Size DesktopMediaListView::GetPreferredSize() {
368   int total_rows = (child_count() + kListColumns - 1) / kListColumns;
369   return gfx::Size(kTotalListWidth, kListItemHeight * total_rows);
370 }
371
372 void DesktopMediaListView::Layout() {
373   int x = 0;
374   int y = 0;
375
376   for (int i = 0; i < child_count(); ++i) {
377     if (x + kListItemWidth > kTotalListWidth) {
378       x = 0;
379       y += kListItemHeight;
380     }
381
382     View* source_view = child_at(i);
383     source_view->SetBounds(x, y, kListItemWidth, kListItemHeight);
384
385     x += kListItemWidth;
386   }
387
388   y += kListItemHeight;
389   SetSize(gfx::Size(kTotalListWidth, y));
390 }
391
392 bool DesktopMediaListView::OnKeyPressed(const ui::KeyEvent& event) {
393   int position_increment = 0;
394   switch (event.key_code()) {
395     case ui::VKEY_UP:
396       position_increment = -kListColumns;
397       break;
398     case ui::VKEY_DOWN:
399       position_increment = kListColumns;
400       break;
401     case ui::VKEY_LEFT:
402       position_increment = -1;
403       break;
404     case ui::VKEY_RIGHT:
405       position_increment = 1;
406       break;
407     default:
408       return false;
409   }
410
411   if (position_increment != 0) {
412     DesktopMediaSourceView* selected = GetSelection();
413     DesktopMediaSourceView* new_selected = NULL;
414
415     if (selected) {
416       int index = GetIndexOf(selected);
417       int new_index = index + position_increment;
418       if (new_index >= child_count())
419         new_index = child_count() - 1;
420       else if (new_index < 0)
421         new_index = 0;
422       if (index != new_index) {
423         new_selected =
424             static_cast<DesktopMediaSourceView*>(child_at(new_index));
425       }
426     } else if (has_children()) {
427       new_selected = static_cast<DesktopMediaSourceView*>(child_at(0));
428     }
429
430     if (new_selected) {
431       GetFocusManager()->SetFocusedView(new_selected);
432     }
433
434     return true;
435   }
436
437   return false;
438 }
439
440 void DesktopMediaListView::OnSourceAdded(int index) {
441   const DesktopMediaList::Source& source = media_list_->GetSource(index);
442   DesktopMediaSourceView* source_view =
443       new DesktopMediaSourceView(this, source.id);
444   source_view->SetName(source.name);
445   source_view->SetGroup(kDesktopMediaSourceViewGroupId);
446   AddChildViewAt(source_view, index);
447
448   PreferredSizeChanged();
449 }
450
451 void DesktopMediaListView::OnSourceRemoved(int index) {
452   DesktopMediaSourceView* view =
453       static_cast<DesktopMediaSourceView*>(child_at(index));
454   DCHECK(view);
455   DCHECK_EQ(view->GetClassName(), kDesktopMediaSourceViewClassName);
456   bool was_selected = view->is_selected();
457   RemoveChildView(view);
458   delete view;
459
460   if (was_selected)
461     OnSelectionChanged();
462
463   PreferredSizeChanged();
464 }
465
466 void DesktopMediaListView::OnSourceMoved(int old_index, int new_index) {
467   DesktopMediaSourceView* view =
468       static_cast<DesktopMediaSourceView*>(child_at(old_index));
469   ReorderChildView(view, new_index);
470   PreferredSizeChanged();
471 }
472
473 void DesktopMediaListView::OnSourceNameChanged(int index) {
474   const DesktopMediaList::Source& source = media_list_->GetSource(index);
475   DesktopMediaSourceView* source_view =
476       static_cast<DesktopMediaSourceView*>(child_at(index));
477   source_view->SetName(source.name);
478 }
479
480 void DesktopMediaListView::OnSourceThumbnailChanged(int index) {
481   const DesktopMediaList::Source& source = media_list_->GetSource(index);
482   DesktopMediaSourceView* source_view =
483       static_cast<DesktopMediaSourceView*>(child_at(index));
484   source_view->SetThumbnail(source.thumbnail);
485 }
486
487 DesktopMediaPickerDialogView::DesktopMediaPickerDialogView(
488     gfx::NativeWindow context,
489     gfx::NativeWindow parent_window,
490     DesktopMediaPickerViews* parent,
491     const base::string16& app_name,
492     const base::string16& target_name,
493     scoped_ptr<DesktopMediaList> media_list)
494     : parent_(parent),
495       app_name_(app_name),
496       label_(new views::Label()),
497       scroll_view_(views::ScrollView::CreateScrollViewWithBorder()),
498       list_view_(new DesktopMediaListView(this, media_list.Pass())) {
499   if (app_name == target_name) {
500     label_->SetText(
501         l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TEXT, app_name));
502   } else {
503     label_->SetText(l10n_util::GetStringFUTF16(
504         IDS_DESKTOP_MEDIA_PICKER_TEXT_DELEGATED, app_name, target_name));
505   }
506   label_->SetMultiLine(true);
507   label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
508   AddChildView(label_);
509
510   scroll_view_->SetContents(list_view_);
511   AddChildView(scroll_view_);
512
513   DialogDelegate::CreateDialogWidget(this, context, parent_window);
514
515   // DesktopMediaList needs to know the ID of the picker window which
516   // matches the ID it gets from the OS. Depending on the OS and configuration
517   // we get this ID differently.
518   content::DesktopMediaID::Id dialog_window_id = 0;
519
520 #if defined(USE_ASH)
521   if (chrome::IsNativeWindowInAsh(GetWidget()->GetNativeWindow())) {
522     dialog_window_id = content::DesktopMediaID::RegisterAuraWindow(
523         GetWidget()->GetNativeWindow()).id;
524   } else
525 #endif
526   {
527     dialog_window_id = AcceleratedWidgetToDesktopMediaId(
528         GetWidget()->GetNativeWindow()->GetHost()->
529             GetAcceleratedWidget());
530   }
531
532   list_view_->StartUpdating(dialog_window_id);
533
534   GetWidget()->Show();
535 }
536
537 DesktopMediaPickerDialogView::~DesktopMediaPickerDialogView() {}
538
539 void DesktopMediaPickerDialogView::DetachParent() {
540   parent_ = NULL;
541 }
542
543 gfx::Size DesktopMediaPickerDialogView::GetPreferredSize() {
544   return gfx::Size(600, 500);
545 }
546
547 void DesktopMediaPickerDialogView::Layout() {
548   gfx::Rect rect = GetLocalBounds();
549   rect.Inset(views::kPanelHorizMargin, views::kPanelVertMargin);
550
551   gfx::Rect label_rect(rect.x(), rect.y(), rect.width(),
552                        label_->GetHeightForWidth(rect.width()));
553   label_->SetBoundsRect(label_rect);
554
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);
559 }
560
561 base::string16 DesktopMediaPickerDialogView::GetWindowTitle() const {
562   return l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TITLE, app_name_);
563 }
564
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;
570 }
571
572 base::string16 DesktopMediaPickerDialogView::GetDialogButtonLabel(
573     ui::DialogButton button) const {
574   return l10n_util::GetStringUTF16(button == ui::DIALOG_BUTTON_OK ?
575       IDS_DESKTOP_MEDIA_PICKER_SHARE : IDS_CANCEL);
576 }
577
578 bool DesktopMediaPickerDialogView::Accept() {
579   DesktopMediaSourceView* selection = list_view_->GetSelection();
580
581   // Ok button should only be enabled when a source is selected.
582   DCHECK(selection);
583
584   DesktopMediaID source;
585   if (selection)
586     source = selection->source_id();
587
588   if (parent_)
589     parent_->NotifyDialogResult(source);
590
591   // Return true to close the window.
592   return true;
593 }
594
595 void DesktopMediaPickerDialogView::DeleteDelegate() {
596   // If the dialog is being closed then notify the parent about it.
597   if (parent_)
598     parent_->NotifyDialogResult(DesktopMediaID());
599   delete this;
600 }
601
602 void DesktopMediaPickerDialogView::OnSelectionChanged() {
603   GetDialogClientView()->UpdateDialogButtons();
604 }
605
606 void DesktopMediaPickerDialogView::OnDoubleClick() {
607   // This will call Accept() and close the dialog.
608   GetDialogClientView()->AcceptWindow();
609 }
610
611 DesktopMediaPickerViews::DesktopMediaPickerViews()
612     : dialog_(NULL) {
613 }
614
615 DesktopMediaPickerViews::~DesktopMediaPickerViews() {
616   if (dialog_) {
617     dialog_->DetachParent();
618     dialog_->GetWidget()->Close();
619   }
620 }
621
622 void DesktopMediaPickerViews::Show(gfx::NativeWindow context,
623                                    gfx::NativeWindow parent,
624                                    const base::string16& app_name,
625                                    const base::string16& target_name,
626                                    scoped_ptr<DesktopMediaList> media_list,
627                                    const DoneCallback& done_callback) {
628   callback_ = done_callback;
629   dialog_ = new DesktopMediaPickerDialogView(
630       context, parent, this, app_name, target_name, media_list.Pass());
631 }
632
633 void DesktopMediaPickerViews::NotifyDialogResult(
634     DesktopMediaID source) {
635   // Once this method is called the |dialog_| will close and destroy itself.
636   dialog_->DetachParent();
637   dialog_ = NULL;
638
639   DCHECK(!callback_.is_null());
640
641   // Notify the |callback_| asynchronously because it may need to destroy
642   // DesktopMediaPicker.
643   content::BrowserThread::PostTask(
644       content::BrowserThread::UI, FROM_HERE,
645       base::Bind(callback_, source));
646   callback_.Reset();
647 }
648
649 }  // namespace
650
651 // static
652 scoped_ptr<DesktopMediaPicker> DesktopMediaPicker::Create() {
653   return scoped_ptr<DesktopMediaPicker>(new DesktopMediaPickerViews());
654 }