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