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/path.h" 16 #include "ui/gfx/scoped_sk_region.h" 17 #include "ui/gfx/scrollbar_size.h" 18 #include "ui/views/focus/external_focus_tracker.h" 19 #include "ui/views/focus/view_storage.h" 20 #include "ui/views/widget/widget.h" 21 22 using gfx::Path; 23 24 // static 25 bool DropdownBarHost::disable_animations_during_testing_ = false; 26 27 //////////////////////////////////////////////////////////////////////////////// 28 // DropdownBarHost, public: 29 30 DropdownBarHost::DropdownBarHost(BrowserView* browser_view) 31 : browser_view_(browser_view), 32 view_(NULL), 33 delegate_(NULL), 34 animation_offset_(0), 35 focus_manager_(NULL), 36 esc_accel_target_registered_(false), 37 is_visible_(false) { 38 } 39 40 void DropdownBarHost::Init(views::View* host_view, 41 views::View* view, 42 DropdownBarHostDelegate* delegate) { 43 DCHECK(view); 44 DCHECK(delegate); 45 46 view_ = view; 47 delegate_ = delegate; 48 49 // Initialize the host. 50 host_.reset(new views::Widget); 51 views::Widget::InitParams params(views::Widget::InitParams::TYPE_CONTROL); 52 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 53 params.parent = browser_view_->GetWidget()->GetNativeView(); 54 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; 55 host_->Init(params); 56 host_->SetContentsView(view_); 57 58 SetHostViewNative(host_view); 59 60 // Start listening to focus changes, so we can register and unregister our 61 // own handler for Escape. 62 focus_manager_ = host_->GetFocusManager(); 63 if (focus_manager_) { 64 focus_manager_->AddFocusChangeListener(this); 65 } else { 66 // In some cases (see bug http://crbug.com/17056) it seems we may not have 67 // a focus manager. Please reopen the bug if you hit this. 68 NOTREACHED(); 69 } 70 71 // Start the process of animating the opening of the widget. 72 animation_.reset(new gfx::SlideAnimation(this)); 73 } 74 75 DropdownBarHost::~DropdownBarHost() { 76 focus_manager_->RemoveFocusChangeListener(this); 77 focus_tracker_.reset(NULL); 78 } 79 80 void DropdownBarHost::Show(bool animate) { 81 // Stores the currently focused view, and tracks focus changes so that we can 82 // restore focus when the dropdown widget is closed. 83 focus_tracker_.reset(new views::ExternalFocusTracker(view_, focus_manager_)); 84 85 bool was_visible = is_visible_; 86 is_visible_ = true; 87 if (!animate || disable_animations_during_testing_) { 88 animation_->Reset(1); 89 AnimationProgressed(animation_.get()); 90 } else if (!was_visible) { 91 // Don't re-start the animation. 92 animation_->Reset(); 93 animation_->Show(); 94 } 95 96 if (!was_visible) 97 OnVisibilityChanged(); 98 } 99 100 void DropdownBarHost::SetFocusAndSelection() { 101 delegate_->SetFocusAndSelection(true); 102 } 103 104 bool DropdownBarHost::IsAnimating() const { 105 return animation_->is_animating(); 106 } 107 108 void DropdownBarHost::Hide(bool animate) { 109 if (!IsVisible()) 110 return; 111 if (animate && !disable_animations_during_testing_ && 112 !animation_->IsClosing()) { 113 animation_->Hide(); 114 } else { 115 if (animation_->IsClosing()) { 116 // If we're in the middle of a close animation, skip immediately to the 117 // end of the animation. 118 StopAnimation(); 119 } else { 120 // Otherwise we need to set both the animation state to ended and the 121 // DropdownBarHost state to ended/hidden, otherwise the next time we try 122 // to show the bar, it might refuse to do so. Note that we call 123 // AnimationEnded ourselves as Reset does not call it if we are not 124 // animating here. 125 animation_->Reset(); 126 AnimationEnded(animation_.get()); 127 } 128 } 129 } 130 131 void DropdownBarHost::StopAnimation() { 132 animation_->End(); 133 } 134 135 bool DropdownBarHost::IsVisible() const { 136 return is_visible_; 137 } 138 139 //////////////////////////////////////////////////////////////////////////////// 140 // DropdownBarHost, views::FocusChangeListener implementation: 141 void DropdownBarHost::OnWillChangeFocus(views::View* focused_before, 142 views::View* focused_now) { 143 // First we need to determine if one or both of the views passed in are child 144 // views of our view. 145 bool our_view_before = focused_before && view_->Contains(focused_before); 146 bool our_view_now = focused_now && view_->Contains(focused_now); 147 148 // When both our_view_before and our_view_now are false, it means focus is 149 // changing hands elsewhere in the application (and we shouldn't do anything). 150 // Similarly, when both are true, focus is changing hands within the dropdown 151 // widget (and again, we should not do anything). We therefore only need to 152 // look at when we gain initial focus and when we loose it. 153 if (!our_view_before && our_view_now) { 154 // We are gaining focus from outside the dropdown widget so we must register 155 // a handler for Escape. 156 RegisterAccelerators(); 157 } else if (our_view_before && !our_view_now) { 158 // We are losing focus to something outside our widget so we restore the 159 // original handler for Escape. 160 UnregisterAccelerators(); 161 } 162 } 163 164 void DropdownBarHost::OnDidChangeFocus(views::View* focused_before, 165 views::View* focused_now) { 166 } 167 168 //////////////////////////////////////////////////////////////////////////////// 169 // DropdownBarHost, gfx::AnimationDelegate implementation: 170 171 void DropdownBarHost::AnimationProgressed(const gfx::Animation* animation) { 172 // First, we calculate how many pixels to slide the widget. 173 gfx::Size pref_size = view_->GetPreferredSize(); 174 animation_offset_ = static_cast<int>((1.0 - animation_->GetCurrentValue()) * 175 pref_size.height()); 176 177 // This call makes sure it appears in the right location, the size and shape 178 // is correct and that it slides in the right direction. 179 gfx::Rect dlg_rect = GetDialogPosition(gfx::Rect()); 180 SetDialogPosition(dlg_rect, false); 181 182 // Let the view know if we are animating, and at which offset to draw the 183 // edges. 184 delegate_->SetAnimationOffset(animation_offset_); 185 view_->SchedulePaint(); 186 } 187 188 void DropdownBarHost::AnimationEnded(const gfx::Animation* animation) { 189 // Place the dropdown widget in its fully opened state. 190 animation_offset_ = 0; 191 192 if (!animation_->IsShowing()) { 193 // Animation has finished closing. 194 host_->Hide(); 195 is_visible_ = false; 196 OnVisibilityChanged(); 197 } else { 198 // Animation has finished opening. 199 } 200 } 201 202 //////////////////////////////////////////////////////////////////////////////// 203 // DropdownBarHost protected: 204 205 void DropdownBarHost::ResetFocusTracker() { 206 focus_tracker_.reset(NULL); 207 } 208 209 void DropdownBarHost::OnVisibilityChanged() { 210 } 211 212 void DropdownBarHost::GetWidgetBounds(gfx::Rect* bounds) { 213 DCHECK(bounds); 214 *bounds = browser_view_->bounds(); 215 } 216 217 void DropdownBarHost::UpdateWindowEdges(const gfx::Rect& new_pos) { 218 // |w| is used to make it easier to create the part of the polygon that curves 219 // the right side of the Find window. It essentially keeps track of the 220 // x-pixel position of the right-most background image inside the view. 221 // TODO(finnur): Let the view tell us how to draw the curves or convert 222 // this to a CustomFrameWindow. 223 int w = new_pos.width() - 6; // -6 positions us at the left edge of the 224 // rightmost background image of the view. 225 int h = new_pos.height(); 226 227 // This polygon array represents the outline of the background image for the 228 // window. Basically, it encompasses only the visible pixels of the 229 // concatenated find_dlg_LMR_bg images (where LMR = [left | middle | right]). 230 const Path::Point polygon[] = { 231 {2, 0}, {3, 1}, {3, h - 2}, {4, h - 1}, 232 {4, h}, {w+0, h}, 233 {w+2, h - 1}, {w+3, h - 2}, {w+3, 1}, {w+4, 0} 234 }; 235 236 // Find the largest x and y value in the polygon. 237 int max_x = 0, max_y = 0; 238 for (size_t i = 0; i < arraysize(polygon); i++) { 239 max_x = std::max(max_x, static_cast<int>(polygon[i].x)); 240 max_y = std::max(max_y, static_cast<int>(polygon[i].y)); 241 } 242 243 // We then create the polygon and use SetWindowRgn to force the window to draw 244 // only within that area. This region may get reduced in size below. 245 Path path(polygon, arraysize(polygon)); 246 gfx::ScopedSkRegion region(path.CreateNativeRegion()); 247 // Are we animating? 248 if (animation_offset() > 0) { 249 // The animation happens in two steps: First, we clip the window and then in 250 // GetWidgetPosition we offset the window position so that it still looks 251 // attached to the toolbar as it grows. We clip the window by creating a 252 // rectangle region (that gradually increases as the animation progresses) 253 // and find the intersection between the two regions using CombineRgn. 254 255 // |y| shrinks as the animation progresses from the height of the view down 256 // to 0 (and reverses when closing). 257 int y = animation_offset(); 258 // |y| shrinking means the animation (visible) region gets larger. In other 259 // words: the rectangle grows upward (when the widget is opening). 260 Path animation_path; 261 SkRect animation_rect = { SkIntToScalar(0), SkIntToScalar(y), 262 SkIntToScalar(max_x), SkIntToScalar(max_y) }; 263 animation_path.addRect(animation_rect); 264 gfx::ScopedSkRegion animation_region( 265 animation_path.CreateNativeRegion()); 266 region.Set(Path::IntersectRegions(animation_region.Get(), region.Get())); 267 268 // Next, we need to increase the region a little bit to account for the 269 // curved edges that the view will draw to make it look like grows out of 270 // the toolbar. 271 Path::Point left_curve[] = { 272 {2, y+0}, {3, y+1}, {3, y+0}, {2, y+0} 273 }; 274 Path::Point right_curve[] = { 275 {w+3, y+1}, {w+4, y+0}, {w+3, y+0}, {w+3, y+1} 276 }; 277 278 // Combine the region for the curve on the left with our main region. 279 Path left_path(left_curve, arraysize(left_curve)); 280 gfx::ScopedSkRegion r(left_path.CreateNativeRegion()); 281 region.Set(Path::CombineRegions(r.Get(), region.Get())); 282 283 // Combine the region for the curve on the right with our main region. 284 Path right_path(right_curve, arraysize(right_curve)); 285 region.Set(Path::CombineRegions(r.Get(), region.Get())); 286 } 287 288 // Now see if we need to truncate the region because parts of it obscures 289 // the main window border. 290 gfx::Rect widget_bounds; 291 GetWidgetBounds(&widget_bounds); 292 293 // Calculate how much our current position overlaps our boundaries. If we 294 // overlap, it means we have too little space to draw the whole widget and 295 // we allow overwriting the scrollbar before we start truncating our widget. 296 // 297 // TODO(brettw) this constant is evil. This is the amount of room we've added 298 // to the window size, when we set the region, it can change the size. 299 static const int kAddedWidth = 7; 300 int difference = new_pos.right() - kAddedWidth - widget_bounds.right() - 301 gfx::scrollbar_size() + 1; 302 if (difference > 0) { 303 Path::Point exclude[4]; 304 exclude[0].x = max_x - difference; // Top left corner. 305 exclude[0].y = 0; 306 307 exclude[1].x = max_x; // Top right corner. 308 exclude[1].y = 0; 309 310 exclude[2].x = max_x; // Bottom right corner. 311 exclude[2].y = max_y; 312 313 exclude[3].x = max_x - difference; // Bottom left corner. 314 exclude[3].y = max_y; 315 316 // Subtract this region from the original region. 317 gfx::Path exclude_path(exclude, arraysize(exclude)); 318 gfx::ScopedSkRegion exclude_region(exclude_path.CreateNativeRegion()); 319 region.Set(Path::SubtractRegion(region.Get(), exclude_region.Get())); 320 } 321 322 // Window takes ownership of the region. 323 host()->SetShape(region.release()); 324 } 325 326 void DropdownBarHost::RegisterAccelerators() { 327 DCHECK(!esc_accel_target_registered_); 328 ui::Accelerator escape(ui::VKEY_ESCAPE, ui::EF_NONE); 329 focus_manager_->RegisterAccelerator( 330 escape, ui::AcceleratorManager::kNormalPriority, this); 331 esc_accel_target_registered_ = true; 332 } 333 334 void DropdownBarHost::UnregisterAccelerators() { 335 DCHECK(esc_accel_target_registered_); 336 ui::Accelerator escape(ui::VKEY_ESCAPE, ui::EF_NONE); 337 focus_manager_->UnregisterAccelerator(escape, this); 338 esc_accel_target_registered_ = false; 339 } 340