1 // Copyright (c) 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 "ui/message_center/views/notifier_settings_view.h"
10 #include "base/strings/string16.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "grit/ui_resources.h"
13 #include "grit/ui_strings.h"
14 #include "skia/ext/image_operations.h"
15 #include "third_party/skia/include/core/SkColor.h"
16 #include "ui/base/keycodes/keyboard_codes.h"
17 #include "ui/base/l10n/l10n_util.h"
18 #include "ui/base/models/simple_menu_model.h"
19 #include "ui/base/resource/resource_bundle.h"
20 #include "ui/gfx/canvas.h"
21 #include "ui/gfx/image/image.h"
22 #include "ui/gfx/size.h"
23 #include "ui/message_center/message_center_style.h"
24 #include "ui/message_center/views/message_center_view.h"
25 #include "ui/views/background.h"
26 #include "ui/views/border.h"
27 #include "ui/views/controls/button/checkbox.h"
28 #include "ui/views/controls/button/custom_button.h"
29 #include "ui/views/controls/button/label_button_border.h"
30 #include "ui/views/controls/button/menu_button.h"
31 #include "ui/views/controls/image_view.h"
32 #include "ui/views/controls/label.h"
33 #include "ui/views/controls/menu/menu_runner.h"
34 #include "ui/views/controls/scroll_view.h"
35 #include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
36 #include "ui/views/layout/box_layout.h"
37 #include "ui/views/layout/fill_layout.h"
38 #include "ui/views/layout/grid_layout.h"
39 #include "ui/views/widget/widget.h"
42 #include "ui/aura/window.h"
45 namespace message_center
{
47 const int kButtonPainterInsets
= 5;
48 // We really want the margin to be 20px, but various views are padded by
50 const int kDesiredMargin
= 20;
51 // The MenuButton has 2px whitespace built-in.
52 const int kMenuButtonInnateMargin
= 2;
53 const int kMinimumHorizontalMargin
= kDesiredMargin
- kMenuButtonInnateMargin
;
54 // The EntryViews' leftmost view is a checkbox with 1px whitespace built in, so
55 // the margin for entry views should be one less than the target margin.
56 const int kCheckboxInnateMargin
= 1;
57 const int kEntryMargin
= kDesiredMargin
- kCheckboxInnateMargin
;
58 const int kMenuButtonLeftPadding
= 12;
59 const int kMenuButtonRightPadding
= 13;
60 const int kMenuButtonVerticalPadding
= 9;
61 const int kMenuWhitespaceOffset
= 2;
62 const int kMinimumWindowHeight
= 480;
63 const int kMinimumWindowWidth
= 320;
64 const int kSettingsTitleBottomMargin
= 12;
65 const int kSettingsTitleTopMargin
= 15;
66 const int kSpaceInButtonComponents
= 16;
67 const int kTitleVerticalMargin
= 1;
68 const int kTitleElementSpacing
= 10;
69 const int kEntryHeight
= kMinimumWindowHeight
/ 10;
71 // The view to guarantee the 48px height and place the contents at the
72 // middle. It also guarantee the left margin.
73 class EntryView
: public views::View
{
75 EntryView(views::View
* contents
);
78 // Overridden from views::View:
79 virtual void Layout() OVERRIDE
;
80 virtual gfx::Size
GetPreferredSize() OVERRIDE
;
81 virtual void GetAccessibleState(ui::AccessibleViewState
* state
) OVERRIDE
;
82 virtual void OnFocus() OVERRIDE
;
83 virtual void OnPaintFocusBorder(gfx::Canvas
* canvas
) OVERRIDE
;
84 virtual bool OnKeyPressed(const ui::KeyEvent
& event
) OVERRIDE
;
85 virtual bool OnKeyReleased(const ui::KeyEvent
& event
) OVERRIDE
;
88 DISALLOW_COPY_AND_ASSIGN(EntryView
);
91 EntryView::EntryView(views::View
* contents
) {
92 AddChildView(contents
);
95 EntryView::~EntryView() {
98 void EntryView::Layout() {
99 DCHECK_EQ(1, child_count());
100 views::View
* content
= child_at(0);
101 int content_width
= width() - kEntryMargin
* 2;
102 int content_height
= content
->GetHeightForWidth(content_width
);
103 int y
= std::max((height() - content_height
) / 2, 0);
104 content
->SetBounds(kEntryMargin
, y
, content_width
, content_height
);
107 gfx::Size
EntryView::GetPreferredSize() {
108 DCHECK_EQ(1, child_count());
109 gfx::Size size
= child_at(0)->GetPreferredSize();
110 size
.SetToMax(gfx::Size(kMinimumWindowWidth
, kEntryHeight
));
114 void EntryView::GetAccessibleState(ui::AccessibleViewState
* state
) {
115 DCHECK_EQ(1, child_count());
116 child_at(0)->GetAccessibleState(state
);
119 void EntryView::OnFocus() {
120 views::View::OnFocus();
121 ScrollRectToVisible(GetLocalBounds());
124 void EntryView::OnPaintFocusBorder(gfx::Canvas
* canvas
) {
125 if (HasFocus() && (focusable() || IsAccessibilityFocusable())) {
126 canvas
->DrawRect(gfx::Rect(2, 1, width() - 4, height() - 3),
131 bool EntryView::OnKeyPressed(const ui::KeyEvent
& event
) {
132 return child_at(0)->OnKeyPressed(event
);
135 bool EntryView::OnKeyReleased(const ui::KeyEvent
& event
) {
136 return child_at(0)->OnKeyReleased(event
);
141 // NotifierGroupMenuButtonBorder ///////////////////////////////////////////////
142 ////////////////////////////////////////////////////////////////////////////////
143 class NotifierGroupMenuButtonBorder
: public views::TextButtonDefaultBorder
{
145 NotifierGroupMenuButtonBorder();
148 virtual ~NotifierGroupMenuButtonBorder();
151 NotifierGroupMenuButtonBorder::NotifierGroupMenuButtonBorder()
152 : views::TextButtonDefaultBorder() {
153 ui::ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
155 gfx::Insets
insets(kButtonPainterInsets
,
156 kButtonPainterInsets
,
157 kButtonPainterInsets
,
158 kButtonPainterInsets
);
160 set_normal_painter(views::Painter::CreateImagePainter(
161 *rb
.GetImageSkiaNamed(IDR_BUTTON_NORMAL
), insets
));
162 set_hot_painter(views::Painter::CreateImagePainter(
163 *rb
.GetImageSkiaNamed(IDR_BUTTON_HOVER
), insets
));
164 set_pushed_painter(views::Painter::CreateImagePainter(
165 *rb
.GetImageSkiaNamed(IDR_BUTTON_PRESSED
), insets
));
167 SetInsets(gfx::Insets(kMenuButtonVerticalPadding
,
168 kMenuButtonLeftPadding
,
169 kMenuButtonVerticalPadding
,
170 kMenuButtonRightPadding
));
173 NotifierGroupMenuButtonBorder::~NotifierGroupMenuButtonBorder() {}
175 // NotifierGroupMenuModel //////////////////////////////////////////////////////
176 ////////////////////////////////////////////////////////////////////////////////
177 class NotifierGroupMenuModel
: public ui::SimpleMenuModel
,
178 public ui::SimpleMenuModel::Delegate
{
180 NotifierGroupMenuModel(NotifierSettingsProvider
* notifier_settings_provider
);
181 virtual ~NotifierGroupMenuModel();
183 // Overridden from ui::SimpleMenuModel::Delegate:
184 virtual bool IsCommandIdChecked(int command_id
) const OVERRIDE
;
185 virtual bool IsCommandIdEnabled(int command_id
) const OVERRIDE
;
186 virtual bool GetAcceleratorForCommandId(
188 ui::Accelerator
* accelerator
) OVERRIDE
;
189 virtual void ExecuteCommand(int command_id
, int event_flags
) OVERRIDE
;
192 NotifierSettingsProvider
* notifier_settings_provider_
;
195 NotifierGroupMenuModel::NotifierGroupMenuModel(
196 NotifierSettingsProvider
* notifier_settings_provider
)
197 : ui::SimpleMenuModel(this),
198 notifier_settings_provider_(notifier_settings_provider
) {
199 if (!notifier_settings_provider_
)
202 size_t num_menu_items
= notifier_settings_provider_
->GetNotifierGroupCount();
203 for (size_t i
= 0; i
< num_menu_items
; ++i
) {
204 const NotifierGroup
& group
=
205 notifier_settings_provider_
->GetNotifierGroupAt(i
);
207 AddCheckItem(i
, group
.login_info
.empty() ? group
.name
: group
.login_info
);
211 NotifierGroupMenuModel::~NotifierGroupMenuModel() {}
213 bool NotifierGroupMenuModel::IsCommandIdChecked(int command_id
) const {
214 // If there's no provider, assume only one notifier group - the active one.
215 if (!notifier_settings_provider_
)
218 return notifier_settings_provider_
->IsNotifierGroupActiveAt(command_id
);
221 bool NotifierGroupMenuModel::IsCommandIdEnabled(int command_id
) const {
225 bool NotifierGroupMenuModel::GetAcceleratorForCommandId(
227 ui::Accelerator
* accelerator
) {
231 void NotifierGroupMenuModel::ExecuteCommand(int command_id
, int event_flags
) {
232 if (!notifier_settings_provider_
)
235 size_t notifier_group_index
= static_cast<size_t>(command_id
);
236 size_t num_notifier_groups
=
237 notifier_settings_provider_
->GetNotifierGroupCount();
238 if (notifier_group_index
>= num_notifier_groups
)
241 notifier_settings_provider_
->SwitchToNotifierGroup(notifier_group_index
);
244 // We do not use views::Checkbox class directly because it doesn't support
246 class NotifierSettingsView::NotifierButton
: public views::CustomButton
,
247 public views::ButtonListener
{
249 NotifierButton(Notifier
* notifier
, views::ButtonListener
* listener
)
250 : views::CustomButton(listener
),
253 checkbox_(new views::Checkbox(string16())) {
255 SetLayoutManager(new views::BoxLayout(
256 views::BoxLayout::kHorizontal
, 0, 0, kSpaceInButtonComponents
));
257 checkbox_
->SetChecked(notifier_
->enabled
);
258 checkbox_
->set_listener(this);
259 checkbox_
->set_focusable(false);
260 checkbox_
->SetAccessibleName(notifier_
->name
);
261 AddChildView(checkbox_
);
262 UpdateIconImage(notifier_
->icon
);
263 AddChildView(new views::Label(notifier_
->name
));
266 void UpdateIconImage(const gfx::Image
& icon
) {
267 notifier_
->icon
= icon
;
268 if (icon
.IsEmpty()) {
273 icon_view_
= new views::ImageView();
274 AddChildViewAt(icon_view_
, 1);
276 icon_view_
->SetImage(icon
.ToImageSkia());
277 icon_view_
->SetImageSize(gfx::Size(kSettingsIconSize
, kSettingsIconSize
));
283 void SetChecked(bool checked
) {
284 checkbox_
->SetChecked(checked
);
285 notifier_
->enabled
= checked
;
288 bool checked() const {
289 return checkbox_
->checked();
292 const Notifier
& notifier() const {
293 return *notifier_
.get();
297 // Overridden from views::ButtonListener:
298 virtual void ButtonPressed(views::Button
* button
,
299 const ui::Event
& event
) OVERRIDE
{
300 DCHECK(button
== checkbox_
);
301 // The checkbox state has already changed at this point, but we'll update
302 // the state on NotifierSettingsView::ButtonPressed() too, so here change
303 // back to the previous state.
304 checkbox_
->SetChecked(!checkbox_
->checked());
305 CustomButton::NotifyClick(event
);
308 virtual void GetAccessibleState(ui::AccessibleViewState
* state
) OVERRIDE
{
309 static_cast<views::View
*>(checkbox_
)->GetAccessibleState(state
);
312 scoped_ptr
<Notifier
> notifier_
;
313 views::ImageView
* icon_view_
;
314 views::Checkbox
* checkbox_
;
316 DISALLOW_COPY_AND_ASSIGN(NotifierButton
);
319 NotifierSettingsView::NotifierSettingsView(NotifierSettingsProvider
* provider
)
320 : title_arrow_(NULL
),
322 notifier_group_selector_(NULL
),
324 provider_(provider
) {
325 // |provider_| may be NULL in tests.
327 provider_
->AddObserver(this);
330 set_focus_border(NULL
);
331 set_background(views::Background::CreateSolidBackground(
332 kMessageCenterBackgroundColor
));
333 if (get_use_acceleration_when_possible())
334 SetPaintToLayer(true);
336 gfx::Font title_font
=
337 ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::MediumFont
);
338 title_label_
= new views::Label(
339 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_SETTINGS_BUTTON_LABEL
),
341 title_label_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
342 title_label_
->SetMultiLine(true);
343 title_label_
->set_border(
344 views::Border::CreateEmptyBorder(kSettingsTitleTopMargin
,
346 kSettingsTitleBottomMargin
,
349 AddChildView(title_label_
);
351 scroller_
= new views::ScrollView();
352 scroller_
->SetVerticalScrollBar(new views::OverlayScrollBar(false));
353 AddChildView(scroller_
);
355 std::vector
<Notifier
*> notifiers
;
357 provider_
->GetNotifierList(¬ifiers
);
359 UpdateContentsView(notifiers
);
362 NotifierSettingsView::~NotifierSettingsView() {
363 // |provider_| may be NULL in tests.
365 provider_
->RemoveObserver(this);
368 bool NotifierSettingsView::IsScrollable() {
369 return scroller_
->height() < scroller_
->contents()->height();
372 void NotifierSettingsView::UpdateIconImage(const NotifierId
& notifier_id
,
373 const gfx::Image
& icon
) {
374 for (std::set
<NotifierButton
*>::iterator iter
= buttons_
.begin();
375 iter
!= buttons_
.end(); ++iter
) {
376 if ((*iter
)->notifier().notifier_id
== notifier_id
) {
377 (*iter
)->UpdateIconImage(icon
);
383 void NotifierSettingsView::NotifierGroupChanged() {
384 std::vector
<Notifier
*> notifiers
;
386 provider_
->GetNotifierList(¬ifiers
);
388 UpdateContentsView(notifiers
);
391 void NotifierSettingsView::UpdateContentsView(
392 const std::vector
<Notifier
*>& notifiers
) {
395 views::View
* contents_view
= new views::View();
396 contents_view
->SetLayoutManager(
397 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0));
399 views::View
* contents_title_view
= new views::View();
400 contents_title_view
->SetLayoutManager(
401 new views::BoxLayout(views::BoxLayout::kVertical
,
402 kMinimumHorizontalMargin
,
403 kTitleVerticalMargin
,
404 kTitleElementSpacing
));
406 bool need_account_switcher
=
407 provider_
&& provider_
->GetNotifierGroupCount() > 1;
408 int top_label_resource_id
=
409 need_account_switcher
? IDS_MESSAGE_CENTER_SETTINGS_DESCRIPTION_MULTIUSER
410 : IDS_MESSAGE_CENTER_SETTINGS_DIALOG_DESCRIPTION
;
412 views::Label
* top_label
=
413 new views::Label(l10n_util::GetStringUTF16(top_label_resource_id
));
415 top_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
416 top_label
->SetMultiLine(true);
417 top_label
->set_border(views::Border::CreateEmptyBorder(
418 0, kMenuButtonInnateMargin
, 0, kMenuButtonInnateMargin
));
419 contents_title_view
->AddChildView(top_label
);
421 string16 notifier_group_text
;
423 const NotifierGroup
& active_group
= provider_
->GetActiveNotifierGroup();
424 notifier_group_text
= active_group
.login_info
.empty()
426 : active_group
.login_info
;
429 if (need_account_switcher
) {
430 notifier_group_selector_
=
431 new views::MenuButton(NULL
, notifier_group_text
, this, true);
432 notifier_group_selector_
->set_border(new NotifierGroupMenuButtonBorder
);
433 notifier_group_selector_
->set_focus_border(NULL
);
434 notifier_group_selector_
->set_animate_on_state_change(false);
435 notifier_group_selector_
->set_focusable(true);
436 contents_title_view
->AddChildView(notifier_group_selector_
);
439 contents_view
->AddChildView(contents_title_view
);
441 for (size_t i
= 0; i
< notifiers
.size(); ++i
) {
442 NotifierButton
* button
= new NotifierButton(notifiers
[i
], this);
443 EntryView
* entry
= new EntryView(button
);
444 entry
->set_focusable(true);
445 contents_view
->AddChildView(entry
);
446 buttons_
.insert(button
);
449 scroller_
->SetContents(contents_view
);
451 contents_view
->SetBoundsRect(gfx::Rect(contents_view
->GetPreferredSize()));
455 void NotifierSettingsView::Layout() {
456 int title_height
= title_label_
->GetHeightForWidth(width());
457 title_label_
->SetBounds(0, 0, width(), title_height
);
459 views::View
* contents_view
= scroller_
->contents();
460 int content_width
= width();
461 int content_height
= contents_view
->GetHeightForWidth(content_width
);
462 if (title_height
+ content_height
> height()) {
463 content_width
-= scroller_
->GetScrollBarWidth();
464 content_height
= contents_view
->GetHeightForWidth(content_width
);
466 contents_view
->SetBounds(0, 0, content_width
, content_height
);
467 scroller_
->SetBounds(0, title_height
, width(), height() - title_height
);
470 gfx::Size
NotifierSettingsView::GetMinimumSize() {
471 gfx::Size
size(kMinimumWindowWidth
, kMinimumWindowHeight
);
472 int total_height
= title_label_
->GetPreferredSize().height() +
473 scroller_
->contents()->GetPreferredSize().height();
474 if (total_height
> kMinimumWindowHeight
)
475 size
.Enlarge(scroller_
->GetScrollBarWidth(), 0);
479 gfx::Size
NotifierSettingsView::GetPreferredSize() {
480 gfx::Size preferred_size
;
481 std::vector
<gfx::Size
> child_sizes
;
482 gfx::Size title_size
= title_label_
->GetPreferredSize();
483 gfx::Size content_size
= scroller_
->contents()->GetPreferredSize();
484 return gfx::Size(std::max(title_size
.width(), content_size
.width()),
485 title_size
.height() + content_size
.height());
488 bool NotifierSettingsView::OnKeyPressed(const ui::KeyEvent
& event
) {
489 if (event
.key_code() == ui::VKEY_ESCAPE
) {
490 GetWidget()->Close();
494 return scroller_
->OnKeyPressed(event
);
497 bool NotifierSettingsView::OnMouseWheel(const ui::MouseWheelEvent
& event
) {
498 return scroller_
->OnMouseWheel(event
);
501 void NotifierSettingsView::ButtonPressed(views::Button
* sender
,
502 const ui::Event
& event
) {
503 if (sender
== title_arrow_
) {
504 MessageCenterView
* center_view
= static_cast<MessageCenterView
*>(parent());
505 center_view
->SetSettingsVisible(!center_view
->settings_visible());
509 std::set
<NotifierButton
*>::iterator iter
= buttons_
.find(
510 static_cast<NotifierButton
*>(sender
));
512 if (iter
== buttons_
.end())
515 (*iter
)->SetChecked(!(*iter
)->checked());
517 provider_
->SetNotifierEnabled((*iter
)->notifier(), (*iter
)->checked());
520 void NotifierSettingsView::OnMenuButtonClicked(views::View
* source
,
521 const gfx::Point
& point
) {
522 notifier_group_menu_model_
.reset(new NotifierGroupMenuModel(provider_
));
523 notifier_group_menu_runner_
.reset(
524 new views::MenuRunner(notifier_group_menu_model_
.get()));
525 gfx::Rect menu_anchor
= source
->GetBoundsInScreen();
527 gfx::Insets(0, kMenuWhitespaceOffset
, 0, kMenuWhitespaceOffset
));
528 if (views::MenuRunner::MENU_DELETED
==
529 notifier_group_menu_runner_
->RunMenuAt(GetWidget(),
530 notifier_group_selector_
,
532 views::MenuItemView::BUBBLE_ABOVE
,
533 ui::MENU_SOURCE_MOUSE
,
534 views::MenuRunner::CONTEXT_MENU
))
536 MessageCenterView
* center_view
= static_cast<MessageCenterView
*>(parent());
537 center_view
->OnSettingsChanged();
540 } // namespace message_center