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