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 3 // found in the LICENSE file. 4 5 #include "chrome/browser/ui/touch/frame/touch_browser_frame_view.h" 6 7 #include "chrome/browser/profiles/profile.h" 8 #include "chrome/browser/renderer_host/render_widget_host_view_views.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 "ui/base/animation/slide_animation.h" 22 #include "ui/gfx/rect.h" 23 #include "views/controls/button/image_button.h" 24 #include "views/controls/textfield/textfield.h" 25 #include "views/focus/focus_manager.h" 26 27 namespace { 28 29 const int kKeyboardHeight = 300; 30 const int kKeyboardSlideDuration = 500; // In milliseconds 31 32 PropertyAccessor<bool>* GetFocusedStateAccessor() { 33 static PropertyAccessor<bool> state; 34 return &state; 35 } 36 37 bool TabContentsHasFocus(const TabContents* contents) { 38 views::View* view = static_cast<TabContentsViewTouch*>(contents->view()); 39 return view->Contains(view->GetFocusManager()->GetFocusedView()); 40 } 41 42 } // namespace 43 44 /////////////////////////////////////////////////////////////////////////////// 45 // TouchBrowserFrameView, public: 46 47 TouchBrowserFrameView::TouchBrowserFrameView(BrowserFrame* frame, 48 BrowserView* browser_view) 49 : OpaqueBrowserFrameView(frame, browser_view), 50 keyboard_showing_(false), 51 focus_listener_added_(false), 52 keyboard_(NULL) { 53 registrar_.Add(this, 54 NotificationType::NAV_ENTRY_COMMITTED, 55 NotificationService::AllSources()); 56 registrar_.Add(this, 57 NotificationType::FOCUS_CHANGED_IN_PAGE, 58 NotificationService::AllSources()); 59 registrar_.Add(this, 60 NotificationType::TAB_CONTENTS_DESTROYED, 61 NotificationService::AllSources()); 62 63 browser_view->browser()->tabstrip_model()->AddObserver(this); 64 65 animation_.reset(new ui::SlideAnimation(this)); 66 animation_->SetTweenType(ui::Tween::LINEAR); 67 animation_->SetSlideDuration(kKeyboardSlideDuration); 68 } 69 70 TouchBrowserFrameView::~TouchBrowserFrameView() { 71 browser_view()->browser()->tabstrip_model()->RemoveObserver(this); 72 } 73 74 void TouchBrowserFrameView::Layout() { 75 OpaqueBrowserFrameView::Layout(); 76 77 if (!keyboard_) 78 return; 79 80 keyboard_->SetVisible(keyboard_showing_ || animation_->is_animating()); 81 gfx::Rect bounds = GetBoundsForReservedArea(); 82 if (animation_->is_animating() && !keyboard_showing_) { 83 // The keyboard is in the process of hiding. So pretend it still has the 84 // same bounds as when the keyboard is visible. But 85 // |GetBoundsForReservedArea| should not take this into account so that the 86 // render view gets the entire area to relayout itself. 87 bounds.set_y(bounds.y() - kKeyboardHeight); 88 bounds.set_height(kKeyboardHeight); 89 } 90 keyboard_->SetBoundsRect(bounds); 91 } 92 93 void TouchBrowserFrameView::FocusWillChange(views::View* focused_before, 94 views::View* focused_now) { 95 VirtualKeyboardType before = DecideKeyboardStateForView(focused_before); 96 VirtualKeyboardType now = DecideKeyboardStateForView(focused_now); 97 if (before != now) { 98 // TODO(varunjain): support other types of keyboard. 99 UpdateKeyboardAndLayout(now == GENERIC); 100 } 101 } 102 103 /////////////////////////////////////////////////////////////////////////////// 104 // TouchBrowserFrameView, protected: 105 int TouchBrowserFrameView::GetReservedHeight() const { 106 return keyboard_showing_ ? kKeyboardHeight : 0; 107 } 108 109 void TouchBrowserFrameView::ViewHierarchyChanged(bool is_add, 110 View* parent, 111 View* child) { 112 OpaqueBrowserFrameView::ViewHierarchyChanged(is_add, parent, child); 113 if (!GetFocusManager()) 114 return; 115 116 if (is_add && !focus_listener_added_) { 117 // Add focus listener when this view is added to the hierarchy. 118 GetFocusManager()->AddFocusChangeListener(this); 119 focus_listener_added_ = true; 120 } else if (!is_add && focus_listener_added_) { 121 // Remove focus listener when this view is removed from the hierarchy. 122 GetFocusManager()->RemoveFocusChangeListener(this); 123 focus_listener_added_ = false; 124 } 125 } 126 127 /////////////////////////////////////////////////////////////////////////////// 128 // TouchBrowserFrameView, private: 129 130 void TouchBrowserFrameView::InitVirtualKeyboard() { 131 if (keyboard_) 132 return; 133 134 Profile* keyboard_profile = browser_view()->browser()->profile(); 135 DCHECK(keyboard_profile) << "Profile required for virtual keyboard."; 136 137 keyboard_ = new KeyboardContainerView(keyboard_profile); 138 keyboard_->SetVisible(false); 139 AddChildView(keyboard_); 140 } 141 142 void TouchBrowserFrameView::UpdateKeyboardAndLayout(bool should_show_keyboard) { 143 if (should_show_keyboard) 144 InitVirtualKeyboard(); 145 146 if (should_show_keyboard == keyboard_showing_) 147 return; 148 149 DCHECK(keyboard_); 150 151 keyboard_showing_ = should_show_keyboard; 152 if (keyboard_showing_) { 153 animation_->Show(); 154 155 // We don't re-layout the client view until the animation ends (see 156 // AnimationEnded below) because we want the client view to occupy the 157 // entire height during the animation. 158 Layout(); 159 } else { 160 animation_->Hide(); 161 162 browser_view()->set_clip_y(ui::Tween::ValueBetween( 163 animation_->GetCurrentValue(), 0, kKeyboardHeight)); 164 parent()->Layout(); 165 } 166 } 167 168 TouchBrowserFrameView::VirtualKeyboardType 169 TouchBrowserFrameView::DecideKeyboardStateForView(views::View* view) { 170 if (!view) 171 return NONE; 172 173 std::string cname = view->GetClassName(); 174 if (cname == views::Textfield::kViewClassName) { 175 return GENERIC; 176 } else if (cname == RenderWidgetHostViewViews::kViewClassName) { 177 TabContents* contents = browser_view()->browser()->GetSelectedTabContents(); 178 bool* editable = contents ? GetFocusedStateAccessor()->GetProperty( 179 contents->property_bag()) : NULL; 180 if (editable && *editable) 181 return GENERIC; 182 } 183 return NONE; 184 } 185 186 bool TouchBrowserFrameView::HitTest(const gfx::Point& point) const { 187 if (OpaqueBrowserFrameView::HitTest(point)) 188 return true; 189 190 if (close_button()->IsVisible() && 191 close_button()->GetMirroredBounds().Contains(point)) 192 return true; 193 if (restore_button()->IsVisible() && 194 restore_button()->GetMirroredBounds().Contains(point)) 195 return true; 196 if (maximize_button()->IsVisible() && 197 maximize_button()->GetMirroredBounds().Contains(point)) 198 return true; 199 if (minimize_button()->IsVisible() && 200 minimize_button()->GetMirroredBounds().Contains(point)) 201 return true; 202 203 return false; 204 } 205 206 void TouchBrowserFrameView::TabSelectedAt(TabContentsWrapper* old_contents, 207 TabContentsWrapper* new_contents, 208 int index, 209 bool user_gesture) { 210 if (new_contents == old_contents) 211 return; 212 213 TabContents* contents = new_contents->tab_contents(); 214 if (!TabContentsHasFocus(contents)) 215 return; 216 217 bool* editable = GetFocusedStateAccessor()->GetProperty( 218 contents->property_bag()); 219 UpdateKeyboardAndLayout(editable ? *editable : false); 220 } 221 222 223 void TouchBrowserFrameView::Observe(NotificationType type, 224 const NotificationSource& source, 225 const NotificationDetails& details) { 226 Browser* browser = browser_view()->browser(); 227 if (type == NotificationType::FOCUS_CHANGED_IN_PAGE) { 228 // Only modify the keyboard state if the currently active tab sent the 229 // notification. 230 const TabContents* current_tab = browser->GetSelectedTabContents(); 231 TabContents* source_tab = Source<TabContents>(source).ptr(); 232 const bool editable = *Details<const bool>(details).ptr(); 233 234 if (current_tab == source_tab && TabContentsHasFocus(source_tab)) 235 UpdateKeyboardAndLayout(editable); 236 237 // Save the state of the focused field so that the keyboard visibility 238 // can be determined after tab switching. 239 GetFocusedStateAccessor()->SetProperty( 240 source_tab->property_bag(), editable); 241 } else if (type == NotificationType::NAV_ENTRY_COMMITTED) { 242 Browser* source_browser = Browser::GetBrowserForController( 243 Source<NavigationController>(source).ptr(), NULL); 244 // If the Browser for the keyboard has navigated, re-evaluate the visibility 245 // of the keyboard. 246 if (source_browser == browser) 247 UpdateKeyboardAndLayout(DecideKeyboardStateForView( 248 GetFocusManager()->GetFocusedView()) == GENERIC); 249 } else if (type == NotificationType::TAB_CONTENTS_DESTROYED) { 250 GetFocusedStateAccessor()->DeleteProperty( 251 Source<TabContents>(source).ptr()->property_bag()); 252 } 253 } 254 255 /////////////////////////////////////////////////////////////////////////////// 256 // ui::AnimationDelegate implementation 257 void TouchBrowserFrameView::AnimationProgressed(const ui::Animation* anim) { 258 keyboard_->SetTranslateY( 259 ui::Tween::ValueBetween(anim->GetCurrentValue(), kKeyboardHeight, 0)); 260 browser_view()->set_clip_y( 261 ui::Tween::ValueBetween(anim->GetCurrentValue(), 0, kKeyboardHeight)); 262 SchedulePaint(); 263 } 264 265 void TouchBrowserFrameView::AnimationEnded(const ui::Animation* animation) { 266 browser_view()->set_clip_y(0); 267 if (keyboard_showing_) { 268 // Because the NonClientFrameView is a sibling of the ClientView, we rely on 269 // the parent to resize the ClientView instead of resizing it directly. 270 parent()->Layout(); 271 272 // The keyboard that pops up may end up hiding the text entry. So make sure 273 // the renderer scrolls when necessary to keep the textfield visible. 274 RenderViewHost* host = 275 browser_view()->browser()->GetSelectedTabContents()->render_view_host(); 276 host->ScrollFocusedEditableNodeIntoView(); 277 } 278 SchedulePaint(); 279 } 280