Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / ui / views / controls / menu / menu_scroll_view_container.cc
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   gfx::Size GetPreferredSize() const override {
44     return gfx::Size(
45         host_->GetMenuItem()->GetMenuConfig().scroll_arrow_height * 2 - 1,
46         pref_height_);
47   }
48
49   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   void OnDragEntered(const ui::DropTargetEvent& event) override {
55     DCHECK(host_->GetMenuItem()->GetMenuController());
56     host_->GetMenuItem()->GetMenuController()->OnDragEnteredScrollButton(
57         host_, is_up_);
58   }
59
60   int OnDragUpdated(const ui::DropTargetEvent& event) override {
61     return ui::DragDropTypes::DRAG_NONE;
62   }
63
64   void OnDragExited() override {
65     DCHECK(host_->GetMenuItem()->GetMenuController());
66     host_->GetMenuItem()->GetMenuController()->OnDragExitedScrollButton(host_);
67   }
68
69   int OnPerformDrop(const ui::DropTargetEvent& event) override {
70     return ui::DragDropTypes::DRAG_NONE;
71   }
72
73   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   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