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