OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 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 "ui/views/controls/menu/menu_controller.h" | 5 #include "ui/views/controls/menu/menu_controller.h" |
6 | 6 |
7 #include "base/i18n/case_conversion.h" | 7 #include "base/i18n/case_conversion.h" |
8 #include "base/i18n/rtl.h" | 8 #include "base/i18n/rtl.h" |
9 #include "base/macros.h" | 9 #include "base/macros.h" |
10 #include "base/strings/utf_string_conversions.h" | 10 #include "base/strings/utf_string_conversions.h" |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
46 #endif | 46 #endif |
47 | 47 |
48 #if defined(USE_AURA) | 48 #if defined(USE_AURA) |
49 #include "ui/views/controls/menu/menu_key_event_handler.h" | 49 #include "ui/views/controls/menu/menu_key_event_handler.h" |
50 #endif | 50 #endif |
51 | 51 |
52 using base::Time; | 52 using base::Time; |
53 using base::TimeDelta; | 53 using base::TimeDelta; |
54 using ui::OSExchangeData; | 54 using ui::OSExchangeData; |
55 | 55 |
56 // Period of the scroll timer (in milliseconds). | |
57 static const int kScrollTimerMS = 30; | |
58 | |
59 // Amount of time from when the drop exits the menu and the menu is hidden. | |
60 static const int kCloseOnExitTime = 1200; | |
61 | |
62 // If a context menu is invoked by touch, we shift the menu by this offset so | |
63 // that the finger does not obscure the menu. | |
64 static const int kCenteredContextMenuYOffset = -15; | |
65 | |
66 namespace views { | 56 namespace views { |
67 | 57 |
68 namespace { | 58 namespace { |
69 | 59 |
70 // When showing context menu on mouse down, the user might accidentally select | 60 // When showing context menu on mouse down, the user might accidentally select |
71 // the menu item on the subsequent mouse up. To prevent this, we add the | 61 // the menu item on the subsequent mouse up. To prevent this, we add the |
72 // following delay before the user is able to select an item. | 62 // following delay before the user is able to select an item. |
73 static int menu_selection_hold_time_ms = kMinimumMsPressedToActivate; | 63 int menu_selection_hold_time_ms = kMinimumMsPressedToActivate; |
| 64 |
| 65 // Period of the scroll timer (in milliseconds). |
| 66 const int kScrollTimerMS = 30; |
| 67 |
| 68 // Amount of time from when the drop exits the menu and the menu is hidden. |
| 69 const int kCloseOnExitTime = 1200; |
| 70 |
| 71 // If a context menu is invoked by touch, we shift the menu by this offset so |
| 72 // that the finger does not obscure the menu. |
| 73 const int kCenteredContextMenuYOffset = -15; |
74 | 74 |
75 // The spacing offset for the bubble tip. | 75 // The spacing offset for the bubble tip. |
76 const int kBubbleTipSizeLeftRight = 12; | 76 const int kBubbleTipSizeLeftRight = 12; |
77 const int kBubbleTipSizeTopBottom = 11; | 77 const int kBubbleTipSizeTopBottom = 11; |
78 | 78 |
79 // The maximum distance (in DIPS) that the mouse can be moved before it should | 79 // The maximum distance (in DIPS) that the mouse can be moved before it should |
80 // trigger a mouse menu item activation (regardless of how long the menu has | 80 // trigger a mouse menu item activation (regardless of how long the menu has |
81 // been showing). | 81 // been showing). |
82 const float kMaximumLengthMovedToActivate = 4.0f; | 82 const float kMaximumLengthMovedToActivate = 4.0f; |
83 | 83 |
84 // Returns true if the mnemonic of |menu| matches key. | 84 // Returns true if the mnemonic of |menu| matches key. |
85 bool MatchesMnemonic(MenuItemView* menu, base::char16 key) { | 85 bool MatchesMnemonic(MenuItemView* menu, base::char16 key) { |
86 return key != 0 && menu->GetMnemonic() == key; | 86 return key != 0 && menu->GetMnemonic() == key; |
87 } | 87 } |
88 | 88 |
89 // Returns true if |menu| doesn't have a mnemonic and first character of the its | 89 // Returns true if |menu| doesn't have a mnemonic and first character of the its |
90 // title is |key|. | 90 // title is |key|. |
91 bool TitleMatchesMnemonic(MenuItemView* menu, base::char16 key) { | 91 bool TitleMatchesMnemonic(MenuItemView* menu, base::char16 key) { |
92 if (menu->GetMnemonic()) | 92 if (menu->GetMnemonic()) |
93 return false; | 93 return false; |
94 | 94 |
95 base::string16 lower_title = base::i18n::ToLower(menu->title()); | 95 base::string16 lower_title = base::i18n::ToLower(menu->title()); |
96 return !lower_title.empty() && lower_title[0] == key; | 96 return !lower_title.empty() && lower_title[0] == key; |
97 } | 97 } |
98 | 98 |
99 // Returns the first descendant of |view| that is hot tracked. | 99 // Returns the first descendant of |view| that is hot tracked. |
100 static CustomButton* GetFirstHotTrackedView(View* view) { | 100 CustomButton* GetFirstHotTrackedView(View* view) { |
101 if (!view) | 101 if (!view) |
102 return NULL; | 102 return NULL; |
103 CustomButton* button = CustomButton::AsCustomButton(view); | 103 CustomButton* button = CustomButton::AsCustomButton(view); |
104 if (button) { | 104 if (button) { |
105 if (button->IsHotTracked()) | 105 if (button->IsHotTracked()) |
106 return button; | 106 return button; |
107 } | 107 } |
108 | 108 |
109 for (int i = 0; i < view->child_count(); ++i) { | 109 for (int i = 0; i < view->child_count(); ++i) { |
110 CustomButton* hot_view = GetFirstHotTrackedView(view->child_at(i)); | 110 CustomButton* hot_view = GetFirstHotTrackedView(view->child_at(i)); |
111 if (hot_view) | 111 if (hot_view) |
112 return hot_view; | 112 return hot_view; |
113 } | 113 } |
114 return NULL; | 114 return NULL; |
115 } | 115 } |
116 | 116 |
117 // Recurses through the child views of |view| returning the first view starting | 117 // Recurses through the child views of |view| returning the first view starting |
118 // at |start| that is focusable. A value of -1 for |start| indicates to start at | 118 // at |start| that is focusable. A value of -1 for |start| indicates to start at |
119 // the first view (if |forward| is false, iterating starts at the last view). If | 119 // the first view (if |forward| is false, iterating starts at the last view). If |
120 // |forward| is true the children are considered first to last, otherwise last | 120 // |forward| is true the children are considered first to last, otherwise last |
121 // to first. | 121 // to first. |
122 static View* GetFirstFocusableView(View* view, int start, bool forward) { | 122 View* GetFirstFocusableView(View* view, int start, bool forward) { |
123 if (forward) { | 123 if (forward) { |
124 for (int i = start == -1 ? 0 : start; i < view->child_count(); ++i) { | 124 for (int i = start == -1 ? 0 : start; i < view->child_count(); ++i) { |
125 View* deepest = GetFirstFocusableView(view->child_at(i), -1, forward); | 125 View* deepest = GetFirstFocusableView(view->child_at(i), -1, forward); |
126 if (deepest) | 126 if (deepest) |
127 return deepest; | 127 return deepest; |
128 } | 128 } |
129 } else { | 129 } else { |
130 for (int i = start == -1 ? view->child_count() - 1 : start; i >= 0; --i) { | 130 for (int i = start == -1 ? view->child_count() - 1 : start; i >= 0; --i) { |
131 View* deepest = GetFirstFocusableView(view->child_at(i), -1, forward); | 131 View* deepest = GetFirstFocusableView(view->child_at(i), -1, forward); |
132 if (deepest) | 132 if (deepest) |
133 return deepest; | 133 return deepest; |
134 } | 134 } |
135 } | 135 } |
136 return view->IsFocusable() ? view : NULL; | 136 return view->IsFocusable() ? view : NULL; |
137 } | 137 } |
138 | 138 |
139 // Returns the first child of |start| that is focusable. | 139 // Returns the first child of |start| that is focusable. |
140 static View* GetInitialFocusableView(View* start, bool forward) { | 140 View* GetInitialFocusableView(View* start, bool forward) { |
141 return GetFirstFocusableView(start, -1, forward); | 141 return GetFirstFocusableView(start, -1, forward); |
142 } | 142 } |
143 | 143 |
144 // Returns the next view after |start_at| that is focusable. Returns NULL if | 144 // Returns the next view after |start_at| that is focusable. Returns NULL if |
145 // there are no focusable children of |ancestor| after |start_at|. | 145 // there are no focusable children of |ancestor| after |start_at|. |
146 static View* GetNextFocusableView(View* ancestor, | 146 View* GetNextFocusableView(View* ancestor, View* start_at, bool forward) { |
147 View* start_at, | |
148 bool forward) { | |
149 DCHECK(ancestor->Contains(start_at)); | 147 DCHECK(ancestor->Contains(start_at)); |
150 View* parent = start_at; | 148 View* parent = start_at; |
151 do { | 149 do { |
152 View* new_parent = parent->parent(); | 150 View* new_parent = parent->parent(); |
153 int index = new_parent->GetIndexOf(parent); | 151 int index = new_parent->GetIndexOf(parent); |
154 index += forward ? 1 : -1; | 152 index += forward ? 1 : -1; |
155 if (forward || index != -1) { | 153 if (forward || index != -1) { |
156 View* next = GetFirstFocusableView(new_parent, index, forward); | 154 View* next = GetFirstFocusableView(new_parent, index, forward); |
157 if (next) | 155 if (next) |
158 return next; | 156 return next; |
(...skipping 551 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
710 ui::MouseEvent event_for_root(event); | 708 ui::MouseEvent event_for_root(event); |
711 ConvertLocatedEventForRootView(source, current_mouse_event_target_, | 709 ConvertLocatedEventForRootView(source, current_mouse_event_target_, |
712 &event_for_root); | 710 &event_for_root); |
713 current_mouse_event_target_->ProcessMouseMoved(event_for_root); | 711 current_mouse_event_target_->ProcessMouseMoved(event_for_root); |
714 return; | 712 return; |
715 } | 713 } |
716 | 714 |
717 MenuHostRootView* root_view = GetRootView(source, event.location()); | 715 MenuHostRootView* root_view = GetRootView(source, event.location()); |
718 if (root_view) | 716 if (root_view) |
719 root_view->ProcessMouseMoved(event); | 717 root_view->ProcessMouseMoved(event); |
| 718 // TODO(varkha): It is possible that another child CustomButton has become |
| 719 // hot-tracked as a result of this event. We need to track it for accurate |
| 720 // hot-tracking when both mouse and keyboard are used to navigate the menu. |
720 HandleMouseLocation(source, event.location()); | 721 HandleMouseLocation(source, event.location()); |
721 } | 722 } |
722 | 723 |
723 void MenuController::OnMouseEntered(SubmenuView* source, | 724 void MenuController::OnMouseEntered(SubmenuView* source, |
724 const ui::MouseEvent& event) { | 725 const ui::MouseEvent& event) { |
725 // MouseEntered is always followed by a mouse moved, so don't need to | 726 // MouseEntered is always followed by a mouse moved, so don't need to |
726 // do anything here. | 727 // do anything here. |
727 } | 728 } |
728 | 729 |
729 bool MenuController::OnMouseWheel(SubmenuView* source, | 730 bool MenuController::OnMouseWheel(SubmenuView* source, |
(...skipping 1347 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2077 void MenuController::IncrementSelection( | 2078 void MenuController::IncrementSelection( |
2078 SelectionIncrementDirectionType direction) { | 2079 SelectionIncrementDirectionType direction) { |
2079 MenuItemView* item = pending_state_.item; | 2080 MenuItemView* item = pending_state_.item; |
2080 DCHECK(item); | 2081 DCHECK(item); |
2081 if (pending_state_.submenu_open && item->HasSubmenu() && | 2082 if (pending_state_.submenu_open && item->HasSubmenu() && |
2082 item->GetSubmenu()->IsShowing()) { | 2083 item->GetSubmenu()->IsShowing()) { |
2083 // A menu is selected and open, but none of its children are selected, | 2084 // A menu is selected and open, but none of its children are selected, |
2084 // select the first menu item that is visible and enabled. | 2085 // select the first menu item that is visible and enabled. |
2085 if (item->GetSubmenu()->GetMenuItemCount()) { | 2086 if (item->GetSubmenu()->GetMenuItemCount()) { |
2086 MenuItemView* to_select = FindInitialSelectableMenuItem(item, direction); | 2087 MenuItemView* to_select = FindInitialSelectableMenuItem(item, direction); |
2087 if (to_select) | 2088 SetInitialHotTrackedView(to_select, direction); |
2088 SetSelection(to_select, SELECTION_DEFAULT); | |
2089 return; | 2089 return; |
2090 } | 2090 } |
2091 } | 2091 } |
2092 | 2092 |
2093 if (item->has_children()) { | 2093 if (item->has_children()) { |
2094 CustomButton* button = GetFirstHotTrackedView(item); | 2094 CustomButton* button = GetFirstHotTrackedView(item); |
2095 if (button) { | 2095 if (button) { |
2096 button->SetHotTracked(false); | 2096 button->SetHotTracked(false); |
2097 View* to_make_hot = GetNextFocusableView( | 2097 View* to_make_hot = GetNextFocusableView( |
2098 item, button, direction == INCREMENT_SELECTION_DOWN); | 2098 item, button, direction == INCREMENT_SELECTION_DOWN); |
(...skipping 14 matching lines...) Expand all Loading... |
2113 } | 2113 } |
2114 | 2114 |
2115 MenuItemView* parent = item->GetParentMenuItem(); | 2115 MenuItemView* parent = item->GetParentMenuItem(); |
2116 if (parent) { | 2116 if (parent) { |
2117 int parent_count = parent->GetSubmenu()->GetMenuItemCount(); | 2117 int parent_count = parent->GetSubmenu()->GetMenuItemCount(); |
2118 if (parent_count > 1) { | 2118 if (parent_count > 1) { |
2119 for (int i = 0; i < parent_count; ++i) { | 2119 for (int i = 0; i < parent_count; ++i) { |
2120 if (parent->GetSubmenu()->GetMenuItemAt(i) == item) { | 2120 if (parent->GetSubmenu()->GetMenuItemAt(i) == item) { |
2121 MenuItemView* to_select = | 2121 MenuItemView* to_select = |
2122 FindNextSelectableMenuItem(parent, i, direction); | 2122 FindNextSelectableMenuItem(parent, i, direction); |
2123 if (!to_select) | 2123 SetInitialHotTrackedView(to_select, direction); |
2124 break; | |
2125 SetSelection(to_select, SELECTION_DEFAULT); | |
2126 View* to_make_hot = GetInitialFocusableView( | |
2127 to_select, direction == INCREMENT_SELECTION_DOWN); | |
2128 CustomButton* button_hot = CustomButton::AsCustomButton(to_make_hot); | |
2129 if (button_hot) | |
2130 button_hot->SetHotTracked(true); | |
2131 break; | 2124 break; |
2132 } | 2125 } |
2133 } | 2126 } |
2134 } | 2127 } |
2135 } | 2128 } |
2136 } | 2129 } |
2137 | 2130 |
2138 MenuItemView* MenuController::FindInitialSelectableMenuItem( | 2131 MenuItemView* MenuController::FindInitialSelectableMenuItem( |
2139 MenuItemView* parent, | 2132 MenuItemView* parent, |
2140 SelectionIncrementDirectionType direction) { | 2133 SelectionIncrementDirectionType direction) { |
(...skipping 461 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2602 pending_state_.item->GetParentMenuItem() && | 2595 pending_state_.item->GetParentMenuItem() && |
2603 (!pending_state_.item->HasSubmenu() || | 2596 (!pending_state_.item->HasSubmenu() || |
2604 !pending_state_.item->GetSubmenu()->IsShowing())) { | 2597 !pending_state_.item->GetSubmenu()->IsShowing())) { |
2605 // On exit if the user hasn't selected an item with a submenu, move the | 2598 // On exit if the user hasn't selected an item with a submenu, move the |
2606 // selection back to the parent menu item. | 2599 // selection back to the parent menu item. |
2607 SetSelection(pending_state_.item->GetParentMenuItem(), | 2600 SetSelection(pending_state_.item->GetParentMenuItem(), |
2608 SELECTION_OPEN_SUBMENU); | 2601 SELECTION_OPEN_SUBMENU); |
2609 } | 2602 } |
2610 } | 2603 } |
2611 | 2604 |
| 2605 void MenuController::SetInitialHotTrackedView( |
| 2606 MenuItemView* item, |
| 2607 SelectionIncrementDirectionType direction) { |
| 2608 if (!item) |
| 2609 return; |
| 2610 SetSelection(item, SELECTION_DEFAULT); |
| 2611 View* hot_view = |
| 2612 GetInitialFocusableView(item, direction == INCREMENT_SELECTION_DOWN); |
| 2613 CustomButton* hot_button = CustomButton::AsCustomButton(hot_view); |
| 2614 if (hot_button) |
| 2615 hot_button->SetHotTracked(true); |
| 2616 } |
| 2617 |
2612 } // namespace views | 2618 } // namespace views |
OLD | NEW |