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 "build/build_config.h"
10 #include "chrome/browser/ui/view_ids.h"
11 #include "chrome/browser/ui/views/dropdown_bar_host_delegate.h"
12 #include "chrome/browser/ui/views/frame/browser_view.h"
13 #include "chrome/browser/ui/views/theme_copying_widget.h"
14 #include "ui/events/keycodes/keyboard_codes.h"
15 #include "ui/gfx/animation/slide_animation.h"
16 #include "ui/gfx/scrollbar_size.h"
17 #include "ui/views/focus/external_focus_tracker.h"
18 #include "ui/views/widget/widget.h"
19 
20 // static
21 bool DropdownBarHost::disable_animations_during_testing_ = false;
22 
23 ////////////////////////////////////////////////////////////////////////////////
24 // DropdownBarHost, public:
25 
DropdownBarHost(BrowserView * browser_view)26 DropdownBarHost::DropdownBarHost(BrowserView* browser_view)
27     : AnimationDelegateViews(browser_view), browser_view_(browser_view) {}
28 
~DropdownBarHost()29 DropdownBarHost::~DropdownBarHost() {
30   focus_manager_->RemoveFocusChangeListener(this);
31   ResetFocusTracker();
32 }
33 
Init(views::View * host_view,std::unique_ptr<views::View> view,DropdownBarHostDelegate * delegate)34 void DropdownBarHost::Init(views::View* host_view,
35                            std::unique_ptr<views::View> view,
36                            DropdownBarHostDelegate* delegate) {
37   DCHECK(view);
38   DCHECK(delegate);
39 
40   delegate_ = delegate;
41 
42   // The |clip_view| exists to paint to a layer so that it can clip descendent
43   // Views which also paint to a Layer. See http://crbug.com/589497
44   auto clip_view = std::make_unique<views::View>();
45   clip_view->SetPaintToLayer();
46   clip_view->layer()->SetFillsBoundsOpaquely(false);
47   clip_view->layer()->SetMasksToBounds(true);
48   view_ = clip_view->AddChildView(std::move(view));
49 
50   // Initialize the host.
51   host_ = std::make_unique<ThemeCopyingWidget>(browser_view_->GetWidget());
52   views::Widget::InitParams params(views::Widget::InitParams::TYPE_CONTROL);
53   params.delegate = this;
54   params.name = "DropdownBarHost";
55   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
56   params.parent = browser_view_->GetWidget()->GetNativeView();
57   params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
58 #if defined(OS_MAC)
59   params.activatable = views::Widget::InitParams::ACTIVATABLE_YES;
60 #endif
61   host_->Init(std::move(params));
62   host_->SetContentsView(std::move(clip_view));
63 
64   SetHostViewNative(host_view);
65 
66   // Start listening to focus changes, so we can register and unregister our
67   // own handler for Escape.
68   focus_manager_ = host_->GetFocusManager();
69   if (focus_manager_) {
70     focus_manager_->AddFocusChangeListener(this);
71   } else {
72     // In some cases (see bug http://crbug.com/17056) it seems we may not have
73     // a focus manager.  Please reopen the bug if you hit this.
74     NOTREACHED();
75   }
76 
77   animation_ = std::make_unique<gfx::SlideAnimation>(this);
78   if (!gfx::Animation::ShouldRenderRichAnimation())
79     animation_->SetSlideDuration(base::TimeDelta());
80 
81   // Update the widget and |view_| bounds to the hidden state.
82   AnimationProgressed(animation_.get());
83 }
84 
IsAnimating() const85 bool DropdownBarHost::IsAnimating() const {
86   return animation_->is_animating();
87 }
88 
IsVisible() const89 bool DropdownBarHost::IsVisible() const {
90   return is_visible_;
91 }
92 
SetFocusAndSelection()93 void DropdownBarHost::SetFocusAndSelection() {
94   delegate_->FocusAndSelectAll();
95 }
96 
StopAnimation()97 void DropdownBarHost::StopAnimation() {
98   animation_->End();
99 }
100 
Show(bool animate)101 void DropdownBarHost::Show(bool animate) {
102   // Stores the currently focused view, and tracks focus changes so that we can
103   // restore focus when the dropdown widget is closed.
104   focus_tracker_ =
105       std::make_unique<views::ExternalFocusTracker>(view_, focus_manager_);
106 
107   SetDialogPosition(GetDialogPosition(gfx::Rect()));
108 
109   // If we're in the middle of a close animation, stop it and skip to the end.
110   // This ensures that the state is consistent and prepared to show the drop-
111   // down bar.
112   if (animation_->IsClosing())
113     StopAnimation();
114 
115   host_->Show();
116 
117   bool was_visible = is_visible_;
118   is_visible_ = true;
119   if (!animate || disable_animations_during_testing_) {
120     animation_->Reset(1);
121     AnimationProgressed(animation_.get());
122   } else if (!was_visible) {
123     // Don't re-start the animation.
124     animation_->Reset();
125     animation_->Show();
126   }
127 
128   if (!was_visible)
129     OnVisibilityChanged();
130 }
131 
Hide(bool animate)132 void DropdownBarHost::Hide(bool animate) {
133   if (!IsVisible())
134     return;
135   if (animate && !disable_animations_during_testing_ &&
136       !animation_->IsClosing()) {
137     animation_->Hide();
138   } else {
139     if (animation_->IsClosing()) {
140       // If we're in the middle of a close animation, skip immediately to the
141       // end of the animation.
142       StopAnimation();
143     } else {
144       // Otherwise we need to set both the animation state to ended and the
145       // DropdownBarHost state to ended/hidden, otherwise the next time we try
146       // to show the bar, it might refuse to do so. Note that we call
147       // AnimationEnded ourselves as Reset does not call it if we are not
148       // animating here.
149       animation_->Reset();
150       AnimationEnded(animation_.get());
151     }
152   }
153 }
154 
SetDialogPosition(const gfx::Rect & new_pos)155 void DropdownBarHost::SetDialogPosition(const gfx::Rect& new_pos) {
156   view_->SetSize(new_pos.size());
157 
158   if (new_pos.IsEmpty())
159     return;
160 
161   host()->SetBounds(new_pos);
162 }
163 
OnWillChangeFocus(views::View * focused_before,views::View * focused_now)164 void DropdownBarHost::OnWillChangeFocus(views::View* focused_before,
165                                         views::View* focused_now) {
166   // First we need to determine if one or both of the views passed in are child
167   // views of our view.
168   bool our_view_before = focused_before && view_->Contains(focused_before);
169   bool our_view_now = focused_now && view_->Contains(focused_now);
170 
171   // When both our_view_before and our_view_now are false, it means focus is
172   // changing hands elsewhere in the application (and we shouldn't do anything).
173   // Similarly, when both are true, focus is changing hands within the dropdown
174   // widget (and again, we should not do anything). We therefore only need to
175   // look at when we gain initial focus and when we loose it.
176   if (!our_view_before && our_view_now) {
177     // We are gaining focus from outside the dropdown widget so we must register
178     // a handler for Escape.
179     RegisterAccelerators();
180   } else if (our_view_before && !our_view_now) {
181     // We are losing focus to something outside our widget so we restore the
182     // original handler for Escape.
183     UnregisterAccelerators();
184   }
185 }
186 
OnDidChangeFocus(views::View * focused_before,views::View * focused_now)187 void DropdownBarHost::OnDidChangeFocus(views::View* focused_before,
188                                        views::View* focused_now) {
189 }
190 
AnimationProgressed(const gfx::Animation * animation)191 void DropdownBarHost::AnimationProgressed(const gfx::Animation* animation) {
192   // First, we calculate how many pixels to slide the widget.
193   gfx::Size pref_size = view_->GetPreferredSize();
194   int view_offset = static_cast<int>((animation_->GetCurrentValue() - 1.0) *
195                                      pref_size.height());
196 
197   // This call makes sure |view_| appears in the right location, the size and
198   // shape is correct and that it slides in the right direction.
199   view_->SetPosition(gfx::Point(0, view_offset));
200 }
201 
AnimationEnded(const gfx::Animation * animation)202 void DropdownBarHost::AnimationEnded(const gfx::Animation* animation) {
203   if (!animation_->IsShowing()) {
204     // Animation has finished closing.
205     host_->Hide();
206     is_visible_ = false;
207     OnVisibilityChanged();
208   } else {
209     // Animation has finished opening.
210   }
211 }
212 
RegisterAccelerators()213 void 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 
UnregisterAccelerators()221 void 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 
OnVisibilityChanged()228 void DropdownBarHost::OnVisibilityChanged() {}
229 
ResetFocusTracker()230 void DropdownBarHost::ResetFocusTracker() {
231   focus_tracker_.reset();
232 }
233 
GetWidgetBounds(gfx::Rect * bounds)234 void DropdownBarHost::GetWidgetBounds(gfx::Rect* bounds) {
235   DCHECK(bounds);
236   *bounds = browser_view_->bounds();
237 }
238 
GetWidget()239 views::Widget* DropdownBarHost::GetWidget() {
240   return host_.get();
241 }
242 
GetWidget() const243 const views::Widget* DropdownBarHost::GetWidget() const {
244   return host_.get();
245 }
246