Home | History | Annotate | Download | only in frame
      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