desktop_media_picker_views.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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 29using content::DesktopMediaID; 30 31namespace { 32 33const int kThumbnailWidth = 160; 34const int kThumbnailHeight = 100; 35const int kThumbnailMargin = 10; 36const int kLabelHeight = 40; 37const int kListItemWidth = kThumbnailMargin * 2 + kThumbnailWidth; 38const int kListItemHeight = 39 kThumbnailMargin * 2 + kThumbnailHeight + kLabelHeight; 40const int kListColumns = 3; 41const int kTotalListWidth = kListColumns * kListItemWidth; 42 43const int kDesktopMediaSourceViewGroupId = 1; 44 45const char kDesktopMediaSourceViewClassName[] = 46 "DesktopMediaPicker_DesktopMediaSourceView"; 47 48content::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 57class DesktopMediaListView; 58class DesktopMediaPickerDialogView; 59class 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. 63class 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. 110class 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. 148class 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. 189class 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 215DesktopMediaSourceView::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 228DesktopMediaSourceView::~DesktopMediaSourceView() {} 229 230void DesktopMediaSourceView::SetName(const base::string16& name) { 231 label_->SetText(name); 232} 233 234void DesktopMediaSourceView::SetThumbnail(const gfx::ImageSkia& thumbnail) { 235 image_view_->SetImage(thumbnail); 236} 237 238void 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 268const char* DesktopMediaSourceView::GetClassName() const { 269 return kDesktopMediaSourceViewClassName; 270} 271 272void DesktopMediaSourceView::Layout() { 273 image_view_->SetBounds(kThumbnailMargin, kThumbnailMargin, 274 kThumbnailWidth, kThumbnailHeight); 275 label_->SetBounds(kThumbnailMargin, kThumbnailHeight + kThumbnailMargin, 276 kThumbnailWidth, kLabelHeight); 277} 278 279views::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 295bool DesktopMediaSourceView::IsGroupFocusTraversable() const { 296 return false; 297} 298 299void 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 308void DesktopMediaSourceView::OnFocus() { 309 View::OnFocus(); 310 SetSelected(true); 311 ScrollRectToVisible(gfx::Rect(size())); 312 // We paint differently when focused. 313 SchedulePaint(); 314} 315 316void DesktopMediaSourceView::OnBlur() { 317 View::OnBlur(); 318 // We paint differently when focused. 319 SchedulePaint(); 320} 321 322bool 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 332DesktopMediaListView::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 340DesktopMediaListView::~DesktopMediaListView() {} 341 342void DesktopMediaListView::StartUpdating( 343 content::DesktopMediaID::Id dialog_window_id) { 344 media_list_->SetViewDialogWindowId(dialog_window_id); 345 media_list_->StartUpdating(this); 346} 347 348void DesktopMediaListView::OnSelectionChanged() { 349 parent_->OnSelectionChanged(); 350} 351 352void DesktopMediaListView::OnDoubleClick() { 353 parent_->OnDoubleClick(); 354} 355 356DesktopMediaSourceView* 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 367gfx::Size DesktopMediaListView::GetPreferredSize() { 368 int total_rows = (child_count() + kListColumns - 1) / kListColumns; 369 return gfx::Size(kTotalListWidth, kListItemHeight * total_rows); 370} 371 372void 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 392bool 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 440void 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 451void 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 466void 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 473void 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 480void 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 487DesktopMediaPickerDialogView::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 537DesktopMediaPickerDialogView::~DesktopMediaPickerDialogView() {} 538 539void DesktopMediaPickerDialogView::DetachParent() { 540 parent_ = NULL; 541} 542 543gfx::Size DesktopMediaPickerDialogView::GetPreferredSize() { 544 return gfx::Size(600, 500); 545} 546 547void 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 561base::string16 DesktopMediaPickerDialogView::GetWindowTitle() const { 562 return l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TITLE, app_name_); 563} 564 565bool 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 572base::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 578bool 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 595void 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 602void DesktopMediaPickerDialogView::OnSelectionChanged() { 603 GetDialogClientView()->UpdateDialogButtons(); 604} 605 606void DesktopMediaPickerDialogView::OnDoubleClick() { 607 // This will call Accept() and close the dialog. 608 GetDialogClientView()->AcceptWindow(); 609} 610 611DesktopMediaPickerViews::DesktopMediaPickerViews() 612 : dialog_(NULL) { 613} 614 615DesktopMediaPickerViews::~DesktopMediaPickerViews() { 616 if (dialog_) { 617 dialog_->DetachParent(); 618 dialog_->GetWidget()->Close(); 619 } 620} 621 622void 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 633void 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 652scoped_ptr<DesktopMediaPicker> DesktopMediaPicker::Create() { 653 return scoped_ptr<DesktopMediaPicker>(new DesktopMediaPickerViews()); 654} 655