Upstream version 5.34.92.0
[platform/framework/web/crosswalk.git] / src / ui / views / controls / menu / submenu_view.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/submenu_view.h"
6
7 #include <algorithm>
8
9 #include "base/compiler_specific.h"
10 #include "ui/base/accessibility/accessible_view_state.h"
11 #include "ui/events/event.h"
12 #include "ui/gfx/canvas.h"
13 #include "ui/gfx/geometry/safe_integer_conversions.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_host.h"
17 #include "ui/views/controls/menu/menu_scroll_view_container.h"
18 #include "ui/views/widget/root_view.h"
19 #include "ui/views/widget/widget.h"
20
21 namespace {
22
23 // Height of the drop indicator. This should be an even number.
24 const int kDropIndicatorHeight = 2;
25
26 // Color of the drop indicator.
27 const SkColor kDropIndicatorColor = SK_ColorBLACK;
28
29 }  // namespace
30
31 namespace views {
32
33 // static
34 const char SubmenuView::kViewClassName[] = "SubmenuView";
35
36 SubmenuView::SubmenuView(MenuItemView* parent)
37     : parent_menu_item_(parent),
38       host_(NULL),
39       drop_item_(NULL),
40       drop_position_(MenuDelegate::DROP_NONE),
41       scroll_view_container_(NULL),
42       max_minor_text_width_(0),
43       minimum_preferred_width_(0),
44       resize_open_menu_(false),
45       scroll_animator_(new ScrollAnimator(this)),
46       roundoff_error_(0) {
47   DCHECK(parent);
48   // We'll delete ourselves, otherwise the ScrollView would delete us on close.
49   set_owned_by_client();
50 }
51
52 SubmenuView::~SubmenuView() {
53   // The menu may not have been closed yet (it will be hidden, but not
54   // necessarily closed).
55   Close();
56
57   delete scroll_view_container_;
58 }
59
60 int SubmenuView::GetMenuItemCount() {
61   int count = 0;
62   for (int i = 0; i < child_count(); ++i) {
63     if (child_at(i)->id() == MenuItemView::kMenuItemViewID)
64       count++;
65   }
66   return count;
67 }
68
69 MenuItemView* SubmenuView::GetMenuItemAt(int index) {
70   for (int i = 0, count = 0; i < child_count(); ++i) {
71     if (child_at(i)->id() == MenuItemView::kMenuItemViewID &&
72         count++ == index) {
73       return static_cast<MenuItemView*>(child_at(i));
74     }
75   }
76   NOTREACHED();
77   return NULL;
78 }
79
80 void SubmenuView::ChildPreferredSizeChanged(View* child) {
81   if (!resize_open_menu_)
82     return;
83
84   MenuItemView *item = GetMenuItem();
85   MenuController* controller = item->GetMenuController();
86
87   if (controller) {
88     bool dir;
89     gfx::Rect bounds = controller->CalculateMenuBounds(item, false, &dir);
90     Reposition(bounds);
91   }
92 }
93
94 void SubmenuView::Layout() {
95   // We're in a ScrollView, and need to set our width/height ourselves.
96   if (!parent())
97     return;
98
99   // Use our current y, unless it means part of the menu isn't visible anymore.
100   int pref_height = GetPreferredSize().height();
101   int new_y;
102   if (pref_height > parent()->height())
103     new_y = std::max(parent()->height() - pref_height, y());
104   else
105     new_y = 0;
106   SetBounds(x(), new_y, parent()->width(), pref_height);
107
108   gfx::Insets insets = GetInsets();
109   int x = insets.left();
110   int y = insets.top();
111   int menu_item_width = width() - insets.width();
112   for (int i = 0; i < child_count(); ++i) {
113     View* child = child_at(i);
114     if (child->visible()) {
115       gfx::Size child_pref_size = child->GetPreferredSize();
116       child->SetBounds(x, y, menu_item_width, child_pref_size.height());
117       y += child_pref_size.height();
118     }
119   }
120 }
121
122 gfx::Size SubmenuView::GetPreferredSize() {
123   if (!has_children())
124     return gfx::Size();
125
126   max_minor_text_width_ = 0;
127   // The maximum width of items which contain maybe a label and multiple views.
128   int max_complex_width = 0;
129   // The max. width of items which contain a label and maybe an accelerator.
130   int max_simple_width = 0;
131   int height = 0;
132   for (int i = 0; i < child_count(); ++i) {
133     View* child = child_at(i);
134     if (!child->visible())
135       continue;
136     if (child->id() == MenuItemView::kMenuItemViewID) {
137       MenuItemView* menu = static_cast<MenuItemView*>(child);
138       const MenuItemView::MenuItemDimensions& dimensions =
139           menu->GetDimensions();
140       max_simple_width = std::max(
141           max_simple_width, dimensions.standard_width);
142       max_minor_text_width_ =
143           std::max(max_minor_text_width_, dimensions.minor_text_width);
144       max_complex_width = std::max(max_complex_width,
145           dimensions.standard_width + dimensions.children_width);
146       height += dimensions.height;
147     } else {
148       gfx::Size child_pref_size =
149           child->visible() ? child->GetPreferredSize() : gfx::Size();
150       max_complex_width = std::max(max_complex_width, child_pref_size.width());
151       height += child_pref_size.height();
152     }
153   }
154   if (max_minor_text_width_ > 0) {
155     max_minor_text_width_ +=
156         GetMenuItem()->GetMenuConfig().label_to_minor_text_padding;
157   }
158   gfx::Insets insets = GetInsets();
159   return gfx::Size(
160       std::max(max_complex_width,
161                std::max(max_simple_width + max_minor_text_width_ +
162                         insets.width(),
163                minimum_preferred_width_ - 2 * insets.width())),
164       height + insets.height());
165 }
166
167 void SubmenuView::GetAccessibleState(ui::AccessibleViewState* state) {
168   // Inherit most of the state from the parent menu item, except the role.
169   if (GetMenuItem())
170     GetMenuItem()->GetAccessibleState(state);
171   state->role = ui::AccessibilityTypes::ROLE_MENUPOPUP;
172 }
173
174 void SubmenuView::PaintChildren(gfx::Canvas* canvas) {
175   View::PaintChildren(canvas);
176
177   if (drop_item_ && drop_position_ != MenuDelegate::DROP_ON)
178     PaintDropIndicator(canvas, drop_item_, drop_position_);
179 }
180
181 bool SubmenuView::GetDropFormats(
182       int* formats,
183       std::set<OSExchangeData::CustomFormat>* custom_formats) {
184   DCHECK(GetMenuItem()->GetMenuController());
185   return GetMenuItem()->GetMenuController()->GetDropFormats(this, formats,
186                                                             custom_formats);
187 }
188
189 bool SubmenuView::AreDropTypesRequired() {
190   DCHECK(GetMenuItem()->GetMenuController());
191   return GetMenuItem()->GetMenuController()->AreDropTypesRequired(this);
192 }
193
194 bool SubmenuView::CanDrop(const OSExchangeData& data) {
195   DCHECK(GetMenuItem()->GetMenuController());
196   return GetMenuItem()->GetMenuController()->CanDrop(this, data);
197 }
198
199 void SubmenuView::OnDragEntered(const ui::DropTargetEvent& event) {
200   DCHECK(GetMenuItem()->GetMenuController());
201   GetMenuItem()->GetMenuController()->OnDragEntered(this, event);
202 }
203
204 int SubmenuView::OnDragUpdated(const ui::DropTargetEvent& event) {
205   DCHECK(GetMenuItem()->GetMenuController());
206   return GetMenuItem()->GetMenuController()->OnDragUpdated(this, event);
207 }
208
209 void SubmenuView::OnDragExited() {
210   DCHECK(GetMenuItem()->GetMenuController());
211   GetMenuItem()->GetMenuController()->OnDragExited(this);
212 }
213
214 int SubmenuView::OnPerformDrop(const ui::DropTargetEvent& event) {
215   DCHECK(GetMenuItem()->GetMenuController());
216   return GetMenuItem()->GetMenuController()->OnPerformDrop(this, event);
217 }
218
219 bool SubmenuView::OnMouseWheel(const ui::MouseWheelEvent& e) {
220   gfx::Rect vis_bounds = GetVisibleBounds();
221   int menu_item_count = GetMenuItemCount();
222   if (vis_bounds.height() == height() || !menu_item_count) {
223     // All menu items are visible, nothing to scroll.
224     return true;
225   }
226
227   // Find the index of the first menu item whose y-coordinate is >= visible
228   // y-coordinate.
229   int i = 0;
230   while ((i < menu_item_count) && (GetMenuItemAt(i)->y() < vis_bounds.y()))
231     ++i;
232   if (i == menu_item_count)
233     return true;
234   int first_vis_index = std::max(0,
235       (GetMenuItemAt(i)->y() == vis_bounds.y()) ? i : i - 1);
236
237   // If the first item isn't entirely visible, make it visible, otherwise make
238   // the next/previous one entirely visible. If enough wasn't scrolled to show
239   // any new rows, then just scroll the amount so that smooth scrolling using
240   // the trackpad is possible.
241   int delta = abs(e.y_offset() / ui::MouseWheelEvent::kWheelDelta);
242   if (delta == 0)
243     return OnScroll(0, e.y_offset());
244   for (bool scroll_up = (e.y_offset() > 0); delta != 0; --delta) {
245     int scroll_target;
246     if (scroll_up) {
247       if (GetMenuItemAt(first_vis_index)->y() == vis_bounds.y()) {
248         if (first_vis_index == 0)
249           break;
250         first_vis_index--;
251       }
252       scroll_target = GetMenuItemAt(first_vis_index)->y();
253     } else {
254       if (first_vis_index + 1 == menu_item_count)
255         break;
256       scroll_target = GetMenuItemAt(first_vis_index + 1)->y();
257       if (GetMenuItemAt(first_vis_index)->y() == vis_bounds.y())
258         first_vis_index++;
259     }
260     ScrollRectToVisible(gfx::Rect(gfx::Point(0, scroll_target),
261                                   vis_bounds.size()));
262     vis_bounds = GetVisibleBounds();
263   }
264
265   return true;
266 }
267
268 void SubmenuView::OnGestureEvent(ui::GestureEvent* event) {
269   bool handled = true;
270   switch (event->type()) {
271     case ui::ET_GESTURE_SCROLL_BEGIN:
272       scroll_animator_->Stop();
273       break;
274     case ui::ET_GESTURE_SCROLL_UPDATE:
275       handled = OnScroll(0, event->details().scroll_y());
276       break;
277     case ui::ET_GESTURE_SCROLL_END:
278       break;
279     case ui::ET_SCROLL_FLING_START:
280       if (event->details().velocity_y() != 0.0f)
281         scroll_animator_->Start(0, event->details().velocity_y());
282       break;
283     case ui::ET_GESTURE_TAP_DOWN:
284     case ui::ET_SCROLL_FLING_CANCEL:
285       if (scroll_animator_->is_scrolling())
286         scroll_animator_->Stop();
287       else
288         handled = false;
289       break;
290     default:
291       handled = false;
292       break;
293   }
294   if (handled)
295     event->SetHandled();
296 }
297
298 bool SubmenuView::IsShowing() {
299   return host_ && host_->IsMenuHostVisible();
300 }
301
302 void SubmenuView::ShowAt(Widget* parent,
303                          const gfx::Rect& bounds,
304                          bool do_capture) {
305   if (host_) {
306     host_->ShowMenuHost(do_capture);
307   } else {
308     host_ = new MenuHost(this);
309     // Force construction of the scroll view container.
310     GetScrollViewContainer();
311     // Force a layout since our preferred size may not have changed but our
312     // content may have.
313     InvalidateLayout();
314     host_->InitMenuHost(parent, bounds, scroll_view_container_, do_capture);
315   }
316
317   GetScrollViewContainer()->NotifyAccessibilityEvent(
318       ui::AccessibilityTypes::EVENT_MENUSTART,
319       true);
320   NotifyAccessibilityEvent(
321       ui::AccessibilityTypes::EVENT_MENUPOPUPSTART,
322       true);
323 }
324
325 void SubmenuView::Reposition(const gfx::Rect& bounds) {
326   if (host_)
327     host_->SetMenuHostBounds(bounds);
328 }
329
330 void SubmenuView::Close() {
331   if (host_) {
332     NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_MENUPOPUPEND, true);
333     GetScrollViewContainer()->NotifyAccessibilityEvent(
334         ui::AccessibilityTypes::EVENT_MENUEND, true);
335
336     host_->DestroyMenuHost();
337     host_ = NULL;
338   }
339 }
340
341 void SubmenuView::Hide() {
342   if (host_)
343     host_->HideMenuHost();
344   if (scroll_animator_->is_scrolling())
345     scroll_animator_->Stop();
346 }
347
348 void SubmenuView::ReleaseCapture() {
349   if (host_)
350     host_->ReleaseMenuHostCapture();
351 }
352
353 bool SubmenuView::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) {
354   return views::FocusManager::IsTabTraversalKeyEvent(e);
355 }
356
357 MenuItemView* SubmenuView::GetMenuItem() const {
358   return parent_menu_item_;
359 }
360
361 void SubmenuView::SetDropMenuItem(MenuItemView* item,
362                                   MenuDelegate::DropPosition position) {
363   if (drop_item_ == item && drop_position_ == position)
364     return;
365   SchedulePaintForDropIndicator(drop_item_, drop_position_);
366   drop_item_ = item;
367   drop_position_ = position;
368   SchedulePaintForDropIndicator(drop_item_, drop_position_);
369 }
370
371 bool SubmenuView::GetShowSelection(MenuItemView* item) {
372   if (drop_item_ == NULL)
373     return true;
374   // Something is being dropped on one of this menus items. Show the
375   // selection if the drop is on the passed in item and the drop position is
376   // ON.
377   return (drop_item_ == item && drop_position_ == MenuDelegate::DROP_ON);
378 }
379
380 MenuScrollViewContainer* SubmenuView::GetScrollViewContainer() {
381   if (!scroll_view_container_) {
382     scroll_view_container_ = new MenuScrollViewContainer(this);
383     // Otherwise MenuHost would delete us.
384     scroll_view_container_->set_owned_by_client();
385   }
386   return scroll_view_container_;
387 }
388
389 void SubmenuView::MenuHostDestroyed() {
390   host_ = NULL;
391   GetMenuItem()->GetMenuController()->Cancel(MenuController::EXIT_DESTROYED);
392 }
393
394 const char* SubmenuView::GetClassName() const {
395   return kViewClassName;
396 }
397
398 void SubmenuView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
399   SchedulePaint();
400 }
401
402 void SubmenuView::PaintDropIndicator(gfx::Canvas* canvas,
403                                      MenuItemView* item,
404                                      MenuDelegate::DropPosition position) {
405   if (position == MenuDelegate::DROP_NONE)
406     return;
407
408   gfx::Rect bounds = CalculateDropIndicatorBounds(item, position);
409   canvas->FillRect(bounds, kDropIndicatorColor);
410 }
411
412 void SubmenuView::SchedulePaintForDropIndicator(
413     MenuItemView* item,
414     MenuDelegate::DropPosition position) {
415   if (item == NULL)
416     return;
417
418   if (position == MenuDelegate::DROP_ON) {
419     item->SchedulePaint();
420   } else if (position != MenuDelegate::DROP_NONE) {
421     SchedulePaintInRect(CalculateDropIndicatorBounds(item, position));
422   }
423 }
424
425 gfx::Rect SubmenuView::CalculateDropIndicatorBounds(
426     MenuItemView* item,
427     MenuDelegate::DropPosition position) {
428   DCHECK(position != MenuDelegate::DROP_NONE);
429   gfx::Rect item_bounds = item->bounds();
430   switch (position) {
431     case MenuDelegate::DROP_BEFORE:
432       item_bounds.Offset(0, -kDropIndicatorHeight / 2);
433       item_bounds.set_height(kDropIndicatorHeight);
434       return item_bounds;
435
436     case MenuDelegate::DROP_AFTER:
437       item_bounds.Offset(0, item_bounds.height() - kDropIndicatorHeight / 2);
438       item_bounds.set_height(kDropIndicatorHeight);
439       return item_bounds;
440
441     default:
442       // Don't render anything for on.
443       return gfx::Rect();
444   }
445 }
446
447 bool SubmenuView::OnScroll(float dx, float dy) {
448   const gfx::Rect& vis_bounds = GetVisibleBounds();
449   const gfx::Rect& full_bounds = bounds();
450   int x = vis_bounds.x();
451   float y_f = vis_bounds.y() - dy - roundoff_error_;
452   int y = gfx::ToRoundedInt(y_f);
453   roundoff_error_ = y - y_f;
454   // clamp y to [0, full_height - vis_height)
455   y = std::min(y, full_bounds.height() - vis_bounds.height() - 1);
456   y = std::max(y, 0);
457   gfx::Rect new_vis_bounds(x, y, vis_bounds.width(), vis_bounds.height());
458   if (new_vis_bounds != vis_bounds) {
459     ScrollRectToVisible(new_vis_bounds);
460     return true;
461   }
462   return false;
463 }
464
465 }  // namespace views