blob: 30f5ed1a7f7fcf42f24eea8bd04a2614946544f4 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/media/desktop_media_picker.h"
#include "base/callback.h"
#include "chrome/browser/media/desktop_media_list.h"
#include "chrome/browser/media/desktop_media_list_observer.h"
#include "chrome/browser/ui/ash/ash_util.h"
#include "components/web_modal/web_contents_modal_dialog_host.h"
#include "components/web_modal/web_contents_modal_dialog_manager.h"
#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
#include "content/public/browser/browser_thread.h"
#include "grit/generated_resources.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/canvas.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/background.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/layout_constants.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/dialog_client_view.h"
#include "ui/views/window/dialog_delegate.h"
#include "ui/wm/core/shadow_types.h"
using content::DesktopMediaID;
using web_modal::WebContentsModalDialogManager;
using web_modal::WebContentsModalDialogManagerDelegate;
namespace {
const int kThumbnailWidth = 160;
const int kThumbnailHeight = 100;
const int kThumbnailMargin = 10;
const int kLabelHeight = 40;
const int kListItemWidth = kThumbnailMargin * 2 + kThumbnailWidth;
const int kListItemHeight =
kThumbnailMargin * 2 + kThumbnailHeight + kLabelHeight;
const int kListColumns = 3;
const int kTotalListWidth = kListColumns * kListItemWidth;
const int kDesktopMediaSourceViewGroupId = 1;
const char kDesktopMediaSourceViewClassName[] =
"DesktopMediaPicker_DesktopMediaSourceView";
content::DesktopMediaID::Id AcceleratedWidgetToDesktopMediaId(
gfx::AcceleratedWidget accelerated_widget) {
#if defined(OS_WIN)
return reinterpret_cast<content::DesktopMediaID::Id>(accelerated_widget);
#else
return static_cast<content::DesktopMediaID::Id>(accelerated_widget);
#endif
}
class DesktopMediaListView;
class DesktopMediaPickerDialogView;
class DesktopMediaPickerViews;
// View used for each item in DesktopMediaListView. Shows a single desktop media
// source as a thumbnail with the title under it.
class DesktopMediaSourceView : public views::View {
public:
DesktopMediaSourceView(DesktopMediaListView* parent,
DesktopMediaID source_id);
virtual ~DesktopMediaSourceView();
// Updates thumbnail and title from |source|.
void SetName(const base::string16& name);
void SetThumbnail(const gfx::ImageSkia& thumbnail);
// Id for the source shown by this View.
const DesktopMediaID& source_id() const {
return source_id_;
}
// Returns true if the source is selected.
bool is_selected() const { return selected_; }
// Updates selection state of the element. If |selected| is true then also
// calls SetSelected(false) for the source view that was selected before that
// (if any).
void SetSelected(bool selected);
// views::View interface.
virtual const char* GetClassName() const OVERRIDE;
virtual void Layout() OVERRIDE;
virtual views::View* GetSelectedViewForGroup(int group) OVERRIDE;
virtual bool IsGroupFocusTraversable() const OVERRIDE;
virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
virtual void OnFocus() OVERRIDE;
virtual void OnBlur() OVERRIDE;
virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE;
private:
DesktopMediaListView* parent_;
DesktopMediaID source_id_;
views::ImageView* image_view_;
views::Label* label_;
bool selected_;
DISALLOW_COPY_AND_ASSIGN(DesktopMediaSourceView);
};
// View that shows list of desktop media sources available from
// DesktopMediaList.
class DesktopMediaListView : public views::View,
public DesktopMediaListObserver {
public:
DesktopMediaListView(DesktopMediaPickerDialogView* parent,
scoped_ptr<DesktopMediaList> media_list);
virtual ~DesktopMediaListView();
void StartUpdating(content::DesktopMediaID::Id dialog_window_id);
// Called by DesktopMediaSourceView when selection has changed.
void OnSelectionChanged();
// Called by DesktopMediaSourceView when a source has been double-clicked.
void OnDoubleClick();
// Returns currently selected source.
DesktopMediaSourceView* GetSelection();
// views::View overrides.
virtual gfx::Size GetPreferredSize() const OVERRIDE;
virtual void Layout() OVERRIDE;
virtual bool OnKeyPressed(const ui::KeyEvent& event) OVERRIDE;
private:
// DesktopMediaList::Observer interface
virtual void OnSourceAdded(int index) OVERRIDE;
virtual void OnSourceRemoved(int index) OVERRIDE;
virtual void OnSourceMoved(int old_index, int new_index) OVERRIDE;
virtual void OnSourceNameChanged(int index) OVERRIDE;
virtual void OnSourceThumbnailChanged(int index) OVERRIDE;
DesktopMediaPickerDialogView* parent_;
scoped_ptr<DesktopMediaList> media_list_;
DISALLOW_COPY_AND_ASSIGN(DesktopMediaListView);
};
// Dialog view used for DesktopMediaPickerViews.
class DesktopMediaPickerDialogView : public views::DialogDelegateView {
public:
DesktopMediaPickerDialogView(content::WebContents* parent_web_contents,
gfx::NativeWindow context,
gfx::NativeWindow parent_window,
DesktopMediaPickerViews* parent,
const base::string16& app_name,
const base::string16& target_name,
scoped_ptr<DesktopMediaList> media_list);
virtual ~DesktopMediaPickerDialogView();
// Called by parent (DesktopMediaPickerViews) when it's destroyed.
void DetachParent();
// Called by DesktopMediaListView.
void OnSelectionChanged();
void OnDoubleClick();
// views::View overrides.
virtual gfx::Size GetPreferredSize() const OVERRIDE;
virtual void Layout() OVERRIDE;
// views::DialogDelegateView overrides.
virtual base::string16 GetWindowTitle() const OVERRIDE;
virtual bool IsDialogButtonEnabled(ui::DialogButton button) const OVERRIDE;
virtual base::string16 GetDialogButtonLabel(
ui::DialogButton button) const OVERRIDE;
virtual bool Accept() OVERRIDE;
virtual void DeleteDelegate() OVERRIDE;
private:
DesktopMediaPickerViews* parent_;
base::string16 app_name_;
views::Label* label_;
views::ScrollView* scroll_view_;
DesktopMediaListView* list_view_;
DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerDialogView);
};
// Implementation of DesktopMediaPicker for Views.
class DesktopMediaPickerViews : public DesktopMediaPicker {
public:
DesktopMediaPickerViews();
virtual ~DesktopMediaPickerViews();
void NotifyDialogResult(DesktopMediaID source);
// DesktopMediaPicker overrides.
virtual void Show(content::WebContents* web_contents,
gfx::NativeWindow context,
gfx::NativeWindow parent,
const base::string16& app_name,
const base::string16& target_name,
scoped_ptr<DesktopMediaList> media_list,
const DoneCallback& done_callback) OVERRIDE;
private:
DoneCallback callback_;
// The |dialog_| is owned by the corresponding views::Widget instance.
// When DesktopMediaPickerViews is destroyed the |dialog_| is destroyed
// asynchronously by closing the widget.
DesktopMediaPickerDialogView* dialog_;
DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerViews);
};
DesktopMediaSourceView::DesktopMediaSourceView(
DesktopMediaListView* parent,
DesktopMediaID source_id)
: parent_(parent),
source_id_(source_id),
image_view_(new views::ImageView()),
label_(new views::Label()),
selected_(false) {
AddChildView(image_view_);
AddChildView(label_);
SetFocusable(true);
}
DesktopMediaSourceView::~DesktopMediaSourceView() {}
void DesktopMediaSourceView::SetName(const base::string16& name) {
label_->SetText(name);
}
void DesktopMediaSourceView::SetThumbnail(const gfx::ImageSkia& thumbnail) {
image_view_->SetImage(thumbnail);
}
void DesktopMediaSourceView::SetSelected(bool selected) {
if (selected == selected_)
return;
selected_ = selected;
if (selected) {
// Unselect all other sources.
Views neighbours;
parent()->GetViewsInGroup(GetGroup(), &neighbours);
for (Views::iterator i(neighbours.begin()); i != neighbours.end(); ++i) {
if (*i != this) {
DCHECK_EQ((*i)->GetClassName(), kDesktopMediaSourceViewClassName);
DesktopMediaSourceView* source_view =
static_cast<DesktopMediaSourceView*>(*i);
source_view->SetSelected(false);
}
}
const SkColor bg_color = GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor);
set_background(views::Background::CreateSolidBackground(bg_color));
parent_->OnSelectionChanged();
} else {
set_background(NULL);
}
SchedulePaint();
}
const char* DesktopMediaSourceView::GetClassName() const {
return kDesktopMediaSourceViewClassName;
}
void DesktopMediaSourceView::Layout() {
image_view_->SetBounds(kThumbnailMargin, kThumbnailMargin,
kThumbnailWidth, kThumbnailHeight);
label_->SetBounds(kThumbnailMargin, kThumbnailHeight + kThumbnailMargin,
kThumbnailWidth, kLabelHeight);
}
views::View* DesktopMediaSourceView::GetSelectedViewForGroup(int group) {
Views neighbours;
parent()->GetViewsInGroup(group, &neighbours);
if (neighbours.empty())
return NULL;
for (Views::iterator i(neighbours.begin()); i != neighbours.end(); ++i) {
DCHECK_EQ((*i)->GetClassName(), kDesktopMediaSourceViewClassName);
DesktopMediaSourceView* source_view =
static_cast<DesktopMediaSourceView*>(*i);
if (source_view->selected_)
return source_view;
}
return NULL;
}
bool DesktopMediaSourceView::IsGroupFocusTraversable() const {
return false;
}
void DesktopMediaSourceView::OnPaint(gfx::Canvas* canvas) {
View::OnPaint(canvas);
if (HasFocus()) {
gfx::Rect bounds(GetLocalBounds());
bounds.Inset(kThumbnailMargin / 2, kThumbnailMargin / 2);
canvas->DrawFocusRect(bounds);
}
}
void DesktopMediaSourceView::OnFocus() {
View::OnFocus();
SetSelected(true);
ScrollRectToVisible(gfx::Rect(size()));
// We paint differently when focused.
SchedulePaint();
}
void DesktopMediaSourceView::OnBlur() {
View::OnBlur();
// We paint differently when focused.
SchedulePaint();
}
bool DesktopMediaSourceView::OnMousePressed(const ui::MouseEvent& event) {
if (event.GetClickCount() == 1) {
RequestFocus();
} else if (event.GetClickCount() == 2) {
RequestFocus();
parent_->OnDoubleClick();
}
return true;
}
DesktopMediaListView::DesktopMediaListView(
DesktopMediaPickerDialogView* parent,
scoped_ptr<DesktopMediaList> media_list)
: parent_(parent),
media_list_(media_list.Pass()) {
media_list_->SetThumbnailSize(gfx::Size(kThumbnailWidth, kThumbnailHeight));
}
DesktopMediaListView::~DesktopMediaListView() {}
void DesktopMediaListView::StartUpdating(
content::DesktopMediaID::Id dialog_window_id) {
media_list_->SetViewDialogWindowId(dialog_window_id);
media_list_->StartUpdating(this);
}
void DesktopMediaListView::OnSelectionChanged() {
parent_->OnSelectionChanged();
}
void DesktopMediaListView::OnDoubleClick() {
parent_->OnDoubleClick();
}
DesktopMediaSourceView* DesktopMediaListView::GetSelection() {
for (int i = 0; i < child_count(); ++i) {
DesktopMediaSourceView* source_view =
static_cast<DesktopMediaSourceView*>(child_at(i));
DCHECK_EQ(source_view->GetClassName(), kDesktopMediaSourceViewClassName);
if (source_view->is_selected())
return source_view;
}
return NULL;
}
gfx::Size DesktopMediaListView::GetPreferredSize() const {
int total_rows = (child_count() + kListColumns - 1) / kListColumns;
return gfx::Size(kTotalListWidth, kListItemHeight * total_rows);
}
void DesktopMediaListView::Layout() {
int x = 0;
int y = 0;
for (int i = 0; i < child_count(); ++i) {
if (x + kListItemWidth > kTotalListWidth) {
x = 0;
y += kListItemHeight;
}
View* source_view = child_at(i);
source_view->SetBounds(x, y, kListItemWidth, kListItemHeight);
x += kListItemWidth;
}
y += kListItemHeight;
SetSize(gfx::Size(kTotalListWidth, y));
}
bool DesktopMediaListView::OnKeyPressed(const ui::KeyEvent& event) {
int position_increment = 0;
switch (event.key_code()) {
case ui::VKEY_UP:
position_increment = -kListColumns;
break;
case ui::VKEY_DOWN:
position_increment = kListColumns;
break;
case ui::VKEY_LEFT:
position_increment = -1;
break;
case ui::VKEY_RIGHT:
position_increment = 1;
break;
default:
return false;
}
if (position_increment != 0) {
DesktopMediaSourceView* selected = GetSelection();
DesktopMediaSourceView* new_selected = NULL;
if (selected) {
int index = GetIndexOf(selected);
int new_index = index + position_increment;
if (new_index >= child_count())
new_index = child_count() - 1;
else if (new_index < 0)
new_index = 0;
if (index != new_index) {
new_selected =
static_cast<DesktopMediaSourceView*>(child_at(new_index));
}
} else if (has_children()) {
new_selected = static_cast<DesktopMediaSourceView*>(child_at(0));
}
if (new_selected) {
GetFocusManager()->SetFocusedView(new_selected);
}
return true;
}
return false;
}
void DesktopMediaListView::OnSourceAdded(int index) {
const DesktopMediaList::Source& source = media_list_->GetSource(index);
DesktopMediaSourceView* source_view =
new DesktopMediaSourceView(this, source.id);
source_view->SetName(source.name);
source_view->SetGroup(kDesktopMediaSourceViewGroupId);
AddChildViewAt(source_view, index);
PreferredSizeChanged();
}
void DesktopMediaListView::OnSourceRemoved(int index) {
DesktopMediaSourceView* view =
static_cast<DesktopMediaSourceView*>(child_at(index));
DCHECK(view);
DCHECK_EQ(view->GetClassName(), kDesktopMediaSourceViewClassName);
bool was_selected = view->is_selected();
RemoveChildView(view);
delete view;
if (was_selected)
OnSelectionChanged();
PreferredSizeChanged();
}
void DesktopMediaListView::OnSourceMoved(int old_index, int new_index) {
DesktopMediaSourceView* view =
static_cast<DesktopMediaSourceView*>(child_at(old_index));
ReorderChildView(view, new_index);
PreferredSizeChanged();
}
void DesktopMediaListView::OnSourceNameChanged(int index) {
const DesktopMediaList::Source& source = media_list_->GetSource(index);
DesktopMediaSourceView* source_view =
static_cast<DesktopMediaSourceView*>(child_at(index));
source_view->SetName(source.name);
}
void DesktopMediaListView::OnSourceThumbnailChanged(int index) {
const DesktopMediaList::Source& source = media_list_->GetSource(index);
DesktopMediaSourceView* source_view =
static_cast<DesktopMediaSourceView*>(child_at(index));
source_view->SetThumbnail(source.thumbnail);
}
DesktopMediaPickerDialogView::DesktopMediaPickerDialogView(
content::WebContents* parent_web_contents,
gfx::NativeWindow context,
gfx::NativeWindow parent_window,
DesktopMediaPickerViews* parent,
const base::string16& app_name,
const base::string16& target_name,
scoped_ptr<DesktopMediaList> media_list)
: parent_(parent),
app_name_(app_name),
label_(new views::Label()),
scroll_view_(views::ScrollView::CreateScrollViewWithBorder()),
list_view_(new DesktopMediaListView(this, media_list.Pass())) {
if (app_name == target_name) {
label_->SetText(
l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TEXT, app_name));
} else {
label_->SetText(l10n_util::GetStringFUTF16(
IDS_DESKTOP_MEDIA_PICKER_TEXT_DELEGATED, app_name, target_name));
}
label_->SetMultiLine(true);
label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
AddChildView(label_);
scroll_view_->SetContents(list_view_);
AddChildView(scroll_view_);
WebContentsModalDialogManager* web_contents_modal_dialog_manager =
WebContentsModalDialogManager::FromWebContents(parent_web_contents);
DCHECK(web_contents_modal_dialog_manager);
WebContentsModalDialogManagerDelegate* delegate =
web_contents_modal_dialog_manager->delegate();
DCHECK(delegate);
views::Widget::CreateWindowAsFramelessChild(
this,
delegate->GetWebContentsModalDialogHost()->GetHostView());
// DesktopMediaList needs to know the ID of the picker window which
// matches the ID it gets from the OS. Depending on the OS and configuration
// we get this ID differently.
content::DesktopMediaID::Id dialog_window_id = 0;
#if defined(USE_ASH)
if (chrome::IsNativeWindowInAsh(GetWidget()->GetNativeWindow())) {
dialog_window_id = content::DesktopMediaID::RegisterAuraWindow(
GetWidget()->GetNativeWindow()).id;
} else
#endif
{
dialog_window_id = AcceleratedWidgetToDesktopMediaId(
GetWidget()->GetNativeWindow()->GetHost()->
GetAcceleratedWidget());
}
list_view_->StartUpdating(dialog_window_id);
web_contents_modal_dialog_manager->ShowModalDialog(
GetWidget()->GetNativeView());
}
DesktopMediaPickerDialogView::~DesktopMediaPickerDialogView() {}
void DesktopMediaPickerDialogView::DetachParent() {
parent_ = NULL;
}
gfx::Size DesktopMediaPickerDialogView::GetPreferredSize() const {
return gfx::Size(600, 500);
}
void DesktopMediaPickerDialogView::Layout() {
gfx::Rect rect = GetLocalBounds();
rect.Inset(views::kPanelHorizMargin, views::kPanelVertMargin);
gfx::Rect label_rect(rect.x(), rect.y(), rect.width(),
label_->GetHeightForWidth(rect.width()));
label_->SetBoundsRect(label_rect);
int scroll_view_top = label_rect.bottom() + views::kPanelVerticalSpacing;
scroll_view_->SetBounds(
rect.x(), scroll_view_top,
rect.width(), rect.height() - scroll_view_top);
}
base::string16 DesktopMediaPickerDialogView::GetWindowTitle() const {
return l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TITLE, app_name_);
}
bool DesktopMediaPickerDialogView::IsDialogButtonEnabled(
ui::DialogButton button) const {
if (button == ui::DIALOG_BUTTON_OK)
return list_view_->GetSelection() != NULL;
return true;
}
base::string16 DesktopMediaPickerDialogView::GetDialogButtonLabel(
ui::DialogButton button) const {
return l10n_util::GetStringUTF16(button == ui::DIALOG_BUTTON_OK ?
IDS_DESKTOP_MEDIA_PICKER_SHARE : IDS_CANCEL);
}
bool DesktopMediaPickerDialogView::Accept() {
DesktopMediaSourceView* selection = list_view_->GetSelection();
// Ok button should only be enabled when a source is selected.
DCHECK(selection);
DesktopMediaID source;
if (selection)
source = selection->source_id();
if (parent_)
parent_->NotifyDialogResult(source);
// Return true to close the window.
return true;
}
void DesktopMediaPickerDialogView::DeleteDelegate() {
// If the dialog is being closed then notify the parent about it.
if (parent_)
parent_->NotifyDialogResult(DesktopMediaID());
delete this;
}
void DesktopMediaPickerDialogView::OnSelectionChanged() {
GetDialogClientView()->UpdateDialogButtons();
}
void DesktopMediaPickerDialogView::OnDoubleClick() {
// This will call Accept() and close the dialog.
GetDialogClientView()->AcceptWindow();
}
DesktopMediaPickerViews::DesktopMediaPickerViews()
: dialog_(NULL) {
}
DesktopMediaPickerViews::~DesktopMediaPickerViews() {
if (dialog_) {
dialog_->DetachParent();
dialog_->GetWidget()->Close();
}
}
void DesktopMediaPickerViews::Show(content::WebContents* web_contents,
gfx::NativeWindow context,
gfx::NativeWindow parent,
const base::string16& app_name,
const base::string16& target_name,
scoped_ptr<DesktopMediaList> media_list,
const DoneCallback& done_callback) {
callback_ = done_callback;
dialog_ = new DesktopMediaPickerDialogView(
web_contents, context, parent, this, app_name, target_name,
media_list.Pass());
}
void DesktopMediaPickerViews::NotifyDialogResult(
DesktopMediaID source) {
// Once this method is called the |dialog_| will close and destroy itself.
dialog_->DetachParent();
dialog_ = NULL;
DCHECK(!callback_.is_null());
// Notify the |callback_| asynchronously because it may need to destroy
// DesktopMediaPicker.
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(callback_, source));
callback_.Reset();
}
} // namespace
// static
scoped_ptr<DesktopMediaPicker> DesktopMediaPicker::Create() {
return scoped_ptr<DesktopMediaPicker>(new DesktopMediaPickerViews());
}