Home | History | Annotate | Download | only in views
      1 // Copyright (c) 2012 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/message_view.h"
      6 
      7 #include "grit/ui_resources.h"
      8 #include "grit/ui_strings.h"
      9 #include "ui/base/accessibility/accessible_view_state.h"
     10 #include "ui/base/l10n/l10n_util.h"
     11 #include "ui/base/models/simple_menu_model.h"
     12 #include "ui/base/resource/resource_bundle.h"
     13 #include "ui/compositor/scoped_layer_animation_settings.h"
     14 #include "ui/gfx/canvas.h"
     15 #include "ui/message_center/message_center.h"
     16 #include "ui/message_center/message_center_style.h"
     17 #include "ui/message_center/message_center_tray.h"
     18 #include "ui/message_center/message_center_util.h"
     19 #include "ui/views/context_menu_controller.h"
     20 #include "ui/views/controls/button/image_button.h"
     21 #include "ui/views/controls/menu/menu_runner.h"
     22 #include "ui/views/controls/scroll_view.h"
     23 #include "ui/views/shadow_border.h"
     24 #include "ui/views/widget/widget.h"
     25 
     26 namespace {
     27 
     28 const int kCloseIconTopPadding = 5;
     29 const int kCloseIconRightPadding = 5;
     30 const int kExpandIconBottomPadding = 8;
     31 const int kExpandIconRightPadding = 11;
     32 
     33 const int kShadowOffset = 1;
     34 const int kShadowBlur = 4;
     35 
     36 // Menu constants
     37 const int kTogglePermissionCommand = 0;
     38 const int kToggleExtensionCommand = 1;
     39 const int kShowSettingsCommand = 2;
     40 
     41 // ControlButtons are ImageButtons whose image can be padded within the button.
     42 // This allows the creation of buttons like the notification close and expand
     43 // buttons whose clickable areas extends beyond their image areas
     44 // (<http://crbug.com/168822>) without the need to create and maintain
     45 // corresponding resource images with alpha padding. In the future, this class
     46 // will also allow for buttons whose touch areas extend beyond their clickable
     47 // area (<http://crbug.com/168856>).
     48 class ControlButton : public views::ImageButton {
     49  public:
     50   ControlButton(views::ButtonListener* listener);
     51   virtual ~ControlButton();
     52 
     53   // Overridden from views::ImageButton:
     54   virtual gfx::Size GetPreferredSize() OVERRIDE;
     55   virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
     56   virtual void OnFocus() OVERRIDE;
     57   virtual void OnPaintFocusBorder(gfx::Canvas* canvas) OVERRIDE;
     58 
     59   // The SetPadding() method also sets the button's image alignment (positive
     60   // values yield left/top alignments, negative values yield right/bottom ones,
     61   // and zero values center/middle ones). ImageButton::SetImageAlignment() calls
     62   // will not affect ControlButton image alignments.
     63   void SetPadding(int horizontal_padding, int vertical_padding);
     64 
     65   void SetNormalImage(int resource_id);
     66   void SetHoveredImage(int resource_id);
     67   void SetPressedImage(int resource_id);
     68 
     69  protected:
     70   gfx::Point ComputePaddedImagePaintPosition(const gfx::ImageSkia& image);
     71 
     72  private:
     73   gfx::Insets padding_;
     74 
     75   DISALLOW_COPY_AND_ASSIGN(ControlButton);
     76 };
     77 
     78 ControlButton::ControlButton(views::ButtonListener* listener)
     79   : views::ImageButton(listener) {
     80   set_focusable(true);
     81   set_request_focus_on_press(false);
     82 }
     83 
     84 ControlButton::~ControlButton() {
     85 }
     86 
     87 void ControlButton::SetPadding(int horizontal_padding, int vertical_padding) {
     88   padding_.Set(std::max(vertical_padding, 0),
     89                std::max(horizontal_padding, 0),
     90                std::max(-vertical_padding, 0),
     91                std::max(-horizontal_padding, 0));
     92 }
     93 
     94 void ControlButton::SetNormalImage(int resource_id) {
     95   SetImage(views::CustomButton::STATE_NORMAL,
     96            ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
     97                resource_id));
     98 }
     99 
    100 void ControlButton::SetHoveredImage(int resource_id) {
    101   SetImage(views::CustomButton::STATE_HOVERED,
    102            ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
    103                resource_id));
    104 }
    105 
    106 void ControlButton::SetPressedImage(int resource_id) {
    107   SetImage(views::CustomButton::STATE_PRESSED,
    108            ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
    109                resource_id));
    110 }
    111 
    112 gfx::Size ControlButton::GetPreferredSize() {
    113   return gfx::Size(message_center::kControlButtonSize,
    114                    message_center::kControlButtonSize);
    115 }
    116 
    117 void ControlButton::OnPaint(gfx::Canvas* canvas) {
    118   // This is the same implementation as ImageButton::OnPaint except
    119   // that it calls ComputePaddedImagePaintPosition() instead of
    120   // ComputeImagePaintPosition(), in effect overriding that private method.
    121   View::OnPaint(canvas);
    122   gfx::ImageSkia image = GetImageToPaint();
    123   if (!image.isNull()) {
    124     gfx::Point position = ComputePaddedImagePaintPosition(image);
    125     if (!background_image_.isNull())
    126       canvas->DrawImageInt(background_image_, position.x(), position.y());
    127     canvas->DrawImageInt(image, position.x(), position.y());
    128     if (!overlay_image_.isNull())
    129       canvas->DrawImageInt(overlay_image_, position.x(), position.y());
    130   }
    131   OnPaintFocusBorder(canvas);
    132 }
    133 
    134 void ControlButton::OnFocus() {
    135   views::ImageButton::OnFocus();
    136   ScrollRectToVisible(GetLocalBounds());
    137 }
    138 
    139 void ControlButton::OnPaintFocusBorder(gfx::Canvas* canvas) {
    140   if (HasFocus() && (focusable() || IsAccessibilityFocusable())) {
    141     canvas->DrawRect(gfx::Rect(2, 1, width() - 4, height() - 3),
    142                      message_center::kFocusBorderColor);
    143   }
    144 }
    145 
    146 gfx::Point ControlButton::ComputePaddedImagePaintPosition(
    147     const gfx::ImageSkia& image) {
    148   gfx::Vector2d offset;
    149   gfx::Rect bounds = GetContentsBounds();
    150   bounds.Inset(padding_);
    151 
    152   if (padding_.left() == 0 && padding_.right() == 0)
    153     offset.set_x((bounds.width() - image.width()) / 2);  // Center align.
    154   else if (padding_.right() > 0)
    155     offset.set_x(bounds.width() - image.width());  // Right align.
    156 
    157   if (padding_.top() == 0 && padding_.bottom() == 0)
    158     offset.set_y((bounds.height() - image.height()) / 2);  // Middle align.
    159   else if (padding_.bottom() > 0)
    160     offset.set_y(bounds.height() - image.height());  // Bottom align.
    161 
    162   return bounds.origin() + offset;
    163 }
    164 
    165 // A dropdown menu for notifications.
    166 class MenuModel : public ui::SimpleMenuModel,
    167                   public ui::SimpleMenuModel::Delegate {
    168  public:
    169   MenuModel(message_center::MessageCenter* message_center,
    170             message_center::MessageCenterTray* tray,
    171             const std::string& notification_id,
    172             const string16& display_source,
    173             const std::string& extension_id);
    174   virtual ~MenuModel();
    175 
    176   // Overridden from ui::SimpleMenuModel::Delegate:
    177   virtual bool IsItemForCommandIdDynamic(int command_id) const OVERRIDE;
    178   virtual bool IsCommandIdChecked(int command_id) const OVERRIDE;
    179   virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE;
    180   virtual bool GetAcceleratorForCommandId(
    181       int command_id,
    182       ui::Accelerator* accelerator) OVERRIDE;
    183   virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE;
    184 
    185  private:
    186   message_center::MessageCenter* message_center_;  // Weak reference.
    187   message_center::MessageCenterTray* tray_;  // Weak reference.
    188   std::string notification_id_;
    189 
    190   DISALLOW_COPY_AND_ASSIGN(MenuModel);
    191 };
    192 
    193 MenuModel::MenuModel(message_center::MessageCenter* message_center,
    194                      message_center::MessageCenterTray* tray,
    195                      const std::string& notification_id,
    196                      const string16& display_source,
    197                      const std::string& extension_id)
    198     : ui::SimpleMenuModel(this),
    199       message_center_(message_center),
    200       tray_(tray),
    201       notification_id_(notification_id) {
    202   // Add 'disable notifications' menu item.
    203   if (!extension_id.empty() && !display_source.empty()) {
    204     AddItem(kToggleExtensionCommand,
    205             l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_EXTENSIONS_DISABLE,
    206                                        display_source));
    207   } else if (!display_source.empty()) {
    208     AddItem(kTogglePermissionCommand,
    209             l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_SITE_DISABLE,
    210                                        display_source));
    211   }
    212   // Add settings menu item.
    213   AddItem(kShowSettingsCommand,
    214           l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_SETTINGS));
    215 }
    216 
    217 MenuModel::~MenuModel() {
    218 }
    219 
    220 bool MenuModel::IsItemForCommandIdDynamic(int command_id) const {
    221   return false;
    222 }
    223 
    224 bool MenuModel::IsCommandIdChecked(int command_id) const {
    225   return false;
    226 }
    227 
    228 bool MenuModel::IsCommandIdEnabled(int command_id) const {
    229   return true;
    230 }
    231 
    232 bool MenuModel::GetAcceleratorForCommandId(int command_id,
    233                                            ui::Accelerator* accelerator) {
    234   return false;
    235 }
    236 
    237 void MenuModel::ExecuteCommand(int command_id, int event_flags) {
    238   switch (command_id) {
    239     case kToggleExtensionCommand:
    240       message_center_->DisableNotificationsByExtension(notification_id_);
    241       break;
    242     case kTogglePermissionCommand:
    243       message_center_->DisableNotificationsByUrl(notification_id_);
    244       break;
    245     case kShowSettingsCommand:
    246       // |tray_| may be NULL in tests.
    247       if (tray_)
    248         tray_->ShowNotifierSettingsBubble();
    249       else
    250         message_center_->ShowNotificationSettings(notification_id_);
    251       break;
    252     default:
    253       NOTREACHED();
    254   }
    255 }
    256 
    257 }  // namespace
    258 
    259 namespace message_center {
    260 
    261 class MessageViewContextMenuController : public views::ContextMenuController {
    262  public:
    263   MessageViewContextMenuController(
    264       MessageCenter* message_center,
    265       MessageCenterTray* tray,
    266       const Notification& notification);
    267   virtual ~MessageViewContextMenuController();
    268 
    269  protected:
    270   // Overridden from views::ContextMenuController:
    271   virtual void ShowContextMenuForView(views::View* source,
    272                                       const gfx::Point& point,
    273                                       ui::MenuSourceType source_type) OVERRIDE;
    274 
    275   MessageCenter* message_center_;  // Weak reference.
    276   MessageCenterTray* tray_;  // Weak reference.
    277   std::string notification_id_;
    278   string16 display_source_;
    279   std::string extension_id_;
    280 };
    281 
    282 MessageViewContextMenuController::MessageViewContextMenuController(
    283     MessageCenter* message_center,
    284     MessageCenterTray* tray,
    285     const Notification& notification)
    286     : message_center_(message_center),
    287       tray_(tray),
    288       notification_id_(notification.id()),
    289       display_source_(notification.display_source()),
    290       extension_id_(notification.extension_id()) {
    291 }
    292 
    293 MessageViewContextMenuController::~MessageViewContextMenuController() {
    294 }
    295 
    296 void MessageViewContextMenuController::ShowContextMenuForView(
    297     views::View* source,
    298     const gfx::Point& point,
    299     ui::MenuSourceType source_type) {
    300   MenuModel menu_model(message_center_, tray_, notification_id_,
    301                        display_source_, extension_id_);
    302   if (menu_model.GetItemCount() == 0)
    303     return;
    304 
    305   views::MenuRunner menu_runner(&menu_model);
    306 
    307   ignore_result(menu_runner.RunMenuAt(
    308       source->GetWidget()->GetTopLevelWidget(),
    309       NULL,
    310       gfx::Rect(point, gfx::Size()),
    311       views::MenuItemView::TOPRIGHT,
    312       source_type,
    313       views::MenuRunner::HAS_MNEMONICS));
    314 }
    315 
    316 MessageView::MessageView(const Notification& notification,
    317                          MessageCenter* message_center,
    318                          MessageCenterTray* tray,
    319                          bool expanded)
    320     : message_center_(message_center),
    321       notification_id_(notification.id()),
    322       context_menu_controller_(new MessageViewContextMenuController(
    323           message_center, tray, notification)),
    324       scroller_(NULL),
    325       is_expanded_(expanded) {
    326   set_focusable(true);
    327   set_context_menu_controller(context_menu_controller_.get());
    328 
    329   ControlButton *close = new ControlButton(this);
    330   close->SetPadding(-kCloseIconRightPadding, kCloseIconTopPadding);
    331   close->SetNormalImage(IDR_NOTIFICATION_CLOSE);
    332   close->SetHoveredImage(IDR_NOTIFICATION_CLOSE_HOVER);
    333   close->SetPressedImage(IDR_NOTIFICATION_CLOSE_PRESSED);
    334   close->set_owned_by_client();
    335   close->set_animate_on_state_change(false);
    336   close->SetAccessibleName(l10n_util::GetStringUTF16(
    337       IDS_MESSAGE_CENTER_CLOSE_NOTIFICATION_BUTTON_ACCESSIBLE_NAME));
    338   close_button_.reset(close);
    339 
    340   ControlButton *expand = new ControlButton(this);
    341   expand->SetPadding(-kExpandIconRightPadding, -kExpandIconBottomPadding);
    342   expand->SetNormalImage(IDR_NOTIFICATION_EXPAND);
    343   expand->SetHoveredImage(IDR_NOTIFICATION_EXPAND_HOVER);
    344   expand->SetPressedImage(IDR_NOTIFICATION_EXPAND_PRESSED);
    345   expand->set_owned_by_client();
    346   expand->set_animate_on_state_change(false);
    347   expand->SetAccessibleName(l10n_util::GetStringUTF16(
    348       IDS_MESSAGE_CENTER_EXPAND_NOTIFICATION_BUTTON_ACCESSIBLE_NAME));
    349   expand_button_.reset(expand);
    350 }
    351 
    352 MessageView::MessageView() {
    353 }
    354 
    355 MessageView::~MessageView() {
    356 }
    357 
    358 // static
    359 gfx::Insets MessageView::GetShadowInsets() {
    360   return gfx::Insets(kShadowBlur / 2 - kShadowOffset,
    361                      kShadowBlur / 2,
    362                      kShadowBlur / 2 + kShadowOffset,
    363                      kShadowBlur / 2);
    364 }
    365 
    366 void MessageView::CreateShadowBorder() {
    367   set_border(new views::ShadowBorder(kShadowBlur,
    368                                      message_center::kShadowColor,
    369                                      kShadowOffset,  // Vertical offset.
    370                                      0));            // Horizontal offset.
    371 }
    372 
    373 bool MessageView::IsCloseButtonFocused() {
    374   views::FocusManager* focus_manager = GetFocusManager();
    375   return focus_manager && focus_manager->GetFocusedView() == close_button();
    376 }
    377 
    378 void MessageView::RequestFocusOnCloseButton() {
    379   close_button_->RequestFocus();
    380 }
    381 
    382 void MessageView::GetAccessibleState(ui::AccessibleViewState* state) {
    383   state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
    384   state->name = accessible_name_;
    385 }
    386 
    387 bool MessageView::OnMousePressed(const ui::MouseEvent& event) {
    388   if (event.IsOnlyLeftMouseButton()) {
    389     message_center_->ClickOnNotification(notification_id_);
    390     return true;
    391   }
    392   return false;
    393 }
    394 
    395 bool MessageView::OnKeyPressed(const ui::KeyEvent& event) {
    396   if (event.flags() != ui::EF_NONE)
    397     return false;
    398 
    399   if (event.key_code() == ui::VKEY_RETURN) {
    400     message_center_->ClickOnNotification(notification_id_);
    401     return true;
    402   } else if ((event.key_code() == ui::VKEY_DELETE ||
    403               event.key_code() == ui::VKEY_BACK)) {
    404     message_center_->RemoveNotification(notification_id_, true);  // By user.
    405     return true;
    406   }
    407 
    408   return false;
    409 }
    410 
    411 bool MessageView::OnKeyReleased(const ui::KeyEvent& event) {
    412   // Space key handling is triggerred at key-release timing. See
    413   // ui/views/controls/buttons/custom_button.cc for why.
    414   if (event.flags() != ui::EF_NONE || event.flags() != ui::VKEY_SPACE)
    415     return false;
    416 
    417   message_center_->ClickOnNotification(notification_id_);
    418   return true;
    419 }
    420 
    421 void MessageView::OnGestureEvent(ui::GestureEvent* event) {
    422   if (event->type() == ui::ET_GESTURE_TAP) {
    423     message_center_->ClickOnNotification(notification_id_);
    424     event->SetHandled();
    425     return;
    426   }
    427 
    428   SlideOutView::OnGestureEvent(event);
    429   // Do not return here by checking handled(). SlideOutView calls SetHandled()
    430   // even though the scroll gesture doesn't make no (or little) effects on the
    431   // slide-out behavior. See http://crbug.com/172991
    432 
    433   if (!event->IsScrollGestureEvent() && !event->IsFlingScrollEvent())
    434     return;
    435 
    436   if (scroller_)
    437     scroller_->OnGestureEvent(event);
    438   event->SetHandled();
    439 }
    440 
    441 void MessageView::OnPaintFocusBorder(gfx::Canvas* canvas) {
    442   if (HasFocus()) {
    443     canvas->DrawRect(gfx::Rect(1, 0, width() - 2, height() - 2),
    444                      message_center::kFocusBorderColor);
    445   }
    446 }
    447 
    448 void MessageView::ButtonPressed(views::Button* sender,
    449                                 const ui::Event& event) {
    450   if (sender == close_button()) {
    451     message_center_->RemoveNotification(notification_id_, true);  // By user.
    452   } else if (sender == expand_button()) {
    453     is_expanded_ = true;
    454     message_center_->ExpandNotification(notification_id_);
    455   }
    456 }
    457 
    458 void MessageView::OnSlideOut() {
    459   message_center_->RemoveNotification(notification_id_, true);  // By user.
    460 }
    461 
    462 }  // namespace message_center
    463