bubble_delegate.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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 "ui/views/bubble/bubble_delegate.h" 6 7#include "ui/base/animation/slide_animation.h" 8#include "ui/gfx/color_utils.h" 9#include "ui/views/bubble/bubble_frame_view.h" 10#include "ui/views/widget/widget.h" 11#include "ui/views/widget/widget_observer.h" 12 13// The duration of the fade animation in milliseconds. 14static const int kHideFadeDurationMS = 200; 15 16// The defaut margin between the content and the inside border, in pixels. 17static const int kDefaultMargin = 6; 18 19namespace views { 20 21namespace { 22 23// Create a widget to host the bubble. 24Widget* CreateBubbleWidget(BubbleDelegateView* bubble) { 25 Widget* bubble_widget = new Widget(); 26 Widget::InitParams bubble_params(Widget::InitParams::TYPE_BUBBLE); 27 bubble_params.delegate = bubble; 28 bubble_params.transparent = true; 29 bubble_params.accept_events = bubble->accept_events(); 30 if (bubble->parent_window()) 31 bubble_params.parent = bubble->parent_window(); 32 else 33 bubble_params.parent_widget = bubble->anchor_widget(); 34 bubble_params.can_activate = bubble->CanActivate(); 35#if defined(OS_WIN) && !defined(USE_AURA) 36 bubble_params.type = Widget::InitParams::TYPE_WINDOW_FRAMELESS; 37 bubble_params.transparent = false; 38#endif 39 bubble_widget->Init(bubble_params); 40 return bubble_widget; 41} 42 43#if defined(OS_WIN) && !defined(USE_AURA) 44// Windows uses two widgets and some extra complexity to host partially 45// transparent native controls and use per-pixel HWND alpha on the border. 46// TODO(msw): Clean these up when Windows native controls are no longer needed. 47class BubbleBorderDelegate : public WidgetDelegate, 48 public WidgetObserver { 49 public: 50 BubbleBorderDelegate(BubbleDelegateView* bubble, Widget* widget) 51 : bubble_(bubble), 52 widget_(widget) { 53 bubble_->GetWidget()->AddObserver(this); 54 } 55 56 virtual ~BubbleBorderDelegate() { 57 if (bubble_ && bubble_->GetWidget()) 58 bubble_->GetWidget()->RemoveObserver(this); 59 } 60 61 // WidgetDelegate overrides: 62 virtual bool CanActivate() const OVERRIDE { return false; } 63 virtual void DeleteDelegate() OVERRIDE { delete this; } 64 virtual Widget* GetWidget() OVERRIDE { return widget_; } 65 virtual const Widget* GetWidget() const OVERRIDE { return widget_; } 66 virtual NonClientFrameView* CreateNonClientFrameView( 67 Widget* widget) OVERRIDE { 68 return bubble_->CreateNonClientFrameView(widget); 69 } 70 71 // WidgetObserver overrides: 72 virtual void OnWidgetClosing(Widget* widget) OVERRIDE { 73 bubble_ = NULL; 74 widget_->Close(); 75 } 76 77 private: 78 BubbleDelegateView* bubble_; 79 Widget* widget_; 80 81 DISALLOW_COPY_AND_ASSIGN(BubbleBorderDelegate); 82}; 83 84// Create a widget to host the bubble's border. 85Widget* CreateBorderWidget(BubbleDelegateView* bubble) { 86 Widget* border_widget = new Widget(); 87 Widget::InitParams border_params(Widget::InitParams::TYPE_BUBBLE); 88 border_params.delegate = new BubbleBorderDelegate(bubble, border_widget); 89 border_params.transparent = true; 90 border_params.parent_widget = bubble->GetWidget(); 91 border_params.can_activate = false; 92 border_widget->Init(border_params); 93 border_widget->set_focus_on_creation(false); 94 return border_widget; 95} 96#endif 97 98} // namespace 99 100#if defined(OS_WIN) && !defined(USE_AURA) 101const SkColor BubbleDelegateView::kBackgroundColor = 102 color_utils::GetSysSkColor(COLOR_WINDOW); 103#else 104// TODO(beng): source from theme provider. 105const SkColor BubbleDelegateView::kBackgroundColor = SK_ColorWHITE; 106#endif 107 108BubbleDelegateView::BubbleDelegateView() 109 : close_on_esc_(true), 110 close_on_deactivate_(true), 111 anchor_view_(NULL), 112 anchor_widget_(NULL), 113 move_with_anchor_(false), 114 arrow_location_(BubbleBorder::TOP_LEFT), 115 shadow_(BubbleBorder::SMALL_SHADOW), 116 color_(kBackgroundColor), 117 margins_(kDefaultMargin, kDefaultMargin, kDefaultMargin, kDefaultMargin), 118 original_opacity_(255), 119 border_widget_(NULL), 120 use_focusless_(false), 121 accept_events_(true), 122 adjust_if_offscreen_(true), 123 parent_window_(NULL) { 124 set_background(Background::CreateSolidBackground(color_)); 125 AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE)); 126} 127 128BubbleDelegateView::BubbleDelegateView( 129 View* anchor_view, 130 BubbleBorder::ArrowLocation arrow_location) 131 : close_on_esc_(true), 132 close_on_deactivate_(true), 133 anchor_view_(anchor_view), 134 anchor_widget_(NULL), 135 move_with_anchor_(false), 136 arrow_location_(arrow_location), 137 shadow_(BubbleBorder::SMALL_SHADOW), 138 color_(kBackgroundColor), 139 margins_(kDefaultMargin, kDefaultMargin, kDefaultMargin, kDefaultMargin), 140 original_opacity_(255), 141 border_widget_(NULL), 142 use_focusless_(false), 143 accept_events_(true), 144 adjust_if_offscreen_(true), 145 parent_window_(NULL) { 146 set_background(Background::CreateSolidBackground(color_)); 147 AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE)); 148} 149 150BubbleDelegateView::~BubbleDelegateView() { 151 if (anchor_widget() != NULL) 152 anchor_widget()->RemoveObserver(this); 153 anchor_widget_ = NULL; 154 anchor_view_ = NULL; 155} 156 157// static 158Widget* BubbleDelegateView::CreateBubble(BubbleDelegateView* bubble_delegate) { 159 bubble_delegate->Init(); 160 // Determine the anchor widget from the anchor view at bubble creation time. 161 bubble_delegate->anchor_widget_ = bubble_delegate->anchor_view() ? 162 bubble_delegate->anchor_view()->GetWidget() : NULL; 163 if (bubble_delegate->anchor_widget()) 164 bubble_delegate->anchor_widget()->AddObserver(bubble_delegate); 165 166 Widget* bubble_widget = CreateBubbleWidget(bubble_delegate); 167 168#if defined(OS_WIN) && !defined(USE_AURA) 169 // First set the contents view to initialize view bounds for widget sizing. 170 bubble_widget->SetContentsView(bubble_delegate->GetContentsView()); 171 bubble_delegate->border_widget_ = CreateBorderWidget(bubble_delegate); 172#endif 173 174 bubble_delegate->SizeToContents(); 175 bubble_widget->AddObserver(bubble_delegate); 176 return bubble_widget; 177} 178 179BubbleDelegateView* BubbleDelegateView::AsBubbleDelegate() { 180 return this; 181} 182 183bool BubbleDelegateView::CanActivate() const { 184 return !use_focusless(); 185} 186 187View* BubbleDelegateView::GetContentsView() { 188 return this; 189} 190 191NonClientFrameView* BubbleDelegateView::CreateNonClientFrameView( 192 Widget* widget) { 193 BubbleBorder::ArrowLocation arrow_loc = arrow_location(); 194 if (base::i18n::IsRTL()) 195 arrow_loc = BubbleBorder::horizontal_mirror(arrow_loc); 196 BubbleBorder* border = new BubbleBorder(arrow_loc, shadow_); 197 border->set_background_color(color()); 198 BubbleFrameView* frame_view = new BubbleFrameView(margins(), border); 199 frame_view->set_background(new BubbleBackground(border)); 200 return frame_view; 201} 202 203void BubbleDelegateView::OnWidgetClosing(Widget* widget) { 204 if (anchor_widget() == widget) { 205 anchor_view_ = NULL; 206 anchor_widget_ = NULL; 207 } 208} 209 210void BubbleDelegateView::OnWidgetVisibilityChanged(Widget* widget, 211 bool visible) { 212 if (widget != GetWidget()) 213 return; 214 215 if (visible) { 216 if (border_widget_) 217 border_widget_->ShowInactive(); 218 if (anchor_widget() && anchor_widget()->GetTopLevelWidget()) 219 anchor_widget()->GetTopLevelWidget()->DisableInactiveRendering(); 220 } else { 221 if (border_widget_) 222 border_widget_->Hide(); 223 } 224} 225 226void BubbleDelegateView::OnWidgetActivationChanged(Widget* widget, 227 bool active) { 228 if (close_on_deactivate() && widget == GetWidget() && !active) 229 GetWidget()->Close(); 230} 231 232void BubbleDelegateView::OnWidgetMoved(Widget* widget) { 233 if (move_with_anchor() && anchor_widget() == widget) 234 SizeToContents(); 235} 236 237gfx::Rect BubbleDelegateView::GetAnchorRect() { 238 if (!anchor_view()) 239 return gfx::Rect(anchor_point_, gfx::Size()); 240 gfx::Rect anchor_bounds = anchor_view()->GetBoundsInScreen(); 241 anchor_bounds.Inset(anchor_insets_); 242 return anchor_bounds; 243} 244 245void BubbleDelegateView::Show() { 246 GetWidget()->Show(); 247} 248 249void BubbleDelegateView::StartFade(bool fade_in) { 250 fade_animation_.reset(new ui::SlideAnimation(this)); 251 fade_animation_->SetSlideDuration(kHideFadeDurationMS); 252 fade_animation_->Reset(fade_in ? 0.0 : 1.0); 253 if (fade_in) { 254 original_opacity_ = 0; 255 if (border_widget_) 256 border_widget_->SetOpacity(original_opacity_); 257 GetWidget()->SetOpacity(original_opacity_); 258 Show(); 259 fade_animation_->Show(); 260 } else { 261 original_opacity_ = 255; 262 fade_animation_->Hide(); 263 } 264} 265 266void BubbleDelegateView::ResetFade() { 267 fade_animation_.reset(); 268 if (border_widget_) 269 border_widget_->SetOpacity(original_opacity_); 270 GetWidget()->SetOpacity(original_opacity_); 271} 272 273void BubbleDelegateView::SetAlignment(BubbleBorder::BubbleAlignment alignment) { 274 GetBubbleFrameView()->bubble_border()->set_alignment(alignment); 275 SizeToContents(); 276} 277 278bool BubbleDelegateView::AcceleratorPressed( 279 const ui::Accelerator& accelerator) { 280 if (!close_on_esc() || accelerator.key_code() != ui::VKEY_ESCAPE) 281 return false; 282 if (fade_animation_.get()) 283 fade_animation_->Reset(); 284 GetWidget()->Close(); 285 return true; 286} 287 288void BubbleDelegateView::AnimationEnded(const ui::Animation* animation) { 289 if (animation != fade_animation_.get()) 290 return; 291 bool closed = fade_animation_->GetCurrentValue() == 0; 292 fade_animation_->Reset(); 293 if (closed) 294 GetWidget()->Close(); 295} 296 297void BubbleDelegateView::AnimationProgressed(const ui::Animation* animation) { 298 if (animation != fade_animation_.get()) 299 return; 300 DCHECK(fade_animation_->is_animating()); 301 unsigned char opacity = fade_animation_->GetCurrentValue() * 255; 302#if defined(OS_WIN) && !defined(USE_AURA) 303 // Explicitly set the content Widget's layered style and set transparency via 304 // SetLayeredWindowAttributes. This is done because initializing the Widget as 305 // transparent and setting opacity via UpdateLayeredWindow doesn't support 306 // hosting child native Windows controls. 307 const HWND hwnd = GetWidget()->GetNativeView(); 308 const DWORD style = GetWindowLong(hwnd, GWL_EXSTYLE); 309 if ((opacity == 255) == !!(style & WS_EX_LAYERED)) 310 SetWindowLong(hwnd, GWL_EXSTYLE, style ^ WS_EX_LAYERED); 311 SetLayeredWindowAttributes(hwnd, 0, opacity, LWA_ALPHA); 312 // Update the border widget's opacity. 313 border_widget_->SetOpacity(opacity); 314#endif 315 GetWidget()->SetOpacity(opacity); 316} 317 318void BubbleDelegateView::Init() {} 319 320void BubbleDelegateView::SizeToContents() { 321#if defined(OS_WIN) && !defined(USE_AURA) 322 border_widget_->SetBounds(GetBubbleBounds()); 323 GetWidget()->SetBounds(GetBubbleClientBounds()); 324 325 // Update the local client bounds clipped out by the border widget background. 326 // Used to correctly display overlapping semi-transparent widgets on Windows. 327 GetBubbleFrameView()->bubble_border()->set_client_bounds( 328 GetBubbleFrameView()->GetBoundsForClientView()); 329#else 330 GetWidget()->SetBounds(GetBubbleBounds()); 331#endif 332} 333 334BubbleFrameView* BubbleDelegateView::GetBubbleFrameView() const { 335 const Widget* widget = border_widget_ ? border_widget_ : GetWidget(); 336 const NonClientView* view = widget ? widget->non_client_view() : NULL; 337 return view ? static_cast<BubbleFrameView*>(view->frame_view()) : NULL; 338} 339 340gfx::Rect BubbleDelegateView::GetBubbleBounds() { 341 // The argument rect has its origin at the bubble's arrow anchor point; 342 // its size is the preferred size of the bubble's client view (this view). 343 return GetBubbleFrameView()->GetUpdatedWindowBounds(GetAnchorRect(), 344 GetPreferredSize(), adjust_if_offscreen_); 345} 346 347#if defined(OS_WIN) && !defined(USE_AURA) 348gfx::Rect BubbleDelegateView::GetBubbleClientBounds() const { 349 gfx::Rect client_bounds(GetBubbleFrameView()->GetBoundsForClientView()); 350 client_bounds.Offset( 351 border_widget_->GetWindowBoundsInScreen().OffsetFromOrigin()); 352 return client_bounds; 353} 354#endif 355 356} // namespace views 357