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/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"
     40 
     41 #if defined(USE_AURA)
     42 #include "ui/aura/window.h"
     43 #endif
     44 
     45 namespace message_center {
     46 namespace {
     47 const int kButtonPainterInsets = 5;
     48 // We really want the margin to be 20px, but various views are padded by
     49 // whitespace.
     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;
     70 
     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 {
     74  public:
     75   EntryView(views::View* contents);
     76   virtual ~EntryView();
     77 
     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;
     86 
     87  private:
     88   DISALLOW_COPY_AND_ASSIGN(EntryView);
     89 };
     90 
     91 EntryView::EntryView(views::View* contents) {
     92   AddChildView(contents);
     93 }
     94 
     95 EntryView::~EntryView() {
     96 }
     97 
     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);
    105 }
    106 
    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));
    111   return size;
    112 }
    113 
    114 void EntryView::GetAccessibleState(ui::AccessibleViewState* state) {
    115   DCHECK_EQ(1, child_count());
    116   child_at(0)->GetAccessibleState(state);
    117 }
    118 
    119 void EntryView::OnFocus() {
    120   views::View::OnFocus();
    121   ScrollRectToVisible(GetLocalBounds());
    122 }
    123 
    124 void EntryView::OnPaintFocusBorder(gfx::Canvas* canvas) {
    125   if (HasFocus() && (focusable() || IsAccessibilityFocusable())) {
    126     canvas->DrawRect(gfx::Rect(2, 1, width() - 4, height() - 3),
    127                      kFocusBorderColor);
    128   }
    129 }
    130 
    131 bool EntryView::OnKeyPressed(const ui::KeyEvent& event) {
    132   return child_at(0)->OnKeyPressed(event);
    133 }
    134 
    135 bool EntryView::OnKeyReleased(const ui::KeyEvent& event) {
    136   return child_at(0)->OnKeyReleased(event);
    137 }
    138 
    139 }  // namespace
    140 
    141 // NotifierGroupMenuButtonBorder ///////////////////////////////////////////////
    142 ////////////////////////////////////////////////////////////////////////////////
    143 class NotifierGroupMenuButtonBorder : public views::TextButtonDefaultBorder {
    144  public:
    145   NotifierGroupMenuButtonBorder();
    146 
    147  private:
    148   virtual ~NotifierGroupMenuButtonBorder();
    149 };
    150 
    151 NotifierGroupMenuButtonBorder::NotifierGroupMenuButtonBorder()
    152     : views::TextButtonDefaultBorder() {
    153   ui::ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    154 
    155   gfx::Insets insets(kButtonPainterInsets,
    156                      kButtonPainterInsets,
    157                      kButtonPainterInsets,
    158                      kButtonPainterInsets);
    159 
    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));
    166 
    167   SetInsets(gfx::Insets(kMenuButtonVerticalPadding,
    168                         kMenuButtonLeftPadding,
    169                         kMenuButtonVerticalPadding,
    170                         kMenuButtonRightPadding));
    171 }
    172 
    173 NotifierGroupMenuButtonBorder::~NotifierGroupMenuButtonBorder() {}
    174 
    175 // NotifierGroupMenuModel //////////////////////////////////////////////////////
    176 ////////////////////////////////////////////////////////////////////////////////
    177 class NotifierGroupMenuModel : public ui::SimpleMenuModel,
    178                                public ui::SimpleMenuModel::Delegate {
    179  public:
    180   NotifierGroupMenuModel(NotifierSettingsProvider* notifier_settings_provider);
    181   virtual ~NotifierGroupMenuModel();
    182 
    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(
    187       int command_id,
    188       ui::Accelerator* accelerator) OVERRIDE;
    189   virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE;
    190 
    191  private:
    192   NotifierSettingsProvider* notifier_settings_provider_;
    193 };
    194 
    195 NotifierGroupMenuModel::NotifierGroupMenuModel(
    196     NotifierSettingsProvider* notifier_settings_provider)
    197     : ui::SimpleMenuModel(this),
    198       notifier_settings_provider_(notifier_settings_provider) {
    199   if (!notifier_settings_provider_)
    200     return;
    201 
    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);
    206 
    207     AddCheckItem(i, group.login_info.empty() ? group.name : group.login_info);
    208   }
    209 }
    210 
    211 NotifierGroupMenuModel::~NotifierGroupMenuModel() {}
    212 
    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_)
    216     return true;
    217 
    218   return notifier_settings_provider_->IsNotifierGroupActiveAt(command_id);
    219 }
    220 
    221 bool NotifierGroupMenuModel::IsCommandIdEnabled(int command_id) const {
    222   return true;
    223 }
    224 
    225 bool NotifierGroupMenuModel::GetAcceleratorForCommandId(
    226     int command_id,
    227     ui::Accelerator* accelerator) {
    228   return false;
    229 }
    230 
    231 void NotifierGroupMenuModel::ExecuteCommand(int command_id, int event_flags) {
    232   if (!notifier_settings_provider_)
    233     return;
    234 
    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)
    239     return;
    240 
    241   notifier_settings_provider_->SwitchToNotifierGroup(notifier_group_index);
    242 }
    243 
    244 // We do not use views::Checkbox class directly because it doesn't support
    245 // showing 'icon'.
    246 class NotifierSettingsView::NotifierButton : public views::CustomButton,
    247                                              public views::ButtonListener {
    248  public:
    249   NotifierButton(Notifier* notifier, views::ButtonListener* listener)
    250       : views::CustomButton(listener),
    251         notifier_(notifier),
    252         icon_view_(NULL),
    253         checkbox_(new views::Checkbox(string16())) {
    254     DCHECK(notifier);
    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));
    264   }
    265 
    266   void UpdateIconImage(const gfx::Image& icon) {
    267     notifier_->icon = icon;
    268     if (icon.IsEmpty()) {
    269       delete icon_view_;
    270       icon_view_ = NULL;
    271     } else {
    272       if (!icon_view_) {
    273         icon_view_ = new views::ImageView();
    274         AddChildViewAt(icon_view_, 1);
    275       }
    276       icon_view_->SetImage(icon.ToImageSkia());
    277       icon_view_->SetImageSize(gfx::Size(kSettingsIconSize, kSettingsIconSize));
    278     }
    279     Layout();
    280     SchedulePaint();
    281   }
    282 
    283   void SetChecked(bool checked) {
    284     checkbox_->SetChecked(checked);
    285     notifier_->enabled = checked;
    286   }
    287 
    288   bool checked() const {
    289     return checkbox_->checked();
    290   }
    291 
    292   const Notifier& notifier() const {
    293     return *notifier_.get();
    294   }
    295 
    296  private:
    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);
    306   }
    307 
    308   virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE {
    309     static_cast<views::View*>(checkbox_)->GetAccessibleState(state);
    310   }
    311 
    312   scoped_ptr<Notifier> notifier_;
    313   views::ImageView* icon_view_;
    314   views::Checkbox* checkbox_;
    315 
    316   DISALLOW_COPY_AND_ASSIGN(NotifierButton);
    317 };
    318 
    319 NotifierSettingsView::NotifierSettingsView(NotifierSettingsProvider* provider)
    320     : title_arrow_(NULL),
    321       title_label_(NULL),
    322       notifier_group_selector_(NULL),
    323       scroller_(NULL),
    324       provider_(provider) {
    325   // |provider_| may be NULL in tests.
    326   if (provider_)
    327     provider_->AddObserver(this);
    328 
    329   set_focusable(true);
    330   set_focus_border(NULL);
    331   set_background(views::Background::CreateSolidBackground(
    332       kMessageCenterBackgroundColor));
    333   if (get_use_acceleration_when_possible())
    334     SetPaintToLayer(true);
    335 
    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),
    340       title_font);
    341   title_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    342   title_label_->SetMultiLine(true);
    343   title_label_->set_border(
    344       views::Border::CreateEmptyBorder(kSettingsTitleTopMargin,
    345                                        kDesiredMargin,
    346                                        kSettingsTitleBottomMargin,
    347                                        kDesiredMargin));
    348 
    349   AddChildView(title_label_);
    350 
    351   scroller_ = new views::ScrollView();
    352   scroller_->SetVerticalScrollBar(new views::OverlayScrollBar(false));
    353   AddChildView(scroller_);
    354 
    355   std::vector<Notifier*> notifiers;
    356   if (provider_)
    357     provider_->GetNotifierList(&notifiers);
    358 
    359   UpdateContentsView(notifiers);
    360 }
    361 
    362 NotifierSettingsView::~NotifierSettingsView() {
    363   // |provider_| may be NULL in tests.
    364   if (provider_)
    365     provider_->RemoveObserver(this);
    366 }
    367 
    368 bool NotifierSettingsView::IsScrollable() {
    369   return scroller_->height() < scroller_->contents()->height();
    370 }
    371 
    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);
    378       return;
    379     }
    380   }
    381 }
    382 
    383 void NotifierSettingsView::NotifierGroupChanged() {
    384   std::vector<Notifier*> notifiers;
    385   if (provider_)
    386     provider_->GetNotifierList(&notifiers);
    387 
    388   UpdateContentsView(notifiers);
    389 }
    390 
    391 void NotifierSettingsView::UpdateContentsView(
    392     const std::vector<Notifier*>& notifiers) {
    393   buttons_.clear();
    394 
    395   views::View* contents_view = new views::View();
    396   contents_view->SetLayoutManager(
    397       new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
    398 
    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));
    405 
    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;
    411 
    412   views::Label* top_label =
    413       new views::Label(l10n_util::GetStringUTF16(top_label_resource_id));
    414 
    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);
    420 
    421   string16 notifier_group_text;
    422   if (provider_) {
    423     const NotifierGroup& active_group = provider_->GetActiveNotifierGroup();
    424     notifier_group_text = active_group.login_info.empty()
    425                               ? active_group.name
    426                               : active_group.login_info;
    427   }
    428 
    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_);
    437   }
    438 
    439   contents_view->AddChildView(contents_title_view);
    440 
    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);
    447   }
    448 
    449   scroller_->SetContents(contents_view);
    450 
    451   contents_view->SetBoundsRect(gfx::Rect(contents_view->GetPreferredSize()));
    452   InvalidateLayout();
    453 }
    454 
    455 void NotifierSettingsView::Layout() {
    456   int title_height = title_label_->GetHeightForWidth(width());
    457   title_label_->SetBounds(0, 0, width(), title_height);
    458 
    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);
    465   }
    466   contents_view->SetBounds(0, 0, content_width, content_height);
    467   scroller_->SetBounds(0, title_height, width(), height() - title_height);
    468 }
    469 
    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);
    476   return size;
    477 }
    478 
    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());
    486 }
    487 
    488 bool NotifierSettingsView::OnKeyPressed(const ui::KeyEvent& event) {
    489   if (event.key_code() == ui::VKEY_ESCAPE) {
    490     GetWidget()->Close();
    491     return true;
    492   }
    493 
    494   return scroller_->OnKeyPressed(event);
    495 }
    496 
    497 bool NotifierSettingsView::OnMouseWheel(const ui::MouseWheelEvent& event) {
    498   return scroller_->OnMouseWheel(event);
    499 }
    500 
    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());
    506     return;
    507   }
    508 
    509   std::set<NotifierButton*>::iterator iter = buttons_.find(
    510       static_cast<NotifierButton*>(sender));
    511 
    512   if (iter == buttons_.end())
    513     return;
    514 
    515   (*iter)->SetChecked(!(*iter)->checked());
    516   if (provider_)
    517     provider_->SetNotifierEnabled((*iter)->notifier(), (*iter)->checked());
    518 }
    519 
    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();
    526   menu_anchor.Inset(
    527       gfx::Insets(0, kMenuWhitespaceOffset, 0, kMenuWhitespaceOffset));
    528   if (views::MenuRunner::MENU_DELETED ==
    529       notifier_group_menu_runner_->RunMenuAt(GetWidget(),
    530                                              notifier_group_selector_,
    531                                              menu_anchor,
    532                                              views::MenuItemView::BUBBLE_ABOVE,
    533                                              ui::MENU_SOURCE_MOUSE,
    534                                              views::MenuRunner::CONTEXT_MENU))
    535     return;
    536   MessageCenterView* center_view = static_cast<MessageCenterView*>(parent());
    537   center_view->OnSettingsChanged();
    538 }
    539 
    540 }  // namespace message_center
    541