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