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/accessibility/accessible_view_state.h" 8#include "ui/gfx/animation/slide_animation.h" 9#include "ui/gfx/color_utils.h" 10#include "ui/gfx/rect.h" 11#include "ui/native_theme/native_theme.h" 12#include "ui/views/bubble/bubble_frame_view.h" 13#include "ui/views/focus/view_storage.h" 14#include "ui/views/widget/widget.h" 15#include "ui/views/widget/widget_observer.h" 16 17#if defined(OS_WIN) 18#include "ui/base/win/shell.h" 19#endif 20 21// The duration of the fade animation in milliseconds. 22static const int kHideFadeDurationMS = 200; 23 24// The defaut margin between the content and the inside border, in pixels. 25static const int kDefaultMargin = 6; 26 27namespace views { 28 29namespace { 30 31// Create a widget to host the bubble. 32Widget* CreateBubbleWidget(BubbleDelegateView* bubble) { 33 Widget* bubble_widget = new Widget(); 34 Widget::InitParams bubble_params(Widget::InitParams::TYPE_BUBBLE); 35 bubble_params.delegate = bubble; 36 bubble_params.opacity = Widget::InitParams::TRANSLUCENT_WINDOW; 37 bubble_params.accept_events = bubble->accept_events(); 38 if (bubble->parent_window()) 39 bubble_params.parent = bubble->parent_window(); 40 else if (bubble->anchor_widget()) 41 bubble_params.parent = bubble->anchor_widget()->GetNativeView(); 42 bubble_params.can_activate = bubble->CanActivate(); 43 bubble->OnBeforeBubbleWidgetInit(&bubble_params, bubble_widget); 44 bubble_widget->Init(bubble_params); 45 return bubble_widget; 46} 47 48} // namespace 49 50BubbleDelegateView::BubbleDelegateView() 51 : close_on_esc_(true), 52 close_on_deactivate_(true), 53 anchor_view_storage_id_(ViewStorage::GetInstance()->CreateStorageID()), 54 anchor_widget_(NULL), 55 move_with_anchor_(false), 56 arrow_(BubbleBorder::TOP_LEFT), 57 shadow_(BubbleBorder::SMALL_SHADOW), 58 color_explicitly_set_(false), 59 margins_(kDefaultMargin, kDefaultMargin, kDefaultMargin, kDefaultMargin), 60 original_opacity_(255), 61 use_focusless_(false), 62 accept_events_(true), 63 border_accepts_events_(true), 64 adjust_if_offscreen_(true), 65 parent_window_(NULL) { 66 AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE)); 67 UpdateColorsFromTheme(GetNativeTheme()); 68} 69 70BubbleDelegateView::BubbleDelegateView( 71 View* anchor_view, 72 BubbleBorder::Arrow arrow) 73 : close_on_esc_(true), 74 close_on_deactivate_(true), 75 anchor_view_storage_id_(ViewStorage::GetInstance()->CreateStorageID()), 76 anchor_widget_(NULL), 77 move_with_anchor_(false), 78 arrow_(arrow), 79 shadow_(BubbleBorder::SMALL_SHADOW), 80 color_explicitly_set_(false), 81 margins_(kDefaultMargin, kDefaultMargin, kDefaultMargin, kDefaultMargin), 82 original_opacity_(255), 83 use_focusless_(false), 84 accept_events_(true), 85 border_accepts_events_(true), 86 adjust_if_offscreen_(true), 87 parent_window_(NULL) { 88 SetAnchorView(anchor_view); 89 AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE)); 90 UpdateColorsFromTheme(GetNativeTheme()); 91} 92 93BubbleDelegateView::~BubbleDelegateView() { 94 SetLayoutManager(NULL); 95 SetAnchorView(NULL); 96} 97 98// static 99Widget* BubbleDelegateView::CreateBubble(BubbleDelegateView* bubble_delegate) { 100 bubble_delegate->Init(); 101 // Get the latest anchor widget from the anchor view at bubble creation time. 102 bubble_delegate->SetAnchorView(bubble_delegate->GetAnchorView()); 103 Widget* bubble_widget = CreateBubbleWidget(bubble_delegate); 104 105#if defined(OS_WIN) && defined(USE_AURA) 106 // If glass is enabled, the bubble is allowed to extend outside the bounds of 107 // the parent frame and let DWM handle compositing. If not, then we don't 108 // want to allow the bubble to extend the frame because it will be clipped. 109 bubble_delegate->set_adjust_if_offscreen(ui::win::IsAeroGlassEnabled()); 110#endif 111 112 bubble_delegate->SizeToContents(); 113 bubble_widget->AddObserver(bubble_delegate); 114 return bubble_widget; 115} 116 117BubbleDelegateView* BubbleDelegateView::AsBubbleDelegate() { 118 return this; 119} 120 121bool BubbleDelegateView::CanActivate() const { 122 return !use_focusless(); 123} 124 125bool BubbleDelegateView::ShouldShowCloseButton() const { 126 return false; 127} 128 129View* BubbleDelegateView::GetContentsView() { 130 return this; 131} 132 133NonClientFrameView* BubbleDelegateView::CreateNonClientFrameView( 134 Widget* widget) { 135 BubbleFrameView* frame = new BubbleFrameView(margins()); 136 BubbleBorder::Arrow adjusted_arrow = arrow(); 137 if (base::i18n::IsRTL()) 138 adjusted_arrow = BubbleBorder::horizontal_mirror(adjusted_arrow); 139 frame->SetBubbleBorder(new BubbleBorder(adjusted_arrow, shadow(), color())); 140 return frame; 141} 142 143void BubbleDelegateView::GetAccessibleState(ui::AccessibleViewState* state) { 144 state->role = ui::AccessibilityTypes::ROLE_DIALOG; 145} 146 147void BubbleDelegateView::OnWidgetDestroying(Widget* widget) { 148 if (anchor_widget() == widget) 149 SetAnchorView(NULL); 150} 151 152void BubbleDelegateView::OnWidgetVisibilityChanging(Widget* widget, 153 bool visible) { 154#if defined(OS_WIN) 155 // On Windows we need to handle this before the bubble is visible or hidden. 156 // Please see the comment on the OnWidgetVisibilityChanging function. On 157 // other platforms it is fine to handle it after the bubble is shown/hidden. 158 HandleVisibilityChanged(widget, visible); 159#endif 160} 161 162void BubbleDelegateView::OnWidgetVisibilityChanged(Widget* widget, 163 bool visible) { 164#if !defined(OS_WIN) 165 HandleVisibilityChanged(widget, visible); 166#endif 167} 168 169void BubbleDelegateView::OnWidgetActivationChanged(Widget* widget, 170 bool active) { 171 if (close_on_deactivate() && widget == GetWidget() && !active) 172 GetWidget()->Close(); 173} 174 175void BubbleDelegateView::OnWidgetBoundsChanged(Widget* widget, 176 const gfx::Rect& new_bounds) { 177 if (anchor_widget() == widget) { 178 if (move_with_anchor()) 179 SizeToContents(); 180 else 181 GetWidget()->Close(); 182 } 183} 184 185View* BubbleDelegateView::GetAnchorView() const { 186 return ViewStorage::GetInstance()->RetrieveView(anchor_view_storage_id_); 187} 188 189gfx::Rect BubbleDelegateView::GetAnchorRect() { 190 if (!GetAnchorView()) 191 return anchor_rect_; 192 193 anchor_rect_ = GetAnchorView()->GetBoundsInScreen(); 194 anchor_rect_.Inset(anchor_view_insets_); 195 return anchor_rect_; 196} 197 198void BubbleDelegateView::OnBeforeBubbleWidgetInit(Widget::InitParams* params, 199 Widget* widget) const { 200} 201 202void BubbleDelegateView::StartFade(bool fade_in) { 203#if defined(USE_AURA) 204 // Use AURA's window layer animation instead of fading. This ensures that 205 // hosts which rely on the layer animation callbacks to close the window 206 // work correctly. 207 if (fade_in) 208 GetWidget()->Show(); 209 else 210 GetWidget()->Close(); 211#else 212 fade_animation_.reset(new gfx::SlideAnimation(this)); 213 fade_animation_->SetSlideDuration(GetFadeDuration()); 214 fade_animation_->Reset(fade_in ? 0.0 : 1.0); 215 if (fade_in) { 216 original_opacity_ = 0; 217 GetWidget()->SetOpacity(original_opacity_); 218 GetWidget()->Show(); 219 fade_animation_->Show(); 220 } else { 221 original_opacity_ = 255; 222 fade_animation_->Hide(); 223 } 224#endif 225} 226 227void BubbleDelegateView::ResetFade() { 228 fade_animation_.reset(); 229 GetWidget()->SetOpacity(original_opacity_); 230} 231 232void BubbleDelegateView::SetAlignment(BubbleBorder::BubbleAlignment alignment) { 233 GetBubbleFrameView()->bubble_border()->set_alignment(alignment); 234 SizeToContents(); 235} 236 237void BubbleDelegateView::SetArrowPaintType( 238 BubbleBorder::ArrowPaintType paint_type) { 239 GetBubbleFrameView()->bubble_border()->set_paint_arrow(paint_type); 240 SizeToContents(); 241} 242 243void BubbleDelegateView::OnAnchorBoundsChanged() { 244 SizeToContents(); 245} 246 247bool BubbleDelegateView::AcceleratorPressed( 248 const ui::Accelerator& accelerator) { 249 if (!close_on_esc() || accelerator.key_code() != ui::VKEY_ESCAPE) 250 return false; 251 if (fade_animation_.get()) 252 fade_animation_->Reset(); 253 GetWidget()->Close(); 254 return true; 255} 256 257void BubbleDelegateView::OnNativeThemeChanged(const ui::NativeTheme* theme) { 258 UpdateColorsFromTheme(theme); 259} 260 261void BubbleDelegateView::AnimationEnded(const gfx::Animation* animation) { 262 if (animation != fade_animation_.get()) 263 return; 264 bool closed = fade_animation_->GetCurrentValue() == 0; 265 fade_animation_->Reset(); 266 if (closed) 267 GetWidget()->Close(); 268} 269 270void BubbleDelegateView::AnimationProgressed(const gfx::Animation* animation) { 271 if (animation != fade_animation_.get()) 272 return; 273 DCHECK(fade_animation_->is_animating()); 274 GetWidget()->SetOpacity(fade_animation_->GetCurrentValue() * 255); 275} 276 277void BubbleDelegateView::Init() {} 278 279void BubbleDelegateView::SetAnchorView(View* anchor_view) { 280 // When the anchor view gets set the associated anchor widget might 281 // change as well. 282 if (!anchor_view || anchor_widget() != anchor_view->GetWidget()) { 283 if (anchor_widget()) { 284 anchor_widget_->RemoveObserver(this); 285 anchor_widget_ = NULL; 286 } 287 if (anchor_view) { 288 anchor_widget_ = anchor_view->GetWidget(); 289 if (anchor_widget_) 290 anchor_widget_->AddObserver(this); 291 } 292 } 293 294 // Remove the old storage item and set the new (if there is one). 295 ViewStorage* view_storage = ViewStorage::GetInstance(); 296 if (view_storage->RetrieveView(anchor_view_storage_id_)) 297 view_storage->RemoveView(anchor_view_storage_id_); 298 if (anchor_view) 299 view_storage->StoreView(anchor_view_storage_id_, anchor_view); 300 301 if (GetWidget()) 302 OnAnchorBoundsChanged(); 303} 304 305void BubbleDelegateView::SetAnchorRect(const gfx::Rect& rect) { 306 anchor_rect_ = rect; 307 if (GetWidget()) 308 OnAnchorBoundsChanged(); 309} 310 311void BubbleDelegateView::SizeToContents() { 312 GetWidget()->SetBounds(GetBubbleBounds()); 313} 314 315BubbleFrameView* BubbleDelegateView::GetBubbleFrameView() const { 316 const NonClientView* view = 317 GetWidget() ? GetWidget()->non_client_view() : NULL; 318 return view ? static_cast<BubbleFrameView*>(view->frame_view()) : NULL; 319} 320 321gfx::Rect BubbleDelegateView::GetBubbleBounds() { 322 // The argument rect has its origin at the bubble's arrow anchor point; 323 // its size is the preferred size of the bubble's client view (this view). 324 return GetBubbleFrameView()->GetUpdatedWindowBounds(GetAnchorRect(), 325 GetPreferredSize(), adjust_if_offscreen_); 326} 327 328int BubbleDelegateView::GetFadeDuration() { 329 return kHideFadeDurationMS; 330} 331 332void BubbleDelegateView::UpdateColorsFromTheme(const ui::NativeTheme* theme) { 333 if (!color_explicitly_set_) { 334 color_ = GetNativeTheme()->GetSystemColor( 335 ui::NativeTheme::kColorId_WindowBackground); 336 } 337 set_background(Background::CreateSolidBackground(color())); 338 BubbleFrameView* frame_view = GetBubbleFrameView(); 339 if (frame_view) 340 frame_view->bubble_border()->set_background_color(color()); 341} 342 343void BubbleDelegateView::HandleVisibilityChanged(Widget* widget, bool visible) { 344 if (widget == GetWidget() && visible && anchor_widget() && 345 anchor_widget()->GetTopLevelWidget()) { 346 anchor_widget()->GetTopLevelWidget()->DisableInactiveRendering(); 347 } 348} 349 350} // namespace views 351