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