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