OLD | NEW |
1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "views/controls/menu/menu_controller.h" | 5 #include "views/controls/menu/menu_controller.h" |
6 | 6 |
7 #include "app/gfx/canvas.h" | 7 #include "app/gfx/canvas.h" |
8 #include "app/l10n_util.h" | 8 #include "app/l10n_util.h" |
9 #include "app/os_exchange_data.h" | 9 #include "app/os_exchange_data.h" |
10 #include "base/keyboard_codes.h" | 10 #include "base/keyboard_codes.h" |
11 #include "base/time.h" | 11 #include "base/time.h" |
| 12 #include "views/controls/button/menu_button.h" |
12 #include "views/controls/menu/menu_scroll_view_container.h" | 13 #include "views/controls/menu/menu_scroll_view_container.h" |
13 #include "views/controls/menu/submenu_view.h" | 14 #include "views/controls/menu/submenu_view.h" |
14 #include "views/drag_utils.h" | 15 #include "views/drag_utils.h" |
15 #include "views/screen.h" | 16 #include "views/screen.h" |
16 #include "views/view_constants.h" | 17 #include "views/view_constants.h" |
17 #include "views/widget/root_view.h" | 18 #include "views/widget/root_view.h" |
18 #include "views/widget/widget.h" | 19 #include "views/widget/widget.h" |
19 | 20 |
20 using base::Time; | 21 using base::Time; |
21 using base::TimeDelta; | 22 using base::TimeDelta; |
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
136 MenuController* MenuController::GetActiveInstance() { | 137 MenuController* MenuController::GetActiveInstance() { |
137 return active_instance_; | 138 return active_instance_; |
138 } | 139 } |
139 | 140 |
140 #ifdef DEBUG_MENU | 141 #ifdef DEBUG_MENU |
141 static int instance_count = 0; | 142 static int instance_count = 0; |
142 static int nested_depth = 0; | 143 static int nested_depth = 0; |
143 #endif | 144 #endif |
144 | 145 |
145 MenuItemView* MenuController::Run(gfx::NativeWindow parent, | 146 MenuItemView* MenuController::Run(gfx::NativeWindow parent, |
| 147 MenuButton* button, |
146 MenuItemView* root, | 148 MenuItemView* root, |
147 const gfx::Rect& bounds, | 149 const gfx::Rect& bounds, |
148 MenuItemView::AnchorPosition position, | 150 MenuItemView::AnchorPosition position, |
149 int* result_mouse_event_flags) { | 151 int* result_mouse_event_flags) { |
150 exit_all_ = false; | 152 exit_all_ = false; |
151 possible_drag_ = false; | 153 possible_drag_ = false; |
152 | 154 |
153 bool nested_menu = showing_; | 155 bool nested_menu = showing_; |
154 if (showing_) { | 156 if (showing_) { |
155 // Only support nesting of blocking_run menus, nesting of | 157 // Only support nesting of blocking_run menus, nesting of |
156 // blocking/non-blocking shouldn't be needed. | 158 // blocking/non-blocking shouldn't be needed. |
157 DCHECK(blocking_run_); | 159 DCHECK(blocking_run_); |
158 | 160 |
159 // We're already showing, push the current state. | 161 // We're already showing, push the current state. |
160 menu_stack_.push_back(state_); | 162 menu_stack_.push_back(state_); |
161 | 163 |
162 // The context menu should be owned by the same parent. | 164 // The context menu should be owned by the same parent. |
163 DCHECK(owner_ == parent); | 165 DCHECK(owner_ == parent); |
164 } else { | 166 } else { |
165 showing_ = true; | 167 showing_ = true; |
166 } | 168 } |
167 | 169 |
168 // Reset current state. | 170 // Reset current state. |
169 pending_state_ = State(); | 171 pending_state_ = State(); |
170 state_ = State(); | 172 state_ = State(); |
171 pending_state_.initial_bounds = bounds; | 173 UpdateInitialLocation(bounds, position); |
172 if (bounds.height() > 1) { | 174 |
173 // Inset the bounds slightly, otherwise drag coordinates don't line up | |
174 // nicely and menus close prematurely. | |
175 pending_state_.initial_bounds.Inset(0, 1); | |
176 } | |
177 pending_state_.anchor = position; | |
178 owner_ = parent; | 175 owner_ = parent; |
179 | 176 |
180 // Calculate the bounds of the monitor we'll show menus on. Do this once to | |
181 // avoid repeated system queries for the info. | |
182 pending_state_.monitor_bounds = Screen::GetMonitorAreaNearestPoint( | |
183 bounds.origin()); | |
184 | |
185 // Set the selection, which opens the initial menu. | 177 // Set the selection, which opens the initial menu. |
186 SetSelection(root, true, true); | 178 SetSelection(root, true, true); |
187 | 179 |
188 if (!blocking_run_) { | 180 if (!blocking_run_) { |
189 // Start the timer to hide the menu. This is needed as we get no | 181 // Start the timer to hide the menu. This is needed as we get no |
190 // notification when the drag has finished. | 182 // notification when the drag has finished. |
191 StartCancelAllTimer(); | 183 StartCancelAllTimer(); |
192 return NULL; | 184 return NULL; |
| 185 } else if (button) { |
| 186 menu_button_ = button; |
193 } | 187 } |
194 | 188 |
195 #ifdef DEBUG_MENU | 189 #ifdef DEBUG_MENU |
196 nested_depth++; | 190 nested_depth++; |
197 DLOG(INFO) << " entering nested loop, depth=" << nested_depth; | 191 DLOG(INFO) << " entering nested loop, depth=" << nested_depth; |
198 #endif | 192 #endif |
199 | 193 |
200 MessageLoopForUI* loop = MessageLoopForUI::current(); | 194 MessageLoopForUI* loop = MessageLoopForUI::current(); |
201 if (MenuItemView::allow_task_nesting_during_run_) { | 195 if (MenuItemView::allow_task_nesting_during_run_) { |
202 bool did_allow_task_nesting = loop->NestableTasksAllowed(); | 196 bool did_allow_task_nesting = loop->NestableTasksAllowed(); |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
238 // We're nested and about to return a value. The caller might enter another | 232 // We're nested and about to return a value. The caller might enter another |
239 // blocking loop. We need to make sure all menus are hidden before that | 233 // blocking loop. We need to make sure all menus are hidden before that |
240 // happens otherwise the menus will stay on screen. | 234 // happens otherwise the menus will stay on screen. |
241 CloseAllNestedMenus(); | 235 CloseAllNestedMenus(); |
242 | 236 |
243 // Set exit_all_ to true, which makes sure all nested loops exit | 237 // Set exit_all_ to true, which makes sure all nested loops exit |
244 // immediately. | 238 // immediately. |
245 exit_all_ = true; | 239 exit_all_ = true; |
246 } | 240 } |
247 | 241 |
| 242 if (menu_button_) { |
| 243 menu_button_->SetState(CustomButton::BS_NORMAL); |
| 244 menu_button_->SchedulePaint(); |
| 245 } |
| 246 |
248 return result; | 247 return result; |
249 } | 248 } |
250 | 249 |
251 void MenuController::SetSelection(MenuItemView* menu_item, | 250 void MenuController::SetSelection(MenuItemView* menu_item, |
252 bool open_submenu, | 251 bool open_submenu, |
253 bool update_immediately) { | 252 bool update_immediately) { |
254 size_t paths_differ_at = 0; | 253 size_t paths_differ_at = 0; |
255 std::vector<MenuItemView*> current_path; | 254 std::vector<MenuItemView*> current_path; |
256 std::vector<MenuItemView*> new_path; | 255 std::vector<MenuItemView*> new_path; |
257 BuildPathsAndCalculateDiff(pending_state_.item, menu_item, ¤t_path, | 256 BuildPathsAndCalculateDiff(pending_state_.item, menu_item, ¤t_path, |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
309 } | 308 } |
310 | 309 |
311 void MenuController::OnMousePressed(SubmenuView* source, | 310 void MenuController::OnMousePressed(SubmenuView* source, |
312 const MouseEvent& event) { | 311 const MouseEvent& event) { |
313 #ifdef DEBUG_MENU | 312 #ifdef DEBUG_MENU |
314 DLOG(INFO) << "OnMousePressed source=" << source; | 313 DLOG(INFO) << "OnMousePressed source=" << source; |
315 #endif | 314 #endif |
316 if (!blocking_run_) | 315 if (!blocking_run_) |
317 return; | 316 return; |
318 | 317 |
319 MenuPart part = | 318 MenuPart part = GetMenuPartByScreenCoordinate(source, event.x(), event.y()); |
320 GetMenuPartByScreenCoordinate(source, event.x(), event.y()); | |
321 if (part.is_scroll()) | 319 if (part.is_scroll()) |
322 return; // Ignore presses on scroll buttons. | 320 return; // Ignore presses on scroll buttons. |
323 | 321 |
324 if (part.type == MenuPart::NONE || | 322 if (part.type == MenuPart::NONE || |
325 (part.type == MenuPart::MENU_ITEM && part.menu && | 323 (part.type == MenuPart::MENU_ITEM && part.menu && |
326 part.menu->GetRootMenuItem() != state_.item->GetRootMenuItem())) { | 324 part.menu->GetRootMenuItem() != state_.item->GetRootMenuItem())) { |
327 // Mouse wasn't pressed over any menu, or the active menu, cancel. | 325 // Mouse wasn't pressed over any menu, or the active menu, cancel. |
328 | 326 |
329 // We're going to close and we own the mouse capture. We need to repost the | 327 // We're going to close and we own the mouse capture. We need to repost the |
330 // mouse down, otherwise the window the user clicked on won't get the | 328 // mouse down, otherwise the window the user clicked on won't get the |
(...skipping 24 matching lines...) Expand all Loading... |
355 // On a press we immediately commit the selection, that way a submenu | 353 // On a press we immediately commit the selection, that way a submenu |
356 // pops up immediately rather than after a delay. | 354 // pops up immediately rather than after a delay. |
357 SetSelection(part.menu, open_submenu, true); | 355 SetSelection(part.menu, open_submenu, true); |
358 } | 356 } |
359 | 357 |
360 void MenuController::OnMouseDragged(SubmenuView* source, | 358 void MenuController::OnMouseDragged(SubmenuView* source, |
361 const MouseEvent& event) { | 359 const MouseEvent& event) { |
362 #ifdef DEBUG_MENU | 360 #ifdef DEBUG_MENU |
363 DLOG(INFO) << "OnMouseDragged source=" << source; | 361 DLOG(INFO) << "OnMouseDragged source=" << source; |
364 #endif | 362 #endif |
365 MenuPart part = | 363 MenuPart part = GetMenuPartByScreenCoordinate(source, event.x(), event.y()); |
366 GetMenuPartByScreenCoordinate(source, event.x(), event.y()); | |
367 UpdateScrolling(part); | 364 UpdateScrolling(part); |
368 | 365 |
369 if (!blocking_run_) | 366 if (!blocking_run_) |
370 return; | 367 return; |
371 | 368 |
372 if (possible_drag_) { | 369 if (possible_drag_) { |
373 if (View::ExceededDragThreshold(event.x() - press_x_, | 370 if (View::ExceededDragThreshold(event.x() - press_x_, |
374 event.y() - press_y_)) { | 371 event.y() - press_y_)) { |
375 MenuItemView* item = state_.item; | 372 MenuItemView* item = state_.item; |
376 DCHECK(item); | 373 DCHECK(item); |
(...skipping 25 matching lines...) Expand all Loading... |
402 Cancel(true); | 399 Cancel(true); |
403 } // else case, drop was on us. | 400 } // else case, drop was on us. |
404 } // else case, someone canceled us, don't do anything | 401 } // else case, someone canceled us, don't do anything |
405 } | 402 } |
406 return; | 403 return; |
407 } | 404 } |
408 if (part.type == MenuPart::MENU_ITEM) { | 405 if (part.type == MenuPart::MENU_ITEM) { |
409 if (!part.menu) | 406 if (!part.menu) |
410 part.menu = source->GetMenuItem(); | 407 part.menu = source->GetMenuItem(); |
411 SetSelection(part.menu ? part.menu : state_.item, true, false); | 408 SetSelection(part.menu ? part.menu : state_.item, true, false); |
| 409 } else if (part.type == MenuPart::NONE) { |
| 410 ShowSiblingMenu(source, event); |
412 } | 411 } |
413 } | 412 } |
414 | 413 |
415 void MenuController::OnMouseReleased(SubmenuView* source, | 414 void MenuController::OnMouseReleased(SubmenuView* source, |
416 const MouseEvent& event) { | 415 const MouseEvent& event) { |
417 #ifdef DEBUG_MENU | 416 #ifdef DEBUG_MENU |
418 DLOG(INFO) << "OnMouseReleased source=" << source; | 417 DLOG(INFO) << "OnMouseReleased source=" << source; |
419 #endif | 418 #endif |
420 if (!blocking_run_) | 419 if (!blocking_run_) |
421 return; | 420 return; |
422 | 421 |
423 DCHECK(state_.item); | 422 DCHECK(state_.item); |
424 possible_drag_ = false; | 423 possible_drag_ = false; |
425 DCHECK(blocking_run_); | 424 DCHECK(blocking_run_); |
426 MenuPart part = | 425 MenuPart part = GetMenuPartByScreenCoordinate(source, event.x(), event.y()); |
427 GetMenuPartByScreenCoordinate(source, event.x(), event.y()); | |
428 if (event.IsRightMouseButton() && (part.type == MenuPart::MENU_ITEM && | 426 if (event.IsRightMouseButton() && (part.type == MenuPart::MENU_ITEM && |
429 part.menu)) { | 427 part.menu)) { |
430 // Set the selection immediately, making sure the submenu is only open | 428 // Set the selection immediately, making sure the submenu is only open |
431 // if it already was. | 429 // if it already was. |
432 bool open_submenu = (state_.item == pending_state_.item && | 430 bool open_submenu = (state_.item == pending_state_.item && |
433 state_.submenu_open); | 431 state_.submenu_open); |
434 SetSelection(pending_state_.item, open_submenu, true); | 432 SetSelection(pending_state_.item, open_submenu, true); |
435 gfx::Point loc(event.location()); | 433 gfx::Point loc(event.location()); |
436 View::ConvertPointToScreen(source->GetScrollViewContainer(), &loc); | 434 View::ConvertPointToScreen(source->GetScrollViewContainer(), &loc); |
437 | 435 |
(...skipping 15 matching lines...) Expand all Loading... |
453 } | 451 } |
454 | 452 |
455 void MenuController::OnMouseMoved(SubmenuView* source, | 453 void MenuController::OnMouseMoved(SubmenuView* source, |
456 const MouseEvent& event) { | 454 const MouseEvent& event) { |
457 #ifdef DEBUG_MENU | 455 #ifdef DEBUG_MENU |
458 DLOG(INFO) << "OnMouseMoved source=" << source; | 456 DLOG(INFO) << "OnMouseMoved source=" << source; |
459 #endif | 457 #endif |
460 if (showing_submenu_) | 458 if (showing_submenu_) |
461 return; | 459 return; |
462 | 460 |
463 MenuPart part = | 461 MenuPart part = GetMenuPartByScreenCoordinate(source, event.x(), event.y()); |
464 GetMenuPartByScreenCoordinate(source, event.x(), event.y()); | |
465 | 462 |
466 UpdateScrolling(part); | 463 UpdateScrolling(part); |
467 | 464 |
468 if (!blocking_run_) | 465 if (!blocking_run_) |
469 return; | 466 return; |
470 | 467 |
| 468 if (part.type == MenuPart::NONE && ShowSiblingMenu(source, event)) |
| 469 return; |
| 470 |
471 if (part.type == MenuPart::MENU_ITEM && part.menu) { | 471 if (part.type == MenuPart::MENU_ITEM && part.menu) { |
472 SetSelection(part.menu, true, false); | 472 SetSelection(part.menu, true, false); |
473 } else if (!part.is_scroll() && pending_state_.item && | 473 } else if (!part.is_scroll() && pending_state_.item && |
474 (!pending_state_.item->HasSubmenu() || | 474 (!pending_state_.item->HasSubmenu() || |
475 !pending_state_.item->GetSubmenu()->IsShowing())) { | 475 !pending_state_.item->GetSubmenu()->IsShowing())) { |
476 // On exit if the user hasn't selected an item with a submenu, move the | 476 // On exit if the user hasn't selected an item with a submenu, move the |
477 // selection back to the parent menu item. | 477 // selection back to the parent menu item. |
478 SetSelection(pending_state_.item->GetParentMenuItem(), true, false); | 478 SetSelection(pending_state_.item->GetParentMenuItem(), true, false); |
479 } | 479 } |
480 } | 480 } |
(...skipping 315 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
796 : blocking_run_(blocking), | 796 : blocking_run_(blocking), |
797 showing_(false), | 797 showing_(false), |
798 exit_all_(false), | 798 exit_all_(false), |
799 did_capture_(false), | 799 did_capture_(false), |
800 result_(NULL), | 800 result_(NULL), |
801 result_mouse_event_flags_(0), | 801 result_mouse_event_flags_(0), |
802 drop_target_(NULL), | 802 drop_target_(NULL), |
803 owner_(NULL), | 803 owner_(NULL), |
804 possible_drag_(false), | 804 possible_drag_(false), |
805 valid_drop_coordinates_(false), | 805 valid_drop_coordinates_(false), |
806 showing_submenu_(false) { | 806 showing_submenu_(false), |
| 807 menu_button_(NULL) { |
807 #ifdef DEBUG_MENU | 808 #ifdef DEBUG_MENU |
808 instance_count++; | 809 instance_count++; |
809 DLOG(INFO) << "created MC, count=" << instance_count; | 810 DLOG(INFO) << "created MC, count=" << instance_count; |
810 #endif | 811 #endif |
811 } | 812 } |
812 | 813 |
813 MenuController::~MenuController() { | 814 MenuController::~MenuController() { |
814 DCHECK(!showing_); | 815 DCHECK(!showing_); |
815 StopShowTimer(); | 816 StopShowTimer(); |
816 StopCancelAllTimer(); | 817 StopCancelAllTimer(); |
817 #ifdef DEBUG_MENU | 818 #ifdef DEBUG_MENU |
818 instance_count--; | 819 instance_count--; |
819 DLOG(INFO) << "destroyed MC, count=" << instance_count; | 820 DLOG(INFO) << "destroyed MC, count=" << instance_count; |
820 #endif | 821 #endif |
821 } | 822 } |
822 | 823 |
| 824 void MenuController::UpdateInitialLocation( |
| 825 const gfx::Rect& bounds, |
| 826 MenuItemView::AnchorPosition position) { |
| 827 pending_state_.initial_bounds = bounds; |
| 828 if (bounds.height() > 1) { |
| 829 // Inset the bounds slightly, otherwise drag coordinates don't line up |
| 830 // nicely and menus close prematurely. |
| 831 pending_state_.initial_bounds.Inset(0, 1); |
| 832 } |
| 833 pending_state_.anchor = position; |
| 834 |
| 835 // Calculate the bounds of the monitor we'll show menus on. Do this once to |
| 836 // avoid repeated system queries for the info. |
| 837 pending_state_.monitor_bounds = Screen::GetMonitorAreaNearestPoint( |
| 838 bounds.origin()); |
| 839 } |
| 840 |
823 void MenuController::Accept(MenuItemView* item, int mouse_event_flags) { | 841 void MenuController::Accept(MenuItemView* item, int mouse_event_flags) { |
824 DCHECK(IsBlockingRun()); | 842 DCHECK(IsBlockingRun()); |
825 result_ = item; | 843 result_ = item; |
826 exit_all_ = true; | 844 exit_all_ = true; |
827 result_mouse_event_flags_ = mouse_event_flags; | 845 result_mouse_event_flags_ = mouse_event_flags; |
828 } | 846 } |
829 | 847 |
| 848 bool MenuController::ShowSiblingMenu(SubmenuView* source, const MouseEvent& e) { |
| 849 if (!menu_stack_.empty() || !menu_button_) |
| 850 return false; |
| 851 |
| 852 View* source_view = source->GetScrollViewContainer(); |
| 853 if (e.x() >= 0 && e.x() < source_view->width() && e.y() >= 0 && |
| 854 e.y() < source_view->height()) { |
| 855 // The mouse is over the menu, no need to continue. |
| 856 return false; |
| 857 } |
| 858 |
| 859 gfx::NativeWindow window_under_mouse = Screen::GetWindowAtCursorScreenPoint(); |
| 860 if (window_under_mouse != owner_) |
| 861 return false; |
| 862 |
| 863 // The user moved the mouse outside the menu and over the owning window. See |
| 864 // if there is a sibling menu we should show. |
| 865 gfx::Point screen_point(e.location()); |
| 866 View::ConvertPointToScreen(source_view, &screen_point); |
| 867 MenuItemView::AnchorPosition anchor; |
| 868 bool has_mnemonics; |
| 869 MenuButton* button = NULL; |
| 870 MenuItemView* alt_menu = source->GetMenuItem()->GetDelegate()-> |
| 871 GetSiblingMenu(source->GetMenuItem()->GetRootMenuItem(), |
| 872 screen_point, &anchor, &has_mnemonics, &button); |
| 873 if (!alt_menu || alt_menu == state_.item) |
| 874 return false; |
| 875 |
| 876 if (!button) { |
| 877 // If the delegate returns a menu, they must also return a button. |
| 878 NOTREACHED(); |
| 879 return false; |
| 880 } |
| 881 |
| 882 // There is a sibling menu, update the button state, hide the current menu |
| 883 // and show the new one. |
| 884 menu_button_->SetState(CustomButton::BS_NORMAL); |
| 885 menu_button_->SchedulePaint(); |
| 886 menu_button_ = button; |
| 887 menu_button_->SetState(CustomButton::BS_PUSHED); |
| 888 menu_button_->SchedulePaint(); |
| 889 |
| 890 // Need to reset capture when we show the menu again, otherwise we aren't |
| 891 // going to get any events. |
| 892 did_capture_ = false; |
| 893 gfx::Point screen_menu_loc; |
| 894 View::ConvertPointToScreen(button, &screen_menu_loc); |
| 895 // Subtract 1 from the height to make the popup flush with the button border. |
| 896 UpdateInitialLocation(gfx::Rect(screen_menu_loc.x(), screen_menu_loc.y(), |
| 897 button->width(), button->height() - 1), |
| 898 anchor); |
| 899 alt_menu->PrepareForRun(has_mnemonics); |
| 900 alt_menu->controller_ = this; |
| 901 SetSelection(alt_menu, true, true); |
| 902 return true; |
| 903 } |
| 904 |
830 void MenuController::CloseAllNestedMenus() { | 905 void MenuController::CloseAllNestedMenus() { |
831 for (std::list<State>::iterator i = menu_stack_.begin(); | 906 for (std::list<State>::iterator i = menu_stack_.begin(); |
832 i != menu_stack_.end(); ++i) { | 907 i != menu_stack_.end(); ++i) { |
833 MenuItemView* item = i->item; | 908 MenuItemView* item = i->item; |
834 MenuItemView* last_item = item; | 909 MenuItemView* last_item = item; |
835 while (item) { | 910 while (item) { |
836 CloseMenu(item); | 911 CloseMenu(item); |
837 last_item = item; | 912 last_item = item; |
838 item = item->GetParentMenuItem(); | 913 item = item->GetParentMenuItem(); |
839 } | 914 } |
(...skipping 564 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1404 if (!scroll_task_.get()) | 1479 if (!scroll_task_.get()) |
1405 scroll_task_.reset(new MenuScrollTask()); | 1480 scroll_task_.reset(new MenuScrollTask()); |
1406 scroll_task_->Update(part); | 1481 scroll_task_->Update(part); |
1407 } | 1482 } |
1408 | 1483 |
1409 void MenuController::StopScrolling() { | 1484 void MenuController::StopScrolling() { |
1410 scroll_task_.reset(NULL); | 1485 scroll_task_.reset(NULL); |
1411 } | 1486 } |
1412 | 1487 |
1413 } // namespace views | 1488 } // namespace views |
OLD | NEW |