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/media/desktop_media_picker.h"
      6 
      7 #include "ash/shell.h"
      8 #include "base/callback.h"
      9 #include "chrome/browser/media/desktop_media_picker_model.h"
     10 #include "content/public/browser/browser_thread.h"
     11 #include "grit/generated_resources.h"
     12 #include "ui/base/keycodes/keyboard_codes.h"
     13 #include "ui/base/l10n/l10n_util.h"
     14 #include "ui/native_theme/native_theme.h"
     15 #include "ui/views/controls/image_view.h"
     16 #include "ui/views/controls/label.h"
     17 #include "ui/views/controls/scroll_view.h"
     18 #include "ui/views/corewm/shadow_types.h"
     19 #include "ui/views/focus_border.h"
     20 #include "ui/views/layout/box_layout.h"
     21 #include "ui/views/layout/layout_constants.h"
     22 #include "ui/views/widget/widget.h"
     23 #include "ui/views/window/dialog_client_view.h"
     24 #include "ui/views/window/dialog_delegate.h"
     25 
     26 using content::DesktopMediaID;
     27 
     28 namespace {
     29 
     30 const int kThumbnailWidth = 160;
     31 const int kThumbnailHeight = 100;
     32 const int kThumbnailMargin = 10;
     33 const int kLabelHeight = 40;
     34 const int kListItemWidth = kThumbnailMargin * 2 + kThumbnailWidth;
     35 const int kListItemHeight =
     36     kThumbnailMargin * 2 + kThumbnailHeight + kLabelHeight;
     37 const int kListColumns = 3;
     38 const int kTotalListWidth = kListColumns * kListItemWidth;
     39 
     40 const int kDesktopMediaSourceViewGroupId = 1;
     41 
     42 const char kDesktopMediaSourceViewClassName[] =
     43     "DesktopMediaPicker_DesktopMediaSourceView";
     44 
     45 class DesktopMediaListView;
     46 class DesktopMediaPickerDialogView;
     47 class DesktopMediaPickerViews;
     48 
     49 // View used for each item in DesktopMediaListView. Shows a single desktop media
     50 // source as a thumbnail with the title under it.
     51 class DesktopMediaSourceView : public views::View {
     52  public:
     53   DesktopMediaSourceView(DesktopMediaListView* parent,
     54                          DesktopMediaID source_id);
     55   virtual ~DesktopMediaSourceView();
     56 
     57   // Updates thumbnail and title from |source|.
     58   void SetName(const string16& name);
     59   void SetThumbnail(const gfx::ImageSkia& thumbnail);
     60 
     61   // Id for the source shown by this View.
     62   const DesktopMediaID& source_id() const {
     63     return source_id_;
     64   }
     65 
     66   // Returns true if the source is selected.
     67   bool is_selected() const { return selected_; }
     68 
     69   // Updates selection state of the element. If |selected| is true then also
     70   // calls SetSelected(false) for the source view that was selected before that
     71   // (if any).
     72   void SetSelected(bool selected);
     73 
     74   // views::View interface.
     75   virtual const char* GetClassName() const OVERRIDE;
     76   virtual void Layout() OVERRIDE;
     77   virtual views::View* GetSelectedViewForGroup(int group) OVERRIDE;
     78   virtual bool IsGroupFocusTraversable() const OVERRIDE;
     79   virtual void OnFocus() OVERRIDE;
     80   virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE;
     81 
     82  private:
     83   DesktopMediaListView* parent_;
     84   DesktopMediaID source_id_;
     85 
     86   views::ImageView* image_view_;
     87   views::Label* label_;
     88 
     89   bool selected_;
     90 
     91   DISALLOW_COPY_AND_ASSIGN(DesktopMediaSourceView);
     92 };
     93 
     94 // View that shows list of desktop media sources available from
     95 // DesktopMediaPickerModel.
     96 class DesktopMediaListView : public views::View,
     97                              public DesktopMediaPickerModel::Observer {
     98  public:
     99   DesktopMediaListView(DesktopMediaPickerDialogView* parent,
    100                        scoped_ptr<DesktopMediaPickerModel> model);
    101   virtual ~DesktopMediaListView();
    102 
    103   // Called by DesktopMediaSourceView when selection has changed.
    104   void OnSelectionChanged();
    105 
    106   // Returns currently selected source.
    107   DesktopMediaSourceView* GetSelection();
    108 
    109   // views::View overrides.
    110   virtual gfx::Size GetPreferredSize() OVERRIDE;
    111   virtual void Layout() OVERRIDE;
    112   virtual bool OnKeyPressed(const ui::KeyEvent& event) OVERRIDE;
    113 
    114  private:
    115   // DesktopMediaPickerModel::Observer interface
    116   virtual void OnSourceAdded(int index) OVERRIDE;
    117   virtual void OnSourceRemoved(int index) OVERRIDE;
    118   virtual void OnSourceNameChanged(int index) OVERRIDE;
    119   virtual void OnSourceThumbnailChanged(int index) OVERRIDE;
    120 
    121   DesktopMediaPickerDialogView* parent_;
    122   scoped_ptr<DesktopMediaPickerModel> model_;
    123 
    124   DISALLOW_COPY_AND_ASSIGN(DesktopMediaListView);
    125 };
    126 
    127 // Dialog view used for DesktopMediaPickerViews.
    128 class DesktopMediaPickerDialogView : public views::DialogDelegateView {
    129  public:
    130   DesktopMediaPickerDialogView(gfx::NativeWindow context,
    131                                gfx::NativeWindow parent_window,
    132                                DesktopMediaPickerViews* parent,
    133                                const string16& app_name,
    134                                scoped_ptr<DesktopMediaPickerModel> model);
    135   virtual ~DesktopMediaPickerDialogView();
    136 
    137   // Called by parent (DesktopMediaPickerViews) when it's destroyed.
    138   void DetachParent();
    139 
    140   // Called by DesktopMediaListView.
    141   void OnSelectionChanged();
    142 
    143   // views::View overrides.
    144   virtual gfx::Size GetPreferredSize() OVERRIDE;
    145   virtual void Layout() OVERRIDE;
    146 
    147   // views::DialogDelegateView overrides.
    148   virtual base::string16 GetWindowTitle() const OVERRIDE;
    149   virtual bool IsDialogButtonEnabled(ui::DialogButton button) const OVERRIDE;
    150   virtual bool Accept() OVERRIDE;
    151   virtual void DeleteDelegate() OVERRIDE;
    152 
    153  private:
    154   DesktopMediaPickerViews* parent_;
    155   string16 app_name_;
    156 
    157   views::Label* label_;
    158   views::ScrollView* scroll_view_;
    159   DesktopMediaListView* list_view_;
    160 
    161   DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerDialogView);
    162 };
    163 
    164 // Implementation of DesktopMediaPicker for Views.
    165 class DesktopMediaPickerViews : public DesktopMediaPicker {
    166  public:
    167   DesktopMediaPickerViews();
    168   virtual ~DesktopMediaPickerViews();
    169 
    170   void NotifyDialogResult(DesktopMediaID source);
    171 
    172   // DesktopMediaPicker overrides.
    173   virtual void Show(gfx::NativeWindow context,
    174                     gfx::NativeWindow parent,
    175                     const string16& app_name,
    176                     scoped_ptr<DesktopMediaPickerModel> model,
    177                     const DoneCallback& done_callback) OVERRIDE;
    178 
    179  private:
    180   DoneCallback callback_;
    181 
    182   // The |dialog_| is owned by the corresponding views::Widget instance.
    183   // When DesktopMediaPickerViews is destroyed the |dialog_| is destroyed
    184   // asynchronously by closing the widget.
    185   DesktopMediaPickerDialogView* dialog_;
    186 
    187   DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerViews);
    188 };
    189 
    190 DesktopMediaSourceView::DesktopMediaSourceView(
    191     DesktopMediaListView* parent,
    192     DesktopMediaID source_id)
    193     : parent_(parent),
    194       source_id_(source_id),
    195       image_view_(new views::ImageView()),
    196       label_(new views::Label()),
    197       selected_(false)  {
    198   AddChildView(image_view_);
    199   AddChildView(label_);
    200   set_focusable(true);
    201 }
    202 
    203 DesktopMediaSourceView::~DesktopMediaSourceView() {}
    204 
    205 void DesktopMediaSourceView::SetName(const string16& name) {
    206   label_->SetText(name);
    207 }
    208 
    209 void DesktopMediaSourceView::SetThumbnail(const gfx::ImageSkia& thumbnail) {
    210   image_view_->SetImage(thumbnail);
    211 }
    212 
    213 void DesktopMediaSourceView::SetSelected(bool selected) {
    214   if (selected == selected_)
    215     return;
    216   selected_ = selected;
    217 
    218   if (selected) {
    219     // Unselect all other sources.
    220     Views neighbours;
    221     parent()->GetViewsInGroup(GetGroup(), &neighbours);
    222     for (Views::iterator i(neighbours.begin()); i != neighbours.end(); ++i) {
    223       if (*i != this) {
    224         DCHECK_EQ((*i)->GetClassName(), kDesktopMediaSourceViewClassName);
    225         DesktopMediaSourceView* source_view =
    226             static_cast<DesktopMediaSourceView*>(*i);
    227         source_view->SetSelected(false);
    228       }
    229     }
    230 
    231     const SkColor bg_color = GetNativeTheme()->GetSystemColor(
    232         ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor);
    233     set_background(views::Background::CreateSolidBackground(bg_color));
    234 
    235     parent_->OnSelectionChanged();
    236   } else {
    237     set_background(NULL);
    238   }
    239 
    240   SchedulePaint();
    241 }
    242 
    243 const char* DesktopMediaSourceView::GetClassName() const {
    244   return kDesktopMediaSourceViewClassName;
    245 }
    246 
    247 void DesktopMediaSourceView::Layout() {
    248   image_view_->SetBounds(kThumbnailMargin, kThumbnailMargin,
    249                          kThumbnailWidth, kThumbnailHeight);
    250   label_->SetBounds(kThumbnailMargin, kThumbnailHeight + kThumbnailMargin,
    251                     kThumbnailWidth, kLabelHeight);
    252 
    253   set_focus_border(views::FocusBorder::CreateDashedFocusBorder(
    254       kThumbnailMargin / 2, kThumbnailMargin / 2,
    255       kThumbnailMargin / 2, kThumbnailMargin / 2));
    256 }
    257 
    258 views::View* DesktopMediaSourceView::GetSelectedViewForGroup(int group) {
    259   Views neighbours;
    260   parent()->GetViewsInGroup(group, &neighbours);
    261   if (neighbours.empty())
    262     return NULL;
    263 
    264   for (Views::iterator i(neighbours.begin()); i != neighbours.end(); ++i) {
    265     DCHECK_EQ((*i)->GetClassName(), kDesktopMediaSourceViewClassName);
    266     DesktopMediaSourceView* source_view =
    267         static_cast<DesktopMediaSourceView*>(*i);
    268     if (source_view->selected_)
    269       return source_view;
    270   }
    271   return NULL;
    272 }
    273 
    274 bool DesktopMediaSourceView::IsGroupFocusTraversable() const {
    275   return false;
    276 }
    277 
    278 void DesktopMediaSourceView::OnFocus() {
    279   View::OnFocus();
    280   SetSelected(true);
    281   ScrollRectToVisible(gfx::Rect(size()));
    282 }
    283 
    284 bool DesktopMediaSourceView::OnMousePressed(const ui::MouseEvent& event) {
    285   RequestFocus();
    286   return true;
    287 }
    288 
    289 DesktopMediaListView::DesktopMediaListView(
    290     DesktopMediaPickerDialogView* parent,
    291     scoped_ptr<DesktopMediaPickerModel> model)
    292     : parent_(parent),
    293       model_(model.Pass()) {
    294   model_->SetThumbnailSize(gfx::Size(kThumbnailWidth, kThumbnailHeight));
    295   model_->StartUpdating(this);
    296 }
    297 
    298 DesktopMediaListView::~DesktopMediaListView() {
    299 }
    300 
    301 void DesktopMediaListView::OnSelectionChanged() {
    302   parent_->OnSelectionChanged();
    303 }
    304 
    305 DesktopMediaSourceView* DesktopMediaListView::GetSelection() {
    306   for (int i = 0; i < child_count(); ++i) {
    307     DesktopMediaSourceView* source_view =
    308         static_cast<DesktopMediaSourceView*>(child_at(i));
    309     DCHECK_EQ(source_view->GetClassName(), kDesktopMediaSourceViewClassName);
    310     if (source_view->is_selected())
    311       return source_view;
    312   }
    313   return NULL;
    314 }
    315 
    316 gfx::Size DesktopMediaListView::GetPreferredSize() {
    317   int total_rows = (child_count() + kListColumns - 1) / kListColumns;
    318   return gfx::Size(kTotalListWidth, kListItemHeight * total_rows);
    319 }
    320 
    321 void DesktopMediaListView::Layout() {
    322   int x = 0;
    323   int y = 0;
    324 
    325   for (int i = 0; i < child_count(); ++i) {
    326     if (x + kListItemWidth > kTotalListWidth) {
    327       x = 0;
    328       y += kListItemHeight;
    329     }
    330 
    331     View* source_view = child_at(i);
    332     source_view->SetBounds(x, y, kListItemWidth, kListItemHeight);
    333 
    334     x += kListItemWidth;
    335   }
    336 
    337 
    338   y += kListItemHeight;
    339   SetSize(gfx::Size(kTotalListWidth, y));
    340 }
    341 
    342 bool DesktopMediaListView::OnKeyPressed(const ui::KeyEvent& event) {
    343   int position_increment = 0;
    344   switch (event.key_code()) {
    345     case ui::VKEY_UP:
    346       position_increment = -kListColumns;
    347       break;
    348     case ui::VKEY_DOWN:
    349       position_increment = kListColumns;
    350       break;
    351     case ui::VKEY_LEFT:
    352       position_increment = -1;
    353       break;
    354     case ui::VKEY_RIGHT:
    355       position_increment = 1;
    356       break;
    357     default:
    358       return false;
    359   }
    360 
    361 
    362   if (position_increment != 0) {
    363     DesktopMediaSourceView* selected = GetSelection();
    364     DesktopMediaSourceView* new_selected = NULL;
    365 
    366     if (selected) {
    367       int index = GetIndexOf(selected);
    368       int new_index = index + position_increment;
    369       if (new_index >= child_count())
    370         new_index = child_count() - 1;
    371       else if (new_index < 0)
    372         new_index = 0;
    373       if (index != new_index) {
    374         new_selected =
    375             static_cast<DesktopMediaSourceView*>(child_at(new_index));
    376       }
    377     } else if (has_children()) {
    378       new_selected = static_cast<DesktopMediaSourceView*>(child_at(0));
    379     }
    380 
    381     if (new_selected) {
    382       GetFocusManager()->SetFocusedView(new_selected);
    383     }
    384 
    385     return true;
    386   }
    387 
    388   return false;
    389 }
    390 
    391 void DesktopMediaListView::OnSourceAdded(int index) {
    392   const DesktopMediaPickerModel::Source& source = model_->source(index);
    393   DesktopMediaSourceView* source_view =
    394       new DesktopMediaSourceView(this, source.id);
    395   source_view->SetName(source.name);
    396   source_view->SetGroup(kDesktopMediaSourceViewGroupId);
    397   AddChildViewAt(source_view, index);
    398   Layout();
    399 }
    400 
    401 void DesktopMediaListView::OnSourceRemoved(int index) {
    402   DesktopMediaSourceView* view =
    403       static_cast<DesktopMediaSourceView*>(child_at(index));
    404   DCHECK(view);
    405   DCHECK_EQ(view->GetClassName(), kDesktopMediaSourceViewClassName);
    406   bool was_selected = view->is_selected();
    407   RemoveChildView(view);
    408   delete view;
    409 
    410   if (was_selected)
    411     OnSelectionChanged();
    412 
    413   PreferredSizeChanged();
    414 }
    415 
    416 void DesktopMediaListView::OnSourceNameChanged(int index) {
    417   const DesktopMediaPickerModel::Source& source = model_->source(index);
    418   DesktopMediaSourceView* source_view =
    419       static_cast<DesktopMediaSourceView*>(child_at(index));
    420   source_view->SetName(source.name);
    421 }
    422 
    423 void DesktopMediaListView::OnSourceThumbnailChanged(int index) {
    424   const DesktopMediaPickerModel::Source& source = model_->source(index);
    425   DesktopMediaSourceView* source_view =
    426       static_cast<DesktopMediaSourceView*>(child_at(index));
    427   source_view->SetThumbnail(source.thumbnail);
    428 }
    429 
    430 DesktopMediaPickerDialogView::DesktopMediaPickerDialogView(
    431     gfx::NativeWindow context,
    432     gfx::NativeWindow parent_window,
    433     DesktopMediaPickerViews* parent,
    434     const string16& app_name,
    435     scoped_ptr<DesktopMediaPickerModel> model)
    436     : parent_(parent),
    437       app_name_(app_name),
    438       label_(new views::Label()),
    439       scroll_view_(views::ScrollView::CreateScrollViewWithBorder()),
    440       list_view_(new DesktopMediaListView(this, model.Pass())) {
    441   label_->SetText(
    442       l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TEXT, app_name_));
    443   label_->SetMultiLine(true);
    444   label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    445   AddChildView(label_);
    446 
    447   scroll_view_->SetContents(list_view_);
    448   AddChildView(scroll_view_);
    449 
    450   DialogDelegate::CreateDialogWidget(this, context, parent_window);
    451   GetWidget()->Show();
    452 }
    453 
    454 DesktopMediaPickerDialogView::~DesktopMediaPickerDialogView() {}
    455 
    456 void DesktopMediaPickerDialogView::DetachParent() {
    457   parent_ = NULL;
    458 }
    459 
    460 gfx::Size DesktopMediaPickerDialogView::GetPreferredSize() {
    461   return gfx::Size(600, 500);
    462 }
    463 
    464 void DesktopMediaPickerDialogView::Layout() {
    465   gfx::Rect rect = GetLocalBounds();
    466   rect.Inset(views::kPanelHorizMargin, views::kPanelVertMargin);
    467 
    468   gfx::Rect label_rect(rect.x(), rect.y(), rect.width(),
    469                        label_->GetHeightForWidth(rect.width()));
    470   label_->SetBoundsRect(label_rect);
    471 
    472   int scroll_view_top = label_rect.bottom() + views::kPanelVerticalSpacing;
    473   scroll_view_->SetBounds(
    474       rect.x(), scroll_view_top,
    475       rect.width(), rect.height() - scroll_view_top);
    476 }
    477 
    478 base::string16 DesktopMediaPickerDialogView::GetWindowTitle() const {
    479   return l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TITLE, app_name_);
    480 }
    481 
    482 bool DesktopMediaPickerDialogView::IsDialogButtonEnabled(
    483     ui::DialogButton button) const {
    484   if (button == ui::DIALOG_BUTTON_OK)
    485     return list_view_->GetSelection() != NULL;
    486   return true;
    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 DesktopMediaPickerViews::DesktopMediaPickerViews()
    518     : dialog_(NULL) {
    519 }
    520 
    521 DesktopMediaPickerViews::~DesktopMediaPickerViews() {
    522   if (dialog_) {
    523     dialog_->DetachParent();
    524     dialog_->GetWidget()->Close();
    525   }
    526 }
    527 
    528 void DesktopMediaPickerViews::Show(gfx::NativeWindow context,
    529                                    gfx::NativeWindow parent,
    530                                    const string16& app_name,
    531                                    scoped_ptr<DesktopMediaPickerModel> model,
    532                                    const DoneCallback& done_callback) {
    533   callback_ = done_callback;
    534   dialog_ = new DesktopMediaPickerDialogView(
    535       context, parent, this, app_name, model.Pass());
    536 }
    537 
    538 void DesktopMediaPickerViews::NotifyDialogResult(
    539     DesktopMediaID source) {
    540   // Once this method is called the |dialog_| will close and destroy itself.
    541   dialog_->DetachParent();
    542   dialog_ = NULL;
    543 
    544   DCHECK(!callback_.is_null());
    545 
    546   // Notify the |callback_| asynchronously because it may need to destroy
    547   // DesktopMediaPicker.
    548   content::BrowserThread::PostTask(
    549       content::BrowserThread::UI, FROM_HERE,
    550       base::Bind(callback_, source));
    551   callback_.Reset();
    552 }
    553 
    554 }  // namespace
    555 
    556 // static
    557 scoped_ptr<DesktopMediaPicker> DesktopMediaPicker::Create() {
    558   return scoped_ptr<DesktopMediaPicker>(new DesktopMediaPickerViews());
    559 }
    560