Add a minor text member to ui::MenuModel.
[chromium-blink-merge.git] / chrome / browser / ui / views / desktop_media_picker_views.cc
blobb21ea7f8cb97b308c09c396fcb3ee377a6f6c3bc
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.
5 #include "chrome/browser/media/desktop_media_picker.h"
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"
26 using content::DesktopMediaID;
28 namespace {
30 const int kThumbnailWidth = 160;
31 const int kThumbnailHeight = 100;
32 const int kThumbnailMargin = 10;
33 const int kLabelHeight = 40;
34 const int kListItemWidth = kThumbnailMargin * 2 + kThumbnailWidth;
35 const int kListItemHeight =
36 kThumbnailMargin * 2 + kThumbnailHeight + kLabelHeight;
37 const int kListColumns = 3;
38 const int kTotalListWidth = kListColumns * kListItemWidth;
40 const int kDesktopMediaSourceViewGroupId = 1;
42 const char kDesktopMediaSourceViewClassName[] =
43 "DesktopMediaPicker_DesktopMediaSourceView";
45 class DesktopMediaListView;
46 class DesktopMediaPickerDialogView;
47 class DesktopMediaPickerViews;
49 // View used for each item in DesktopMediaListView. Shows a single desktop media
50 // source as a thumbnail with the title under it.
51 class DesktopMediaSourceView : public views::View {
52 public:
53 DesktopMediaSourceView(DesktopMediaListView* parent,
54 DesktopMediaID source_id);
55 virtual ~DesktopMediaSourceView();
57 // Updates thumbnail and title from |source|.
58 void SetName(const string16& name);
59 void SetThumbnail(const gfx::ImageSkia& thumbnail);
61 // Id for the source shown by this View.
62 const DesktopMediaID& source_id() const {
63 return source_id_;
66 // Returns true if the source is selected.
67 bool is_selected() const { return selected_; }
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);
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;
82 private:
83 DesktopMediaListView* parent_;
84 DesktopMediaID source_id_;
86 views::ImageView* image_view_;
87 views::Label* label_;
89 bool selected_;
91 DISALLOW_COPY_AND_ASSIGN(DesktopMediaSourceView);
94 // View that shows list of desktop media sources available from
95 // DesktopMediaPickerModel.
96 class DesktopMediaListView : public views::View,
97 public DesktopMediaPickerModel::Observer {
98 public:
99 DesktopMediaListView(DesktopMediaPickerDialogView* parent,
100 scoped_ptr<DesktopMediaPickerModel> model);
101 virtual ~DesktopMediaListView();
103 // Called by DesktopMediaSourceView when selection has changed.
104 void OnSelectionChanged();
106 // Returns currently selected source.
107 DesktopMediaSourceView* GetSelection();
109 // views::View overrides.
110 virtual gfx::Size GetPreferredSize() OVERRIDE;
111 virtual void Layout() OVERRIDE;
112 virtual bool OnKeyPressed(const ui::KeyEvent& event) OVERRIDE;
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;
121 DesktopMediaPickerDialogView* parent_;
122 scoped_ptr<DesktopMediaPickerModel> model_;
124 DISALLOW_COPY_AND_ASSIGN(DesktopMediaListView);
127 // Dialog view used for DesktopMediaPickerViews.
128 class 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();
137 // Called by parent (DesktopMediaPickerViews) when it's destroyed.
138 void DetachParent();
140 // Called by DesktopMediaListView.
141 void OnSelectionChanged();
143 // views::View overrides.
144 virtual gfx::Size GetPreferredSize() OVERRIDE;
145 virtual void Layout() OVERRIDE;
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;
153 private:
154 DesktopMediaPickerViews* parent_;
155 string16 app_name_;
157 views::Label* label_;
158 views::ScrollView* scroll_view_;
159 DesktopMediaListView* list_view_;
161 DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerDialogView);
164 // Implementation of DesktopMediaPicker for Views.
165 class DesktopMediaPickerViews : public DesktopMediaPicker {
166 public:
167 DesktopMediaPickerViews();
168 virtual ~DesktopMediaPickerViews();
170 void NotifyDialogResult(DesktopMediaID source);
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;
179 private:
180 DoneCallback callback_;
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_;
187 DISALLOW_COPY_AND_ASSIGN(DesktopMediaPickerViews);
190 DesktopMediaSourceView::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);
203 DesktopMediaSourceView::~DesktopMediaSourceView() {}
205 void DesktopMediaSourceView::SetName(const string16& name) {
206 label_->SetText(name);
209 void DesktopMediaSourceView::SetThumbnail(const gfx::ImageSkia& thumbnail) {
210 image_view_->SetImage(thumbnail);
213 void DesktopMediaSourceView::SetSelected(bool selected) {
214 if (selected == selected_)
215 return;
216 selected_ = selected;
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);
231 const SkColor bg_color = GetNativeTheme()->GetSystemColor(
232 ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor);
233 set_background(views::Background::CreateSolidBackground(bg_color));
235 parent_->OnSelectionChanged();
236 } else {
237 set_background(NULL);
240 SchedulePaint();
243 const char* DesktopMediaSourceView::GetClassName() const {
244 return kDesktopMediaSourceViewClassName;
247 void DesktopMediaSourceView::Layout() {
248 image_view_->SetBounds(kThumbnailMargin, kThumbnailMargin,
249 kThumbnailWidth, kThumbnailHeight);
250 label_->SetBounds(kThumbnailMargin, kThumbnailHeight + kThumbnailMargin,
251 kThumbnailWidth, kLabelHeight);
253 set_focus_border(views::FocusBorder::CreateDashedFocusBorder(
254 kThumbnailMargin / 2, kThumbnailMargin / 2,
255 kThumbnailMargin / 2, kThumbnailMargin / 2));
258 views::View* DesktopMediaSourceView::GetSelectedViewForGroup(int group) {
259 Views neighbours;
260 parent()->GetViewsInGroup(group, &neighbours);
261 if (neighbours.empty())
262 return NULL;
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;
271 return NULL;
274 bool DesktopMediaSourceView::IsGroupFocusTraversable() const {
275 return false;
278 void DesktopMediaSourceView::OnFocus() {
279 View::OnFocus();
280 SetSelected(true);
281 ScrollRectToVisible(gfx::Rect(size()));
284 bool DesktopMediaSourceView::OnMousePressed(const ui::MouseEvent& event) {
285 RequestFocus();
286 return true;
289 DesktopMediaListView::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);
298 DesktopMediaListView::~DesktopMediaListView() {
301 void DesktopMediaListView::OnSelectionChanged() {
302 parent_->OnSelectionChanged();
305 DesktopMediaSourceView* 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;
313 return NULL;
316 gfx::Size DesktopMediaListView::GetPreferredSize() {
317 int total_rows = (child_count() + kListColumns - 1) / kListColumns;
318 return gfx::Size(kTotalListWidth, kListItemHeight * total_rows);
321 void DesktopMediaListView::Layout() {
322 int x = 0;
323 int y = 0;
325 for (int i = 0; i < child_count(); ++i) {
326 if (x + kListItemWidth > kTotalListWidth) {
327 x = 0;
328 y += kListItemHeight;
331 View* source_view = child_at(i);
332 source_view->SetBounds(x, y, kListItemWidth, kListItemHeight);
334 x += kListItemWidth;
338 y += kListItemHeight;
339 SetSize(gfx::Size(kTotalListWidth, y));
342 bool DesktopMediaListView::OnKeyPressed(const ui::KeyEvent& event) {
343 int position_increment = 0;
344 switch (event.key_code()) {
345 case ui::VKEY_UP:
346 position_increment = -kListColumns;
347 break;
348 case ui::VKEY_DOWN:
349 position_increment = kListColumns;
350 break;
351 case ui::VKEY_LEFT:
352 position_increment = -1;
353 break;
354 case ui::VKEY_RIGHT:
355 position_increment = 1;
356 break;
357 default:
358 return false;
362 if (position_increment != 0) {
363 DesktopMediaSourceView* selected = GetSelection();
364 DesktopMediaSourceView* new_selected = NULL;
366 if (selected) {
367 int index = GetIndexOf(selected);
368 int new_index = index + position_increment;
369 if (new_index >= child_count())
370 new_index = child_count() - 1;
371 else if (new_index < 0)
372 new_index = 0;
373 if (index != new_index) {
374 new_selected =
375 static_cast<DesktopMediaSourceView*>(child_at(new_index));
377 } else if (has_children()) {
378 new_selected = static_cast<DesktopMediaSourceView*>(child_at(0));
381 if (new_selected) {
382 GetFocusManager()->SetFocusedView(new_selected);
385 return true;
388 return false;
391 void DesktopMediaListView::OnSourceAdded(int index) {
392 const DesktopMediaPickerModel::Source& source = model_->source(index);
393 DesktopMediaSourceView* source_view =
394 new DesktopMediaSourceView(this, source.id);
395 source_view->SetName(source.name);
396 source_view->SetGroup(kDesktopMediaSourceViewGroupId);
397 AddChildViewAt(source_view, index);
398 Layout();
401 void DesktopMediaListView::OnSourceRemoved(int index) {
402 DesktopMediaSourceView* view =
403 static_cast<DesktopMediaSourceView*>(child_at(index));
404 DCHECK(view);
405 DCHECK_EQ(view->GetClassName(), kDesktopMediaSourceViewClassName);
406 bool was_selected = view->is_selected();
407 RemoveChildView(view);
408 delete view;
410 if (was_selected)
411 OnSelectionChanged();
413 PreferredSizeChanged();
416 void DesktopMediaListView::OnSourceNameChanged(int index) {
417 const DesktopMediaPickerModel::Source& source = model_->source(index);
418 DesktopMediaSourceView* source_view =
419 static_cast<DesktopMediaSourceView*>(child_at(index));
420 source_view->SetName(source.name);
423 void DesktopMediaListView::OnSourceThumbnailChanged(int index) {
424 const DesktopMediaPickerModel::Source& source = model_->source(index);
425 DesktopMediaSourceView* source_view =
426 static_cast<DesktopMediaSourceView*>(child_at(index));
427 source_view->SetThumbnail(source.thumbnail);
430 DesktopMediaPickerDialogView::DesktopMediaPickerDialogView(
431 gfx::NativeWindow context,
432 gfx::NativeWindow parent_window,
433 DesktopMediaPickerViews* parent,
434 const string16& app_name,
435 scoped_ptr<DesktopMediaPickerModel> model)
436 : parent_(parent),
437 app_name_(app_name),
438 label_(new views::Label()),
439 scroll_view_(views::ScrollView::CreateScrollViewWithBorder()),
440 list_view_(new DesktopMediaListView(this, model.Pass())) {
441 label_->SetText(
442 l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TEXT, app_name_));
443 label_->SetMultiLine(true);
444 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
445 AddChildView(label_);
447 scroll_view_->SetContents(list_view_);
448 AddChildView(scroll_view_);
450 DialogDelegate::CreateDialogWidget(this, context, parent_window);
451 GetWidget()->Show();
454 DesktopMediaPickerDialogView::~DesktopMediaPickerDialogView() {}
456 void DesktopMediaPickerDialogView::DetachParent() {
457 parent_ = NULL;
460 gfx::Size DesktopMediaPickerDialogView::GetPreferredSize() {
461 return gfx::Size(600, 500);
464 void DesktopMediaPickerDialogView::Layout() {
465 gfx::Rect rect = GetLocalBounds();
466 rect.Inset(views::kPanelHorizMargin, views::kPanelVertMargin);
468 gfx::Rect label_rect(rect.x(), rect.y(), rect.width(),
469 label_->GetHeightForWidth(rect.width()));
470 label_->SetBoundsRect(label_rect);
472 int scroll_view_top = label_rect.bottom() + views::kPanelVerticalSpacing;
473 scroll_view_->SetBounds(
474 rect.x(), scroll_view_top,
475 rect.width(), rect.height() - scroll_view_top);
478 base::string16 DesktopMediaPickerDialogView::GetWindowTitle() const {
479 return l10n_util::GetStringFUTF16(IDS_DESKTOP_MEDIA_PICKER_TITLE, app_name_);
482 bool DesktopMediaPickerDialogView::IsDialogButtonEnabled(
483 ui::DialogButton button) const {
484 if (button == ui::DIALOG_BUTTON_OK)
485 return list_view_->GetSelection() != NULL;
486 return true;
489 bool DesktopMediaPickerDialogView::Accept() {
490 DesktopMediaSourceView* selection = list_view_->GetSelection();
492 // Ok button should only be enabled when a source is selected.
493 DCHECK(selection);
495 DesktopMediaID source;
496 if (selection)
497 source = selection->source_id();
499 if (parent_)
500 parent_->NotifyDialogResult(source);
502 // Return true to close the window.
503 return true;
506 void 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;
513 void DesktopMediaPickerDialogView::OnSelectionChanged() {
514 GetDialogClientView()->UpdateDialogButtons();
517 DesktopMediaPickerViews::DesktopMediaPickerViews()
518 : dialog_(NULL) {
521 DesktopMediaPickerViews::~DesktopMediaPickerViews() {
522 if (dialog_) {
523 dialog_->DetachParent();
524 dialog_->GetWidget()->Close();
528 void DesktopMediaPickerViews::Show(gfx::NativeWindow context,
529 gfx::NativeWindow parent,
530 const string16& app_name,
531 scoped_ptr<DesktopMediaPickerModel> model,
532 const DoneCallback& done_callback) {
533 callback_ = done_callback;
534 dialog_ = new DesktopMediaPickerDialogView(
535 context, parent, this, app_name, model.Pass());
538 void DesktopMediaPickerViews::NotifyDialogResult(
539 DesktopMediaID source) {
540 // Once this method is called the |dialog_| will close and destroy itself.
541 dialog_->DetachParent();
542 dialog_ = NULL;
544 DCHECK(!callback_.is_null());
546 // Notify the |callback_| asynchronously because it may need to destroy
547 // DesktopMediaPicker.
548 content::BrowserThread::PostTask(
549 content::BrowserThread::UI, FROM_HERE,
550 base::Bind(callback_, source));
551 callback_.Reset();
554 } // namespace
556 // static
557 scoped_ptr<DesktopMediaPicker> DesktopMediaPicker::Create() {
558 return scoped_ptr<DesktopMediaPicker>(new DesktopMediaPickerViews());