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