OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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/chromeos/touch_exploration_controller.h" | 5 #include "ui/chromeos/touch_exploration_controller.h" |
6 | 6 |
7 #include <utility> | 7 #include <utility> |
8 | 8 |
9 #include "base/logging.h" | 9 #include "base/logging.h" |
10 #include "base/strings/string_number_conversions.h" | 10 #include "base/strings/string_number_conversions.h" |
11 #include "base/time/default_tick_clock.h" | 11 #include "base/time/default_tick_clock.h" |
12 #include "ui/accessibility/ax_enums.h" | |
12 #include "ui/aura/client/cursor_client.h" | 13 #include "ui/aura/client/cursor_client.h" |
13 #include "ui/aura/window.h" | 14 #include "ui/aura/window.h" |
14 #include "ui/aura/window_event_dispatcher.h" | 15 #include "ui/aura/window_event_dispatcher.h" |
15 #include "ui/aura/window_tree_host.h" | 16 #include "ui/aura/window_tree_host.h" |
16 #include "ui/events/event.h" | 17 #include "ui/events/event.h" |
17 #include "ui/events/event_processor.h" | 18 #include "ui/events/event_processor.h" |
18 #include "ui/events/event_utils.h" | 19 #include "ui/events/event_utils.h" |
19 #include "ui/gfx/geometry/rect.h" | 20 #include "ui/gfx/geometry/rect.h" |
20 | 21 |
21 #define SET_STATE(state) SetState(state, __func__) | 22 #define SET_STATE(state) SetState(state, __func__) |
22 #define VLOG_EVENT(event) if (VLOG_IS_ON(0)) VlogEvent(event, __func__) | 23 #define VLOG_EVENT(event) if (VLOG_IS_ON(0)) VlogEvent(event, __func__) |
23 | 24 |
24 namespace ui { | 25 namespace ui { |
25 | 26 |
26 namespace { | 27 namespace { |
27 | 28 |
28 // Delay between adjustment sounds. | 29 // Delay between adjustment sounds. |
29 const int kSoundDelayInMS = 150; | 30 const int kSoundDelayInMS = 150; |
30 | 31 |
31 // In ChromeOS, VKEY_LWIN is synonymous for the search key. | |
32 const ui::KeyboardCode kChromeOSSearchKey = ui::VKEY_LWIN; | |
33 | |
34 } // namespace | 32 } // namespace |
35 | 33 |
36 TouchExplorationController::TouchExplorationController( | 34 TouchExplorationController::TouchExplorationController( |
37 aura::Window* root_window, | 35 aura::Window* root_window, |
38 TouchExplorationControllerDelegate* delegate) | 36 TouchExplorationControllerDelegate* delegate) |
39 : root_window_(root_window), | 37 : root_window_(root_window), |
40 delegate_(delegate), | 38 delegate_(delegate), |
41 state_(NO_FINGERS_DOWN), | 39 state_(NO_FINGERS_DOWN), |
42 gesture_provider_(new GestureProviderAura(this, this)), | 40 gesture_provider_(new GestureProviderAura(this, this)), |
43 prev_state_(NO_FINGERS_DOWN), | 41 prev_state_(NO_FINGERS_DOWN), |
44 VLOG_on_(true), | 42 VLOG_on_(true), |
45 tick_clock_(NULL) { | 43 tick_clock_(NULL) { |
46 DCHECK(root_window); | 44 DCHECK(root_window); |
47 root_window->GetHost()->GetEventSource()->AddEventRewriter(this); | 45 root_window->GetHost()->GetEventSource()->AddEventRewriter(this); |
48 InitializeSwipeGestureMaps(); | |
49 } | 46 } |
50 | 47 |
51 TouchExplorationController::~TouchExplorationController() { | 48 TouchExplorationController::~TouchExplorationController() { |
52 root_window_->GetHost()->GetEventSource()->RemoveEventRewriter(this); | 49 root_window_->GetHost()->GetEventSource()->RemoveEventRewriter(this); |
53 } | 50 } |
54 | 51 |
55 ui::EventRewriteStatus TouchExplorationController::RewriteEvent( | 52 ui::EventRewriteStatus TouchExplorationController::RewriteEvent( |
56 const ui::Event& event, | 53 const ui::Event& event, |
57 std::unique_ptr<ui::Event>* rewritten_event) { | 54 std::unique_ptr<ui::Event>* rewritten_event) { |
58 if (!event.IsTouchEvent()) { | 55 if (!event.IsTouchEvent()) { |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
114 if (it == current_touch_ids_.end()) | 111 if (it == current_touch_ids_.end()) |
115 return ui::EVENT_REWRITE_CONTINUE; | 112 return ui::EVENT_REWRITE_CONTINUE; |
116 | 113 |
117 touch_locations_[*it] = location; | 114 touch_locations_[*it] = location; |
118 } else { | 115 } else { |
119 NOTREACHED() << "Unexpected event type received: " << event.name(); | 116 NOTREACHED() << "Unexpected event type received: " << event.name(); |
120 return ui::EVENT_REWRITE_CONTINUE; | 117 return ui::EVENT_REWRITE_CONTINUE; |
121 } | 118 } |
122 VLOG_EVENT(touch_event); | 119 VLOG_EVENT(touch_event); |
123 | 120 |
121 /** | |
David Tseng
2016/05/05 22:30:39
nit: remove
dmazzoni
2016/05/06 19:06:09
Done.
| |
124 // In order to avoid accidentally double tapping when moving off the edge | 122 // In order to avoid accidentally double tapping when moving off the edge |
125 // of the screen, the state will be rewritten to NoFingersDown. | 123 // of the screen, the state will be rewritten to NoFingersDown. |
126 if ((type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) && | 124 if ((type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) && |
127 FindEdgesWithinBounds(touch_event.location(), kLeavingScreenEdge) != | 125 FindEdgesWithinBounds(touch_event.location(), kLeavingScreenEdge) != |
128 NO_EDGE) { | 126 NO_EDGE) { |
129 if (VLOG_on_) | 127 if (VLOG_on_) |
130 VLOG(0) << "Leaving screen"; | 128 VLOG(0) << "Leaving screen"; |
131 | 129 |
132 // Indicates to the user that they are leaving the screen. | 130 // Indicates to the user that they are leaving the screen. |
133 delegate_->PlayExitScreenEarcon(); | 131 delegate_->PlayExitScreenEarcon(); |
134 | 132 |
135 if (current_touch_ids_.size() == 0) { | 133 if (current_touch_ids_.size() == 0) { |
136 SET_STATE(NO_FINGERS_DOWN); | 134 SET_STATE(NO_FINGERS_DOWN); |
137 if (VLOG_on_) { | 135 if (VLOG_on_) { |
138 VLOG(0) << "Reset to no fingers in Rewrite event because the touch " | 136 VLOG(0) << "Reset to no fingers in Rewrite event because the touch " |
139 "release or cancel was on the edge of the screen."; | 137 "release or cancel was on the edge of the screen."; |
140 } | 138 } |
141 return ui::EVENT_REWRITE_DISCARD; | 139 return ui::EVENT_REWRITE_DISCARD; |
142 } | 140 } |
143 } | 141 } |
142 **/ | |
David Tseng
2016/05/05 22:30:39
nit: remove?
dmazzoni
2016/05/06 19:06:09
Done.
| |
144 | 143 |
145 // If the user is in a gesture state, or if there is a possiblity that the | 144 // If the user is in a gesture state, or if there is a possiblity that the |
146 // user will enter it in the future, we send the event to the gesture | 145 // user will enter it in the future, we send the event to the gesture |
147 // provider so it can keep track of the state of the fingers. When the user | 146 // provider so it can keep track of the state of the fingers. When the user |
148 // leaves one of these states, SET_STATE will set the gesture provider to | 147 // leaves one of these states, SET_STATE will set the gesture provider to |
149 // NULL. | 148 // NULL. |
150 if (gesture_provider_.get()) { | 149 if (gesture_provider_.get()) { |
151 ui::TouchEvent mutable_touch_event = touch_event; | 150 ui::TouchEvent mutable_touch_event = touch_event; |
152 if (gesture_provider_->OnTouchEvent(&mutable_touch_event)) { | 151 if (gesture_provider_->OnTouchEvent(&mutable_touch_event)) { |
153 gesture_provider_->OnTouchEventAck(mutable_touch_event.unique_event_id(), | 152 gesture_provider_->OnTouchEventAck(mutable_touch_event.unique_event_id(), |
(...skipping 634 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
788 | 787 |
789 // This is an override for a function that is only called for timer-based events | 788 // This is an override for a function that is only called for timer-based events |
790 // like long press. Events that are created synchronously as a result of | 789 // like long press. Events that are created synchronously as a result of |
791 // certain touch events are added to the vector accessible via | 790 // certain touch events are added to the vector accessible via |
792 // GetAndResetPendingGestures(). We only care about swipes (which are created | 791 // GetAndResetPendingGestures(). We only care about swipes (which are created |
793 // synchronously), so we ignore this callback. | 792 // synchronously), so we ignore this callback. |
794 void TouchExplorationController::OnGestureEvent(ui::GestureConsumer* consumer, | 793 void TouchExplorationController::OnGestureEvent(ui::GestureConsumer* consumer, |
795 ui::GestureEvent* gesture) {} | 794 ui::GestureEvent* gesture) {} |
796 | 795 |
797 void TouchExplorationController::ProcessGestureEvents() { | 796 void TouchExplorationController::ProcessGestureEvents() { |
797 LOG(ERROR) << "ProcessGestureEvents"; | |
David Tseng
2016/05/05 22:30:39
nit: remove
dmazzoni
2016/05/06 19:06:09
Done.
| |
798 std::unique_ptr<ScopedVector<ui::GestureEvent>> gestures( | 798 std::unique_ptr<ScopedVector<ui::GestureEvent>> gestures( |
799 gesture_provider_->GetAndResetPendingGestures()); | 799 gesture_provider_->GetAndResetPendingGestures()); |
800 if (gestures) { | 800 if (gestures) { |
801 for (ScopedVector<GestureEvent>::iterator i = gestures->begin(); | 801 for (ScopedVector<GestureEvent>::iterator i = gestures->begin(); |
802 i != gestures->end(); | 802 i != gestures->end(); |
803 ++i) { | 803 ++i) { |
804 if ((*i)->type() == ui::ET_GESTURE_SWIPE && | 804 if ((*i)->type() == ui::ET_GESTURE_SWIPE && |
805 state_ == GESTURE_IN_PROGRESS) { | 805 state_ == GESTURE_IN_PROGRESS) { |
806 OnSwipeEvent(*i); | 806 OnSwipeEvent(*i); |
807 // The tap timer to leave gesture state is ended, and we now wait for | 807 // The tap timer to leave gesture state is ended, and we now wait for |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
866 void TouchExplorationController::OnSwipeEvent(ui::GestureEvent* swipe_gesture) { | 866 void TouchExplorationController::OnSwipeEvent(ui::GestureEvent* swipe_gesture) { |
867 // A swipe gesture contains details for the direction in which the swipe | 867 // A swipe gesture contains details for the direction in which the swipe |
868 // occurred. TODO(evy) : Research which swipe results users most want and | 868 // occurred. TODO(evy) : Research which swipe results users most want and |
869 // remap these swipes to the best events. Hopefully in the near future | 869 // remap these swipes to the best events. Hopefully in the near future |
870 // there will also be a menu for users to pick custom mappings. | 870 // there will also be a menu for users to pick custom mappings. |
871 GestureEventDetails event_details = swipe_gesture->details(); | 871 GestureEventDetails event_details = swipe_gesture->details(); |
872 int num_fingers = event_details.touch_points(); | 872 int num_fingers = event_details.touch_points(); |
873 if (VLOG_on_) | 873 if (VLOG_on_) |
874 VLOG(0) << "\nSwipe with " << num_fingers << " fingers."; | 874 VLOG(0) << "\nSwipe with " << num_fingers << " fingers."; |
875 | 875 |
876 if (num_fingers > 4) | 876 ui::AXGesture gesture = ui::AX_GESTURE_NONE; |
877 return; | 877 if (event_details.swipe_left()) { |
878 switch (num_fingers) { | |
879 case 1: gesture = ui::AX_GESTURE_SWIPE_LEFT_1; break; | |
880 case 2: gesture = ui::AX_GESTURE_SWIPE_LEFT_2; break; | |
881 case 3: gesture = ui::AX_GESTURE_SWIPE_LEFT_3; break; | |
882 case 4: gesture = ui::AX_GESTURE_SWIPE_LEFT_4; break; | |
883 default: break; | |
884 } | |
885 } else if (event_details.swipe_up()) { | |
886 switch (num_fingers) { | |
887 case 1: gesture = ui::AX_GESTURE_SWIPE_UP_1; break; | |
888 case 2: gesture = ui::AX_GESTURE_SWIPE_UP_2; break; | |
889 case 3: gesture = ui::AX_GESTURE_SWIPE_UP_3; break; | |
890 case 4: gesture = ui::AX_GESTURE_SWIPE_UP_4; break; | |
891 default: break; | |
892 } | |
893 } else if (event_details.swipe_right()) { | |
894 switch (num_fingers) { | |
895 case 1: gesture = ui::AX_GESTURE_SWIPE_RIGHT_1; break; | |
896 case 2: gesture = ui::AX_GESTURE_SWIPE_RIGHT_2; break; | |
897 case 3: gesture = ui::AX_GESTURE_SWIPE_RIGHT_3; break; | |
898 case 4: gesture = ui::AX_GESTURE_SWIPE_RIGHT_4; break; | |
899 default: break; | |
900 } | |
901 } else if (event_details.swipe_down()) { | |
902 switch (num_fingers) { | |
903 case 1: gesture = ui::AX_GESTURE_SWIPE_DOWN_1; break; | |
904 case 2: gesture = ui::AX_GESTURE_SWIPE_DOWN_2; break; | |
905 case 3: gesture = ui::AX_GESTURE_SWIPE_DOWN_3; break; | |
906 case 4: gesture = ui::AX_GESTURE_SWIPE_DOWN_4; break; | |
907 default: break; | |
908 } | |
909 } | |
878 | 910 |
879 if (event_details.swipe_left() && | 911 if (gesture != ui::AX_GESTURE_NONE) |
880 !left_swipe_gestures_[num_fingers].is_null()) { | 912 delegate_->HandleAccessibilityGesture(gesture); |
881 left_swipe_gestures_[num_fingers].Run(); | |
882 } else if (event_details.swipe_right() && | |
883 !right_swipe_gestures_[num_fingers].is_null()) { | |
884 right_swipe_gestures_[num_fingers].Run(); | |
885 } else if (event_details.swipe_up() && | |
886 !up_swipe_gestures_[num_fingers].is_null()) { | |
887 up_swipe_gestures_[num_fingers].Run(); | |
888 } else if (event_details.swipe_down() && | |
889 !down_swipe_gestures_[num_fingers].is_null()) { | |
890 down_swipe_gestures_[num_fingers].Run(); | |
891 } | |
892 } | 913 } |
893 | 914 |
894 int TouchExplorationController::FindEdgesWithinBounds(gfx::Point point, | 915 int TouchExplorationController::FindEdgesWithinBounds(gfx::Point point, |
895 float bounds) { | 916 float bounds) { |
896 // Since GetBoundsInScreen is in DIPs but point is not, then point needs to be | 917 // Since GetBoundsInScreen is in DIPs but point is not, then point needs to be |
897 // converted. | 918 // converted. |
898 root_window_->GetHost()->ConvertPointFromNativeScreen(&point); | 919 root_window_->GetHost()->ConvertPointFromNativeScreen(&point); |
899 gfx::Rect window = root_window_->GetBoundsInScreen(); | 920 gfx::Rect window = root_window_->GetBoundsInScreen(); |
900 | 921 |
901 float left_edge_limit = window.x() + bounds; | 922 float left_edge_limit = window.x() + bounds; |
(...skipping 10 matching lines...) Expand all Loading... | |
912 result |= LEFT_EDGE; | 933 result |= LEFT_EDGE; |
913 if (point.x() > right_edge_limit) | 934 if (point.x() > right_edge_limit) |
914 result |= RIGHT_EDGE; | 935 result |= RIGHT_EDGE; |
915 if (point.y() < top_edge_limit) | 936 if (point.y() < top_edge_limit) |
916 result |= TOP_EDGE; | 937 result |= TOP_EDGE; |
917 if (point.y() > bottom_edge_limit) | 938 if (point.y() > bottom_edge_limit) |
918 result |= BOTTOM_EDGE; | 939 result |= BOTTOM_EDGE; |
919 return result; | 940 return result; |
920 } | 941 } |
921 | 942 |
922 void TouchExplorationController::DispatchShiftSearchKeyEvent( | |
923 const ui::KeyboardCode third_key) { | |
924 // In order to activate the shortcut shift+search+<arrow key> | |
925 // three KeyPressed events must be dispatched in succession along | |
926 // with three KeyReleased events. | |
927 | |
928 ui::KeyEvent shift_down( | |
929 ui::ET_KEY_PRESSED, ui::VKEY_SHIFT, ui::EF_SHIFT_DOWN); | |
930 ui::KeyEvent search_down( | |
931 ui::ET_KEY_PRESSED, kChromeOSSearchKey, ui::EF_SHIFT_DOWN); | |
932 ui::KeyEvent third_key_down(ui::ET_KEY_PRESSED, third_key, ui::EF_SHIFT_DOWN); | |
933 | |
934 ui::KeyEvent third_key_up(ui::ET_KEY_RELEASED, third_key, ui::EF_SHIFT_DOWN); | |
935 ui::KeyEvent search_up( | |
936 ui::ET_KEY_RELEASED, kChromeOSSearchKey, ui::EF_SHIFT_DOWN); | |
937 ui ::KeyEvent shift_up(ui::ET_KEY_RELEASED, ui::VKEY_SHIFT, ui::EF_NONE); | |
938 | |
939 DispatchEvent(&shift_down); | |
940 DispatchEvent(&search_down); | |
941 DispatchEvent(&third_key_down); | |
942 DispatchEvent(&third_key_up); | |
943 DispatchEvent(&search_up); | |
944 DispatchEvent(&shift_up); | |
945 } | |
946 | |
947 base::Closure TouchExplorationController::BindShiftSearchKeyEvent( | |
948 const ui::KeyboardCode third_key) { | |
949 return base::Bind(&TouchExplorationController::DispatchShiftSearchKeyEvent, | |
950 base::Unretained(this), | |
951 third_key); | |
952 } | |
953 | |
954 void TouchExplorationController::DispatchKeyWithFlags( | 943 void TouchExplorationController::DispatchKeyWithFlags( |
955 const ui::KeyboardCode key, | 944 const ui::KeyboardCode key, |
956 int flags) { | 945 int flags) { |
957 ui::KeyEvent key_down(ui::ET_KEY_PRESSED, key, flags); | 946 ui::KeyEvent key_down(ui::ET_KEY_PRESSED, key, flags); |
958 ui::KeyEvent key_up(ui::ET_KEY_RELEASED, key, flags); | 947 ui::KeyEvent key_up(ui::ET_KEY_RELEASED, key, flags); |
959 DispatchEvent(&key_down); | 948 DispatchEvent(&key_down); |
960 DispatchEvent(&key_up); | 949 DispatchEvent(&key_up); |
961 if (VLOG_on_) { | 950 if (VLOG_on_) { |
962 VLOG(0) << "\nKey down: key code : " << key_down.key_code() | 951 VLOG(0) << "\nKey down: key code : " << key_down.key_code() |
963 << ", flags: " << key_down.flags() | 952 << ", flags: " << key_down.flags() |
(...skipping 151 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1115 case ONE_FINGER_PASSTHROUGH: | 1104 case ONE_FINGER_PASSTHROUGH: |
1116 return "ONE_FINGER_PASSTHROUGH"; | 1105 return "ONE_FINGER_PASSTHROUGH"; |
1117 case WAIT_FOR_NO_FINGERS: | 1106 case WAIT_FOR_NO_FINGERS: |
1118 return "WAIT_FOR_NO_FINGERS"; | 1107 return "WAIT_FOR_NO_FINGERS"; |
1119 case TWO_FINGER_TAP: | 1108 case TWO_FINGER_TAP: |
1120 return "TWO_FINGER_TAP"; | 1109 return "TWO_FINGER_TAP"; |
1121 } | 1110 } |
1122 return "Not a state"; | 1111 return "Not a state"; |
1123 } | 1112 } |
1124 | 1113 |
1125 // TODO(evy, lisayin) : Just call abstracted methods on the delegate (e.g. | |
1126 // Swipe(Direction direction, int num_fingers)), and add the DispatchXYZ | |
1127 // methods to the delegate. Avoid the middle step of dispatching keys at all, | |
1128 // and simply have ChromeVox/ChromeOS complete the required action. | |
1129 | |
1130 void TouchExplorationController::InitializeSwipeGestureMaps() { | |
1131 // Gestures with one finger are used for navigation. | |
1132 left_swipe_gestures_[1] = BindShiftSearchKeyEvent(ui::VKEY_LEFT); | |
1133 right_swipe_gestures_[1] = BindShiftSearchKeyEvent(ui::VKEY_RIGHT); | |
1134 up_swipe_gestures_[1] = BindShiftSearchKeyEvent(ui::VKEY_UP); | |
1135 down_swipe_gestures_[1] = BindShiftSearchKeyEvent(ui::VKEY_DOWN); | |
1136 | |
1137 // Gestures with two fingers. | |
1138 left_swipe_gestures_[2] = | |
1139 BindKeyEventWithFlags(ui::VKEY_BROWSER_BACK, ui::EF_NONE); | |
1140 right_swipe_gestures_[2] = | |
1141 BindKeyEventWithFlags(ui::VKEY_BROWSER_FORWARD, ui::EF_NONE); | |
1142 // Jump to top. | |
1143 up_swipe_gestures_[2] = BindShiftSearchKeyEvent(ui::VKEY_A); | |
1144 // Read from here. | |
1145 down_swipe_gestures_[2] = BindShiftSearchKeyEvent(ui::VKEY_R); | |
1146 | |
1147 // Gestures with three fingers switch tabs left/right and scroll up/down. | |
1148 left_swipe_gestures_[3] = BindKeyEventWithFlags( | |
1149 ui::VKEY_TAB, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN); | |
1150 right_swipe_gestures_[3] = | |
1151 BindKeyEventWithFlags(ui::VKEY_TAB, ui::EF_CONTROL_DOWN); | |
1152 up_swipe_gestures_[3] = BindKeyEventWithFlags(ui::VKEY_NEXT, ui::EF_NONE); | |
1153 down_swipe_gestures_[3] = BindKeyEventWithFlags(ui::VKEY_PRIOR, ui::EF_NONE); | |
1154 | |
1155 // Gestures with four fingers should probably eventually be used for rare | |
1156 // needs that are hard to access through menus. | |
1157 // Note that brightness levels are here because they can be important for low | |
1158 // vision users. However, none of these mappings are permanent. | |
1159 left_swipe_gestures_[4] = | |
1160 BindKeyEventWithFlags(ui::VKEY_BRIGHTNESS_DOWN, ui::EF_NONE); | |
1161 right_swipe_gestures_[4] = | |
1162 BindKeyEventWithFlags(VKEY_BRIGHTNESS_UP, ui::EF_NONE); | |
1163 up_swipe_gestures_[4] = BindKeyEventWithFlags(VKEY_BROWSER_HOME, ui::EF_NONE); | |
1164 down_swipe_gestures_[4] = | |
1165 BindKeyEventWithFlags(VKEY_BROWSER_REFRESH, ui::EF_NONE); | |
1166 } | |
1167 | |
1168 float TouchExplorationController::GetSplitTapTouchSlop() { | 1114 float TouchExplorationController::GetSplitTapTouchSlop() { |
1169 return gesture_detector_config_.touch_slop * 3; | 1115 return gesture_detector_config_.touch_slop * 3; |
1170 } | 1116 } |
1171 | 1117 |
1172 } // namespace ui | 1118 } // namespace ui |
OLD | NEW |