Don't show supervised user as "already on this device" while they're being imported.
[chromium-blink-merge.git] / ui / views / controls / menu / submenu_view.cc
blobb147aa6c958ef81d7cf5cb73c6fb78b9ee546031
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.
5 #include "ui/views/controls/menu/submenu_view.h"
7 #include <algorithm>
9 #include "base/compiler_specific.h"
10 #include "ui/accessibility/ax_view_state.h"
11 #include "ui/compositor/paint_recorder.h"
12 #include "ui/events/event.h"
13 #include "ui/gfx/canvas.h"
14 #include "ui/gfx/geometry/safe_integer_conversions.h"
15 #include "ui/views/controls/menu/menu_config.h"
16 #include "ui/views/controls/menu/menu_controller.h"
17 #include "ui/views/controls/menu/menu_host.h"
18 #include "ui/views/controls/menu/menu_item_view.h"
19 #include "ui/views/controls/menu/menu_scroll_view_container.h"
20 #include "ui/views/widget/root_view.h"
21 #include "ui/views/widget/widget.h"
23 namespace {
25 // Height of the drop indicator. This should be an even number.
26 const int kDropIndicatorHeight = 2;
28 // Color of the drop indicator.
29 const SkColor kDropIndicatorColor = SK_ColorBLACK;
31 } // namespace
33 namespace views {
35 // static
36 const char SubmenuView::kViewClassName[] = "SubmenuView";
38 SubmenuView::SubmenuView(MenuItemView* parent)
39 : parent_menu_item_(parent),
40 host_(NULL),
41 drop_item_(NULL),
42 drop_position_(MenuDelegate::DROP_NONE),
43 scroll_view_container_(NULL),
44 max_minor_text_width_(0),
45 minimum_preferred_width_(0),
46 resize_open_menu_(false),
47 scroll_animator_(new ScrollAnimator(this)),
48 roundoff_error_(0),
49 prefix_selector_(this) {
50 DCHECK(parent);
51 // We'll delete ourselves, otherwise the ScrollView would delete us on close.
52 set_owned_by_client();
55 SubmenuView::~SubmenuView() {
56 // The menu may not have been closed yet (it will be hidden, but not
57 // necessarily closed).
58 Close();
60 delete scroll_view_container_;
63 int SubmenuView::GetMenuItemCount() {
64 int count = 0;
65 for (int i = 0; i < child_count(); ++i) {
66 if (child_at(i)->id() == MenuItemView::kMenuItemViewID)
67 count++;
69 return count;
72 MenuItemView* SubmenuView::GetMenuItemAt(int index) {
73 for (int i = 0, count = 0; i < child_count(); ++i) {
74 if (child_at(i)->id() == MenuItemView::kMenuItemViewID &&
75 count++ == index) {
76 return static_cast<MenuItemView*>(child_at(i));
79 NOTREACHED();
80 return NULL;
83 void SubmenuView::ChildPreferredSizeChanged(View* child) {
84 if (!resize_open_menu_)
85 return;
87 MenuItemView *item = GetMenuItem();
88 MenuController* controller = item->GetMenuController();
90 if (controller) {
91 bool dir;
92 gfx::Rect bounds = controller->CalculateMenuBounds(item, false, &dir);
93 Reposition(bounds);
97 void SubmenuView::Layout() {
98 // We're in a ScrollView, and need to set our width/height ourselves.
99 if (!parent())
100 return;
102 // Use our current y, unless it means part of the menu isn't visible anymore.
103 int pref_height = GetPreferredSize().height();
104 int new_y;
105 if (pref_height > parent()->height())
106 new_y = std::max(parent()->height() - pref_height, y());
107 else
108 new_y = 0;
109 SetBounds(x(), new_y, parent()->width(), pref_height);
111 gfx::Insets insets = GetInsets();
112 int x = insets.left();
113 int y = insets.top();
114 int menu_item_width = width() - insets.width();
115 for (int i = 0; i < child_count(); ++i) {
116 View* child = child_at(i);
117 if (child->visible()) {
118 int child_height = child->GetHeightForWidth(menu_item_width);
119 child->SetBounds(x, y, menu_item_width, child_height);
120 y += child_height;
125 gfx::Size SubmenuView::GetPreferredSize() const {
126 if (!has_children())
127 return gfx::Size();
129 max_minor_text_width_ = 0;
130 // The maximum width of items which contain maybe a label and multiple views.
131 int max_complex_width = 0;
132 // The max. width of items which contain a label and maybe an accelerator.
133 int max_simple_width = 0;
135 // We perform the size calculation in two passes. In the first pass, we
136 // calculate the width of the menu. In the second, we calculate the height
137 // using that width. This allows views that have flexible widths to adjust
138 // accordingly.
139 for (int i = 0; i < child_count(); ++i) {
140 const View* child = child_at(i);
141 if (!child->visible())
142 continue;
143 if (child->id() == MenuItemView::kMenuItemViewID) {
144 const MenuItemView* menu = static_cast<const MenuItemView*>(child);
145 const MenuItemView::MenuItemDimensions& dimensions =
146 menu->GetDimensions();
147 max_simple_width = std::max(
148 max_simple_width, dimensions.standard_width);
149 max_minor_text_width_ =
150 std::max(max_minor_text_width_, dimensions.minor_text_width);
151 max_complex_width = std::max(max_complex_width,
152 dimensions.standard_width + dimensions.children_width);
153 } else {
154 max_complex_width = std::max(max_complex_width,
155 child->GetPreferredSize().width());
158 if (max_minor_text_width_ > 0) {
159 max_minor_text_width_ +=
160 GetMenuItem()->GetMenuConfig().label_to_minor_text_padding;
162 // Finish calculating our optimum width.
163 gfx::Insets insets = GetInsets();
164 int width = std::max(max_complex_width,
165 std::max(max_simple_width + max_minor_text_width_ +
166 insets.width(),
167 minimum_preferred_width_ - 2 * insets.width()));
169 // Then, the height for that width.
170 int height = 0;
171 int menu_item_width = width - insets.width();
172 for (int i = 0; i < child_count(); ++i) {
173 const View* child = child_at(i);
174 height += child->visible() ? child->GetHeightForWidth(menu_item_width) : 0;
177 return gfx::Size(width, height + insets.height());
180 void SubmenuView::GetAccessibleState(ui::AXViewState* state) {
181 // Inherit most of the state from the parent menu item, except the role.
182 if (GetMenuItem())
183 GetMenuItem()->GetAccessibleState(state);
184 state->role = ui::AX_ROLE_MENU_LIST_POPUP;
187 ui::TextInputClient* SubmenuView::GetTextInputClient() {
188 return &prefix_selector_;
191 void SubmenuView::PaintChildren(const ui::PaintContext& context) {
192 View::PaintChildren(context);
194 bool paint_drop_indicator = false;
195 if (drop_item_) {
196 switch (drop_position_) {
197 case MenuDelegate::DROP_NONE:
198 case MenuDelegate::DROP_ON:
199 break;
200 case MenuDelegate::DROP_UNKNOWN:
201 case MenuDelegate::DROP_BEFORE:
202 case MenuDelegate::DROP_AFTER:
203 paint_drop_indicator = true;
204 break;
208 if (paint_drop_indicator) {
209 gfx::Rect bounds = CalculateDropIndicatorBounds(drop_item_, drop_position_);
210 ui::PaintRecorder recorder(context);
211 recorder.canvas()->FillRect(bounds, kDropIndicatorColor);
215 bool SubmenuView::GetDropFormats(
216 int* formats,
217 std::set<OSExchangeData::CustomFormat>* custom_formats) {
218 DCHECK(GetMenuItem()->GetMenuController());
219 return GetMenuItem()->GetMenuController()->GetDropFormats(this, formats,
220 custom_formats);
223 bool SubmenuView::AreDropTypesRequired() {
224 DCHECK(GetMenuItem()->GetMenuController());
225 return GetMenuItem()->GetMenuController()->AreDropTypesRequired(this);
228 bool SubmenuView::CanDrop(const OSExchangeData& data) {
229 DCHECK(GetMenuItem()->GetMenuController());
230 return GetMenuItem()->GetMenuController()->CanDrop(this, data);
233 void SubmenuView::OnDragEntered(const ui::DropTargetEvent& event) {
234 DCHECK(GetMenuItem()->GetMenuController());
235 GetMenuItem()->GetMenuController()->OnDragEntered(this, event);
238 int SubmenuView::OnDragUpdated(const ui::DropTargetEvent& event) {
239 DCHECK(GetMenuItem()->GetMenuController());
240 return GetMenuItem()->GetMenuController()->OnDragUpdated(this, event);
243 void SubmenuView::OnDragExited() {
244 DCHECK(GetMenuItem()->GetMenuController());
245 GetMenuItem()->GetMenuController()->OnDragExited(this);
248 int SubmenuView::OnPerformDrop(const ui::DropTargetEvent& event) {
249 DCHECK(GetMenuItem()->GetMenuController());
250 return GetMenuItem()->GetMenuController()->OnPerformDrop(this, event);
253 bool SubmenuView::OnMouseWheel(const ui::MouseWheelEvent& e) {
254 gfx::Rect vis_bounds = GetVisibleBounds();
255 int menu_item_count = GetMenuItemCount();
256 if (vis_bounds.height() == height() || !menu_item_count) {
257 // All menu items are visible, nothing to scroll.
258 return true;
261 // Find the index of the first menu item whose y-coordinate is >= visible
262 // y-coordinate.
263 int i = 0;
264 while ((i < menu_item_count) && (GetMenuItemAt(i)->y() < vis_bounds.y()))
265 ++i;
266 if (i == menu_item_count)
267 return true;
268 int first_vis_index = std::max(0,
269 (GetMenuItemAt(i)->y() == vis_bounds.y()) ? i : i - 1);
271 // If the first item isn't entirely visible, make it visible, otherwise make
272 // the next/previous one entirely visible. If enough wasn't scrolled to show
273 // any new rows, then just scroll the amount so that smooth scrolling using
274 // the trackpad is possible.
275 int delta = abs(e.y_offset() / ui::MouseWheelEvent::kWheelDelta);
276 if (delta == 0)
277 return OnScroll(0, e.y_offset());
278 for (bool scroll_up = (e.y_offset() > 0); delta != 0; --delta) {
279 int scroll_target;
280 if (scroll_up) {
281 if (GetMenuItemAt(first_vis_index)->y() == vis_bounds.y()) {
282 if (first_vis_index == 0)
283 break;
284 first_vis_index--;
286 scroll_target = GetMenuItemAt(first_vis_index)->y();
287 } else {
288 if (first_vis_index + 1 == menu_item_count)
289 break;
290 scroll_target = GetMenuItemAt(first_vis_index + 1)->y();
291 if (GetMenuItemAt(first_vis_index)->y() == vis_bounds.y())
292 first_vis_index++;
294 ScrollRectToVisible(gfx::Rect(gfx::Point(0, scroll_target),
295 vis_bounds.size()));
296 vis_bounds = GetVisibleBounds();
299 return true;
302 void SubmenuView::OnGestureEvent(ui::GestureEvent* event) {
303 bool handled = true;
304 switch (event->type()) {
305 case ui::ET_GESTURE_SCROLL_BEGIN:
306 scroll_animator_->Stop();
307 break;
308 case ui::ET_GESTURE_SCROLL_UPDATE:
309 handled = OnScroll(0, event->details().scroll_y());
310 break;
311 case ui::ET_GESTURE_SCROLL_END:
312 break;
313 case ui::ET_SCROLL_FLING_START:
314 if (event->details().velocity_y() != 0.0f)
315 scroll_animator_->Start(0, event->details().velocity_y());
316 break;
317 case ui::ET_GESTURE_TAP_DOWN:
318 case ui::ET_SCROLL_FLING_CANCEL:
319 if (scroll_animator_->is_scrolling())
320 scroll_animator_->Stop();
321 else
322 handled = false;
323 break;
324 default:
325 handled = false;
326 break;
328 if (handled)
329 event->SetHandled();
332 int SubmenuView::GetRowCount() {
333 return GetMenuItemCount();
336 int SubmenuView::GetSelectedRow() {
337 int row = 0;
338 for (int i = 0; i < child_count(); ++i) {
339 if (child_at(i)->id() != MenuItemView::kMenuItemViewID)
340 continue;
342 if (static_cast<MenuItemView*>(child_at(i))->IsSelected())
343 return row;
345 row++;
348 return -1;
351 void SubmenuView::SetSelectedRow(int row) {
352 GetMenuItem()->GetMenuController()->SetSelection(
353 GetMenuItemAt(row),
354 MenuController::SELECTION_DEFAULT);
357 base::string16 SubmenuView::GetTextForRow(int row) {
358 return GetMenuItemAt(row)->title();
361 bool SubmenuView::IsShowing() {
362 return host_ && host_->IsMenuHostVisible();
365 void SubmenuView::ShowAt(Widget* parent,
366 const gfx::Rect& bounds,
367 bool do_capture) {
368 if (host_) {
369 host_->ShowMenuHost(do_capture);
370 } else {
371 host_ = new MenuHost(this);
372 // Force construction of the scroll view container.
373 GetScrollViewContainer();
374 // Force a layout since our preferred size may not have changed but our
375 // content may have.
376 InvalidateLayout();
377 host_->InitMenuHost(parent, bounds, scroll_view_container_, do_capture);
380 GetScrollViewContainer()->NotifyAccessibilityEvent(
381 ui::AX_EVENT_MENU_START,
382 true);
383 NotifyAccessibilityEvent(
384 ui::AX_EVENT_MENU_POPUP_START,
385 true);
388 void SubmenuView::Reposition(const gfx::Rect& bounds) {
389 if (host_)
390 host_->SetMenuHostBounds(bounds);
393 void SubmenuView::Close() {
394 if (host_) {
395 NotifyAccessibilityEvent(ui::AX_EVENT_MENU_POPUP_END, true);
396 GetScrollViewContainer()->NotifyAccessibilityEvent(
397 ui::AX_EVENT_MENU_END, true);
399 host_->DestroyMenuHost();
400 host_ = NULL;
404 void SubmenuView::Hide() {
405 if (host_)
406 host_->HideMenuHost();
407 if (scroll_animator_->is_scrolling())
408 scroll_animator_->Stop();
411 void SubmenuView::ReleaseCapture() {
412 if (host_)
413 host_->ReleaseMenuHostCapture();
416 bool SubmenuView::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) {
417 return views::FocusManager::IsTabTraversalKeyEvent(e);
420 MenuItemView* SubmenuView::GetMenuItem() const {
421 return parent_menu_item_;
424 void SubmenuView::SetDropMenuItem(MenuItemView* item,
425 MenuDelegate::DropPosition position) {
426 if (drop_item_ == item && drop_position_ == position)
427 return;
428 SchedulePaintForDropIndicator(drop_item_, drop_position_);
429 drop_item_ = item;
430 drop_position_ = position;
431 SchedulePaintForDropIndicator(drop_item_, drop_position_);
434 bool SubmenuView::GetShowSelection(MenuItemView* item) {
435 if (drop_item_ == NULL)
436 return true;
437 // Something is being dropped on one of this menus items. Show the
438 // selection if the drop is on the passed in item and the drop position is
439 // ON.
440 return (drop_item_ == item && drop_position_ == MenuDelegate::DROP_ON);
443 MenuScrollViewContainer* SubmenuView::GetScrollViewContainer() {
444 if (!scroll_view_container_) {
445 scroll_view_container_ = new MenuScrollViewContainer(this);
446 // Otherwise MenuHost would delete us.
447 scroll_view_container_->set_owned_by_client();
449 return scroll_view_container_;
452 void SubmenuView::MenuHostDestroyed() {
453 host_ = NULL;
454 GetMenuItem()->GetMenuController()->Cancel(MenuController::EXIT_DESTROYED);
457 const char* SubmenuView::GetClassName() const {
458 return kViewClassName;
461 void SubmenuView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
462 SchedulePaint();
465 void SubmenuView::SchedulePaintForDropIndicator(
466 MenuItemView* item,
467 MenuDelegate::DropPosition position) {
468 if (item == NULL)
469 return;
471 if (position == MenuDelegate::DROP_ON) {
472 item->SchedulePaint();
473 } else if (position != MenuDelegate::DROP_NONE) {
474 SchedulePaintInRect(CalculateDropIndicatorBounds(item, position));
478 gfx::Rect SubmenuView::CalculateDropIndicatorBounds(
479 MenuItemView* item,
480 MenuDelegate::DropPosition position) {
481 DCHECK(position != MenuDelegate::DROP_NONE);
482 gfx::Rect item_bounds = item->bounds();
483 switch (position) {
484 case MenuDelegate::DROP_BEFORE:
485 item_bounds.Offset(0, -kDropIndicatorHeight / 2);
486 item_bounds.set_height(kDropIndicatorHeight);
487 return item_bounds;
489 case MenuDelegate::DROP_AFTER:
490 item_bounds.Offset(0, item_bounds.height() - kDropIndicatorHeight / 2);
491 item_bounds.set_height(kDropIndicatorHeight);
492 return item_bounds;
494 default:
495 // Don't render anything for on.
496 return gfx::Rect();
500 bool SubmenuView::OnScroll(float dx, float dy) {
501 const gfx::Rect& vis_bounds = GetVisibleBounds();
502 const gfx::Rect& full_bounds = bounds();
503 int x = vis_bounds.x();
504 float y_f = vis_bounds.y() - dy - roundoff_error_;
505 int y = gfx::ToRoundedInt(y_f);
506 roundoff_error_ = y - y_f;
507 // clamp y to [0, full_height - vis_height)
508 y = std::min(y, full_bounds.height() - vis_bounds.height() - 1);
509 y = std::max(y, 0);
510 gfx::Rect new_vis_bounds(x, y, vis_bounds.width(), vis_bounds.height());
511 if (new_vis_bounds != vis_bounds) {
512 ScrollRectToVisible(new_vis_bounds);
513 return true;
515 return false;
518 } // namespace views