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