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
27namespace {
28
29const int kKeyboardHeight = 300;
30const int kKeyboardSlideDuration = 500;  // In milliseconds
31
32PropertyAccessor<bool>* GetFocusedStateAccessor() {
33  static PropertyAccessor<bool> state;
34  return &state;
35}
36
37bool 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
47TouchBrowserFrameView::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
70TouchBrowserFrameView::~TouchBrowserFrameView() {
71  browser_view()->browser()->tabstrip_model()->RemoveObserver(this);
72}
73
74void 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
93void 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:
105int TouchBrowserFrameView::GetReservedHeight() const {
106  return keyboard_showing_ ? kKeyboardHeight : 0;
107}
108
109void 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
130void 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
142void 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
168TouchBrowserFrameView::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
186bool 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
206void 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
223void 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
257void 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
265void 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