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