Home | History | Annotate | Download | only in views
      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/browser/ui/views/constrained_window_views.h"
     13 #include "chrome/common/chrome_switches.h"
     14 #include "chrome/grit/generated_resources.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 }
    602