Home | History | Annotate | Download | only in menu
      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/views/controls/menu/menu_scroll_view_container.h"
      6 
      7 #include "third_party/skia/include/core/SkPaint.h"
      8 #include "third_party/skia/include/core/SkPath.h"
      9 #include "ui/accessibility/ax_view_state.h"
     10 #include "ui/gfx/canvas.h"
     11 #include "ui/native_theme/native_theme_aura.h"
     12 #include "ui/views/border.h"
     13 #include "ui/views/bubble/bubble_border.h"
     14 #include "ui/views/controls/menu/menu_config.h"
     15 #include "ui/views/controls/menu/menu_controller.h"
     16 #include "ui/views/controls/menu/menu_item_view.h"
     17 #include "ui/views/controls/menu/submenu_view.h"
     18 #include "ui/views/round_rect_painter.h"
     19 
     20 using ui::NativeTheme;
     21 
     22 namespace views {
     23 
     24 namespace {
     25 
     26 static const int kBorderPaddingDueToRoundedCorners = 1;
     27 
     28 // MenuScrollButton ------------------------------------------------------------
     29 
     30 // MenuScrollButton is used for the scroll buttons when not all menu items fit
     31 // on screen. MenuScrollButton forwards appropriate events to the
     32 // MenuController.
     33 
     34 class MenuScrollButton : public View {
     35  public:
     36   MenuScrollButton(SubmenuView* host, bool is_up)
     37       : host_(host),
     38         is_up_(is_up),
     39         // Make our height the same as that of other MenuItemViews.
     40         pref_height_(MenuItemView::pref_menu_height()) {
     41   }
     42 
     43   virtual gfx::Size GetPreferredSize() const OVERRIDE {
     44     return gfx::Size(
     45         host_->GetMenuItem()->GetMenuConfig().scroll_arrow_height * 2 - 1,
     46         pref_height_);
     47   }
     48 
     49   virtual bool CanDrop(const OSExchangeData& data) OVERRIDE {
     50     DCHECK(host_->GetMenuItem()->GetMenuController());
     51     return true;  // Always return true so that drop events are targeted to us.
     52   }
     53 
     54   virtual void OnDragEntered(const ui::DropTargetEvent& event) OVERRIDE {
     55     DCHECK(host_->GetMenuItem()->GetMenuController());
     56     host_->GetMenuItem()->GetMenuController()->OnDragEnteredScrollButton(
     57         host_, is_up_);
     58   }
     59 
     60   virtual int OnDragUpdated(const ui::DropTargetEvent& event) OVERRIDE {
     61     return ui::DragDropTypes::DRAG_NONE;
     62   }
     63 
     64   virtual void OnDragExited() OVERRIDE {
     65     DCHECK(host_->GetMenuItem()->GetMenuController());
     66     host_->GetMenuItem()->GetMenuController()->OnDragExitedScrollButton(host_);
     67   }
     68 
     69   virtual int OnPerformDrop(const ui::DropTargetEvent& event) OVERRIDE {
     70     return ui::DragDropTypes::DRAG_NONE;
     71   }
     72 
     73   virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
     74     const MenuConfig& config = host_->GetMenuItem()->GetMenuConfig();
     75 
     76     // The background.
     77     gfx::Rect item_bounds(0, 0, width(), height());
     78     NativeTheme::ExtraParams extra;
     79     extra.menu_item.is_selected = false;
     80     GetNativeTheme()->Paint(canvas->sk_canvas(),
     81                             NativeTheme::kMenuItemBackground,
     82                             NativeTheme::kNormal, item_bounds, extra);
     83 
     84     // Then the arrow.
     85     int x = width() / 2;
     86     int y = (height() - config.scroll_arrow_height) / 2;
     87 
     88     int x_left = x - config.scroll_arrow_height;
     89     int x_right = x + config.scroll_arrow_height;
     90     int y_bottom;
     91 
     92     if (!is_up_) {
     93       y_bottom = y;
     94       y = y_bottom + config.scroll_arrow_height;
     95     } else {
     96       y_bottom = y + config.scroll_arrow_height;
     97     }
     98     SkPath path;
     99     path.setFillType(SkPath::kWinding_FillType);
    100     path.moveTo(SkIntToScalar(x), SkIntToScalar(y));
    101     path.lineTo(SkIntToScalar(x_left), SkIntToScalar(y_bottom));
    102     path.lineTo(SkIntToScalar(x_right), SkIntToScalar(y_bottom));
    103     path.lineTo(SkIntToScalar(x), SkIntToScalar(y));
    104     SkPaint paint;
    105     paint.setStyle(SkPaint::kFill_Style);
    106     paint.setAntiAlias(true);
    107     paint.setColor(config.arrow_color);
    108     canvas->DrawPath(path, paint);
    109   }
    110 
    111  private:
    112   // SubmenuView we were created for.
    113   SubmenuView* host_;
    114 
    115   // Direction of the button.
    116   bool is_up_;
    117 
    118   // Preferred height.
    119   int pref_height_;
    120 
    121   DISALLOW_COPY_AND_ASSIGN(MenuScrollButton);
    122 };
    123 
    124 }  // namespace
    125 
    126 // MenuScrollView --------------------------------------------------------------
    127 
    128 // MenuScrollView is a viewport for the SubmenuView. It's reason to exist is so
    129 // that ScrollRectToVisible works.
    130 //
    131 // NOTE: It is possible to use ScrollView directly (after making it deal with
    132 // null scrollbars), but clicking on a child of ScrollView forces the window to
    133 // become active, which we don't want. As we really only need a fraction of
    134 // what ScrollView does, so we use a one off variant.
    135 
    136 class MenuScrollViewContainer::MenuScrollView : public View {
    137  public:
    138   explicit MenuScrollView(View* child) {
    139     AddChildView(child);
    140   }
    141 
    142   virtual void ScrollRectToVisible(const gfx::Rect& rect) OVERRIDE {
    143     // NOTE: this assumes we only want to scroll in the y direction.
    144 
    145     // If the rect is already visible, do not scroll.
    146     if (GetLocalBounds().Contains(rect))
    147       return;
    148 
    149     // Scroll just enough so that the rect is visible.
    150     int dy = 0;
    151     if (rect.bottom() > GetLocalBounds().bottom())
    152       dy = rect.bottom() - GetLocalBounds().bottom();
    153     else
    154       dy = rect.y();
    155 
    156     // Convert rect.y() to view's coordinates and make sure we don't show past
    157     // the bottom of the view.
    158     View* child = GetContents();
    159     child->SetY(-std::max(0, std::min(
    160         child->GetPreferredSize().height() - this->height(),
    161         dy - child->y())));
    162   }
    163 
    164   // Returns the contents, which is the SubmenuView.
    165   View* GetContents() {
    166     return child_at(0);
    167   }
    168 
    169  private:
    170   DISALLOW_COPY_AND_ASSIGN(MenuScrollView);
    171 };
    172 
    173 // MenuScrollViewContainer ----------------------------------------------------
    174 
    175 MenuScrollViewContainer::MenuScrollViewContainer(SubmenuView* content_view)
    176     : content_view_(content_view),
    177       arrow_(BubbleBorder::NONE),
    178       bubble_border_(NULL) {
    179   scroll_up_button_ = new MenuScrollButton(content_view, true);
    180   scroll_down_button_ = new MenuScrollButton(content_view, false);
    181   AddChildView(scroll_up_button_);
    182   AddChildView(scroll_down_button_);
    183 
    184   scroll_view_ = new MenuScrollView(content_view);
    185   AddChildView(scroll_view_);
    186 
    187   arrow_ = BubbleBorderTypeFromAnchor(
    188       content_view_->GetMenuItem()->GetMenuController()->GetAnchorPosition());
    189 
    190   if (arrow_ != BubbleBorder::NONE)
    191     CreateBubbleBorder();
    192   else
    193     CreateDefaultBorder();
    194 }
    195 
    196 bool MenuScrollViewContainer::HasBubbleBorder() {
    197   return arrow_ != BubbleBorder::NONE;
    198 }
    199 
    200 void MenuScrollViewContainer::SetBubbleArrowOffset(int offset) {
    201   DCHECK(HasBubbleBorder());
    202   bubble_border_->set_arrow_offset(offset);
    203 }
    204 
    205 void MenuScrollViewContainer::OnPaintBackground(gfx::Canvas* canvas) {
    206   if (background()) {
    207     View::OnPaintBackground(canvas);
    208     return;
    209   }
    210 
    211   gfx::Rect bounds(0, 0, width(), height());
    212   NativeTheme::ExtraParams extra;
    213   const MenuConfig& menu_config = content_view_->GetMenuItem()->GetMenuConfig();
    214   extra.menu_background.corner_radius = menu_config.corner_radius;
    215   GetNativeTheme()->Paint(canvas->sk_canvas(),
    216       NativeTheme::kMenuPopupBackground, NativeTheme::kNormal, bounds, extra);
    217 }
    218 
    219 void MenuScrollViewContainer::Layout() {
    220   gfx::Insets insets = GetInsets();
    221   int x = insets.left();
    222   int y = insets.top();
    223   int width = View::width() - insets.width();
    224   int content_height = height() - insets.height();
    225   if (!scroll_up_button_->visible()) {
    226     scroll_view_->SetBounds(x, y, width, content_height);
    227     scroll_view_->Layout();
    228     return;
    229   }
    230 
    231   gfx::Size pref = scroll_up_button_->GetPreferredSize();
    232   scroll_up_button_->SetBounds(x, y, width, pref.height());
    233   content_height -= pref.height();
    234 
    235   const int scroll_view_y = y + pref.height();
    236 
    237   pref = scroll_down_button_->GetPreferredSize();
    238   scroll_down_button_->SetBounds(x, height() - pref.height() - insets.top(),
    239                                  width, pref.height());
    240   content_height -= pref.height();
    241 
    242   scroll_view_->SetBounds(x, scroll_view_y, width, content_height);
    243   scroll_view_->Layout();
    244 }
    245 
    246 gfx::Size MenuScrollViewContainer::GetPreferredSize() const {
    247   gfx::Size prefsize = scroll_view_->GetContents()->GetPreferredSize();
    248   gfx::Insets insets = GetInsets();
    249   prefsize.Enlarge(insets.width(), insets.height());
    250   return prefsize;
    251 }
    252 
    253 void MenuScrollViewContainer::GetAccessibleState(
    254     ui::AXViewState* state) {
    255   // Get the name from the submenu view.
    256   content_view_->GetAccessibleState(state);
    257 
    258   // Now change the role.
    259   state->role = ui::AX_ROLE_MENU_BAR;
    260   // Some AT (like NVDA) will not process focus events on menu item children
    261   // unless a parent claims to be focused.
    262   state->AddStateFlag(ui::AX_STATE_FOCUSED);
    263 }
    264 
    265 void MenuScrollViewContainer::OnBoundsChanged(
    266     const gfx::Rect& previous_bounds) {
    267   gfx::Size content_pref = scroll_view_->GetContents()->GetPreferredSize();
    268   scroll_up_button_->SetVisible(content_pref.height() > height());
    269   scroll_down_button_->SetVisible(content_pref.height() > height());
    270   Layout();
    271 }
    272 
    273 void MenuScrollViewContainer::CreateDefaultBorder() {
    274   arrow_ = BubbleBorder::NONE;
    275   bubble_border_ = NULL;
    276 
    277   const MenuConfig& menu_config =
    278       content_view_->GetMenuItem()->GetMenuConfig();
    279 
    280   bool use_border = true;
    281   int padding = menu_config.corner_radius > 0 ?
    282         kBorderPaddingDueToRoundedCorners : 0;
    283 
    284 #if defined(USE_AURA) && !(defined(OS_LINUX) && !defined(OS_CHROMEOS))
    285   if (menu_config.native_theme == ui::NativeThemeAura::instance()) {
    286     // In case of NativeThemeAura the border gets drawn with the shadow.
    287     // Furthermore no additional padding is wanted.
    288     use_border = false;
    289     padding = 0;
    290   }
    291 #endif
    292 
    293   int top = menu_config.menu_vertical_border_size + padding;
    294   int left = menu_config.menu_horizontal_border_size + padding;
    295   int bottom = menu_config.menu_vertical_border_size + padding;
    296   int right = menu_config.menu_horizontal_border_size + padding;
    297 
    298   if (use_border) {
    299     SetBorder(views::Border::CreateBorderPainter(
    300         new views::RoundRectPainter(
    301             menu_config.native_theme->GetSystemColor(
    302                 ui::NativeTheme::kColorId_MenuBorderColor),
    303             menu_config.corner_radius),
    304         gfx::Insets(top, left, bottom, right)));
    305   } else {
    306     SetBorder(Border::CreateEmptyBorder(top, left, bottom, right));
    307   }
    308 }
    309 
    310 void MenuScrollViewContainer::CreateBubbleBorder() {
    311   bubble_border_ = new BubbleBorder(arrow_,
    312                                     BubbleBorder::SMALL_SHADOW,
    313                                     SK_ColorWHITE);
    314   SetBorder(scoped_ptr<Border>(bubble_border_));
    315   set_background(new BubbleBackground(bubble_border_));
    316 }
    317 
    318 BubbleBorder::Arrow MenuScrollViewContainer::BubbleBorderTypeFromAnchor(
    319     MenuAnchorPosition anchor) {
    320   switch (anchor) {
    321     case MENU_ANCHOR_BUBBLE_LEFT:
    322       return BubbleBorder::RIGHT_CENTER;
    323     case MENU_ANCHOR_BUBBLE_RIGHT:
    324       return BubbleBorder::LEFT_CENTER;
    325     case MENU_ANCHOR_BUBBLE_ABOVE:
    326       return BubbleBorder::BOTTOM_CENTER;
    327     case MENU_ANCHOR_BUBBLE_BELOW:
    328       return BubbleBorder::TOP_CENTER;
    329     default:
    330       return BubbleBorder::NONE;
    331   }
    332 }
    333 
    334 }  // namespace views
    335