desktop_media_picker_views.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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/ui/views/desktop_media_picker_views.h" 6 7#include "base/callback.h" 8#include "chrome/browser/media/desktop_media_list.h" 9#include "chrome/browser/ui/ash/ash_util.h" 10#include "chrome/browser/ui/views/constrained_window_views.h" 11#include "components/web_modal/popup_manager.h" 12#include "content/public/browser/browser_thread.h" 13#include "grit/generated_resources.h" 14#include "ui/aura/window_tree_host.h" 15#include "ui/base/l10n/l10n_util.h" 16#include "ui/events/event_constants.h" 17#include "ui/events/keycodes/keyboard_codes.h" 18#include "ui/gfx/canvas.h" 19#include "ui/native_theme/native_theme.h" 20#include "ui/views/background.h" 21#include "ui/views/bubble/bubble_frame_view.h" 22#include "ui/views/controls/image_view.h" 23#include "ui/views/controls/label.h" 24#include "ui/views/controls/scroll_view.h" 25#include "ui/views/layout/layout_constants.h" 26#include "ui/views/widget/widget.h" 27#include "ui/views/window/dialog_client_view.h" 28#include "ui/wm/core/shadow_types.h" 29 30using content::DesktopMediaID; 31 32namespace { 33 34const int kThumbnailWidth = 160; 35const int kThumbnailHeight = 100; 36const int kThumbnailMargin = 10; 37const int kLabelHeight = 40; 38const int kListItemWidth = kThumbnailMargin * 2 + kThumbnailWidth; 39const int kListItemHeight = 40 kThumbnailMargin * 2 + kThumbnailHeight + kLabelHeight; 41const int kListColumns = 3; 42const int kTotalListWidth = kListColumns * kListItemWidth; 43 44const int kDesktopMediaSourceViewGroupId = 1; 45 46const char kDesktopMediaSourceViewClassName[] = 47 "DesktopMediaPicker_DesktopMediaSourceView"; 48 49DesktopMediaID::Id AcceleratedWidgetToDesktopMediaId( 50 gfx::AcceleratedWidget accelerated_widget) { 51#if defined(OS_WIN) 52 return reinterpret_cast<DesktopMediaID::Id>(accelerated_widget); 53#else 54 return static_cast<DesktopMediaID::Id>(accelerated_widget); 55#endif 56} 57 58int GetMediaListViewHeightForRows(size_t rows) { 59 return kListItemHeight * rows; 60} 61 62} // namespace 63 64DesktopMediaSourceView::DesktopMediaSourceView( 65 DesktopMediaListView* parent, 66 DesktopMediaID source_id) 67 : parent_(parent), 68 source_id_(source_id), 69 image_view_(new views::ImageView()), 70 label_(new views::Label()), 71 selected_(false) { 72 AddChildView(image_view_); 73 AddChildView(label_); 74 SetFocusable(true); 75} 76 77DesktopMediaSourceView::~DesktopMediaSourceView() {} 78 79void DesktopMediaSourceView::SetName(const base::string16& name) { 80 label_->SetText(name); 81} 82 83void DesktopMediaSourceView::SetThumbnail(const gfx::ImageSkia& thumbnail) { 84 image_view_->SetImage(thumbnail); 85} 86 87void DesktopMediaSourceView::SetSelected(bool selected) { 88 if (selected == selected_) 89 return; 90 selected_ = selected; 91 92 if (selected) { 93 // Unselect all other sources. 94 Views neighbours; 95 parent()->GetViewsInGroup(GetGroup(), &neighbours); 96 for (Views::iterator i(neighbours.begin()); i != neighbours.end(); ++i) { 97 if (*i != this) { 98 DCHECK_EQ((*i)->GetClassName(), kDesktopMediaSourceViewClassName); 99 DesktopMediaSourceView* source_view = 100 static_cast<DesktopMediaSourceView*>(*i); 101 source_view->SetSelected(false); 102 } 103 } 104 105 const SkColor bg_color = GetNativeTheme()->GetSystemColor( 106 ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor); 107 set_background(views::Background::CreateSolidBackground(bg_color)); 108 109 parent_->OnSelectionChanged(); 110 } else { 111 set_background(NULL); 112 } 113 114 SchedulePaint(); 115} 116 117const char* DesktopMediaSourceView::GetClassName() const { 118 return kDesktopMediaSourceViewClassName; 119} 120 121void DesktopMediaSourceView::Layout() { 122 image_view_->SetBounds(kThumbnailMargin, kThumbnailMargin, 123 kThumbnailWidth, kThumbnailHeight); 124 label_->SetBounds(kThumbnailMargin, kThumbnailHeight + kThumbnailMargin, 125 kThumbnailWidth, kLabelHeight); 126} 127 128views::View* DesktopMediaSourceView::GetSelectedViewForGroup(int group) { 129 Views neighbours; 130 parent()->GetViewsInGroup(group, &neighbours); 131 if (neighbours.empty()) 132 return NULL; 133 134 for (Views::iterator i(neighbours.begin()); i != neighbours.end(); ++i) { 135 DCHECK_EQ((*i)->GetClassName(), kDesktopMediaSourceViewClassName); 136 DesktopMediaSourceView* source_view = 137 static_cast<DesktopMediaSourceView*>(*i); 138 if (source_view->selected_) 139 return source_view; 140 } 141 return NULL; 142} 143 144bool DesktopMediaSourceView::IsGroupFocusTraversable() const { 145 return false; 146} 147 148void DesktopMediaSourceView::OnPaint(gfx::Canvas* canvas) { 149 View::OnPaint(canvas); 150 if (HasFocus()) { 151 gfx::Rect bounds(GetLocalBounds()); 152 bounds.Inset(kThumbnailMargin / 2, kThumbnailMargin / 2); 153 canvas->DrawFocusRect(bounds); 154 } 155} 156 157void DesktopMediaSourceView::OnFocus() { 158 View::OnFocus(); 159 SetSelected(true); 160 ScrollRectToVisible(gfx::Rect(size())); 161 // We paint differently when focused. 162 SchedulePaint(); 163} 164 165void DesktopMediaSourceView::OnBlur() { 166 View::OnBlur(); 167 // We paint differently when focused. 168 SchedulePaint(); 169} 170 171bool DesktopMediaSourceView::OnMousePressed(const ui::MouseEvent& event) { 172 if (event.GetClickCount() == 1) { 173 RequestFocus(); 174 } else if (event.GetClickCount() == 2) { 175 RequestFocus(); 176 parent_->OnDoubleClick(); 177 } 178 return true; 179} 180 181void DesktopMediaSourceView::OnGestureEvent(ui::GestureEvent* event) { 182 if (event->type() == ui::ET_GESTURE_TAP && 183 event->details().tap_count() == 2) { 184 RequestFocus(); 185 parent_->OnDoubleClick(); 186 event->SetHandled(); 187 return; 188 } 189 190 // Detect tap gesture using ET_GESTURE_TAP_DOWN so the view also gets focused 191 // on the long tap (when the tap gesture starts). 192 if (event->type() == ui::ET_GESTURE_TAP_DOWN) { 193 RequestFocus(); 194 event->SetHandled(); 195 } 196} 197 198DesktopMediaListView::DesktopMediaListView( 199 DesktopMediaPickerDialogView* parent, 200 scoped_ptr<DesktopMediaList> media_list) 201 : parent_(parent), 202 media_list_(media_list.Pass()) { 203 media_list_->SetThumbnailSize(gfx::Size(kThumbnailWidth, kThumbnailHeight)); 204} 205 206DesktopMediaListView::~DesktopMediaListView() {} 207 208void DesktopMediaListView::StartUpdating(DesktopMediaID::Id dialog_window_id) { 209 media_list_->SetViewDialogWindowId(dialog_window_id); 210 media_list_->StartUpdating(this); 211} 212 213void DesktopMediaListView::OnSelectionChanged() { 214 parent_->OnSelectionChanged(); 215} 216 217void DesktopMediaListView::OnDoubleClick() { 218 parent_->OnDoubleClick(); 219} 220 221DesktopMediaSourceView* DesktopMediaListView::GetSelection() { 222 for (int i = 0; i < child_count(); ++i) { 223 DesktopMediaSourceView* source_view = 224 static_cast<DesktopMediaSourceView*>(child_at(i)); 225 DCHECK_EQ(source_view->GetClassName(), kDesktopMediaSourceViewClassName); 226 if (source_view->is_selected()) 227 return source_view; 228 } 229 return NULL; 230} 231 232gfx::Size DesktopMediaListView::GetPreferredSize() const { 233 int total_rows = (child_count() + kListColumns - 1) / kListColumns; 234 return gfx::Size(kTotalListWidth, GetMediaListViewHeightForRows(total_rows)); 235} 236 237void DesktopMediaListView::Layout() { 238 int x = 0; 239 int y = 0; 240 241 for (int i = 0; i < child_count(); ++i) { 242 if (x + kListItemWidth > kTotalListWidth) { 243 x = 0; 244 y += kListItemHeight; 245 } 246 247 View* source_view = child_at(i); 248 source_view->SetBounds(x, y, kListItemWidth, kListItemHeight); 249 250 x += kListItemWidth; 251 } 252 253 y += kListItemHeight; 254 SetSize(gfx::Size(kTotalListWidth, y)); 255} 256 257bool DesktopMediaListView::OnKeyPressed(const ui::KeyEvent& event) { 258 int position_increment = 0; 259 switch (event.key_code()) { 260 case ui::VKEY_UP: 261 position_increment = -kListColumns; 262 break; 263 case ui::VKEY_DOWN: 264 position_increment = kListColumns; 265 break; 266 case ui::VKEY_LEFT: 267 position_increment = -1; 268 break; 269 case ui::VKEY_RIGHT: 270 position_increment = 1; 271 break; 272 default: 273 return false; 274 } 275 276 if (position_increment != 0) { 277 DesktopMediaSourceView* selected = GetSelection(); 278 DesktopMediaSourceView* new_selected = NULL; 279 280 if (selected) { 281 int index = GetIndexOf(selected); 282 int new_index = index + position_increment; 283 if (new_index >= child_count()) 284 new_index = child_count() - 1; 285 else if (new_index < 0) 286 new_index = 0; 287 if (index != new_index) { 288 new_selected = 289 static_cast<DesktopMediaSourceView*>(child_at(new_index)); 290 } 291 } else if (has_children()) { 292 new_selected = static_cast<DesktopMediaSourceView*>(child_at(0)); 293 } 294 295 if (new_selected) { 296 GetFocusManager()->SetFocusedView(new_selected); 297 } 298 299 return true; 300 } 301 302 return false; 303} 304 305void DesktopMediaListView::OnSourceAdded(int index) { 306 const DesktopMediaList::Source& source = media_list_->GetSource(index); 307 DesktopMediaSourceView* source_view = 308 new DesktopMediaSourceView(this, source.id); 309 source_view->SetName(source.name); 310 source_view->SetGroup(kDesktopMediaSourceViewGroupId); 311 AddChildViewAt(source_view, index); 312 313 PreferredSizeChanged(); 314 315 if (child_count() % kListColumns == 1) 316 parent_->OnMediaListRowsChanged(); 317} 318 319void DesktopMediaListView::OnSourceRemoved(int index) { 320 DesktopMediaSourceView* view = 321 static_cast<DesktopMediaSourceView*>(child_at(index)); 322 DCHECK(view); 323 DCHECK_EQ(view->GetClassName(), kDesktopMediaSourceViewClassName); 324 bool was_selected = view->is_selected(); 325 RemoveChildView(view); 326 delete view; 327 328 if (was_selected) 329 OnSelectionChanged(); 330 331 PreferredSizeChanged(); 332 333 if (child_count() % kListColumns == 0) 334 parent_->OnMediaListRowsChanged(); 335} 336 337void DesktopMediaListView::OnSourceMoved(int old_index, int new_index) { 338 DesktopMediaSourceView* view = 339 static_cast<DesktopMediaSourceView*>(child_at(old_index)); 340 ReorderChildView(view, new_index); 341 PreferredSizeChanged(); 342} 343 344void DesktopMediaListView::OnSourceNameChanged(int index) { 345 const DesktopMediaList::Source& source = media_list_->GetSource(index); 346 DesktopMediaSourceView* source_view = 347 static_cast<DesktopMediaSourceView*>(child_at(index)); 348 source_view->SetName(source.name); 349} 350 351void DesktopMediaListView::OnSourceThumbnailChanged(int index) { 352 const DesktopMediaList::Source& source = media_list_->GetSource(index); 353 DesktopMediaSourceView* source_view = 354 static_cast<DesktopMediaSourceView*>(child_at(index)); 355 source_view->SetThumbnail(source.thumbnail); 356} 357 358DesktopMediaPickerDialogView::DesktopMediaPickerDialogView( 359 content::WebContents* parent_web_contents, 360 gfx::NativeWindow context, 361 gfx::NativeWindow parent_window, 362 DesktopMediaPickerViews* parent, 363 const base::string16& app_name, 364 const base::string16& target_name, 365 scoped_ptr<DesktopMediaList> media_list) 366 : parent_(parent), 367 app_name_(app_name), 368 label_(new views::Label()), 369 scroll_view_(views::ScrollView::CreateScrollViewWithBorder()), 370 list_view_(new DesktopMediaListView(this, media_list.Pass())) { 371 if (app_name == target_name) { 372 label_->SetText( 373 l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TEXT, app_name)); 374 } else { 375 label_->SetText(l10n_util::GetStringFUTF16( 376 IDS_DESKTOP_MEDIA_PICKER_TEXT_DELEGATED, app_name, target_name)); 377 } 378 label_->SetMultiLine(true); 379 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 380 AddChildView(label_); 381 382 scroll_view_->SetContents(list_view_); 383 scroll_view_->ClipHeightTo( 384 GetMediaListViewHeightForRows(1), GetMediaListViewHeightForRows(2)); 385 AddChildView(scroll_view_); 386 387 // If |parent_web_contents| is set, the picker will be shown modal to the 388 // web contents. Otherwise, a new dialog widget inside |parent_window| will be 389 // created for the picker. Note that |parent_window| may also be NULL if 390 // parent web contents is not set. In this case the picker will be parented 391 // by a root window. 392 views::Widget* widget = NULL; 393 if (parent_web_contents) 394 widget = CreateWebModalDialogViews(this, parent_web_contents); 395 else 396 widget = DialogDelegate::CreateDialogWidget(this, context, parent_window); 397 398 // DesktopMediaList needs to know the ID of the picker window which 399 // matches the ID it gets from the OS. Depending on the OS and configuration 400 // we get this ID differently. 401 DesktopMediaID::Id dialog_window_id = 0; 402 403 // If there is |parent_window| or |parent_web_contents|, the picker window 404 // is embedded in the parent and does not have its own native window id, so we 405 // do not filter in that case. 406 if (!parent_window && !parent_web_contents) { 407#if defined(USE_ASH) 408 if (chrome::IsNativeWindowInAsh(widget->GetNativeWindow())) { 409 dialog_window_id = 410 DesktopMediaID::RegisterAuraWindow(widget->GetNativeWindow()).id; 411 DCHECK_NE(dialog_window_id, 0); 412 } 413#endif 414 415 if (dialog_window_id == 0) { 416 dialog_window_id = AcceleratedWidgetToDesktopMediaId( 417 widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget()); 418 } 419 } 420 421 list_view_->StartUpdating(dialog_window_id); 422 423 if (parent_web_contents) { 424 web_modal::PopupManager* popup_manager = 425 web_modal::PopupManager::FromWebContents(parent_web_contents); 426 popup_manager->ShowModalDialog(GetWidget()->GetNativeView(), 427 parent_web_contents); 428 } else { 429 widget->Show(); 430 } 431} 432 433DesktopMediaPickerDialogView::~DesktopMediaPickerDialogView() {} 434 435void DesktopMediaPickerDialogView::DetachParent() { 436 parent_ = NULL; 437} 438 439gfx::Size DesktopMediaPickerDialogView::GetPreferredSize() const { 440 static const size_t kDialogViewWidth = 600; 441 const gfx::Insets title_insets = views::BubbleFrameView::GetTitleInsets(); 442 size_t label_height = 443 label_->GetHeightForWidth(kDialogViewWidth - title_insets.height() * 2); 444 445 return gfx::Size(kDialogViewWidth, 446 views::kPanelVertMargin * 2 + label_height + 447 views::kPanelVerticalSpacing + 448 scroll_view_->GetPreferredSize().height()); 449} 450 451void DesktopMediaPickerDialogView::Layout() { 452 // DialogDelegate uses the bubble style frame. 453 const gfx::Insets title_insets = views::BubbleFrameView::GetTitleInsets(); 454 gfx::Rect rect = GetLocalBounds(); 455 456 rect.Inset(title_insets.left(), views::kPanelVertMargin); 457 458 gfx::Rect label_rect(rect.x(), rect.y(), rect.width(), 459 label_->GetHeightForWidth(rect.width())); 460 label_->SetBoundsRect(label_rect); 461 462 int scroll_view_top = label_rect.bottom() + views::kPanelVerticalSpacing; 463 scroll_view_->SetBounds( 464 rect.x(), scroll_view_top, 465 rect.width(), rect.height() - scroll_view_top); 466} 467 468ui::ModalType DesktopMediaPickerDialogView::GetModalType() const { 469 return ui::MODAL_TYPE_CHILD; 470} 471 472base::string16 DesktopMediaPickerDialogView::GetWindowTitle() const { 473 return l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TITLE, app_name_); 474} 475 476bool DesktopMediaPickerDialogView::IsDialogButtonEnabled( 477 ui::DialogButton button) const { 478 if (button == ui::DIALOG_BUTTON_OK) 479 return list_view_->GetSelection() != NULL; 480 return true; 481} 482 483base::string16 DesktopMediaPickerDialogView::GetDialogButtonLabel( 484 ui::DialogButton button) const { 485 return l10n_util::GetStringUTF16(button == ui::DIALOG_BUTTON_OK ? 486 IDS_DESKTOP_MEDIA_PICKER_SHARE : IDS_CANCEL); 487} 488 489bool DesktopMediaPickerDialogView::Accept() { 490 DesktopMediaSourceView* selection = list_view_->GetSelection(); 491 492 // Ok button should only be enabled when a source is selected. 493 DCHECK(selection); 494 495 DesktopMediaID source; 496 if (selection) 497 source = selection->source_id(); 498 499 if (parent_) 500 parent_->NotifyDialogResult(source); 501 502 // Return true to close the window. 503 return true; 504} 505 506void DesktopMediaPickerDialogView::DeleteDelegate() { 507 // If the dialog is being closed then notify the parent about it. 508 if (parent_) 509 parent_->NotifyDialogResult(DesktopMediaID()); 510 delete this; 511} 512 513void DesktopMediaPickerDialogView::OnSelectionChanged() { 514 GetDialogClientView()->UpdateDialogButtons(); 515} 516 517void DesktopMediaPickerDialogView::OnDoubleClick() { 518 // This will call Accept() and close the dialog. 519 GetDialogClientView()->AcceptWindow(); 520} 521 522void DesktopMediaPickerDialogView::OnMediaListRowsChanged() { 523 gfx::Rect widget_bound = GetWidget()->GetWindowBoundsInScreen(); 524 525 int new_height = widget_bound.height() - scroll_view_->height() + 526 scroll_view_->GetPreferredSize().height(); 527 528 GetWidget()->CenterWindow(gfx::Size(widget_bound.width(), new_height)); 529} 530 531DesktopMediaSourceView* 532DesktopMediaPickerDialogView::GetMediaSourceViewForTesting(int index) const { 533 if (list_view_->child_count() <= index) 534 return NULL; 535 536 return reinterpret_cast<DesktopMediaSourceView*>(list_view_->child_at(index)); 537} 538 539DesktopMediaPickerViews::DesktopMediaPickerViews() : dialog_(NULL) { 540} 541 542DesktopMediaPickerViews::~DesktopMediaPickerViews() { 543 if (dialog_) { 544 dialog_->DetachParent(); 545 dialog_->GetWidget()->Close(); 546 } 547} 548 549void DesktopMediaPickerViews::Show(content::WebContents* web_contents, 550 gfx::NativeWindow context, 551 gfx::NativeWindow parent, 552 const base::string16& app_name, 553 const base::string16& target_name, 554 scoped_ptr<DesktopMediaList> media_list, 555 const DoneCallback& done_callback) { 556 callback_ = done_callback; 557 dialog_ = new DesktopMediaPickerDialogView( 558 web_contents, context, parent, this, app_name, target_name, 559 media_list.Pass()); 560} 561 562void DesktopMediaPickerViews::NotifyDialogResult(DesktopMediaID source) { 563 // Once this method is called the |dialog_| will close and destroy itself. 564 dialog_->DetachParent(); 565 dialog_ = NULL; 566 567 DCHECK(!callback_.is_null()); 568 569 // Notify the |callback_| asynchronously because it may need to destroy 570 // DesktopMediaPicker. 571 content::BrowserThread::PostTask( 572 content::BrowserThread::UI, FROM_HERE, 573 base::Bind(callback_, source)); 574 callback_.Reset(); 575} 576 577// static 578scoped_ptr<DesktopMediaPicker> DesktopMediaPicker::Create() { 579 return scoped_ptr<DesktopMediaPicker>(new DesktopMediaPickerViews()); 580} 581