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