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/accessibility/ax_view_state.h" 8#include "ui/base/resource/resource_bundle.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 defaut margin between the content and the inside border, in pixels. 22static const int kDefaultMargin = 6; 23 24namespace views { 25 26namespace { 27 28// Create a widget to host the bubble. 29Widget* CreateBubbleWidget(BubbleDelegateView* bubble) { 30 Widget* bubble_widget = new Widget(); 31 Widget::InitParams bubble_params(Widget::InitParams::TYPE_BUBBLE); 32 bubble_params.delegate = bubble; 33 bubble_params.opacity = Widget::InitParams::TRANSLUCENT_WINDOW; 34 bubble_params.accept_events = bubble->accept_events(); 35 if (bubble->parent_window()) 36 bubble_params.parent = bubble->parent_window(); 37 else if (bubble->anchor_widget()) 38 bubble_params.parent = bubble->anchor_widget()->GetNativeView(); 39 bubble_params.activatable = bubble->CanActivate() ? 40 Widget::InitParams::ACTIVATABLE_YES : Widget::InitParams::ACTIVATABLE_NO; 41 bubble->OnBeforeBubbleWidgetInit(&bubble_params, bubble_widget); 42 bubble_widget->Init(bubble_params); 43 return bubble_widget; 44} 45 46} // namespace 47 48BubbleDelegateView::BubbleDelegateView() 49 : close_on_esc_(true), 50 close_on_deactivate_(true), 51 anchor_view_storage_id_(ViewStorage::GetInstance()->CreateStorageID()), 52 anchor_widget_(NULL), 53 arrow_(BubbleBorder::TOP_LEFT), 54 shadow_(BubbleBorder::SMALL_SHADOW), 55 color_explicitly_set_(false), 56 margins_(kDefaultMargin, kDefaultMargin, kDefaultMargin, kDefaultMargin), 57 use_focusless_(false), 58 accept_events_(true), 59 border_accepts_events_(true), 60 adjust_if_offscreen_(true), 61 parent_window_(NULL) { 62 AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE)); 63 UpdateColorsFromTheme(GetNativeTheme()); 64} 65 66BubbleDelegateView::BubbleDelegateView( 67 View* anchor_view, 68 BubbleBorder::Arrow arrow) 69 : close_on_esc_(true), 70 close_on_deactivate_(true), 71 anchor_view_storage_id_(ViewStorage::GetInstance()->CreateStorageID()), 72 anchor_widget_(NULL), 73 arrow_(arrow), 74 shadow_(BubbleBorder::SMALL_SHADOW), 75 color_explicitly_set_(false), 76 margins_(kDefaultMargin, kDefaultMargin, kDefaultMargin, kDefaultMargin), 77 use_focusless_(false), 78 accept_events_(true), 79 border_accepts_events_(true), 80 adjust_if_offscreen_(true), 81 parent_window_(NULL) { 82 SetAnchorView(anchor_view); 83 AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE)); 84 UpdateColorsFromTheme(GetNativeTheme()); 85} 86 87BubbleDelegateView::~BubbleDelegateView() { 88 if (GetWidget()) 89 GetWidget()->RemoveObserver(this); 90 SetLayoutManager(NULL); 91 SetAnchorView(NULL); 92} 93 94// static 95Widget* BubbleDelegateView::CreateBubble(BubbleDelegateView* bubble_delegate) { 96 bubble_delegate->Init(); 97 // Get the latest anchor widget from the anchor view at bubble creation time. 98 bubble_delegate->SetAnchorView(bubble_delegate->GetAnchorView()); 99 Widget* bubble_widget = CreateBubbleWidget(bubble_delegate); 100 101#if defined(OS_WIN) 102 // If glass is enabled, the bubble is allowed to extend outside the bounds of 103 // the parent frame and let DWM handle compositing. If not, then we don't 104 // want to allow the bubble to extend the frame because it will be clipped. 105 bubble_delegate->set_adjust_if_offscreen(ui::win::IsAeroGlassEnabled()); 106#elif defined(OS_LINUX) && !defined(OS_CHROMEOS) 107 // Linux clips bubble windows that extend outside their parent window bounds. 108 bubble_delegate->set_adjust_if_offscreen(false); 109#endif 110 111 bubble_delegate->SizeToContents(); 112 bubble_widget->AddObserver(bubble_delegate); 113 return bubble_widget; 114} 115 116BubbleDelegateView* BubbleDelegateView::AsBubbleDelegate() { 117 return this; 118} 119 120bool BubbleDelegateView::CanActivate() const { 121 return !use_focusless(); 122} 123 124bool BubbleDelegateView::ShouldShowCloseButton() const { 125 return false; 126} 127 128View* BubbleDelegateView::GetContentsView() { 129 return this; 130} 131 132NonClientFrameView* BubbleDelegateView::CreateNonClientFrameView( 133 Widget* widget) { 134 BubbleFrameView* frame = new BubbleFrameView(margins()); 135 // Note: In CreateBubble, the call to SizeToContents() will cause 136 // the relayout that this call requires. 137 frame->SetTitleFontList(GetTitleFontList()); 138 BubbleBorder::Arrow adjusted_arrow = arrow(); 139 if (base::i18n::IsRTL()) 140 adjusted_arrow = BubbleBorder::horizontal_mirror(adjusted_arrow); 141 frame->SetBubbleBorder(scoped_ptr<BubbleBorder>( 142 new BubbleBorder(adjusted_arrow, shadow(), color()))); 143 return frame; 144} 145 146void BubbleDelegateView::GetAccessibleState(ui::AXViewState* state) { 147 state->role = ui::AX_ROLE_DIALOG; 148} 149 150void BubbleDelegateView::OnWidgetDestroying(Widget* widget) { 151 if (anchor_widget() == widget) 152 SetAnchorView(NULL); 153} 154 155void BubbleDelegateView::OnWidgetVisibilityChanging(Widget* widget, 156 bool visible) { 157#if defined(OS_WIN) 158 // On Windows we need to handle this before the bubble is visible or hidden. 159 // Please see the comment on the OnWidgetVisibilityChanging function. On 160 // other platforms it is fine to handle it after the bubble is shown/hidden. 161 HandleVisibilityChanged(widget, visible); 162#endif 163} 164 165void BubbleDelegateView::OnWidgetVisibilityChanged(Widget* widget, 166 bool visible) { 167#if !defined(OS_WIN) 168 HandleVisibilityChanged(widget, visible); 169#endif 170} 171 172void BubbleDelegateView::OnWidgetActivationChanged(Widget* widget, 173 bool active) { 174 if (close_on_deactivate() && widget == GetWidget() && !active) 175 GetWidget()->Close(); 176} 177 178void BubbleDelegateView::OnWidgetBoundsChanged(Widget* widget, 179 const gfx::Rect& new_bounds) { 180 if (anchor_widget() == widget) 181 SizeToContents(); 182} 183 184View* BubbleDelegateView::GetAnchorView() const { 185 return ViewStorage::GetInstance()->RetrieveView(anchor_view_storage_id_); 186} 187 188gfx::Rect BubbleDelegateView::GetAnchorRect() const { 189 if (!GetAnchorView()) 190 return anchor_rect_; 191 192 anchor_rect_ = GetAnchorView()->GetBoundsInScreen(); 193 anchor_rect_.Inset(anchor_view_insets_); 194 return anchor_rect_; 195} 196 197void BubbleDelegateView::OnBeforeBubbleWidgetInit(Widget::InitParams* params, 198 Widget* widget) const { 199} 200 201void BubbleDelegateView::SetAlignment(BubbleBorder::BubbleAlignment alignment) { 202 GetBubbleFrameView()->bubble_border()->set_alignment(alignment); 203 SizeToContents(); 204} 205 206void BubbleDelegateView::SetArrowPaintType( 207 BubbleBorder::ArrowPaintType paint_type) { 208 GetBubbleFrameView()->bubble_border()->set_paint_arrow(paint_type); 209 SizeToContents(); 210} 211 212void BubbleDelegateView::OnAnchorBoundsChanged() { 213 SizeToContents(); 214} 215 216bool BubbleDelegateView::AcceleratorPressed( 217 const ui::Accelerator& accelerator) { 218 if (!close_on_esc() || accelerator.key_code() != ui::VKEY_ESCAPE) 219 return false; 220 GetWidget()->Close(); 221 return true; 222} 223 224void BubbleDelegateView::OnNativeThemeChanged(const ui::NativeTheme* theme) { 225 UpdateColorsFromTheme(theme); 226} 227 228void BubbleDelegateView::Init() {} 229 230void BubbleDelegateView::SetAnchorView(View* anchor_view) { 231 // When the anchor view gets set the associated anchor widget might 232 // change as well. 233 if (!anchor_view || anchor_widget() != anchor_view->GetWidget()) { 234 if (anchor_widget()) { 235 anchor_widget_->RemoveObserver(this); 236 anchor_widget_ = NULL; 237 } 238 if (anchor_view) { 239 anchor_widget_ = anchor_view->GetWidget(); 240 if (anchor_widget_) 241 anchor_widget_->AddObserver(this); 242 } 243 } 244 245 // Remove the old storage item and set the new (if there is one). 246 ViewStorage* view_storage = ViewStorage::GetInstance(); 247 if (view_storage->RetrieveView(anchor_view_storage_id_)) 248 view_storage->RemoveView(anchor_view_storage_id_); 249 if (anchor_view) 250 view_storage->StoreView(anchor_view_storage_id_, anchor_view); 251 252 // Do not update anchoring for NULL views; this could indicate that our 253 // NativeWindow is being destroyed, so it would be dangerous for us to update 254 // our anchor bounds at that point. (It's safe to skip this, since if we were 255 // to update the bounds when |anchor_view| is NULL, the bubble won't move.) 256 if (anchor_view && GetWidget()) 257 OnAnchorBoundsChanged(); 258} 259 260void BubbleDelegateView::SetAnchorRect(const gfx::Rect& rect) { 261 anchor_rect_ = rect; 262 if (GetWidget()) 263 OnAnchorBoundsChanged(); 264} 265 266void BubbleDelegateView::SizeToContents() { 267 GetWidget()->SetBounds(GetBubbleBounds()); 268} 269 270BubbleFrameView* BubbleDelegateView::GetBubbleFrameView() const { 271 const NonClientView* view = 272 GetWidget() ? GetWidget()->non_client_view() : NULL; 273 return view ? static_cast<BubbleFrameView*>(view->frame_view()) : NULL; 274} 275 276gfx::Rect BubbleDelegateView::GetBubbleBounds() { 277 // The argument rect has its origin at the bubble's arrow anchor point; 278 // its size is the preferred size of the bubble's client view (this view). 279 bool anchor_minimized = anchor_widget() && anchor_widget()->IsMinimized(); 280 return GetBubbleFrameView()->GetUpdatedWindowBounds(GetAnchorRect(), 281 GetPreferredSize(), adjust_if_offscreen_ && !anchor_minimized); 282} 283 284const gfx::FontList& BubbleDelegateView::GetTitleFontList() const { 285 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 286 return rb.GetFontList(ui::ResourceBundle::MediumFont); 287} 288 289 290void BubbleDelegateView::UpdateColorsFromTheme(const ui::NativeTheme* theme) { 291 if (!color_explicitly_set_) 292 color_ = theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground); 293 set_background(Background::CreateSolidBackground(color())); 294 BubbleFrameView* frame_view = GetBubbleFrameView(); 295 if (frame_view) 296 frame_view->bubble_border()->set_background_color(color()); 297} 298 299void BubbleDelegateView::HandleVisibilityChanged(Widget* widget, bool visible) { 300 if (widget == GetWidget() && anchor_widget() && 301 anchor_widget()->GetTopLevelWidget()) { 302 if (visible) 303 anchor_widget()->GetTopLevelWidget()->DisableInactiveRendering(); 304 else 305 anchor_widget()->GetTopLevelWidget()->EnableInactiveRendering(); 306 } 307} 308 309} // namespace views 310