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