| OLD | NEW |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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 "chrome/browser/ui/touch/frame/touch_browser_frame_view.h" | 5 #include "chrome/browser/ui/touch/frame/touch_browser_frame_view.h" |
| 6 | 6 |
| 7 #include "chrome/browser/profiles/profile.h" | 7 #include "chrome/browser/ui/touch/keyboard/keyboard_manager.h" |
| 8 #include "chrome/browser/renderer_host/render_widget_host_view_views.h" | 8 #include "ui/base/ime/text_input_type.h" |
| 9 #include "chrome/browser/tabs/tab_strip_model.h" | |
| 10 #include "chrome/browser/ui/browser.h" | |
| 11 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" | |
| 12 #include "chrome/browser/ui/touch/frame/keyboard_container_view.h" | |
| 13 #include "chrome/browser/ui/views/frame/browser_view.h" | |
| 14 #include "chrome/browser/ui/views/tab_contents/tab_contents_view_touch.h" | |
| 15 #include "content/browser/renderer_host/render_view_host.h" | |
| 16 #include "content/browser/tab_contents/navigation_controller.h" | |
| 17 #include "content/browser/tab_contents/tab_contents.h" | |
| 18 #include "content/browser/tab_contents/tab_contents_view.h" | |
| 19 #include "content/common/notification_service.h" | |
| 20 #include "content/common/notification_type.h" | |
| 21 #include "content/common/view_messages.h" | |
| 22 #include "ui/base/animation/slide_animation.h" | |
| 23 #include "ui/gfx/rect.h" | |
| 24 #include "ui/gfx/transform.h" | |
| 25 #include "views/controls/button/image_button.h" | 9 #include "views/controls/button/image_button.h" |
| 26 #include "views/controls/textfield/textfield.h" | 10 #include "views/controls/textfield/textfield.h" |
| 27 #include "views/focus/focus_manager.h" | 11 #include "views/focus/focus_manager.h" |
| 28 | 12 #include "views/ime/text_input_client.h" |
| 29 #if defined(OS_CHROMEOS) | |
| 30 #include "chrome/browser/chromeos/input_method/virtual_keyboard_selector.h" | |
| 31 #endif | |
| 32 | |
| 33 namespace { | |
| 34 | |
| 35 const int kDefaultKeyboardHeight = 300; | |
| 36 const int kKeyboardSlideDuration = 300; // In milliseconds | |
| 37 | |
| 38 PropertyAccessor<bool>* GetFocusedStateAccessor() { | |
| 39 static PropertyAccessor<bool> state; | |
| 40 return &state; | |
| 41 } | |
| 42 | |
| 43 bool TabContentsHasFocus(const TabContents* contents) { | |
| 44 views::View* view = static_cast<TabContentsViewTouch*>(contents->view()); | |
| 45 return view->Contains(view->GetFocusManager()->GetFocusedView()); | |
| 46 } | |
| 47 | |
| 48 } // namespace | |
| 49 | 13 |
| 50 // static | 14 // static |
| 51 const char TouchBrowserFrameView::kViewClassName[] = | 15 const char TouchBrowserFrameView::kViewClassName[] = |
| 52 "browser/ui/touch/frame/TouchBrowserFrameView"; | 16 "browser/ui/touch/frame/TouchBrowserFrameView"; |
| 53 | 17 |
| 54 /////////////////////////////////////////////////////////////////////////////// | 18 /////////////////////////////////////////////////////////////////////////////// |
| 55 // TouchBrowserFrameView, public: | 19 // TouchBrowserFrameView, public: |
| 56 | 20 |
| 57 TouchBrowserFrameView::TouchBrowserFrameView(BrowserFrame* frame, | 21 TouchBrowserFrameView::TouchBrowserFrameView(BrowserFrame* frame, |
| 58 BrowserView* browser_view) | 22 BrowserView* browser_view) |
| 59 : OpaqueBrowserFrameView(frame, browser_view), | 23 : OpaqueBrowserFrameView(frame, browser_view), |
| 60 keyboard_showing_(false), | 24 focus_listener_added_(false) { |
| 61 keyboard_height_(kDefaultKeyboardHeight), | 25 // Make sure the singleton KeyboardManager object is initialized. |
| 62 focus_listener_added_(false), | 26 KeyboardManager::GetInstance(); |
| 63 keyboard_(NULL) { | |
| 64 registrar_.Add(this, | |
| 65 NotificationType::NAV_ENTRY_COMMITTED, | |
| 66 NotificationService::AllSources()); | |
| 67 registrar_.Add(this, | |
| 68 NotificationType::FOCUS_CHANGED_IN_PAGE, | |
| 69 NotificationService::AllSources()); | |
| 70 registrar_.Add(this, | |
| 71 NotificationType::TAB_CONTENTS_DESTROYED, | |
| 72 NotificationService::AllSources()); | |
| 73 registrar_.Add(this, | |
| 74 NotificationType::HIDE_KEYBOARD_INVOKED, | |
| 75 NotificationService::AllSources()); | |
| 76 registrar_.Add(this, | |
| 77 NotificationType::SET_KEYBOARD_HEIGHT_INVOKED, | |
| 78 NotificationService::AllSources()); | |
| 79 registrar_.Add(this, | |
| 80 NotificationType::EDITABLE_ELEMENT_TOUCHED, | |
| 81 NotificationService::AllSources()); | |
| 82 | |
| 83 browser_view->browser()->tabstrip_model()->AddObserver(this); | |
| 84 | |
| 85 animation_.reset(new ui::SlideAnimation(this)); | |
| 86 animation_->SetTweenType(ui::Tween::LINEAR); | |
| 87 animation_->SetSlideDuration(kKeyboardSlideDuration); | |
| 88 | |
| 89 #if defined(OS_CHROMEOS) | |
| 90 chromeos::input_method::InputMethodManager* manager = | |
| 91 chromeos::input_method::InputMethodManager::GetInstance(); | |
| 92 manager->AddVirtualKeyboardObserver(this); | |
| 93 #endif | |
| 94 } | 27 } |
| 95 | 28 |
| 96 TouchBrowserFrameView::~TouchBrowserFrameView() { | 29 TouchBrowserFrameView::~TouchBrowserFrameView() { |
| 97 browser_view()->browser()->tabstrip_model()->RemoveObserver(this); | 30 } |
| 31 |
| 32 /////////////////////////////////////////////////////////////////////////////// |
| 33 // TouchBrowserFrameView, private: |
| 34 |
| 35 void TouchBrowserFrameView::FocusWillChange(views::View* focused_before, |
| 36 views::View* focused_now) { |
| 37 views::Widget* widget = focused_now ? focused_now->GetWidget() : NULL; |
| 38 if (!widget || !widget->IsActive()) |
| 39 return; |
| 40 |
| 41 views::TextInputClient* input = |
| 42 focused_now ? focused_now->GetTextInputClient() : NULL; |
| 43 // Show the keyboard if the focused view supports text-input. |
| 44 KeyboardManager* keyboard = KeyboardManager::GetInstance(); |
| 45 if (input && input->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE) |
| 46 keyboard->ShowKeyboardForWidget(focused_now->GetWidget()); |
| 47 else |
| 48 keyboard->Hide(); |
| 98 } | 49 } |
| 99 | 50 |
| 100 std::string TouchBrowserFrameView::GetClassName() const { | 51 std::string TouchBrowserFrameView::GetClassName() const { |
| 101 return kViewClassName; | 52 return kViewClassName; |
| 102 } | 53 } |
| 103 | 54 |
| 104 void TouchBrowserFrameView::Layout() { | |
| 105 OpaqueBrowserFrameView::Layout(); | |
| 106 | |
| 107 if (!keyboard_) | |
| 108 return; | |
| 109 | |
| 110 keyboard_->SetVisible(keyboard_showing_ || animation_->is_animating()); | |
| 111 gfx::Rect bounds = GetBoundsForReservedArea(); | |
| 112 if (animation_->is_animating() && !keyboard_showing_) { | |
| 113 // The keyboard is in the process of hiding. So pretend it still has the | |
| 114 // same bounds as when the keyboard is visible. But | |
| 115 // |GetBoundsForReservedArea| should not take this into account so that the | |
| 116 // render view gets the entire area to relayout itself. | |
| 117 bounds.set_y(bounds.y() - keyboard_height_); | |
| 118 bounds.set_height(keyboard_height_); | |
| 119 } | |
| 120 keyboard_->SetBoundsRect(bounds); | |
| 121 } | |
| 122 | |
| 123 void TouchBrowserFrameView::FocusWillChange(views::View* focused_before, | |
| 124 views::View* focused_now) { | |
| 125 VirtualKeyboardType before = DecideKeyboardStateForView(focused_before); | |
| 126 VirtualKeyboardType now = DecideKeyboardStateForView(focused_now); | |
| 127 if (before != now) { | |
| 128 // TODO(varunjain): support other types of keyboard. | |
| 129 UpdateKeyboardAndLayout(now == GENERIC); | |
| 130 } | |
| 131 } | |
| 132 | |
| 133 /////////////////////////////////////////////////////////////////////////////// | |
| 134 // TouchBrowserFrameView, protected: | |
| 135 | |
| 136 int TouchBrowserFrameView::GetReservedHeight() const { | |
| 137 return keyboard_showing_ ? keyboard_height_ : 0; | |
| 138 } | |
| 139 | |
| 140 void TouchBrowserFrameView::ViewHierarchyChanged(bool is_add, | 55 void TouchBrowserFrameView::ViewHierarchyChanged(bool is_add, |
| 141 View* parent, | 56 View* parent, |
| 142 View* child) { | 57 View* child) { |
| 143 OpaqueBrowserFrameView::ViewHierarchyChanged(is_add, parent, child); | 58 OpaqueBrowserFrameView::ViewHierarchyChanged(is_add, parent, child); |
| 144 if (!GetFocusManager()) | 59 if (!GetFocusManager()) |
| 145 return; | 60 return; |
| 146 | 61 |
| 147 if (is_add && !focus_listener_added_) { | 62 if (is_add && !focus_listener_added_) { |
| 148 // Add focus listener when this view is added to the hierarchy. | 63 // Add focus listener when this view is added to the hierarchy. |
| 149 GetFocusManager()->AddFocusChangeListener(this); | 64 GetFocusManager()->AddFocusChangeListener(this); |
| 150 focus_listener_added_ = true; | 65 focus_listener_added_ = true; |
| 151 } else if (!is_add && focus_listener_added_) { | 66 } else if (!is_add && focus_listener_added_) { |
| 152 // Remove focus listener when this view is removed from the hierarchy. | 67 // Remove focus listener when this view is removed from the hierarchy. |
| 153 GetFocusManager()->RemoveFocusChangeListener(this); | 68 GetFocusManager()->RemoveFocusChangeListener(this); |
| 154 focus_listener_added_ = false; | 69 focus_listener_added_ = false; |
| 155 } | 70 } |
| 156 } | 71 } |
| 157 | 72 |
| 158 /////////////////////////////////////////////////////////////////////////////// | |
| 159 // TouchBrowserFrameView, private: | |
| 160 | |
| 161 void TouchBrowserFrameView::InitVirtualKeyboard() { | |
| 162 if (keyboard_) | |
| 163 return; | |
| 164 | |
| 165 Profile* keyboard_profile = browser_view()->browser()->profile(); | |
| 166 DCHECK(keyboard_profile) << "Profile required for virtual keyboard."; | |
| 167 | |
| 168 keyboard_ = new KeyboardContainerView(keyboard_profile, | |
| 169 browser_view()->browser()); | |
| 170 keyboard_->SetVisible(false); | |
| 171 AddChildView(keyboard_); | |
| 172 } | |
| 173 | |
| 174 void TouchBrowserFrameView::UpdateKeyboardAndLayout(bool should_show_keyboard) { | |
| 175 if (should_show_keyboard) | |
| 176 InitVirtualKeyboard(); | |
| 177 | |
| 178 if (should_show_keyboard == keyboard_showing_) | |
| 179 return; | |
| 180 | |
| 181 DCHECK(keyboard_); | |
| 182 | |
| 183 keyboard_showing_ = should_show_keyboard; | |
| 184 if (keyboard_showing_) { | |
| 185 // We don't re-layout the client view until the animation ends (see | |
| 186 // AnimationEnded below) because we want the client view to occupy the | |
| 187 // entire height during the animation. It is necessary to reset the | |
| 188 // transform for the keyboard first so that the contents are sized properly | |
| 189 // when layout, and then start the animation. | |
| 190 ui::Transform reset; | |
| 191 keyboard_->SetTransform(reset); | |
| 192 Layout(); | |
| 193 | |
| 194 animation_->Show(); | |
| 195 } else { | |
| 196 animation_->Hide(); | |
| 197 | |
| 198 browser_view()->set_clip_y(ui::Tween::ValueBetween( | |
| 199 animation_->GetCurrentValue(), 0, keyboard_height_)); | |
| 200 parent()->Layout(); | |
| 201 } | |
| 202 } | |
| 203 | |
| 204 TouchBrowserFrameView::VirtualKeyboardType | |
| 205 TouchBrowserFrameView::DecideKeyboardStateForView(views::View* view) { | |
| 206 if (!view) | |
| 207 return NONE; | |
| 208 | |
| 209 std::string cname = view->GetClassName(); | |
| 210 if (cname == views::Textfield::kViewClassName) { | |
| 211 return GENERIC; | |
| 212 } else if (cname == RenderWidgetHostViewViews::kViewClassName) { | |
| 213 TabContents* contents = browser_view()->browser()->GetSelectedTabContents(); | |
| 214 bool* editable = contents ? GetFocusedStateAccessor()->GetProperty( | |
| 215 contents->property_bag()) : NULL; | |
| 216 if (editable && *editable) | |
| 217 return GENERIC; | |
| 218 } | |
| 219 return NONE; | |
| 220 } | |
| 221 | |
| 222 bool TouchBrowserFrameView::HitTest(const gfx::Point& point) const { | 73 bool TouchBrowserFrameView::HitTest(const gfx::Point& point) const { |
| 223 if (OpaqueBrowserFrameView::HitTest(point)) | 74 if (OpaqueBrowserFrameView::HitTest(point)) |
| 224 return true; | 75 return true; |
| 225 | 76 |
| 226 if (close_button()->IsVisible() && | 77 if (close_button()->IsVisible() && |
| 227 close_button()->GetMirroredBounds().Contains(point)) | 78 close_button()->GetMirroredBounds().Contains(point)) |
| 228 return true; | 79 return true; |
| 229 if (restore_button()->IsVisible() && | 80 if (restore_button()->IsVisible() && |
| 230 restore_button()->GetMirroredBounds().Contains(point)) | 81 restore_button()->GetMirroredBounds().Contains(point)) |
| 231 return true; | 82 return true; |
| 232 if (maximize_button()->IsVisible() && | 83 if (maximize_button()->IsVisible() && |
| 233 maximize_button()->GetMirroredBounds().Contains(point)) | 84 maximize_button()->GetMirroredBounds().Contains(point)) |
| 234 return true; | 85 return true; |
| 235 if (minimize_button()->IsVisible() && | 86 if (minimize_button()->IsVisible() && |
| 236 minimize_button()->GetMirroredBounds().Contains(point)) | 87 minimize_button()->GetMirroredBounds().Contains(point)) |
| 237 return true; | 88 return true; |
| 238 | 89 |
| 239 return false; | 90 return false; |
| 240 } | 91 } |
| 241 | |
| 242 void TouchBrowserFrameView::ActiveTabChanged(TabContentsWrapper* old_contents, | |
| 243 TabContentsWrapper* new_contents, | |
| 244 int index, | |
| 245 bool user_gesture) { | |
| 246 TabContents* contents = new_contents->tab_contents(); | |
| 247 if (!TabContentsHasFocus(contents)) | |
| 248 return; | |
| 249 | |
| 250 bool* editable = GetFocusedStateAccessor()->GetProperty( | |
| 251 contents->property_bag()); | |
| 252 UpdateKeyboardAndLayout(editable ? *editable : false); | |
| 253 } | |
| 254 | |
| 255 void TouchBrowserFrameView::TabStripEmpty() { | |
| 256 if (animation_->is_animating()) { | |
| 257 // Reset the delegate so the AnimationEnded callback doesn't trigger. | |
| 258 animation_->set_delegate(NULL); | |
| 259 animation_->Stop(); | |
| 260 } | |
| 261 } | |
| 262 | |
| 263 void TouchBrowserFrameView::Observe(NotificationType type, | |
| 264 const NotificationSource& source, | |
| 265 const NotificationDetails& details) { | |
| 266 Browser* browser = browser_view()->browser(); | |
| 267 if (type == NotificationType::FOCUS_CHANGED_IN_PAGE) { | |
| 268 // Only modify the keyboard state if the currently active tab sent the | |
| 269 // notification. | |
| 270 const TabContents* current_tab = browser->GetSelectedTabContents(); | |
| 271 TabContents* source_tab = Source<TabContents>(source).ptr(); | |
| 272 const bool editable = *Details<const bool>(details).ptr(); | |
| 273 | |
| 274 if (current_tab == source_tab && TabContentsHasFocus(source_tab)) | |
| 275 UpdateKeyboardAndLayout(editable); | |
| 276 | |
| 277 // Save the state of the focused field so that the keyboard visibility | |
| 278 // can be determined after tab switching. | |
| 279 GetFocusedStateAccessor()->SetProperty( | |
| 280 source_tab->property_bag(), editable); | |
| 281 } else if (type == NotificationType::NAV_ENTRY_COMMITTED) { | |
| 282 NavigationController* controller = | |
| 283 Source<NavigationController>(source).ptr(); | |
| 284 Browser* source_browser = Browser::GetBrowserForController( | |
| 285 controller, NULL); | |
| 286 | |
| 287 // If the Browser for the keyboard has navigated, re-evaluate the visibility | |
| 288 // of the keyboard. | |
| 289 TouchBrowserFrameView::VirtualKeyboardType keyboard_type = NONE; | |
| 290 views::View* view = GetFocusManager()->GetFocusedView(); | |
| 291 if (view) { | |
| 292 if (view->GetClassName() == views::Textfield::kViewClassName) | |
| 293 keyboard_type = GENERIC; | |
| 294 if (view->GetClassName() == RenderWidgetHostViewViews::kViewClassName) { | |
| 295 // Reset the state of the focused field in the current tab. | |
| 296 GetFocusedStateAccessor()->SetProperty( | |
| 297 controller->tab_contents()->property_bag(), false); | |
| 298 } | |
| 299 } | |
| 300 if (source_browser == browser) | |
| 301 UpdateKeyboardAndLayout(keyboard_type == GENERIC); | |
| 302 } else if (type == NotificationType::TAB_CONTENTS_DESTROYED) { | |
| 303 GetFocusedStateAccessor()->DeleteProperty( | |
| 304 Source<TabContents>(source).ptr()->property_bag()); | |
| 305 } else if (type == NotificationType::PREF_CHANGED) { | |
| 306 OpaqueBrowserFrameView::Observe(type, source, details); | |
| 307 } else if (type == NotificationType::HIDE_KEYBOARD_INVOKED) { | |
| 308 TabContents* tab_contents = | |
| 309 browser_view()->browser()->GetSelectedTabContents(); | |
| 310 if (tab_contents) { | |
| 311 GetFocusedStateAccessor()->SetProperty(tab_contents->property_bag(), | |
| 312 false); | |
| 313 } | |
| 314 UpdateKeyboardAndLayout(false); | |
| 315 } else if (type == NotificationType::SET_KEYBOARD_HEIGHT_INVOKED) { | |
| 316 // TODO(penghuang) Allow extension conrtol the virtual keyboard directly | |
| 317 // instead of using Notification. | |
| 318 int height = *reinterpret_cast<int*>(details.map_key()); | |
| 319 if (height != keyboard_height_) { | |
| 320 DCHECK_GE(height, 0) << "Height of the keyboard is less than 0."; | |
| 321 DCHECK_LE(height, View::height()) << "Height of the keyboard is greater " | |
| 322 "than the height of frame view."; | |
| 323 keyboard_height_ = height; | |
| 324 parent()->Layout(); | |
| 325 } | |
| 326 } else if (type == NotificationType::EDITABLE_ELEMENT_TOUCHED) { | |
| 327 UpdateKeyboardAndLayout(true); | |
| 328 } | |
| 329 } | |
| 330 | |
| 331 /////////////////////////////////////////////////////////////////////////////// | |
| 332 // ui::AnimationDelegate implementation | |
| 333 void TouchBrowserFrameView::AnimationProgressed(const ui::Animation* anim) { | |
| 334 ui::Transform transform; | |
| 335 transform.SetTranslateY( | |
| 336 ui::Tween::ValueBetween(anim->GetCurrentValue(), keyboard_height_, 0)); | |
| 337 keyboard_->SetTransform(transform); | |
| 338 browser_view()->set_clip_y( | |
| 339 ui::Tween::ValueBetween(anim->GetCurrentValue(), 0, keyboard_height_)); | |
| 340 SchedulePaint(); | |
| 341 } | |
| 342 | |
| 343 void TouchBrowserFrameView::AnimationEnded(const ui::Animation* animation) { | |
| 344 browser_view()->set_clip_y(0); | |
| 345 if (keyboard_showing_) { | |
| 346 // Because the NonClientFrameView is a sibling of the ClientView, we rely on | |
| 347 // the parent to resize the ClientView instead of resizing it directly. | |
| 348 parent()->Layout(); | |
| 349 | |
| 350 // The keyboard that pops up may end up hiding the text entry. So make sure | |
| 351 // the renderer scrolls when necessary to keep the textfield visible. | |
| 352 RenderViewHost* host = | |
| 353 browser_view()->browser()->GetSelectedTabContents()->render_view_host(); | |
| 354 host->Send(new ViewMsg_ScrollFocusedEditableNodeIntoView( | |
| 355 host->routing_id())); | |
| 356 } else { | |
| 357 // Notify the keyboard that it is hidden now. | |
| 358 keyboard_->SetVisible(false); | |
| 359 } | |
| 360 SchedulePaint(); | |
| 361 } | |
| 362 | |
| 363 #if defined(OS_CHROMEOS) | |
| 364 void TouchBrowserFrameView::VirtualKeyboardChanged( | |
| 365 chromeos::input_method::InputMethodManager* manager, | |
| 366 const chromeos::input_method::VirtualKeyboard& virtual_keyboard, | |
| 367 const std::string& virtual_keyboard_layout) { | |
| 368 if (!keyboard_) | |
| 369 return; | |
| 370 | |
| 371 const GURL& url = virtual_keyboard.GetURLForLayout(virtual_keyboard_layout); | |
| 372 keyboard_->LoadURL(url); | |
| 373 VLOG(1) << "VirtualKeyboardChanged: Switched to " << url.spec(); | |
| 374 } | |
| 375 #endif | |
| OLD | NEW |