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