1// Copyright (c) 2012 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/views/dropdown_bar_host.h" 6 7#include <algorithm> 8 9#include "chrome/browser/ui/view_ids.h" 10#include "chrome/browser/ui/views/dropdown_bar_host_delegate.h" 11#include "chrome/browser/ui/views/dropdown_bar_view.h" 12#include "chrome/browser/ui/views/frame/browser_view.h" 13#include "ui/events/keycodes/keyboard_codes.h" 14#include "ui/gfx/animation/slide_animation.h" 15#include "ui/gfx/scrollbar_size.h" 16#include "ui/views/focus/external_focus_tracker.h" 17#include "ui/views/focus/view_storage.h" 18#include "ui/views/widget/widget.h" 19 20// static 21bool DropdownBarHost::disable_animations_during_testing_ = false; 22 23//////////////////////////////////////////////////////////////////////////////// 24// DropdownBarHost, public: 25 26DropdownBarHost::DropdownBarHost(BrowserView* browser_view) 27 : browser_view_(browser_view), 28 view_(NULL), 29 delegate_(NULL), 30 animation_offset_(0), 31 focus_manager_(NULL), 32 esc_accel_target_registered_(false), 33 is_visible_(false) { 34} 35 36void DropdownBarHost::Init(views::View* host_view, 37 views::View* view, 38 DropdownBarHostDelegate* delegate) { 39 DCHECK(view); 40 DCHECK(delegate); 41 42 view_ = view; 43 delegate_ = delegate; 44 45 // Initialize the host. 46 host_.reset(new views::Widget); 47 views::Widget::InitParams params(views::Widget::InitParams::TYPE_CONTROL); 48 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 49 params.parent = browser_view_->GetWidget()->GetNativeView(); 50 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; 51 host_->Init(params); 52 host_->SetContentsView(view_); 53 54 SetHostViewNative(host_view); 55 56 // Start listening to focus changes, so we can register and unregister our 57 // own handler for Escape. 58 focus_manager_ = host_->GetFocusManager(); 59 if (focus_manager_) { 60 focus_manager_->AddFocusChangeListener(this); 61 } else { 62 // In some cases (see bug http://crbug.com/17056) it seems we may not have 63 // a focus manager. Please reopen the bug if you hit this. 64 NOTREACHED(); 65 } 66 67 // Start the process of animating the opening of the widget. 68 animation_.reset(new gfx::SlideAnimation(this)); 69} 70 71DropdownBarHost::~DropdownBarHost() { 72 focus_manager_->RemoveFocusChangeListener(this); 73 focus_tracker_.reset(NULL); 74} 75 76void DropdownBarHost::Show(bool animate) { 77 // Stores the currently focused view, and tracks focus changes so that we can 78 // restore focus when the dropdown widget is closed. 79 focus_tracker_.reset(new views::ExternalFocusTracker(view_, focus_manager_)); 80 81 bool was_visible = is_visible_; 82 is_visible_ = true; 83 if (!animate || disable_animations_during_testing_) { 84 animation_->Reset(1); 85 AnimationProgressed(animation_.get()); 86 } else if (!was_visible) { 87 // Don't re-start the animation. 88 animation_->Reset(); 89 animation_->Show(); 90 } 91 92 if (!was_visible) 93 OnVisibilityChanged(); 94} 95 96void DropdownBarHost::SetFocusAndSelection() { 97 delegate_->SetFocusAndSelection(true); 98} 99 100bool DropdownBarHost::IsAnimating() const { 101 return animation_->is_animating(); 102} 103 104void DropdownBarHost::Hide(bool animate) { 105 if (!IsVisible()) 106 return; 107 if (animate && !disable_animations_during_testing_ && 108 !animation_->IsClosing()) { 109 animation_->Hide(); 110 } else { 111 if (animation_->IsClosing()) { 112 // If we're in the middle of a close animation, skip immediately to the 113 // end of the animation. 114 StopAnimation(); 115 } else { 116 // Otherwise we need to set both the animation state to ended and the 117 // DropdownBarHost state to ended/hidden, otherwise the next time we try 118 // to show the bar, it might refuse to do so. Note that we call 119 // AnimationEnded ourselves as Reset does not call it if we are not 120 // animating here. 121 animation_->Reset(); 122 AnimationEnded(animation_.get()); 123 } 124 } 125} 126 127void DropdownBarHost::StopAnimation() { 128 animation_->End(); 129} 130 131bool DropdownBarHost::IsVisible() const { 132 return is_visible_; 133} 134 135//////////////////////////////////////////////////////////////////////////////// 136// DropdownBarHost, views::FocusChangeListener implementation: 137void DropdownBarHost::OnWillChangeFocus(views::View* focused_before, 138 views::View* focused_now) { 139 // First we need to determine if one or both of the views passed in are child 140 // views of our view. 141 bool our_view_before = focused_before && view_->Contains(focused_before); 142 bool our_view_now = focused_now && view_->Contains(focused_now); 143 144 // When both our_view_before and our_view_now are false, it means focus is 145 // changing hands elsewhere in the application (and we shouldn't do anything). 146 // Similarly, when both are true, focus is changing hands within the dropdown 147 // widget (and again, we should not do anything). We therefore only need to 148 // look at when we gain initial focus and when we loose it. 149 if (!our_view_before && our_view_now) { 150 // We are gaining focus from outside the dropdown widget so we must register 151 // a handler for Escape. 152 RegisterAccelerators(); 153 } else if (our_view_before && !our_view_now) { 154 // We are losing focus to something outside our widget so we restore the 155 // original handler for Escape. 156 UnregisterAccelerators(); 157 } 158} 159 160void DropdownBarHost::OnDidChangeFocus(views::View* focused_before, 161 views::View* focused_now) { 162} 163 164//////////////////////////////////////////////////////////////////////////////// 165// DropdownBarHost, gfx::AnimationDelegate implementation: 166 167void DropdownBarHost::AnimationProgressed(const gfx::Animation* animation) { 168 // First, we calculate how many pixels to slide the widget. 169 gfx::Size pref_size = view_->GetPreferredSize(); 170 animation_offset_ = static_cast<int>((1.0 - animation_->GetCurrentValue()) * 171 pref_size.height()); 172 173 // This call makes sure it appears in the right location, the size and shape 174 // is correct and that it slides in the right direction. 175 gfx::Rect dlg_rect = GetDialogPosition(gfx::Rect()); 176 SetDialogPosition(dlg_rect, false); 177 178 // Let the view know if we are animating, and at which offset to draw the 179 // edges. 180 delegate_->SetAnimationOffset(animation_offset_); 181 view_->SchedulePaint(); 182} 183 184void DropdownBarHost::AnimationEnded(const gfx::Animation* animation) { 185 // Place the dropdown widget in its fully opened state. 186 animation_offset_ = 0; 187 188 if (!animation_->IsShowing()) { 189 // Animation has finished closing. 190 host_->Hide(); 191 is_visible_ = false; 192 OnVisibilityChanged(); 193 } else { 194 // Animation has finished opening. 195 } 196} 197 198//////////////////////////////////////////////////////////////////////////////// 199// DropdownBarHost protected: 200 201void DropdownBarHost::ResetFocusTracker() { 202 focus_tracker_.reset(NULL); 203} 204 205void DropdownBarHost::OnVisibilityChanged() { 206} 207 208void DropdownBarHost::GetWidgetBounds(gfx::Rect* bounds) { 209 DCHECK(bounds); 210 *bounds = browser_view_->bounds(); 211} 212 213void DropdownBarHost::RegisterAccelerators() { 214 DCHECK(!esc_accel_target_registered_); 215 ui::Accelerator escape(ui::VKEY_ESCAPE, ui::EF_NONE); 216 focus_manager_->RegisterAccelerator( 217 escape, ui::AcceleratorManager::kNormalPriority, this); 218 esc_accel_target_registered_ = true; 219} 220 221void DropdownBarHost::UnregisterAccelerators() { 222 DCHECK(esc_accel_target_registered_); 223 ui::Accelerator escape(ui::VKEY_ESCAPE, ui::EF_NONE); 224 focus_manager_->UnregisterAccelerator(escape, this); 225 esc_accel_target_registered_ = false; 226} 227