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
GetFocusedStateAccessor()32 PropertyAccessor<bool>* GetFocusedStateAccessor() {
33 static PropertyAccessor<bool> state;
34 return &state;
35 }
36
TabContentsHasFocus(const TabContents * contents)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
TouchBrowserFrameView(BrowserFrame * frame,BrowserView * browser_view)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
~TouchBrowserFrameView()70 TouchBrowserFrameView::~TouchBrowserFrameView() {
71 browser_view()->browser()->tabstrip_model()->RemoveObserver(this);
72 }
73
Layout()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
FocusWillChange(views::View * focused_before,views::View * focused_now)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:
GetReservedHeight() const105 int TouchBrowserFrameView::GetReservedHeight() const {
106 return keyboard_showing_ ? kKeyboardHeight : 0;
107 }
108
ViewHierarchyChanged(bool is_add,View * parent,View * child)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
InitVirtualKeyboard()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
UpdateKeyboardAndLayout(bool should_show_keyboard)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
DecideKeyboardStateForView(views::View * view)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
HitTest(const gfx::Point & point) const186 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
TabSelectedAt(TabContentsWrapper * old_contents,TabContentsWrapper * new_contents,int index,bool user_gesture)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
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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
AnimationProgressed(const ui::Animation * anim)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
AnimationEnded(const ui::Animation * animation)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