Home | History | Annotate | Download | only in views
      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.
      4 
      5 #include "ui/message_center/views/notifier_settings_view.h"
      6 
      7 #include <set>
      8 #include <string>
      9 
     10 #include "base/strings/string16.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "skia/ext/image_operations.h"
     13 #include "third_party/skia/include/core/SkColor.h"
     14 #include "ui/base/l10n/l10n_util.h"
     15 #include "ui/base/models/simple_menu_model.h"
     16 #include "ui/base/resource/resource_bundle.h"
     17 #include "ui/events/keycodes/keyboard_codes.h"
     18 #include "ui/gfx/canvas.h"
     19 #include "ui/gfx/image/image.h"
     20 #include "ui/gfx/size.h"
     21 #include "ui/message_center/message_center_style.h"
     22 #include "ui/message_center/views/message_center_view.h"
     23 #include "ui/resources/grit/ui_resources.h"
     24 #include "ui/strings/grit/ui_strings.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/label_button_border.h"
     29 #include "ui/views/controls/button/menu_button.h"
     30 #include "ui/views/controls/image_view.h"
     31 #include "ui/views/controls/label.h"
     32 #include "ui/views/controls/link.h"
     33 #include "ui/views/controls/link_listener.h"
     34 #include "ui/views/controls/menu/menu_runner.h"
     35 #include "ui/views/controls/scroll_view.h"
     36 #include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
     37 #include "ui/views/layout/box_layout.h"
     38 #include "ui/views/layout/fill_layout.h"
     39 #include "ui/views/layout/grid_layout.h"
     40 #include "ui/views/painter.h"
     41 #include "ui/views/widget/widget.h"
     42 
     43 namespace message_center {
     44 namespace settings {
     45 
     46 // Additional views-specific parameters.
     47 
     48 // The width of the settings pane in pixels.
     49 const int kWidth = 360;
     50 
     51 // The width of the learn more icon in pixels.
     52 const int kLearnMoreSize = 12;
     53 
     54 // The width of the click target that contains the learn more button in pixels.
     55 const int kLearnMoreTargetWidth = 28;
     56 
     57 // The height of the click target that contains the learn more button in pixels.
     58 const int kLearnMoreTargetHeight = 40;
     59 
     60 // The minimum height of the settings pane in pixels.
     61 const int kMinimumHeight = 480;
     62 
     63 // The horizontal margin of the title area of the settings pane in addition to
     64 // the standard margin from settings::kHorizontalMargin.
     65 const int kTitleMargin = 10;
     66 
     67 }  // namespace settings
     68 
     69 namespace {
     70 
     71 // Menu button metrics to make the text line up.
     72 const int kMenuButtonInnateMargin = 2;
     73 
     74 // Used to place the context menu correctly.
     75 const int kMenuWhitespaceOffset = 2;
     76 
     77 // The innate vertical blank space in the label for the title of the settings
     78 // pane.
     79 const int kInnateTitleBottomMargin = 1;
     80 const int kInnateTitleTopMargin = 7;
     81 
     82 // The innate top blank space in the label for the description of the settings
     83 // pane.
     84 const int kInnateDescriptionTopMargin = 2;
     85 
     86 // Checkboxes have some built-in right padding blank space.
     87 const int kInnateCheckboxRightPadding = 2;
     88 
     89 // Spec defines the checkbox size; the innate padding throws this measurement
     90 // off so we need to compute a slightly different area for the checkbox to
     91 // inhabit.
     92 const int kComputedCheckboxSize =
     93     settings::kCheckboxSizeWithPadding - kInnateCheckboxRightPadding;
     94 
     95 // The menubutton has innate margin, so we need to compensate for that when
     96 // figuring the margin of the title area.
     97 const int kComputedContentsTitleMargin = 0 - kMenuButtonInnateMargin;
     98 
     99 // The spec doesn't include the bottom blank area of the title bar or the innate
    100 // blank area in the description label, so we'll use this as the space between
    101 // the title and description.
    102 const int kComputedTitleBottomMargin = settings::kDescriptionToSwitcherSpace -
    103                                        kInnateTitleBottomMargin -
    104                                        kInnateDescriptionTopMargin;
    105 
    106 // The blank space above the title needs to be adjusted by the amount of blank
    107 // space included in the title label.
    108 const int kComputedTitleTopMargin =
    109     settings::kTopMargin - kInnateTitleTopMargin;
    110 
    111 // The switcher has a lot of blank space built in so we should include that when
    112 // spacing the title area vertically.
    113 const int kComputedTitleElementSpacing =
    114     settings::kDescriptionToSwitcherSpace - 6;
    115 
    116 // A function to create a focus border.
    117 scoped_ptr<views::Painter> CreateFocusPainter() {
    118   return views::Painter::CreateSolidFocusPainter(kFocusBorderColor,
    119                                                  gfx::Insets(1, 2, 3, 2));
    120 }
    121 
    122 // EntryView ------------------------------------------------------------------
    123 
    124 // The view to guarantee the 48px height and place the contents at the
    125 // middle. It also guarantee the left margin.
    126 class EntryView : public views::View {
    127  public:
    128   explicit EntryView(views::View* contents);
    129   virtual ~EntryView();
    130 
    131   // views::View:
    132   virtual void Layout() OVERRIDE;
    133   virtual gfx::Size GetPreferredSize() const OVERRIDE;
    134   virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE;
    135   virtual void OnFocus() OVERRIDE;
    136   virtual bool OnKeyPressed(const ui::KeyEvent& event) OVERRIDE;
    137   virtual bool OnKeyReleased(const ui::KeyEvent& event) OVERRIDE;
    138   virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
    139   virtual void OnBlur() OVERRIDE;
    140 
    141  private:
    142   scoped_ptr<views::Painter> focus_painter_;
    143 
    144   DISALLOW_COPY_AND_ASSIGN(EntryView);
    145 };
    146 
    147 EntryView::EntryView(views::View* contents)
    148     : focus_painter_(CreateFocusPainter()) {
    149   AddChildView(contents);
    150 }
    151 
    152 EntryView::~EntryView() {}
    153 
    154 void EntryView::Layout() {
    155   DCHECK_EQ(1, child_count());
    156   views::View* content = child_at(0);
    157   int content_width = width();
    158   int content_height = content->GetHeightForWidth(content_width);
    159   int y = std::max((height() - content_height) / 2, 0);
    160   content->SetBounds(0, y, content_width, content_height);
    161 }
    162 
    163 gfx::Size EntryView::GetPreferredSize() const {
    164   DCHECK_EQ(1, child_count());
    165   gfx::Size size = child_at(0)->GetPreferredSize();
    166   size.SetToMax(gfx::Size(settings::kWidth, settings::kEntryHeight));
    167   return size;
    168 }
    169 
    170 void EntryView::GetAccessibleState(ui::AXViewState* state) {
    171   DCHECK_EQ(1, child_count());
    172   child_at(0)->GetAccessibleState(state);
    173 }
    174 
    175 void EntryView::OnFocus() {
    176   views::View::OnFocus();
    177   ScrollRectToVisible(GetLocalBounds());
    178   // We render differently when focused.
    179   SchedulePaint();
    180 }
    181 
    182 bool EntryView::OnKeyPressed(const ui::KeyEvent& event) {
    183   return child_at(0)->OnKeyPressed(event);
    184 }
    185 
    186 bool EntryView::OnKeyReleased(const ui::KeyEvent& event) {
    187   return child_at(0)->OnKeyReleased(event);
    188 }
    189 
    190 void EntryView::OnPaint(gfx::Canvas* canvas) {
    191   View::OnPaint(canvas);
    192   views::Painter::PaintFocusPainter(this, canvas, focus_painter_.get());
    193 }
    194 
    195 void EntryView::OnBlur() {
    196   View::OnBlur();
    197   // We render differently when focused.
    198   SchedulePaint();
    199 }
    200 
    201 }  // namespace
    202 
    203 
    204 // NotifierGroupMenuModel -----------------------------------------------------
    205 
    206 class NotifierGroupMenuModel : public ui::SimpleMenuModel,
    207                                public ui::SimpleMenuModel::Delegate {
    208  public:
    209   NotifierGroupMenuModel(NotifierSettingsProvider* notifier_settings_provider);
    210   virtual ~NotifierGroupMenuModel();
    211 
    212   // ui::SimpleMenuModel::Delegate:
    213   virtual bool IsCommandIdChecked(int command_id) const OVERRIDE;
    214   virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE;
    215   virtual bool GetAcceleratorForCommandId(
    216       int command_id,
    217       ui::Accelerator* accelerator) OVERRIDE;
    218   virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE;
    219 
    220  private:
    221   NotifierSettingsProvider* notifier_settings_provider_;
    222 
    223   DISALLOW_COPY_AND_ASSIGN(NotifierGroupMenuModel);
    224 };
    225 
    226 NotifierGroupMenuModel::NotifierGroupMenuModel(
    227     NotifierSettingsProvider* notifier_settings_provider)
    228     : ui::SimpleMenuModel(this),
    229       notifier_settings_provider_(notifier_settings_provider) {
    230   if (!notifier_settings_provider_)
    231     return;
    232 
    233   size_t num_menu_items = notifier_settings_provider_->GetNotifierGroupCount();
    234   for (size_t i = 0; i < num_menu_items; ++i) {
    235     const NotifierGroup& group =
    236         notifier_settings_provider_->GetNotifierGroupAt(i);
    237 
    238     AddCheckItem(i, group.login_info.empty() ? group.name : group.login_info);
    239   }
    240 }
    241 
    242 NotifierGroupMenuModel::~NotifierGroupMenuModel() {}
    243 
    244 bool NotifierGroupMenuModel::IsCommandIdChecked(int command_id) const {
    245   // If there's no provider, assume only one notifier group - the active one.
    246   return !notifier_settings_provider_ ||
    247       notifier_settings_provider_->IsNotifierGroupActiveAt(command_id);
    248 }
    249 
    250 bool NotifierGroupMenuModel::IsCommandIdEnabled(int command_id) const {
    251   return true;
    252 }
    253 
    254 bool NotifierGroupMenuModel::GetAcceleratorForCommandId(
    255     int command_id,
    256     ui::Accelerator* accelerator) {
    257   return false;
    258 }
    259 
    260 void NotifierGroupMenuModel::ExecuteCommand(int command_id, int event_flags) {
    261   if (!notifier_settings_provider_)
    262     return;
    263 
    264   size_t notifier_group_index = static_cast<size_t>(command_id);
    265   size_t num_notifier_groups =
    266       notifier_settings_provider_->GetNotifierGroupCount();
    267   if (notifier_group_index >= num_notifier_groups)
    268     return;
    269 
    270   notifier_settings_provider_->SwitchToNotifierGroup(notifier_group_index);
    271 }
    272 
    273 
    274 // NotifierSettingsView::NotifierButton ---------------------------------------
    275 
    276 // We do not use views::Checkbox class directly because it doesn't support
    277 // showing 'icon'.
    278 NotifierSettingsView::NotifierButton::NotifierButton(
    279     NotifierSettingsProvider* provider,
    280     Notifier* notifier,
    281     views::ButtonListener* listener)
    282     : views::CustomButton(listener),
    283       provider_(provider),
    284       notifier_(notifier),
    285       icon_view_(new views::ImageView()),
    286       name_view_(new views::Label(notifier_->name)),
    287       checkbox_(new views::Checkbox(base::string16())),
    288       learn_more_(NULL) {
    289   DCHECK(provider);
    290   DCHECK(notifier);
    291 
    292   // Since there may never be an icon (but that could change at a later time),
    293   // we own the icon view here.
    294   icon_view_->set_owned_by_client();
    295 
    296   checkbox_->SetChecked(notifier_->enabled);
    297   checkbox_->set_listener(this);
    298   checkbox_->SetFocusable(false);
    299   checkbox_->SetAccessibleName(notifier_->name);
    300 
    301   if (ShouldHaveLearnMoreButton()) {
    302     // Create a more-info button that will be right-aligned.
    303     learn_more_ = new views::ImageButton(this);
    304     learn_more_->SetFocusPainter(CreateFocusPainter());
    305     learn_more_->set_request_focus_on_press(false);
    306     learn_more_->SetFocusable(true);
    307 
    308     ui::ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    309     learn_more_->SetImage(
    310         views::Button::STATE_NORMAL,
    311         rb.GetImageSkiaNamed(IDR_NOTIFICATION_ADVANCED_SETTINGS));
    312     learn_more_->SetImage(
    313         views::Button::STATE_HOVERED,
    314         rb.GetImageSkiaNamed(IDR_NOTIFICATION_ADVANCED_SETTINGS_HOVER));
    315     learn_more_->SetImage(
    316         views::Button::STATE_PRESSED,
    317         rb.GetImageSkiaNamed(IDR_NOTIFICATION_ADVANCED_SETTINGS_PRESSED));
    318     learn_more_->SetState(views::Button::STATE_NORMAL);
    319     int learn_more_border_width =
    320         (settings::kLearnMoreTargetWidth - settings::kLearnMoreSize) / 2;
    321     int learn_more_border_height =
    322         (settings::kLearnMoreTargetHeight - settings::kLearnMoreSize) / 2;
    323     // The image itself is quite small, this large invisible border creates a
    324     // much bigger click target.
    325     learn_more_->SetBorder(
    326         views::Border::CreateEmptyBorder(learn_more_border_height,
    327                                          learn_more_border_width,
    328                                          learn_more_border_height,
    329                                          learn_more_border_width));
    330     learn_more_->SetImageAlignment(views::ImageButton::ALIGN_CENTER,
    331                                    views::ImageButton::ALIGN_MIDDLE);
    332   }
    333 
    334   UpdateIconImage(notifier_->icon);
    335 }
    336 
    337 NotifierSettingsView::NotifierButton::~NotifierButton() {
    338 }
    339 
    340 void NotifierSettingsView::NotifierButton::UpdateIconImage(
    341     const gfx::Image& icon) {
    342   bool has_icon_view = false;
    343 
    344   notifier_->icon = icon;
    345   if (!icon.IsEmpty()) {
    346     icon_view_->SetImage(icon.ToImageSkia());
    347     icon_view_->SetImageSize(
    348         gfx::Size(settings::kEntryIconSize, settings::kEntryIconSize));
    349     has_icon_view = true;
    350   }
    351   GridChanged(ShouldHaveLearnMoreButton(), has_icon_view);
    352 }
    353 
    354 void NotifierSettingsView::NotifierButton::SetChecked(bool checked) {
    355   checkbox_->SetChecked(checked);
    356   notifier_->enabled = checked;
    357 }
    358 
    359 bool NotifierSettingsView::NotifierButton::checked() const {
    360   return checkbox_->checked();
    361 }
    362 
    363 bool NotifierSettingsView::NotifierButton::has_learn_more() const {
    364   return learn_more_ != NULL;
    365 }
    366 
    367 const Notifier& NotifierSettingsView::NotifierButton::notifier() const {
    368   return *notifier_.get();
    369 }
    370 
    371 void NotifierSettingsView::NotifierButton::SendLearnMorePressedForTest() {
    372   if (learn_more_ == NULL)
    373     return;
    374   gfx::Point point(110, 120);
    375   ui::MouseEvent pressed(
    376       ui::ET_MOUSE_PRESSED, point, point, ui::EF_LEFT_MOUSE_BUTTON,
    377       ui::EF_LEFT_MOUSE_BUTTON);
    378   ButtonPressed(learn_more_, pressed);
    379 }
    380 
    381 void NotifierSettingsView::NotifierButton::ButtonPressed(
    382     views::Button* button,
    383     const ui::Event& event) {
    384   if (button == checkbox_) {
    385     // The checkbox state has already changed at this point, but we'll update
    386     // the state on NotifierSettingsView::ButtonPressed() too, so here change
    387     // back to the previous state.
    388     checkbox_->SetChecked(!checkbox_->checked());
    389     CustomButton::NotifyClick(event);
    390   } else if (button == learn_more_) {
    391     DCHECK(provider_);
    392     provider_->OnNotifierAdvancedSettingsRequested(notifier_->notifier_id,
    393                                                    NULL);
    394   }
    395 }
    396 
    397 void NotifierSettingsView::NotifierButton::GetAccessibleState(
    398     ui::AXViewState* state) {
    399   static_cast<views::View*>(checkbox_)->GetAccessibleState(state);
    400 }
    401 
    402 bool NotifierSettingsView::NotifierButton::ShouldHaveLearnMoreButton() const {
    403   if (!provider_)
    404     return false;
    405 
    406   return provider_->NotifierHasAdvancedSettings(notifier_->notifier_id);
    407 }
    408 
    409 void NotifierSettingsView::NotifierButton::GridChanged(bool has_learn_more,
    410                                                        bool has_icon_view) {
    411   using views::ColumnSet;
    412   using views::GridLayout;
    413 
    414   GridLayout* layout = new GridLayout(this);
    415   SetLayoutManager(layout);
    416   ColumnSet* cs = layout->AddColumnSet(0);
    417   // Add a column for the checkbox.
    418   cs->AddPaddingColumn(0, kInnateCheckboxRightPadding);
    419   cs->AddColumn(GridLayout::CENTER,
    420                 GridLayout::CENTER,
    421                 0,
    422                 GridLayout::FIXED,
    423                 kComputedCheckboxSize,
    424                 0);
    425   cs->AddPaddingColumn(0, settings::kInternalHorizontalSpacing);
    426 
    427   if (has_icon_view) {
    428     // Add a column for the icon.
    429     cs->AddColumn(GridLayout::CENTER,
    430                   GridLayout::CENTER,
    431                   0,
    432                   GridLayout::FIXED,
    433                   settings::kEntryIconSize,
    434                   0);
    435     cs->AddPaddingColumn(0, settings::kInternalHorizontalSpacing);
    436   }
    437 
    438   // Add a column for the name.
    439   cs->AddColumn(
    440       GridLayout::LEADING, GridLayout::CENTER, 0, GridLayout::USE_PREF, 0, 0);
    441 
    442   // Add a padding column which contains expandable blank space.
    443   cs->AddPaddingColumn(1, 0);
    444 
    445   // Add a column for the learn more button if necessary.
    446   if (has_learn_more) {
    447     cs->AddPaddingColumn(0, settings::kInternalHorizontalSpacing);
    448     cs->AddColumn(
    449         GridLayout::CENTER, GridLayout::CENTER, 0, GridLayout::USE_PREF, 0, 0);
    450   }
    451 
    452   layout->StartRow(0, 0);
    453   layout->AddView(checkbox_);
    454   if (has_icon_view)
    455     layout->AddView(icon_view_.get());
    456   layout->AddView(name_view_);
    457   if (has_learn_more)
    458     layout->AddView(learn_more_);
    459 
    460   Layout();
    461 }
    462 
    463 
    464 // NotifierSettingsView -------------------------------------------------------
    465 
    466 NotifierSettingsView::NotifierSettingsView(NotifierSettingsProvider* provider)
    467     : title_arrow_(NULL),
    468       title_label_(NULL),
    469       notifier_group_selector_(NULL),
    470       scroller_(NULL),
    471       provider_(provider) {
    472   // |provider_| may be NULL in tests.
    473   if (provider_)
    474     provider_->AddObserver(this);
    475 
    476   SetFocusable(true);
    477   set_background(
    478       views::Background::CreateSolidBackground(kMessageCenterBackgroundColor));
    479   SetPaintToLayer(true);
    480 
    481   title_label_ = new views::Label(
    482       l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_SETTINGS_BUTTON_LABEL),
    483       ui::ResourceBundle::GetSharedInstance().GetFontList(
    484           ui::ResourceBundle::MediumFont));
    485   title_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    486   title_label_->SetMultiLine(true);
    487   title_label_->SetBorder(
    488       views::Border::CreateEmptyBorder(kComputedTitleTopMargin,
    489                                        settings::kTitleMargin,
    490                                        kComputedTitleBottomMargin,
    491                                        settings::kTitleMargin));
    492 
    493   AddChildView(title_label_);
    494 
    495   scroller_ = new views::ScrollView();
    496   scroller_->SetVerticalScrollBar(new views::OverlayScrollBar(false));
    497   AddChildView(scroller_);
    498 
    499   std::vector<Notifier*> notifiers;
    500   if (provider_)
    501     provider_->GetNotifierList(&notifiers);
    502 
    503   UpdateContentsView(notifiers);
    504 }
    505 
    506 NotifierSettingsView::~NotifierSettingsView() {
    507   // |provider_| may be NULL in tests.
    508   if (provider_)
    509     provider_->RemoveObserver(this);
    510 }
    511 
    512 bool NotifierSettingsView::IsScrollable() {
    513   return scroller_->height() < scroller_->contents()->height();
    514 }
    515 
    516 void NotifierSettingsView::UpdateIconImage(const NotifierId& notifier_id,
    517                                            const gfx::Image& icon) {
    518   for (std::set<NotifierButton*>::iterator iter = buttons_.begin();
    519        iter != buttons_.end();
    520        ++iter) {
    521     if ((*iter)->notifier().notifier_id == notifier_id) {
    522       (*iter)->UpdateIconImage(icon);
    523       return;
    524     }
    525   }
    526 }
    527 
    528 void NotifierSettingsView::NotifierGroupChanged() {
    529   std::vector<Notifier*> notifiers;
    530   if (provider_)
    531     provider_->GetNotifierList(&notifiers);
    532 
    533   UpdateContentsView(notifiers);
    534 }
    535 
    536 void NotifierSettingsView::NotifierEnabledChanged(const NotifierId& notifier_id,
    537                                                   bool enabled) {}
    538 
    539 void NotifierSettingsView::UpdateContentsView(
    540     const std::vector<Notifier*>& notifiers) {
    541   buttons_.clear();
    542 
    543   views::View* contents_view = new views::View();
    544   contents_view->SetLayoutManager(new views::BoxLayout(
    545       views::BoxLayout::kVertical, settings::kHorizontalMargin, 0, 0));
    546 
    547   views::View* contents_title_view = new views::View();
    548   contents_title_view->SetLayoutManager(
    549       new views::BoxLayout(views::BoxLayout::kVertical,
    550                            kComputedContentsTitleMargin,
    551                            0,
    552                            kComputedTitleElementSpacing));
    553 
    554   bool need_account_switcher =
    555       provider_ && provider_->GetNotifierGroupCount() > 1;
    556   int top_label_resource_id =
    557       need_account_switcher ? IDS_MESSAGE_CENTER_SETTINGS_DESCRIPTION_MULTIUSER
    558                             : IDS_MESSAGE_CENTER_SETTINGS_DIALOG_DESCRIPTION;
    559 
    560   views::Label* top_label =
    561       new views::Label(l10n_util::GetStringUTF16(top_label_resource_id));
    562 
    563   top_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    564   top_label->SetMultiLine(true);
    565   top_label->SetBorder(views::Border::CreateEmptyBorder(
    566       0,
    567       settings::kTitleMargin + kMenuButtonInnateMargin,
    568       0,
    569       settings::kTitleMargin + kMenuButtonInnateMargin));
    570   contents_title_view->AddChildView(top_label);
    571 
    572   if (need_account_switcher) {
    573     const NotifierGroup& active_group = provider_->GetActiveNotifierGroup();
    574     base::string16 notifier_group_text = active_group.login_info.empty() ?
    575         active_group.name : active_group.login_info;
    576     notifier_group_selector_ =
    577         new views::MenuButton(NULL, notifier_group_text, this, true);
    578     notifier_group_selector_->SetBorder(scoped_ptr<views::Border>(
    579         new views::LabelButtonBorder(views::Button::STYLE_BUTTON)).Pass());
    580     notifier_group_selector_->SetFocusPainter(scoped_ptr<views::Painter>());
    581     notifier_group_selector_->set_animate_on_state_change(false);
    582     notifier_group_selector_->SetFocusable(true);
    583     contents_title_view->AddChildView(notifier_group_selector_);
    584   }
    585 
    586   contents_view->AddChildView(contents_title_view);
    587 
    588   size_t notifier_count = notifiers.size();
    589   for (size_t i = 0; i < notifier_count; ++i) {
    590     NotifierButton* button = new NotifierButton(provider_, notifiers[i], this);
    591     EntryView* entry = new EntryView(button);
    592 
    593     // This code emulates separators using borders.  We will create an invisible
    594     // border on the last notifier, as the spec leaves a space for it.
    595     scoped_ptr<views::Border> entry_border;
    596     if (i == notifier_count - 1) {
    597       entry_border = views::Border::CreateEmptyBorder(
    598           0, 0, settings::kEntrySeparatorHeight, 0);
    599     } else {
    600       entry_border =
    601           views::Border::CreateSolidSidedBorder(0,
    602                                                 0,
    603                                                 settings::kEntrySeparatorHeight,
    604                                                 0,
    605                                                 settings::kEntrySeparatorColor);
    606     }
    607     entry->SetBorder(entry_border.Pass());
    608     entry->SetFocusable(true);
    609     contents_view->AddChildView(entry);
    610     buttons_.insert(button);
    611   }
    612 
    613   scroller_->SetContents(contents_view);
    614 
    615   contents_view->SetBoundsRect(gfx::Rect(contents_view->GetPreferredSize()));
    616   InvalidateLayout();
    617 }
    618 
    619 void NotifierSettingsView::Layout() {
    620   int title_height = title_label_->GetHeightForWidth(width());
    621   title_label_->SetBounds(settings::kTitleMargin,
    622                           0,
    623                           width() - settings::kTitleMargin * 2,
    624                           title_height);
    625 
    626   views::View* contents_view = scroller_->contents();
    627   int content_width = width();
    628   int content_height = contents_view->GetHeightForWidth(content_width);
    629   if (title_height + content_height > height()) {
    630     content_width -= scroller_->GetScrollBarWidth();
    631     content_height = contents_view->GetHeightForWidth(content_width);
    632   }
    633   contents_view->SetBounds(0, 0, content_width, content_height);
    634   scroller_->SetBounds(0, title_height, width(), height() - title_height);
    635 }
    636 
    637 gfx::Size NotifierSettingsView::GetMinimumSize() const {
    638   gfx::Size size(settings::kWidth, settings::kMinimumHeight);
    639   int total_height = title_label_->GetPreferredSize().height() +
    640                      scroller_->contents()->GetPreferredSize().height();
    641   if (total_height > settings::kMinimumHeight)
    642     size.Enlarge(scroller_->GetScrollBarWidth(), 0);
    643   return size;
    644 }
    645 
    646 gfx::Size NotifierSettingsView::GetPreferredSize() const {
    647   gfx::Size preferred_size;
    648   gfx::Size title_size = title_label_->GetPreferredSize();
    649   gfx::Size content_size = scroller_->contents()->GetPreferredSize();
    650   return gfx::Size(std::max(title_size.width(), content_size.width()),
    651                    title_size.height() + content_size.height());
    652 }
    653 
    654 bool NotifierSettingsView::OnKeyPressed(const ui::KeyEvent& event) {
    655   if (event.key_code() == ui::VKEY_ESCAPE) {
    656     GetWidget()->Close();
    657     return true;
    658   }
    659 
    660   return scroller_->OnKeyPressed(event);
    661 }
    662 
    663 bool NotifierSettingsView::OnMouseWheel(const ui::MouseWheelEvent& event) {
    664   return scroller_->OnMouseWheel(event);
    665 }
    666 
    667 void NotifierSettingsView::ButtonPressed(views::Button* sender,
    668                                          const ui::Event& event) {
    669   if (sender == title_arrow_) {
    670     MessageCenterView* center_view = static_cast<MessageCenterView*>(parent());
    671     center_view->SetSettingsVisible(!center_view->settings_visible());
    672     return;
    673   }
    674 
    675   std::set<NotifierButton*>::iterator iter =
    676       buttons_.find(static_cast<NotifierButton*>(sender));
    677 
    678   if (iter == buttons_.end())
    679     return;
    680 
    681   (*iter)->SetChecked(!(*iter)->checked());
    682   if (provider_)
    683     provider_->SetNotifierEnabled((*iter)->notifier(), (*iter)->checked());
    684 }
    685 
    686 void NotifierSettingsView::OnMenuButtonClicked(views::View* source,
    687                                                const gfx::Point& point) {
    688   notifier_group_menu_model_.reset(new NotifierGroupMenuModel(provider_));
    689   notifier_group_menu_runner_.reset(new views::MenuRunner(
    690       notifier_group_menu_model_.get(), views::MenuRunner::CONTEXT_MENU));
    691   gfx::Rect menu_anchor = source->GetBoundsInScreen();
    692   menu_anchor.Inset(
    693       gfx::Insets(0, kMenuWhitespaceOffset, 0, kMenuWhitespaceOffset));
    694   if (views::MenuRunner::MENU_DELETED ==
    695       notifier_group_menu_runner_->RunMenuAt(GetWidget(),
    696                                              notifier_group_selector_,
    697                                              menu_anchor,
    698                                              views::MENU_ANCHOR_BUBBLE_ABOVE,
    699                                              ui::MENU_SOURCE_MOUSE))
    700     return;
    701   MessageCenterView* center_view = static_cast<MessageCenterView*>(parent());
    702   center_view->OnSettingsChanged();
    703 }
    704 
    705 }  // namespace message_center
    706