OLD | NEW |
---|---|
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 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/l10n_util.h" | 7 #include "app/l10n_util.h" |
8 #include "app/os_exchange_data.h" | 8 #include "app/os_exchange_data.h" |
9 #include "base/i18n/rtl.h" | 9 #include "base/i18n/rtl.h" |
10 #include "base/keyboard_codes.h" | 10 #include "base/keyboard_codes.h" |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
44 // Amount to inset submenus. | 44 // Amount to inset submenus. |
45 static const int kSubmenuHorizontalInset = 3; | 45 static const int kSubmenuHorizontalInset = 3; |
46 | 46 |
47 namespace views { | 47 namespace views { |
48 | 48 |
49 // Convenience for scrolling the view such that the origin is visible. | 49 // Convenience for scrolling the view such that the origin is visible. |
50 static void ScrollToVisible(View* view) { | 50 static void ScrollToVisible(View* view) { |
51 view->ScrollRectToVisible(gfx::Rect(gfx::Point(), view->size())); | 51 view->ScrollRectToVisible(gfx::Rect(gfx::Point(), view->size())); |
52 } | 52 } |
53 | 53 |
54 // Returns the first descendant of |view| that is hot tracked. | |
55 static View* GetFirstHotTrackedView(View* view) { | |
56 if (!view) | |
57 return NULL; | |
58 | |
59 if (view->IsHotTracked()) | |
60 return view; | |
61 | |
62 for (int i = 0; i < view->GetChildViewCount(); ++i) { | |
63 View* hot_view = GetFirstHotTrackedView(view->GetChildViewAt(i)); | |
64 if (hot_view) | |
65 return hot_view; | |
66 } | |
67 return NULL; | |
68 } | |
69 | |
70 // Recurses through the child views of |view| returning the first view starting | |
71 // at |start| that is focusable. A value of -1 for |start| indicates to start at | |
72 // the first view (if |forward| is false, iterating starts at the last view). If | |
73 // |forward| is true the children are considered first to last, otherwise last | |
74 // to first. | |
75 static View* GetFirstFocusableView(View* view, int start, bool forward) { | |
76 if (forward) { | |
77 for (int i = start == -1 ? 0 : start; i < view->GetChildViewCount(); ++i) { | |
78 View* deepest = GetFirstFocusableView(view->GetChildViewAt(i), -1, | |
79 forward); | |
Jay Civelli
2010/06/09 16:03:47
Nit: you could use true instead of forward.
| |
80 if (deepest) | |
81 return deepest; | |
82 } | |
83 } else { | |
84 for (int i = start == -1 ? view->GetChildViewCount() - 1 : start; | |
85 i >= 0; --i) { | |
86 View* deepest = GetFirstFocusableView(view->GetChildViewAt(i), -1, | |
87 forward); | |
Jay Civelli
2010/06/09 16:03:47
Nit: you could use false instead of forward.
| |
88 if (deepest) | |
89 return deepest; | |
90 } | |
91 } | |
92 return view->IsFocusable() ? view : NULL; | |
93 } | |
94 | |
95 // Returns the first child of |start| that is focusable. | |
96 static View* GetInitialFocusableView(View* start, bool forward) { | |
97 return GetFirstFocusableView(start, -1, forward); | |
98 } | |
99 | |
100 // Returns the next view after |start_at| that is focusable. Returns NULL if | |
101 // there are no focusable children of |ancestor| after |start_at|. | |
102 static View* GetNextFocusableView(View* ancestor, | |
103 View* start_at, | |
104 bool forward) { | |
105 DCHECK(ancestor->IsParentOf(start_at)); | |
106 View* parent = start_at; | |
107 do { | |
108 View* new_parent = parent->GetParent(); | |
109 int index = new_parent->GetChildIndex(parent); | |
110 index += forward ? 1 : -1; | |
111 if (forward || index != -1) { | |
112 View* next = GetFirstFocusableView(new_parent, index, forward); | |
113 if (next) | |
114 return next; | |
115 } | |
116 parent = new_parent; | |
117 } while (parent != ancestor); | |
118 return NULL; | |
119 } | |
120 | |
54 // MenuScrollTask -------------------------------------------------------------- | 121 // MenuScrollTask -------------------------------------------------------------- |
55 | 122 |
56 // MenuScrollTask is used when the SubmenuView does not all fit on screen and | 123 // MenuScrollTask is used when the SubmenuView does not all fit on screen and |
57 // the mouse is over the scroll up/down buttons. MenuScrollTask schedules | 124 // the mouse is over the scroll up/down buttons. MenuScrollTask schedules |
58 // itself with a RepeatingTimer. When Run is invoked MenuScrollTask scrolls | 125 // itself with a RepeatingTimer. When Run is invoked MenuScrollTask scrolls |
59 // appropriately. | 126 // appropriately. |
60 | 127 |
61 class MenuController::MenuScrollTask { | 128 class MenuController::MenuScrollTask { |
62 public: | 129 public: |
63 MenuScrollTask() : submenu_(NULL) { | 130 MenuScrollTask() : submenu_(NULL) { |
(...skipping 204 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
268 bool update_immediately) { | 335 bool update_immediately) { |
269 size_t paths_differ_at = 0; | 336 size_t paths_differ_at = 0; |
270 std::vector<MenuItemView*> current_path; | 337 std::vector<MenuItemView*> current_path; |
271 std::vector<MenuItemView*> new_path; | 338 std::vector<MenuItemView*> new_path; |
272 BuildPathsAndCalculateDiff(pending_state_.item, menu_item, ¤t_path, | 339 BuildPathsAndCalculateDiff(pending_state_.item, menu_item, ¤t_path, |
273 &new_path, &paths_differ_at); | 340 &new_path, &paths_differ_at); |
274 | 341 |
275 size_t current_size = current_path.size(); | 342 size_t current_size = current_path.size(); |
276 size_t new_size = new_path.size(); | 343 size_t new_size = new_path.size(); |
277 | 344 |
345 if (pending_state_.item != menu_item && pending_state_.item) { | |
346 View* current_hot_view = GetFirstHotTrackedView(pending_state_.item); | |
347 if (current_hot_view) | |
348 current_hot_view->SetHotTracked(false); | |
349 } | |
350 | |
278 // Notify the old path it isn't selected. | 351 // Notify the old path it isn't selected. |
279 for (size_t i = paths_differ_at; i < current_size; ++i) | 352 for (size_t i = paths_differ_at; i < current_size; ++i) |
280 current_path[i]->SetSelected(false); | 353 current_path[i]->SetSelected(false); |
281 | 354 |
282 // Notify the new path it is selected. | 355 // Notify the new path it is selected. |
283 for (size_t i = paths_differ_at; i < new_size; ++i) | 356 for (size_t i = paths_differ_at; i < new_size; ++i) |
284 new_path[i]->SetSelected(true); | 357 new_path[i]->SetSelected(true); |
285 | 358 |
286 if (menu_item && menu_item->GetDelegate()) | 359 if (menu_item && menu_item->GetDelegate()) |
287 menu_item->GetDelegate()->SelectionChanged(menu_item); | 360 menu_item->GetDelegate()->SelectionChanged(menu_item); |
(...skipping 470 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
758 OpenSubmenuChangeSelectionIfCan(); | 831 OpenSubmenuChangeSelectionIfCan(); |
759 break; | 832 break; |
760 | 833 |
761 case base::VKEY_LEFT: | 834 case base::VKEY_LEFT: |
762 if (base::i18n::IsRTL()) | 835 if (base::i18n::IsRTL()) |
763 OpenSubmenuChangeSelectionIfCan(); | 836 OpenSubmenuChangeSelectionIfCan(); |
764 else | 837 else |
765 CloseSubmenu(); | 838 CloseSubmenu(); |
766 break; | 839 break; |
767 | 840 |
841 case base::VKEY_SPACE: | |
842 SendAcceleratorToHotTrackedView(); | |
843 break; | |
844 | |
768 case base::VKEY_RETURN: | 845 case base::VKEY_RETURN: |
769 if (pending_state_.item) { | 846 if (pending_state_.item) { |
770 if (pending_state_.item->HasSubmenu()) { | 847 if (pending_state_.item->HasSubmenu()) { |
771 OpenSubmenuChangeSelectionIfCan(); | 848 OpenSubmenuChangeSelectionIfCan(); |
772 } else if (pending_state_.item->IsEnabled()) { | 849 } else if (!SendAcceleratorToHotTrackedView() && |
850 pending_state_.item->IsEnabled()) { | |
773 Accept(pending_state_.item, 0); | 851 Accept(pending_state_.item, 0); |
774 return false; | 852 return false; |
775 } | 853 } |
776 } | 854 } |
777 break; | 855 break; |
778 | 856 |
779 case base::VKEY_ESCAPE: | 857 case base::VKEY_ESCAPE: |
780 if (!state_.item->GetParentMenuItem() || | 858 if (!state_.item->GetParentMenuItem() || |
781 (!state_.item->GetParentMenuItem()->GetParentMenuItem() && | 859 (!state_.item->GetParentMenuItem()->GetParentMenuItem() && |
782 (!state_.item->HasSubmenu() || | 860 (!state_.item->HasSubmenu() || |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
826 MenuController::~MenuController() { | 904 MenuController::~MenuController() { |
827 DCHECK(!showing_); | 905 DCHECK(!showing_); |
828 StopShowTimer(); | 906 StopShowTimer(); |
829 StopCancelAllTimer(); | 907 StopCancelAllTimer(); |
830 #ifdef DEBUG_MENU | 908 #ifdef DEBUG_MENU |
831 instance_count--; | 909 instance_count--; |
832 DLOG(INFO) << "destroyed MC, count=" << instance_count; | 910 DLOG(INFO) << "destroyed MC, count=" << instance_count; |
833 #endif | 911 #endif |
834 } | 912 } |
835 | 913 |
914 bool MenuController::SendAcceleratorToHotTrackedView() { | |
915 View* hot_view = GetFirstHotTrackedView(pending_state_.item); | |
916 if (!hot_view) | |
917 return false; | |
918 | |
919 Accelerator accelerator(base::VKEY_RETURN, false, false, false); | |
920 hot_view->AcceleratorPressed(accelerator); | |
921 hot_view->SetHotTracked(true); | |
922 return true; | |
923 } | |
924 | |
836 void MenuController::UpdateInitialLocation( | 925 void MenuController::UpdateInitialLocation( |
837 const gfx::Rect& bounds, | 926 const gfx::Rect& bounds, |
838 MenuItemView::AnchorPosition position) { | 927 MenuItemView::AnchorPosition position) { |
839 pending_state_.initial_bounds = bounds; | 928 pending_state_.initial_bounds = bounds; |
840 if (bounds.height() > 1) { | 929 if (bounds.height() > 1) { |
841 // Inset the bounds slightly, otherwise drag coordinates don't line up | 930 // Inset the bounds slightly, otherwise drag coordinates don't line up |
842 // nicely and menus close prematurely. | 931 // nicely and menus close prematurely. |
843 pending_state_.initial_bounds.Inset(0, 1); | 932 pending_state_.initial_bounds.Inset(0, 1); |
844 } | 933 } |
845 pending_state_.anchor = position; | 934 pending_state_.anchor = position; |
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
928 CloseMenu(item); | 1017 CloseMenu(item); |
929 last_item = item; | 1018 last_item = item; |
930 item = item->GetParentMenuItem(); | 1019 item = item->GetParentMenuItem(); |
931 } | 1020 } |
932 i->submenu_open = false; | 1021 i->submenu_open = false; |
933 i->item = last_item; | 1022 i->item = last_item; |
934 } | 1023 } |
935 } | 1024 } |
936 | 1025 |
937 MenuItemView* MenuController::GetMenuItemAt(View* source, int x, int y) { | 1026 MenuItemView* MenuController::GetMenuItemAt(View* source, int x, int y) { |
1027 // Walk the view hierarchy until we find a menu item (or the root). | |
938 View* child_under_mouse = source->GetViewForPoint(gfx::Point(x, y)); | 1028 View* child_under_mouse = source->GetViewForPoint(gfx::Point(x, y)); |
1029 while (child_under_mouse && | |
1030 child_under_mouse->GetID() != MenuItemView::kMenuItemViewID) { | |
1031 child_under_mouse = child_under_mouse->GetParent(); | |
1032 } | |
939 if (child_under_mouse && child_under_mouse->IsEnabled() && | 1033 if (child_under_mouse && child_under_mouse->IsEnabled() && |
940 child_under_mouse->GetID() == MenuItemView::kMenuItemViewID) { | 1034 child_under_mouse->GetID() == MenuItemView::kMenuItemViewID) { |
941 return static_cast<MenuItemView*>(child_under_mouse); | 1035 return static_cast<MenuItemView*>(child_under_mouse); |
942 } | 1036 } |
943 return NULL; | 1037 return NULL; |
944 } | 1038 } |
945 | 1039 |
946 MenuItemView* MenuController::GetEmptyMenuItemAt(View* source, int x, int y) { | 1040 MenuItemView* MenuController::GetEmptyMenuItemAt(View* source, int x, int y) { |
947 View* child_under_mouse = source->GetViewForPoint(gfx::Point(x, y)); | 1041 View* child_under_mouse = source->GetViewForPoint(gfx::Point(x, y)); |
948 if (child_under_mouse && | 1042 if (child_under_mouse && |
(...skipping 368 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1317 void MenuController::IncrementSelection(int delta) { | 1411 void MenuController::IncrementSelection(int delta) { |
1318 MenuItemView* item = pending_state_.item; | 1412 MenuItemView* item = pending_state_.item; |
1319 DCHECK(item); | 1413 DCHECK(item); |
1320 if (pending_state_.submenu_open && item->HasSubmenu() && | 1414 if (pending_state_.submenu_open && item->HasSubmenu() && |
1321 item->GetSubmenu()->IsShowing()) { | 1415 item->GetSubmenu()->IsShowing()) { |
1322 // A menu is selected and open, but none of its children are selected, | 1416 // A menu is selected and open, but none of its children are selected, |
1323 // select the first menu item. | 1417 // select the first menu item. |
1324 if (item->GetSubmenu()->GetMenuItemCount()) { | 1418 if (item->GetSubmenu()->GetMenuItemCount()) { |
1325 SetSelection(item->GetSubmenu()->GetMenuItemAt(0), false, false); | 1419 SetSelection(item->GetSubmenu()->GetMenuItemAt(0), false, false); |
1326 ScrollToVisible(item->GetSubmenu()->GetMenuItemAt(0)); | 1420 ScrollToVisible(item->GetSubmenu()->GetMenuItemAt(0)); |
1327 return; // return so else case can fall through. | 1421 return; |
1328 } | 1422 } |
1329 } | 1423 } |
1424 | |
1425 if (item->GetChildViewCount()) { | |
1426 View* hot_view = GetFirstHotTrackedView(item); | |
1427 if (hot_view) { | |
1428 hot_view->SetHotTracked(false); | |
1429 View* to_make_hot = GetNextFocusableView(item, hot_view, delta == 1); | |
1430 if (to_make_hot) { | |
1431 to_make_hot->SetHotTracked(true); | |
1432 return; | |
1433 } | |
1434 } else { | |
1435 View* to_make_hot = GetInitialFocusableView(item, delta == 1); | |
1436 if (to_make_hot) { | |
1437 to_make_hot->SetHotTracked(true); | |
1438 return; | |
1439 } | |
1440 } | |
1441 } | |
1442 | |
1330 if (item->GetParentMenuItem()) { | 1443 if (item->GetParentMenuItem()) { |
1331 MenuItemView* parent = item->GetParentMenuItem(); | 1444 MenuItemView* parent = item->GetParentMenuItem(); |
1332 int parent_count = parent->GetSubmenu()->GetMenuItemCount(); | 1445 int parent_count = parent->GetSubmenu()->GetMenuItemCount(); |
1333 if (parent_count > 1) { | 1446 if (parent_count > 1) { |
1334 for (int i = 0; i < parent_count; ++i) { | 1447 for (int i = 0; i < parent_count; ++i) { |
1335 if (parent->GetSubmenu()->GetMenuItemAt(i) == item) { | 1448 if (parent->GetSubmenu()->GetMenuItemAt(i) == item) { |
1336 int next_index = (i + delta + parent_count) % parent_count; | 1449 int next_index = (i + delta + parent_count) % parent_count; |
1337 ScrollToVisible(parent->GetSubmenu()->GetMenuItemAt(next_index)); | 1450 ScrollToVisible(parent->GetSubmenu()->GetMenuItemAt(next_index)); |
1338 SetSelection(parent->GetSubmenu()->GetMenuItemAt(next_index), false, | 1451 MenuItemView* to_select = |
1339 false); | 1452 parent->GetSubmenu()->GetMenuItemAt(next_index); |
1453 SetSelection(to_select, false, false); | |
1454 View* to_make_hot = GetInitialFocusableView(to_select, delta == 1); | |
1455 if (to_make_hot) | |
1456 to_make_hot->SetHotTracked(true); | |
1340 break; | 1457 break; |
1341 } | 1458 } |
1342 } | 1459 } |
1343 } | 1460 } |
1344 } | 1461 } |
1345 } | 1462 } |
1346 | 1463 |
1347 void MenuController::OpenSubmenuChangeSelectionIfCan() { | 1464 void MenuController::OpenSubmenuChangeSelectionIfCan() { |
1348 MenuItemView* item = pending_state_.item; | 1465 MenuItemView* item = pending_state_.item; |
1349 if (item->HasSubmenu()) { | 1466 if (item->HasSubmenu()) { |
(...skipping 168 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1518 if (!scroll_task_.get()) | 1635 if (!scroll_task_.get()) |
1519 scroll_task_.reset(new MenuScrollTask()); | 1636 scroll_task_.reset(new MenuScrollTask()); |
1520 scroll_task_->Update(part); | 1637 scroll_task_->Update(part); |
1521 } | 1638 } |
1522 | 1639 |
1523 void MenuController::StopScrolling() { | 1640 void MenuController::StopScrolling() { |
1524 scroll_task_.reset(NULL); | 1641 scroll_task_.reset(NULL); |
1525 } | 1642 } |
1526 | 1643 |
1527 } // namespace views | 1644 } // namespace views |
OLD | NEW |