desktop_media_picker_views.cc revision 58537e28ecd584eab876aee8be7156509866d23a
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 26using content::DesktopMediaID; 27 28namespace { 29 30const int kThumbnailWidth = 160; 31const int kThumbnailHeight = 100; 32const int kThumbnailMargin = 10; 33const int kLabelHeight = 40; 34const int kListItemWidth = kThumbnailMargin * 2 + kThumbnailWidth; 35const int kListItemHeight = 36 kThumbnailMargin * 2 + kThumbnailHeight + kLabelHeight; 37const int kListColumns = 3; 38const int kTotalListWidth = kListColumns * kListItemWidth; 39 40const int kDesktopMediaSourceViewGroupId = 1; 41 42const char kDesktopMediaSourceViewClassName[] = 43 "DesktopMediaPicker_DesktopMediaSourceView"; 44 45class DesktopMediaListView; 46class DesktopMediaPickerDialogView; 47class 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. 51class 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. 96class 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. 128class 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. 165class 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 190DesktopMediaSourceView::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 203DesktopMediaSourceView::~DesktopMediaSourceView() {} 204 205void DesktopMediaSourceView::SetName(const string16& name) { 206 label_->SetText(name); 207} 208 209void DesktopMediaSourceView::SetThumbnail(const gfx::ImageSkia& thumbnail) { 210 image_view_->SetImage(thumbnail); 211} 212 213void 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 243const char* DesktopMediaSourceView::GetClassName() const { 244 return kDesktopMediaSourceViewClassName; 245} 246 247void 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 258views::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 274bool DesktopMediaSourceView::IsGroupFocusTraversable() const { 275 return false; 276} 277 278void DesktopMediaSourceView::OnFocus() { 279 View::OnFocus(); 280 SetSelected(true); 281 ScrollRectToVisible(gfx::Rect(size())); 282} 283 284bool DesktopMediaSourceView::OnMousePressed(const ui::MouseEvent& event) { 285 RequestFocus(); 286 return true; 287} 288 289DesktopMediaListView::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 298DesktopMediaListView::~DesktopMediaListView() { 299} 300 301void DesktopMediaListView::OnSelectionChanged() { 302 parent_->OnSelectionChanged(); 303} 304 305DesktopMediaSourceView* 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 316gfx::Size DesktopMediaListView::GetPreferredSize() { 317 int total_rows = (child_count() + kListColumns - 1) / kListColumns; 318 return gfx::Size(kTotalListWidth, kListItemHeight * total_rows); 319} 320 321void 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 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 if (position_increment != 0) { 361 DesktopMediaSourceView* selected = GetSelection(); 362 DesktopMediaSourceView* new_selected = NULL; 363 364 if (selected) { 365 int index = GetIndexOf(selected); 366 int new_index = index + position_increment; 367 if (new_index >= child_count()) 368 new_index = child_count() - 1; 369 else if (new_index < 0) 370 new_index = 0; 371 if (index != new_index) { 372 new_selected = 373 static_cast<DesktopMediaSourceView*>(child_at(new_index)); 374 } 375 } else if (has_children()) { 376 new_selected = static_cast<DesktopMediaSourceView*>(child_at(0)); 377 } 378 379 if (new_selected) { 380 GetFocusManager()->SetFocusedView(new_selected); 381 } 382 383 return true; 384 } 385 386 return false; 387} 388 389void DesktopMediaListView::OnSourceAdded(int index) { 390 const DesktopMediaPickerModel::Source& source = model_->source(index); 391 DesktopMediaSourceView* source_view = 392 new DesktopMediaSourceView(this, source.id); 393 source_view->SetName(source.name); 394 source_view->SetGroup(kDesktopMediaSourceViewGroupId); 395 AddChildViewAt(source_view, index); 396 397 PreferredSizeChanged(); 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 DesktopMediaID 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(DesktopMediaID()); 509 delete this; 510} 511 512void DesktopMediaPickerDialogView::OnSelectionChanged() { 513 GetDialogClientView()->UpdateDialogButtons(); 514} 515 516DesktopMediaPickerViews::DesktopMediaPickerViews() 517 : dialog_(NULL) { 518} 519 520DesktopMediaPickerViews::~DesktopMediaPickerViews() { 521 if (dialog_) { 522 dialog_->DetachParent(); 523 dialog_->GetWidget()->Close(); 524 } 525} 526 527void DesktopMediaPickerViews::Show(gfx::NativeWindow context, 528 gfx::NativeWindow parent, 529 const string16& app_name, 530 scoped_ptr<DesktopMediaPickerModel> model, 531 const DoneCallback& done_callback) { 532 callback_ = done_callback; 533 dialog_ = new DesktopMediaPickerDialogView( 534 context, parent, this, app_name, model.Pass()); 535} 536 537void DesktopMediaPickerViews::NotifyDialogResult( 538 DesktopMediaID source) { 539 // Once this method is called the |dialog_| will close and destroy itself. 540 dialog_->DetachParent(); 541 dialog_ = NULL; 542 543 DCHECK(!callback_.is_null()); 544 545 // Notify the |callback_| asynchronously because it may need to destroy 546 // DesktopMediaPicker. 547 content::BrowserThread::PostTask( 548 content::BrowserThread::UI, FROM_HERE, 549 base::Bind(callback_, source)); 550 callback_.Reset(); 551} 552 553} // namespace 554 555// static 556scoped_ptr<DesktopMediaPicker> DesktopMediaPicker::Create() { 557 return scoped_ptr<DesktopMediaPicker>(new DesktopMediaPickerViews()); 558} 559