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